mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
				synced 2025-10-31 12:13:34 +00:00 
			
		
		
		
	Compare commits
	
		
			71 Commits
		
	
	
		
			mstx_newtr
			...
			1.6.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2ada887367 | ||
|  | 5042156437 | ||
|  | 70ed3d586e | ||
|  | 3dfbb6d9f6 | ||
|  | 58294fde4b | ||
|  | d372eb2f0b | ||
|  | 287ae681b7 | ||
|  | cca5d93f66 | ||
|  | a5a2275a08 | ||
|  | 1499f0343a | ||
|  | 8c1d59086e | ||
|  | f57a86131e | ||
|  | 7d5c16590c | ||
|  | f8c7a52521 | ||
|  | c0f78a37ed | ||
|  | 19e134a626 | ||
|  | a98521ac05 | ||
|  | df4520df77 | ||
|  | 2f40abd8f5 | ||
|  | 6a3e4b32f0 | ||
|  | c3e515a28b | ||
|  | bcaafcaa9d | ||
|  | ea7bd5fb91 | ||
|  | 135d64b1a9 | ||
|  | e44cf44af4 | ||
|  | c0f0a6105a | ||
|  | da5ffd6e01 | ||
|  | 40978041ad | ||
|  | 3e7f4b0da9 | ||
|  | 4080cd05ba | ||
|  | b3157b91bb | ||
|  | 805e0d9c6b | ||
|  | 4b2b98b067 | ||
|  | 2e6c362b9c | ||
|  | 7e47a521ef | ||
|  | f35515c015 | ||
|  | 874542ca7c | ||
|  | f476a6755b | ||
|  | 1ddd727bb4 | ||
|  | 8a4362459d | ||
|  | 5ba130c381 | ||
|  | fed58d97b8 | ||
|  | 0e13bfd18c | ||
|  | cf1ca2e92e | ||
|  | 20ecc4f531 | ||
|  | 097a16e384 | ||
|  | c9af0b0ba0 | ||
|  | 621a49eb69 | ||
|  | f538397826 | ||
|  | 8c4336dba9 | ||
|  | fe1b9cef40 | ||
|  | 5e63151f9f | ||
|  | 10b4e31655 | ||
|  | 0c433350da | ||
|  | d0c1055051 | ||
|  | 0ce64705e0 | ||
|  | d0b947a0c4 | ||
|  | 4a867be165 | ||
|  | fdcce1fc6e | ||
|  | 2ca77d7ff2 | ||
|  | b6fd0709f4 | ||
|  | d3e3bba2cd | ||
|  | 5561f1129d | ||
|  | b7253c6fdc | ||
|  | 5a23a24bb1 | ||
|  | b60fb8e1e3 | ||
|  | 934e1016ff | ||
|  | ac726b1147 | ||
|  | 508270d83d | ||
|  | 7d897cb5b0 | ||
|  | 94dcf6d29c | 
							
								
								
									
										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 | ||||
| @@ -25,7 +25,7 @@ AllowShortLoopsOnASingleLine: false | ||||
| AlwaysBreakAfterDefinitionReturnType: None | ||||
| AlwaysBreakAfterReturnType: None | ||||
| AlwaysBreakBeforeMultilineStrings: false | ||||
| AlwaysBreakTemplateDeclarations: false | ||||
| AlwaysBreakTemplateDeclarations: true | ||||
| BinPackArguments: true | ||||
| BinPackParameters: true | ||||
| BraceWrapping: | ||||
| @@ -515,7 +515,7 @@ SpacesInContainerLiterals: false | ||||
| SpacesInCStyleCastParentheses: false | ||||
| SpacesInParentheses: false | ||||
| SpacesInSquareBrackets: false | ||||
| Standard: Cpp03 | ||||
| Standard: Cpp11 | ||||
| TabWidth: 8 | ||||
| UseTab: Always | ||||
| ... | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,3 @@ | ||||
| [submodule "osmocom-bb"] | ||||
| 	path = osmocom-bb | ||||
| 	url = https://gitea.osmocom.org/phone-side/osmocom-bb.git | ||||
| 	branch = a4aac5c3554559c2c994609f90b92a9daf6e8a89 | ||||
|   | ||||
| @@ -517,7 +517,7 @@ public: | ||||
| 		@param timeout The blocking timeout in ms. | ||||
| 		@return Pointer at key or NULL on timeout. | ||||
| 	*/ | ||||
| 	D* read(const K &key, unsigned timeout) const | ||||
| 	D* read(const K &key, unsigned timeout) | ||||
| 	{ | ||||
| 		if (timeout==0) return readNoBlock(key); | ||||
| 		ScopedLock lock(mLock); | ||||
| @@ -537,7 +537,7 @@ public: | ||||
| 		@param key The key to read from. | ||||
| 		@return Pointer at key. | ||||
| 	*/ | ||||
| 	D* read(const K &key) const | ||||
| 	D* read(const K &key) | ||||
| 	{ | ||||
| 		ScopedLock lock(mLock); | ||||
| 		typename Map::const_iterator iter = mMap.find(key); | ||||
|   | ||||
| @@ -22,8 +22,8 @@ | ||||
| include $(top_srcdir)/Makefile.common | ||||
|  | ||||
| AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) | ||||
| AM_CXXFLAGS = -Wall -O3 -g -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
| AM_CFLAGS = $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
| AM_CXXFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
| AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
|  | ||||
| noinst_LTLIBRARIES = libcommon.la | ||||
|  | ||||
| @@ -37,7 +37,12 @@ libcommon_la_SOURCES = \ | ||||
| 	trx_rate_ctr.cpp \ | ||||
| 	trx_vty.c \ | ||||
| 	debug.c | ||||
| libcommon_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS) | ||||
| libcommon_la_LIBADD = \ | ||||
| 	$(LIBOSMOCORE_LIBS) \ | ||||
| 	$(LIBOSMOCTRL_LIBS) \ | ||||
| 	$(LIBOSMOVTY_LIBS) \ | ||||
| 	-lpthread \ | ||||
| 	$(NULL) | ||||
|  | ||||
| noinst_HEADERS = \ | ||||
| 	BitVector.h \ | ||||
|   | ||||
| @@ -43,71 +43,6 @@ using namespace std; | ||||
| #endif | ||||
|  | ||||
|  | ||||
| Mutex gStreamLock;		///< Global lock to control access to cout and cerr. | ||||
|  | ||||
| void lockCout() | ||||
| { | ||||
| 	gStreamLock.lock(); | ||||
| 	Timeval entryTime; | ||||
| 	cout << entryTime << " " << osmo_gettid() << ": "; | ||||
| } | ||||
|  | ||||
|  | ||||
| void unlockCout() | ||||
| { | ||||
| 	cout << dec << endl << flush; | ||||
| 	gStreamLock.unlock(); | ||||
| } | ||||
|  | ||||
|  | ||||
| void lockCerr() | ||||
| { | ||||
| 	gStreamLock.lock(); | ||||
| 	Timeval entryTime; | ||||
| 	cerr << entryTime << " " << osmo_gettid() << ": "; | ||||
| } | ||||
|  | ||||
| void unlockCerr() | ||||
| { | ||||
| 	cerr << dec << endl << flush; | ||||
| 	gStreamLock.unlock(); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| Mutex::Mutex() | ||||
| { | ||||
| 	bool res; | ||||
| 	res = pthread_mutexattr_init(&mAttribs); | ||||
| 	assert(!res); | ||||
| 	res = pthread_mutexattr_settype(&mAttribs,PTHREAD_MUTEX_RECURSIVE); | ||||
| 	assert(!res); | ||||
| 	res = pthread_mutex_init(&mMutex,&mAttribs); | ||||
| 	assert(!res); | ||||
| } | ||||
|  | ||||
|  | ||||
| Mutex::~Mutex() | ||||
| { | ||||
| 	pthread_mutex_destroy(&mMutex); | ||||
| 	bool res = pthread_mutexattr_destroy(&mAttribs); | ||||
| 	assert(!res); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| /** Block for the signal up to the cancellation timeout. */ | ||||
| void Signal::wait(Mutex& wMutex, unsigned timeout) const | ||||
| { | ||||
| 	Timeval then(timeout); | ||||
| 	struct timespec waitTime = then.timespec(); | ||||
| 	pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime); | ||||
| } | ||||
|  | ||||
| void set_selfthread_name(const char *name) | ||||
| { | ||||
|   | ||||
| @@ -28,143 +28,96 @@ | ||||
| #ifndef THREADS_H | ||||
| #define THREADS_H | ||||
|  | ||||
| #include "config.h" | ||||
|  | ||||
| #include <chrono> | ||||
| #include <mutex> | ||||
| #include <condition_variable> | ||||
| #include <pthread.h> | ||||
| #include <iostream> | ||||
| #include <assert.h> | ||||
| #include <cassert> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include "config.h" | ||||
| #include "Timeval.h" | ||||
|  | ||||
| class Mutex; | ||||
|  | ||||
|  | ||||
| /**@name Multithreaded access for standard streams. */ | ||||
| //@{ | ||||
|  | ||||
| /**@name Functions for gStreamLock. */ | ||||
| //@{ | ||||
| extern Mutex gStreamLock;	///< global lock for cout and cerr | ||||
| void lockCerr();		///< call prior to writing cerr | ||||
| void unlockCerr();		///< call after writing cerr | ||||
| void lockCout();		///< call prior to writing cout | ||||
| void unlockCout();		///< call after writing cout | ||||
| //@} | ||||
|  | ||||
| /**@name Macros for standard messages. */ | ||||
| //@{ | ||||
| #define COUT(text) { lockCout(); std::cout << text; unlockCout(); } | ||||
| #define CERR(text) { lockCerr(); std::cerr << __FILE__ << ":" << __LINE__ << ": " << text; unlockCerr(); } | ||||
| #ifdef NDEBUG | ||||
| #define DCOUT(text) {} | ||||
| #define OBJDCOUT(text) {} | ||||
| #else | ||||
| #define DCOUT(text) { COUT(__FILE__ << ":" << __LINE__ << " " << text); } | ||||
| #define OBJDCOUT(text) { DCOUT(this << " " << text); } | ||||
| #endif | ||||
| //@} | ||||
| //@} | ||||
|  | ||||
|  | ||||
|  | ||||
| /**@defgroup C++ wrappers for pthread mechanisms. */ | ||||
| //@{ | ||||
|  | ||||
| /** A class for recursive mutexes based on pthread_mutex. */ | ||||
| /** A class for recursive mutexes. */ | ||||
| class Mutex { | ||||
| 	std::recursive_mutex m; | ||||
|  | ||||
| 	private: | ||||
|     public: | ||||
|  | ||||
| 	pthread_mutex_t mMutex; | ||||
| 	pthread_mutexattr_t mAttribs; | ||||
| 	void lock() { | ||||
| 		m.lock(); | ||||
| 	} | ||||
|  | ||||
| 	public: | ||||
| 	bool trylock() { | ||||
| 		return m.try_lock(); | ||||
| 	} | ||||
|  | ||||
| 	Mutex(); | ||||
|  | ||||
| 	~Mutex(); | ||||
|  | ||||
| 	void lock() { pthread_mutex_lock(&mMutex); } | ||||
|  | ||||
| 	bool trylock() { return pthread_mutex_trylock(&mMutex)==0; } | ||||
|  | ||||
| 	void unlock() { pthread_mutex_unlock(&mMutex); } | ||||
| 	void unlock() { | ||||
| 		m.unlock(); | ||||
| 	} | ||||
|  | ||||
| 	friend class Signal; | ||||
|  | ||||
| }; | ||||
|  | ||||
|  | ||||
| class ScopedLock { | ||||
| 	Mutex &mMutex; | ||||
|  | ||||
| 	private: | ||||
| 	Mutex& mMutex; | ||||
|  | ||||
| 	public: | ||||
| 	ScopedLock(Mutex& wMutex) :mMutex(wMutex) { mMutex.lock(); } | ||||
| 	~ScopedLock() { mMutex.unlock(); } | ||||
|  | ||||
|     public: | ||||
| 	ScopedLock(Mutex &wMutex) : mMutex(wMutex) { | ||||
| 		mMutex.lock(); | ||||
| 	} | ||||
| 	~ScopedLock() { | ||||
| 		mMutex.unlock(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| /** A C++ interthread signal based on pthread condition variables. */ | ||||
| /** A C++ interthread signal. */ | ||||
| class Signal { | ||||
| 	/* any, because for some reason our mutex is recursive... */ | ||||
| 	std::condition_variable_any mSignal; | ||||
|  | ||||
| 	private: | ||||
|     public: | ||||
|  | ||||
| 	mutable pthread_cond_t mSignal; | ||||
| 	void wait(Mutex &wMutex, unsigned timeout) { | ||||
| 		mSignal.wait_for(wMutex.m, std::chrono::milliseconds(timeout)); | ||||
| 	} | ||||
|  | ||||
| 	public: | ||||
| 	void wait(Mutex &wMutex) { | ||||
| 		mSignal.wait(wMutex.m); | ||||
| 	} | ||||
|  | ||||
| 	Signal() { int s = pthread_cond_init(&mSignal,NULL); assert(!s); } | ||||
|  | ||||
| 	~Signal() { pthread_cond_destroy(&mSignal); } | ||||
|  | ||||
| 	/** | ||||
| 		Block for the signal up to the cancellation timeout. | ||||
| 		Under Linux, spurious returns are possible. | ||||
| 	*/ | ||||
| 	void wait(Mutex& wMutex, unsigned timeout) const; | ||||
|  | ||||
| 	/** | ||||
| 		Block for the signal. | ||||
| 		Under Linux, spurious returns are possible. | ||||
| 	*/ | ||||
| 	void wait(Mutex& wMutex) const | ||||
| 		{ pthread_cond_wait(&mSignal,&wMutex.mMutex); } | ||||
|  | ||||
| 	void signal() { pthread_cond_signal(&mSignal); } | ||||
|  | ||||
| 	void broadcast() { pthread_cond_broadcast(&mSignal); } | ||||
| 	void signal() { | ||||
| 		mSignal.notify_one(); | ||||
| 	} | ||||
|  | ||||
| 	void broadcast() { | ||||
| 		mSignal.notify_all(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
| #define START_THREAD(thread,function,argument) \ | ||||
| 	thread.start((void *(*)(void*))function, (void*)argument); | ||||
|  | ||||
| void set_selfthread_name(const char *name); | ||||
| void thread_enable_cancel(bool cancel); | ||||
|  | ||||
| /** A C++ wrapper for pthread threads.  */ | ||||
| class Thread { | ||||
|  | ||||
| 	private: | ||||
|  | ||||
|     private: | ||||
| 	pthread_t mThread; | ||||
| 	pthread_attr_t mAttrib; | ||||
| 	// FIXME -- Can this be reduced now? | ||||
| 	size_t mStackSize; | ||||
|  | ||||
|  | ||||
| 	public: | ||||
|  | ||||
|     public: | ||||
| 	/** Create a thread in a non-running state. */ | ||||
| 	Thread(size_t wStackSize = 0):mThread((pthread_t)0) { | ||||
| 		pthread_attr_init(&mAttrib);	// (pat) moved this here. | ||||
| 		mStackSize=wStackSize; | ||||
| 	Thread(size_t wStackSize = 0) : mThread((pthread_t)0) | ||||
| 	{ | ||||
| 		pthread_attr_init(&mAttrib); // (pat) moved this here. | ||||
| 		mStackSize = wStackSize; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -172,14 +125,17 @@ class Thread { | ||||
| 		It should be stopped and joined. | ||||
| 	*/ | ||||
| 	// (pat) If the Thread is destroyed without being started, then mAttrib is undefined.  Oops. | ||||
| 	~Thread() { pthread_attr_destroy(&mAttrib); } | ||||
|  | ||||
| 	~Thread() | ||||
| 	{ | ||||
| 		pthread_attr_destroy(&mAttrib); | ||||
| 	} | ||||
|  | ||||
| 	/** Start the thread on a task. */ | ||||
| 	void start(void *(*task)(void*), void *arg); | ||||
| 	void start(void *(*task)(void *), void *arg); | ||||
|  | ||||
| 	/** Join a thread that will stop on its own. */ | ||||
| 	void join() { | ||||
| 	void join() | ||||
| 	{ | ||||
| 		if (mThread) { | ||||
| 			int s = pthread_join(mThread, NULL); | ||||
| 			assert(!s); | ||||
| @@ -187,7 +143,10 @@ class Thread { | ||||
| 	} | ||||
|  | ||||
| 	/** Send cancellation to thread */ | ||||
| 	void cancel() { pthread_cancel(mThread); } | ||||
| 	void cancel() | ||||
| 	{ | ||||
| 		pthread_cancel(mThread); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| #ifdef HAVE_ATOMIC_OPS | ||||
|   | ||||
| @@ -57,4 +57,15 @@ struct trx_cfg { | ||||
| 	unsigned int stack_size; | ||||
| 	unsigned int num_chans; | ||||
| 	struct trx_chan chans[TRX_CHAN_MAX]; | ||||
| 	struct { | ||||
| 		bool ul_freq_override; | ||||
| 		bool dl_freq_override; | ||||
| 		bool ul_gain_override; | ||||
| 		bool dl_gain_override; | ||||
| 		double ul_freq; | ||||
| 		double dl_freq; | ||||
| 		double ul_gain; | ||||
| 		double dl_gain; | ||||
| 	} overrides; | ||||
| 	bool use_va; | ||||
| }; | ||||
|   | ||||
| @@ -285,6 +285,79 @@ DEFUN_ATTR(cfg_ul_fn_offset, cfg_ul_fn_offset_cmd, | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
|  | ||||
| DEFUN_ATTR(cfg_ul_freq_override, cfg_ul_freq_override_cmd, | ||||
| 	   "ul-freq-override FLOAT", | ||||
| 	   "Overrides Rx carrier frequency\n" | ||||
| 	   "Frequency in Hz (e.g. 145300000)\n", | ||||
| 	   CMD_ATTR_HIDDEN) | ||||
| { | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
|  | ||||
| 	trx->cfg.overrides.ul_freq_override = true; | ||||
| 	trx->cfg.overrides.ul_freq = atof(argv[0]); | ||||
|  | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
| DEFUN_ATTR(cfg_dl_freq_override, cfg_dl_freq_override_cmd, | ||||
| 	   "dl-freq-override FLOAT", | ||||
| 	   "Overrides Tx carrier frequency\n" | ||||
| 	   "Frequency in Hz (e.g. 145300000)\n", | ||||
| 	   CMD_ATTR_HIDDEN) | ||||
| { | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
|  | ||||
| 	trx->cfg.overrides.dl_freq_override = true; | ||||
| 	trx->cfg.overrides.dl_freq = atof(argv[0]); | ||||
|  | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
|  | ||||
| DEFUN_ATTR(cfg_ul_gain_override, cfg_ul_gain_override_cmd, | ||||
| 	   "ul-gain-override FLOAT", | ||||
| 	   "Overrides Rx gain\n" | ||||
| 	   "gain in dB\n", | ||||
| 	   CMD_ATTR_HIDDEN) | ||||
| { | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
|  | ||||
| 	trx->cfg.overrides.ul_gain_override = true; | ||||
| 	trx->cfg.overrides.ul_gain = atof(argv[0]); | ||||
|  | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
| DEFUN_ATTR(cfg_dl_gain_override, cfg_dl_gain_override_cmd, | ||||
| 	   "dl-gain-override FLOAT", | ||||
| 	   "Overrides Tx gain\n" | ||||
| 	   "gain in dB\n", | ||||
| 	   CMD_ATTR_HIDDEN) | ||||
| { | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
|  | ||||
| 	trx->cfg.overrides.dl_gain_override = true; | ||||
| 	trx->cfg.overrides.dl_gain = atof(argv[0]); | ||||
|  | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
|  | ||||
| DEFUN_ATTR(cfg_use_viterbi, cfg_use_viterbi_cmd, | ||||
| 	"viterbi-eq (disable|enable)", | ||||
| 	"Use viterbi equalizer for gmsk (default=disable)\n" | ||||
| 	"Disable VA\n" | ||||
| 	"Enable VA\n", | ||||
| 	CMD_ATTR_HIDDEN) | ||||
| { | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
|  | ||||
| 	if (strcmp("disable", argv[0]) == 0) | ||||
| 		trx->cfg.use_va = false; | ||||
| 	else if (strcmp("enable", argv[0]) == 0) | ||||
| 		trx->cfg.use_va = true; | ||||
| 	else | ||||
| 		return CMD_WARNING; | ||||
|  | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
|  | ||||
| DEFUN(cfg_swap_channels, cfg_swap_channels_cmd, | ||||
| 	"swap-channels (disable|enable)", | ||||
| 	"Swap primary and secondary channels of the PHY (if any)\n" | ||||
| @@ -466,7 +539,6 @@ DEFUN_ATTR(cfg_ctr_error_threshold, cfg_ctr_error_threshold_cmd, | ||||
| 	int rc; | ||||
| 	struct ctr_threshold ctr; | ||||
|  | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
| 	rc = vty_ctr_name_2_id(argv[0]); | ||||
| 	if (rc < 0) { | ||||
| 		vty_out(vty, "No valid ctr_name found for ctr-error-threshold %s%s", | ||||
| @@ -498,7 +570,6 @@ DEFUN_ATTR(cfg_no_ctr_error_threshold, cfg_no_ctr_error_threshold_cmd, | ||||
| 	int rc; | ||||
| 	struct ctr_threshold ctr; | ||||
|  | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
| 	rc = vty_ctr_name_2_id(argv[0]); | ||||
| 	if (rc < 0) { | ||||
| 		vty_out(vty, "No valid ctr_name found for ctr-error-threshold %s%s", | ||||
| @@ -609,7 +680,7 @@ static int config_write_trx(struct vty *vty) | ||||
| 		vty_out(vty, " remote-ip %s%s", trx->cfg.remote_addr, VTY_NEWLINE); | ||||
| 	if (trx->cfg.base_port != DEFAULT_TRX_PORT) | ||||
| 		vty_out(vty, " base-port %u%s", trx->cfg.base_port, VTY_NEWLINE); | ||||
| 	if (trx->cfg.dev_args) | ||||
| 	if (strlen(trx->cfg.dev_args)) | ||||
| 		vty_out(vty, " dev-args %s%s", trx->cfg.dev_args, VTY_NEWLINE); | ||||
| 	if (trx->cfg.tx_sps != DEFAULT_TX_SPS) | ||||
| 		vty_out(vty, " tx-sps %u%s", trx->cfg.tx_sps, VTY_NEWLINE); | ||||
| @@ -640,6 +711,16 @@ static int config_write_trx(struct vty *vty) | ||||
| 		vty_out(vty, " stack-size %u%s", trx->cfg.stack_size, VTY_NEWLINE); | ||||
| 	if (trx->cfg.ul_fn_offset != 0) | ||||
| 		vty_out(vty, " ul-fn-offset %d%s", trx->cfg.ul_fn_offset, VTY_NEWLINE); | ||||
| 	if (trx->cfg.overrides.dl_freq_override) | ||||
| 		vty_out(vty, " dl-freq-override %f%s", trx->cfg.overrides.dl_freq, VTY_NEWLINE); | ||||
| 	if (trx->cfg.overrides.ul_freq_override) | ||||
| 		vty_out(vty, " ul-freq-override %f%s", trx->cfg.overrides.ul_freq, VTY_NEWLINE); | ||||
| 	if (trx->cfg.overrides.dl_gain_override) | ||||
| 		vty_out(vty, " dl-gain-override %f%s", trx->cfg.overrides.dl_gain, VTY_NEWLINE); | ||||
| 	if (trx->cfg.overrides.ul_gain_override) | ||||
| 		vty_out(vty, " ul-gain-override %f%s", trx->cfg.overrides.ul_gain, VTY_NEWLINE); | ||||
| 	if (trx->cfg.use_va) | ||||
| 		vty_out(vty, " viterbi-eq %s%s", trx->cfg.use_va ? "enable" : "disable", VTY_NEWLINE); | ||||
| 	trx_rate_ctr_threshold_write_config(vty, " "); | ||||
|  | ||||
| 	for (i = 0; i < trx->cfg.num_chans; i++) { | ||||
| @@ -762,6 +843,7 @@ struct trx_ctx *vty_trx_ctx_alloc(void *talloc_ctx) | ||||
| 	trx->cfg.rx_sps = DEFAULT_RX_SPS; | ||||
| 	trx->cfg.filler = FILLER_ZERO; | ||||
| 	trx->cfg.rssi_offset = 0.0f; | ||||
| 	trx->cfg.dev_args = talloc_strdup(trx, ""); | ||||
|  | ||||
| 	return trx; | ||||
| } | ||||
| @@ -804,6 +886,11 @@ int trx_vty_init(struct trx_ctx* trx) | ||||
|  | ||||
| 	install_element(TRX_NODE, &cfg_chan_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_ul_fn_offset_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_ul_freq_override_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_dl_freq_override_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_ul_gain_override_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_dl_gain_override_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_use_viterbi_cmd); | ||||
| 	install_node(&chan_node, dummy_config_write); | ||||
| 	install_element(CHAN_NODE, &cfg_chan_rx_path_cmd); | ||||
| 	install_element(CHAN_NODE, &cfg_chan_tx_path_cmd); | ||||
|   | ||||
| @@ -29,12 +29,11 @@ AM_CXXFLAGS = -Wall -pthread | ||||
| SUBDIRS = | ||||
|  | ||||
| if ENABLE_MS_TRX | ||||
| 	SUBDIRS += osmocom-bb/src/host/trxcon | ||||
| SUBDIRS += $(LIBTRXCON_DIR) | ||||
| endif | ||||
|  | ||||
| # Order must be preserved | ||||
| SUBDIRS += \ | ||||
| 	osmocom-bb/src/host/trxcon \ | ||||
| 	CommonLibs \ | ||||
| 	GSM \ | ||||
| 	Transceiver52M \ | ||||
|   | ||||
| @@ -29,7 +29,7 @@ unlike the built-in complex<> templates, these inline most operations for speed | ||||
| template<class Real> class Complex { | ||||
|  | ||||
| public: | ||||
|  | ||||
|   typedef Real value_type; | ||||
|   Real r, i; | ||||
|  | ||||
|   /**@name constructors */ | ||||
|   | ||||
| @@ -23,9 +23,9 @@ include $(top_srcdir)/Makefile.common | ||||
|  | ||||
| SUBDIRS = arch device | ||||
|  | ||||
| AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/ | ||||
| AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/ | ||||
| AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/ | ||||
| AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common | ||||
| AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
| AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
|  | ||||
| noinst_LTLIBRARIES = libtransceiver_common.la | ||||
|  | ||||
| @@ -63,9 +63,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 \ | ||||
| @@ -74,25 +72,47 @@ COMMON_LDADD = \ | ||||
| 	$(COMMON_LA) \ | ||||
| 	$(FFTWF_LIBS) \ | ||||
| 	$(LIBOSMOCORE_LIBS) \ | ||||
| 	$(LIBOSMOCODING_LIBS) \ | ||||
| 	$(LIBOSMOCTRL_LIBS) \ | ||||
| 	$(LIBOSMOVTY_LIBS) | ||||
|  | ||||
| TRXCON_LDADD = \ | ||||
| 	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \ | ||||
| 	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a | ||||
| if ENABLE_MS_TRX | ||||
| AM_CPPFLAGS += -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/ | ||||
| AM_CPPFLAGS += -I${srcdir} | ||||
|  | ||||
| MS_SOURCES = \ | ||||
| TRXCON_LDADD = \ | ||||
| 	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a \ | ||||
| 	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \ | ||||
| 	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1gprs.a \ | ||||
| 	$(LIBOSMOCODING_LIBS) | ||||
|  | ||||
| MS_LOWER_SRC = \ | ||||
| 	ms/sch.c \ | ||||
| 	ms/ms.cpp \ | ||||
| 	ms/ms_rx_lower.cpp | ||||
| 	ms/ms_rx_lower.cpp \ | ||||
| 	grgsm_vitac/grgsm_vitac.cpp \ | ||||
| 	grgsm_vitac/viterbi_detector.cc | ||||
|  | ||||
| MS_UPPER_SRC = \ | ||||
| 	ms/ms_upper.cpp \ | ||||
| 	ms/l1ctl_server.c \ | ||||
| 	ms/logging.c \ | ||||
| 	ms/l1ctl_server_cb.cpp \ | ||||
| 	ms/ms_trxcon_if.cpp | ||||
|  | ||||
| noinst_HEADERS += \ | ||||
| 	ms/ms.h \ | ||||
|    	ms/ms.h \ | ||||
| 	ms/bladerf_specific.h \ | ||||
| 	ms/uhd_specific.h \ | ||||
| 	ms/ms_upper.h \ | ||||
| 	ms/itrq.h | ||||
| 	ms/ms_trxcon_if.h \ | ||||
| 	ms/itrq.h \ | ||||
| 	ms/sch.h \ | ||||
| 	ms/threadpool.h \ | ||||
| 	grgsm_vitac/viterbi_detector.h \ | ||||
| 	grgsm_vitac/constants.h \ | ||||
| 	grgsm_vitac/grgsm_vitac.h | ||||
|  | ||||
| endif | ||||
|  | ||||
| bin_PROGRAMS = | ||||
|  | ||||
| @@ -107,22 +127,13 @@ osmo_trx_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) | ||||
|  | ||||
| if ENABLE_MS_TRX | ||||
| bin_PROGRAMS += osmo-trx-ms-uhd | ||||
| osmo_trx_ms_uhd_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.cpp ms/l1ctl_server_cb.cpp | ||||
| osmo_trx_ms_uhd_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC) | ||||
| osmo_trx_ms_uhd_LDADD = \ | ||||
| 	$(builddir)/device/uhd/libdevice.la \ | ||||
| 	$(COMMON_LDADD) \ | ||||
| 	$(UHD_LIBS) \ | ||||
| 	$(TRXCON_LDADD) | ||||
| osmo_trx_ms_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD | ||||
|  | ||||
| bin_PROGRAMS += osmo-trx-syncthing-uhd | ||||
| osmo_trx_syncthing_uhd_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp | ||||
| osmo_trx_syncthing_uhd_LDADD = \ | ||||
| 	$(builddir)/device/uhd/libdevice.la \ | ||||
| 	$(COMMON_LDADD) \ | ||||
| 	$(UHD_LIBS) \ | ||||
| 	$(TRXCON_LDADD) | ||||
| osmo_trx_syncthing_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DSYNCTHINGONLY -DBUILDUHD | ||||
| endif | ||||
| endif | ||||
|  | ||||
| @@ -157,22 +168,13 @@ osmo_trx_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(LMS_CFLAGS) | ||||
|  | ||||
| if ENABLE_MS_TRX | ||||
| bin_PROGRAMS += osmo-trx-ms-blade | ||||
| osmo_trx_ms_blade_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.cpp ms/l1ctl_server_cb.cpp | ||||
| osmo_trx_ms_blade_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC) | ||||
| osmo_trx_ms_blade_LDADD = \ | ||||
| 	$(builddir)/device/bladerf/libdevice.la \ | ||||
| 	$(COMMON_LDADD) \ | ||||
| 	$(BLADE_LIBS) \ | ||||
| 	$(TRXCON_LDADD) | ||||
| osmo_trx_ms_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE | ||||
|  | ||||
| bin_PROGRAMS += osmo-trx-syncthing-blade | ||||
| 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) \ | ||||
| 	$(TRXCON_LDADD) | ||||
| osmo_trx_syncthing_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DSYNCTHINGONLY -DBUILDBLADE -I../device/ipc | ||||
| endif | ||||
| endif | ||||
|  | ||||
| @@ -183,24 +185,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) | ||||
| # if ENABLE_MS_TRX | ||||
| # bin_PROGRAMS += osmo-trx-ms-ipc | ||||
| # osmo_trx_ms_ipc_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp | ||||
| # osmo_trx_ms_ipc_LDADD = \ | ||||
| # 	$(COMMON_LDADD) \ | ||||
| # 	$(TRXCON_LDADD) | ||||
| # 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_SOURCES) ms/ms_rx_burst_test.cpp | ||||
| # osmo_trx_syncthing_ipc_LDADD =  $(COMMON_LDADD) | ||||
| # osmo_trx_syncthing_ipc_CPPFLAGS  = $(AM_CPPFLAGS) -DSYNCTHINGONLY -DBUILDIPC  -I./device/ipc2 -I../device/ipc2 | ||||
| # endif | ||||
| endif | ||||
|   | ||||
| @@ -29,6 +29,7 @@ | ||||
| #include <fstream> | ||||
| #include "Transceiver.h" | ||||
| #include <Logger.h> | ||||
| #include <grgsm_vitac/grgsm_vitac.h> | ||||
|  | ||||
| extern "C" { | ||||
| #include "osmo_signal.h" | ||||
| @@ -135,12 +136,13 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r | ||||
| Transceiver::Transceiver(const struct trx_cfg *cfg, | ||||
|                          GSM::Time wTransmitLatency, | ||||
|                          RadioInterface *wRadioInterface) | ||||
|   : cfg(cfg), mClockSocket(-1), | ||||
|     mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr), | ||||
|     mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), | ||||
|     mChans(cfg->num_chans), mOn(false), mForceClockInterface(false), | ||||
|     mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), | ||||
|     mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0) | ||||
|   : mChans(cfg->num_chans), cfg(cfg), | ||||
|     mCtrlSockets(mChans), mClockSocket(-1), | ||||
|     mTxPriorityQueues(mChans), mReceiveFIFO(mChans), | ||||
|     mRxServiceLoopThreads(mChans), mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr), | ||||
|     mTxPriorityQueueServiceLoopThreads(mChans), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), | ||||
|     mOn(false),mForceClockInterface(false), mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), | ||||
|     mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0), mVersionTRXD(mChans), mStates(mChans) | ||||
| { | ||||
|   txFullScale = mRadioInterface->fullScaleInputValue(); | ||||
|   rxFullScale = mRadioInterface->fullScaleOutputValue(); | ||||
| @@ -207,15 +209,10 @@ bool Transceiver::init() | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   mDataSockets.resize(mChans, -1); | ||||
|   mCtrlSockets.resize(mChans); | ||||
|   mTxPriorityQueueServiceLoopThreads.resize(mChans); | ||||
|   mRxServiceLoopThreads.resize(mChans); | ||||
|   initvita(); | ||||
|  | ||||
|   mDataSockets.resize(mChans, -1); | ||||
|  | ||||
|   mTxPriorityQueues.resize(mChans); | ||||
|   mReceiveFIFO.resize(mChans); | ||||
|   mStates.resize(mChans); | ||||
|   mVersionTRXD.resize(mChans); | ||||
|  | ||||
|   /* Filler table retransmissions - support only on channel 0 */ | ||||
|   if (cfg->filler == FILLER_DUMMY) | ||||
| @@ -620,6 +617,44 @@ double Transceiver::rssiOffset(size_t chan) | ||||
|   return mRadioInterface->rssiOffset(chan) + cfg->rssi_offset; | ||||
| } | ||||
|  | ||||
| static SoftVector *demodAnyBurst_va(const signalVector &burst, CorrType type, int sps, int rach_max_toa, int tsc) | ||||
| { | ||||
| 	auto conved_beg = reinterpret_cast<const std::complex<float> *>(&burst.begin()[0]); | ||||
| 	std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; | ||||
| 	float ncmax; | ||||
| 	const unsigned burst_len_bits = 148 + 8; | ||||
| 	char demodded_softbits[burst_len_bits]; | ||||
| 	SoftVector *bits = new SoftVector(burst_len_bits); | ||||
|  | ||||
| 	if (type == CorrType::TSC) { | ||||
| 		auto rach_burst_start = get_norm_chan_imp_resp(conved_beg, chan_imp_resp, &ncmax, tsc); | ||||
| 		rach_burst_start = std::max(rach_burst_start, 0); | ||||
| 		detect_burst_nb(conved_beg, chan_imp_resp, rach_burst_start, demodded_softbits); | ||||
| 	} else { | ||||
| 		auto normal_burst_start = get_access_imp_resp(conved_beg, chan_imp_resp, &ncmax, 0); | ||||
| 		normal_burst_start = std::max(normal_burst_start, 0); | ||||
| 		detect_burst_ab(conved_beg, chan_imp_resp, normal_burst_start, demodded_softbits, rach_max_toa); | ||||
| 	} | ||||
|  | ||||
| 	float *s = &bits->begin()[0]; | ||||
| 	for (unsigned int i = 0; i < 148; i++) | ||||
| 		s[i] = demodded_softbits[i] * -1; | ||||
| 	for (unsigned int i = 148; i < burst_len_bits; i++) | ||||
| 		s[i] = 0; | ||||
| 	return bits; | ||||
| } | ||||
|  | ||||
| #define USE_VA | ||||
|  | ||||
| #ifdef USE_VA | ||||
| // signalvector is owning despite claiming not to, but we can pretend, too.. | ||||
| static void dummy_free(void *wData){}; | ||||
| static void *dummy_alloc(size_t newSize) | ||||
| { | ||||
| 	return 0; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * Pull bursts from the FIFO and handle according to the slot | ||||
|  * and burst correlation type. Equalzation is currently disabled. | ||||
| @@ -640,6 +675,9 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) | ||||
|   TransceiverState *state = &mStates[chan]; | ||||
|   bool ctr_changed = false; | ||||
|   double rssi_offset; | ||||
|   static complex burst_shift_buffer[625]; | ||||
|   static signalVector shift_vec(burst_shift_buffer, 0, 625, dummy_alloc, dummy_free); | ||||
|   signalVector *shvec_ptr = &shift_vec; | ||||
|  | ||||
|   /* Blocking FIFO read */ | ||||
|   radioVector *radio_burst = mReceiveFIFO[chan]->read(); | ||||
| @@ -719,8 +757,15 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) | ||||
|   max_toa = (type == RACH || type == EXT_RACH) ? | ||||
|             mMaxExpectedDelayAB : mMaxExpectedDelayNB; | ||||
|  | ||||
|   if (cfg->use_va) { | ||||
|     // shifted burst copy to make the old demod and detection happy | ||||
|     std::copy(burst->begin() + 20, burst->end() - 20, shift_vec.begin()); | ||||
|   } else { | ||||
|     shvec_ptr = burst; | ||||
|   } | ||||
|  | ||||
|   /* Detect normal or RACH bursts */ | ||||
|   rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, cfg->rx_sps, type, max_toa, &ebp); | ||||
|   rc = detectAnyBurst(*shvec_ptr, mTSC, BURST_THRESH, cfg->rx_sps, type, max_toa, &ebp); | ||||
|   if (rc <= 0) { | ||||
|     if (rc == -SIGERR_CLIP) { | ||||
|       LOGCHAN(chan, DTRXDUL, INFO) << "Clipping detected on received RACH or Normal Burst"; | ||||
| @@ -734,7 +779,13 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) | ||||
|     goto ret_idle; | ||||
|   } | ||||
|  | ||||
|   rxBurst = demodAnyBurst(*burst, (CorrType) rc, cfg->rx_sps, &ebp); | ||||
|   if (cfg->use_va) { | ||||
|     scaleVector(*burst, { (1. / (float)((1 << 14) - 1)), 0 }); | ||||
|     rxBurst = demodAnyBurst_va(*burst, (CorrType)rc, cfg->rx_sps, max_toa, mTSC); | ||||
|   } else { | ||||
|     rxBurst = demodAnyBurst(*shvec_ptr, (CorrType)rc, cfg->rx_sps, &ebp); | ||||
|   } | ||||
|  | ||||
|   bi->toa = ebp.toa; | ||||
|   bi->tsc = ebp.tsc; | ||||
|   bi->ci = ebp.ci; | ||||
|   | ||||
| @@ -148,6 +148,7 @@ public: | ||||
|   } ChannelCombination; | ||||
|  | ||||
| private: | ||||
|   size_t mChans; | ||||
| struct ctrl_msg { | ||||
|   char data[101]; | ||||
|   ctrl_msg() {}; | ||||
| @@ -161,9 +162,9 @@ struct ctrl_sock_state { | ||||
|   } | ||||
|   ~ctrl_sock_state() { | ||||
|       if(conn_bfd.fd >= 0) { | ||||
|           osmo_fd_unregister(&conn_bfd); | ||||
|           close(conn_bfd.fd); | ||||
|           conn_bfd.fd = -1; | ||||
|           osmo_fd_unregister(&conn_bfd); | ||||
|       } | ||||
|   } | ||||
| }; | ||||
| @@ -218,7 +219,7 @@ struct ctrl_sock_state { | ||||
|   /** drive handling of control messages from GSM core */ | ||||
|   int ctrl_sock_handle_rx(int chan); | ||||
|  | ||||
|   size_t mChans; | ||||
|  | ||||
|   bool mOn;	                           ///< flag to indicate that transceiver is powered on | ||||
|   bool mForceClockInterface;           ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started | ||||
|   bool mHandover[8][8];                ///< expect handover to the timeslot/subslot | ||||
|   | ||||
| @@ -3,7 +3,7 @@ include $(top_srcdir)/Makefile.common | ||||
| SUBDIRS = common | ||||
|  | ||||
| if DEVICE_IPC | ||||
| SUBDIRS += ipc ipc2 | ||||
| SUBDIRS += ipc | ||||
| endif | ||||
|  | ||||
| if DEVICE_USRP1 | ||||
|   | ||||
| @@ -39,48 +39,19 @@ extern "C" { | ||||
|  | ||||
| #define SAMPLE_BUF_SZ (1 << 20) | ||||
|  | ||||
| /* | ||||
|  * USRP version dependent device timings | ||||
|  */ | ||||
|  | ||||
| #define B2XX_TIMING_4_4SPS 6.18462e-5 | ||||
|  | ||||
| #define CHKRET()                                                                                                       \ | ||||
| 	{                                                                                                              \ | ||||
| 		if (status != 0)                                                                                       \ | ||||
| 			fprintf(stderr, "%s:%s:%d %s\n", __FILE__, __FUNCTION__, __LINE__, bladerf_strerror(status));  \ | ||||
| 			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; | ||||
|  | ||||
| /* Device parameter descriptor */ | ||||
| struct dev_desc { | ||||
| 	unsigned channels; | ||||
| 	double mcr; | ||||
| 	double rate; | ||||
| 	double offset; | ||||
| 	std::string str; | ||||
| }; | ||||
|  | ||||
| static const std::map<dev_key, dev_desc> dev_param_map{ | ||||
| static const dev_map_t 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{ | ||||
| static const power_map_t 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 } }, | ||||
| @@ -99,11 +70,10 @@ 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(InterfaceType iface, const struct trx_cfg *cfg) | ||||
| 	: RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), dev(nullptr), | ||||
| 	  rx_gain_min(0.0), rx_gain_max(0.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) | ||||
| { | ||||
| } | ||||
|  | ||||
| @@ -120,47 +90,6 @@ blade_device::~blade_device() | ||||
| 		delete rx_buffers[i]; | ||||
| } | ||||
|  | ||||
| void blade_device::assign_band_desc(enum gsm_band req_band) | ||||
| { | ||||
| 	dev_band_map_it it; | ||||
|  | ||||
| 	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"; | ||||
| 		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()) | ||||
| 	band_desc = it->second; | ||||
| } | ||||
|  | ||||
| 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); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (req_band != band) { | ||||
| 		band = req_band; | ||||
| 		assign_band_desc(band); | ||||
| 	} | ||||
| 	band_ass_curr_sess = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| 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..."; | ||||
| 		assign_band_desc(GSM_BAND_900); | ||||
| 	} | ||||
| 	desc = band_desc; | ||||
| } | ||||
|  | ||||
| void blade_device::init_gains() | ||||
| { | ||||
| 	double tx_gain_min, tx_gain_max; | ||||
| @@ -181,7 +110,7 @@ void blade_device::init_gains() | ||||
| 		bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m); | ||||
| 		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); | ||||
| @@ -190,7 +119,7 @@ void blade_device::init_gains() | ||||
| 				 << 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() | ||||
| @@ -207,7 +136,7 @@ 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); | ||||
| @@ -222,8 +151,6 @@ void blade_device::init_gains() | ||||
|  | ||||
| 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; | ||||
| 	auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual); | ||||
| 	CHKRET() | ||||
| @@ -237,8 +164,7 @@ void blade_device::set_rates() | ||||
| 	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) | ||||
| @@ -298,7 +224,7 @@ 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); | ||||
|  | ||||
| @@ -330,15 +256,15 @@ 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) | ||||
| int blade_device::open() | ||||
| { | ||||
| 	bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE); | ||||
| 	bladerf_set_usb_reset_on_open(true); | ||||
| 	auto success = bladerf_open(&dev, args.c_str()); | ||||
| 	auto success = bladerf_open(&dev, cfg->dev_args); | ||||
| 	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 << "'"; | ||||
| 		LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << cfg->dev_args << "'"; | ||||
| 		if (num_devs) { | ||||
| 			for (int i = 0; i < num_devs; i++) | ||||
| 				LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial; | ||||
| @@ -356,7 +282,6 @@ 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); | ||||
| @@ -365,7 +290,7 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	rx_gains.resize(chans); | ||||
| 	rx_buffers.resize(chans); | ||||
|  | ||||
| 	switch (ref) { | ||||
| 	switch (cfg->clock_ref) { | ||||
| 	case REF_INTERNAL: | ||||
| 	case REF_EXTERNAL: | ||||
| 		break; | ||||
| @@ -374,7 +299,7 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	if (ref == REF_EXTERNAL) { | ||||
| 	if (cfg->clock_ref == REF_EXTERNAL) { | ||||
| 		bool is_locked; | ||||
| 		int status = bladerf_set_pll_enable(dev, true); | ||||
| 		CHKRET() | ||||
| @@ -393,7 +318,8 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	LOGC(DDEV, INFO) << "Selected clock source is " << ((ref == REF_INTERNAL) ? "internal" : "external 10Mhz"); | ||||
| 	LOGC(DDEV, INFO) | ||||
| 		<< "Selected clock source is " << ((cfg->clock_ref == REF_INTERNAL) ? "internal" : "external 10Mhz"); | ||||
|  | ||||
| 	set_rates(); | ||||
|  | ||||
| @@ -404,7 +330,7 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels) | ||||
|  | ||||
| 	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; | ||||
| @@ -419,17 +345,14 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	/* 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; | ||||
| @@ -463,7 +386,6 @@ bool blade_device::start() | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	// Start streaming | ||||
| 	if (!restart()) | ||||
| 		return false; | ||||
|  | ||||
| @@ -480,7 +402,7 @@ bool blade_device::stop() | ||||
| 	for (size_t i = 0; i < rx_buffers.size(); i++) | ||||
| 		rx_buffers[i]->reset(); | ||||
|  | ||||
| 	band_ass_curr_sess = false; | ||||
| 	band_reset(); | ||||
|  | ||||
| 	started = false; | ||||
| 	return true; | ||||
| @@ -515,22 +437,16 @@ int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun | ||||
|  | ||||
| 	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); | ||||
| 			LOGC(DDEV, ERROR) << "RX broken: " << bladerf_strerror(status); | ||||
| 		if (meta.flags & BLADERF_META_STATUS_OVERRUN) | ||||
| 			std::cerr << "RX fucked OVER: " << bladerf_strerror(status); | ||||
| 			LOGC(DDEV, ERROR) << "RX borken, OVERRUN: " << bladerf_strerror(status); | ||||
|  | ||||
| 		size_t num_smpls = meta.actual_count; | ||||
| 		; | ||||
| @@ -551,7 +467,6 @@ int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun | ||||
| 		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)) { | ||||
| @@ -577,13 +492,10 @@ int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underr | ||||
|  | ||||
| 	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); | ||||
| 	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; | ||||
| 		LOGC(DDEV, ERROR) << "TX broken: " << bladerf_strerror(status); | ||||
|  | ||||
| 	return len; | ||||
| } | ||||
| @@ -613,27 +525,13 @@ bool blade_device::set_freq(double freq, size_t chan, bool tx) | ||||
|  | ||||
| bool blade_device::setTxFreq(double wFreq, size_t chan) | ||||
| { | ||||
| 	uint16_t req_arfcn; | ||||
| 	enum gsm_band req_band; | ||||
|  | ||||
| 	if (chan >= tx_freqs.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return false; | ||||
| 	} | ||||
| 	ScopedLock lock(tune_lock); | ||||
|  | ||||
| 	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 << " )"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!set_band(req_band)) | ||||
| 	if (!update_band_from_freq(wFreq, chan, true)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!set_freq(wFreq, chan, true)) | ||||
| @@ -644,27 +542,13 @@ bool blade_device::setTxFreq(double wFreq, size_t chan) | ||||
|  | ||||
| bool blade_device::setRxFreq(double wFreq, size_t chan) | ||||
| { | ||||
| 	uint16_t req_arfcn; | ||||
| 	enum gsm_band req_band; | ||||
|  | ||||
| 	if (chan >= rx_freqs.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return false; | ||||
| 	} | ||||
| 	ScopedLock lock(tune_lock); | ||||
|  | ||||
| 	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1); | ||||
| 	if (req_arfcn == 0xffff) { | ||||
| 		LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz"; | ||||
| 		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 << " )"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!set_band(req_band)) | ||||
| 	if (!update_band_from_freq(wFreq, chan, false)) | ||||
| 		return false; | ||||
|  | ||||
| 	return set_freq(wFreq, chan, false); | ||||
| @@ -697,10 +581,6 @@ bool blade_device::requiresRadioAlign() | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
| @@ -724,10 +604,7 @@ double blade_device::fullScaleOutputValue() | ||||
| 	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(InterfaceType type, const struct trx_cfg *cfg) | ||||
| { | ||||
| 	return new blade_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); | ||||
| 	return new blade_device(type, cfg); | ||||
| } | ||||
| #endif | ||||
|   | ||||
| @@ -22,10 +22,12 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <map> | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
|  | ||||
| #include "bandmanager.h" | ||||
| #include "radioDevice.h" | ||||
| #include "smpl_buf.h" | ||||
|  | ||||
| @@ -33,8 +35,6 @@ extern "C" { | ||||
| #include <osmocom/gsm/gsm_utils.h> | ||||
| } | ||||
|  | ||||
| #include <bladerf.h> | ||||
|  | ||||
| enum class blade_dev_type { BLADE1, BLADE2 }; | ||||
|  | ||||
| struct dev_band_desc { | ||||
| @@ -52,13 +52,26 @@ struct dev_band_desc { | ||||
| 	double rxgain2rssioffset_rel; /* dB */ | ||||
| }; | ||||
|  | ||||
| class blade_device : public RadioDevice { | ||||
| /* Device parameter descriptor */ | ||||
| struct dev_desc { | ||||
| 	unsigned channels; | ||||
| 	double mcr; | ||||
| 	double rate; | ||||
| 	double offset; | ||||
| 	std::string desc_str; | ||||
| }; | ||||
|  | ||||
| using dev_key = std::tuple<blade_dev_type, int, int>; | ||||
| using dev_band_key = std::tuple<blade_dev_type, enum gsm_band>; | ||||
| using power_map_t = std::map<dev_band_key, dev_band_desc>; | ||||
| using dev_map_t = std::map<dev_key, dev_desc>; | ||||
|  | ||||
| class blade_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> { | ||||
|     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(InterfaceType iface, const struct trx_cfg *cfg); | ||||
| 	~blade_device(); | ||||
|  | ||||
| 	int open(const std::string &args, int ref, bool swap_channels); | ||||
| 	int open(); | ||||
| 	bool start(); | ||||
| 	bool stop(); | ||||
| 	bool restart(); | ||||
| @@ -154,9 +167,6 @@ class blade_device : public RadioDevice { | ||||
|  | ||||
| 	std::vector<double> tx_gains, rx_gains; | ||||
| 	std::vector<double> tx_freqs, rx_freqs; | ||||
| 	bool band_ass_curr_sess; /* true if  "band" was set after last POWEROFF */ | ||||
| 	enum gsm_band band; | ||||
| 	struct dev_band_desc band_desc; | ||||
| 	size_t tx_spp, rx_spp; | ||||
|  | ||||
| 	bool started; | ||||
| @@ -179,9 +189,6 @@ class blade_device : public RadioDevice { | ||||
| 	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); | ||||
| 	bool set_band(enum gsm_band req_band); | ||||
| 	void assign_band_desc(enum gsm_band req_band); | ||||
|  | ||||
| 	Thread *async_event_thrd; | ||||
| 	Mutex tune_lock; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) | ||||
| AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) | ||||
|  | ||||
|  | ||||
| noinst_HEADERS = radioDevice.h smpl_buf.h | ||||
| noinst_HEADERS = radioDevice.h smpl_buf.h bandmanager.h | ||||
|  | ||||
| noinst_LTLIBRARIES = libdevice_common.la | ||||
|  | ||||
|   | ||||
							
								
								
									
										133
									
								
								Transceiver52M/device/common/bandmanager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								Transceiver52M/device/common/bandmanager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| #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 <string> | ||||
| #include <tuple> | ||||
|  | ||||
| #include "Logger.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/gsm/gsm_utils.h> | ||||
| } | ||||
|  | ||||
| template <typename powermapt, typename devmapt> | ||||
| class band_manager { | ||||
| 	using powerkeyt = typename powermapt::key_type; | ||||
| 	using powermappedt = typename powermapt::mapped_type; | ||||
| 	using devkeyt = typename devmapt::key_type; | ||||
| 	const devkeyt &m_dev_type; | ||||
| 	const powermapt &m_power_map; | ||||
| 	const devmapt &m_dev_map; | ||||
| 	powerkeyt m_fallback; | ||||
| 	enum gsm_band m_band; | ||||
| 	powermappedt m_band_desc; | ||||
| 	bool band_ass_curr_sess{}; /* true if  "band" was set after last POWEROFF */ | ||||
|  | ||||
| 	// looks up either first tuple element (->enum) or straight enum | ||||
| 	template <typename T, typename std::enable_if<std::is_enum<T>::value>::type *dummy = nullptr> | ||||
| 	auto key_helper(T &t) -> T | ||||
| 	{ | ||||
| 		return t; | ||||
| 	} | ||||
|  | ||||
| 	template <typename T> | ||||
| 	auto key_helper(T t) -> typename std::tuple_element<0, T>::type | ||||
| 	{ | ||||
| 		return std::get<0>(t); | ||||
| 	} | ||||
|  | ||||
| 	void assign_band_desc(enum gsm_band req_band) | ||||
| 	{ | ||||
| 		auto key = key_helper(m_dev_type); | ||||
| 		auto fallback_key = key_helper(m_fallback); | ||||
| 		auto it = m_power_map.find({ key, req_band }); | ||||
| 		if (it == m_power_map.end()) { | ||||
| 			auto desc = m_dev_map.at(m_dev_type); | ||||
| 			LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " << desc.desc_str | ||||
| 					  << " on band " << gsm_band_name(req_band) << ", using fallback.."; | ||||
| 			it = m_power_map.find({ fallback_key, req_band }); | ||||
| 		} | ||||
| 		OSMO_ASSERT(it != m_power_map.end()); | ||||
| 		m_band_desc = it->second; | ||||
| 	} | ||||
|  | ||||
| 	bool set_band(enum gsm_band req_band) | ||||
| 	{ | ||||
| 		if (band_ass_curr_sess && req_band != m_band) { | ||||
| 			LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band) | ||||
| 					  << " different from previous band " << gsm_band_name(m_band); | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		if (req_band != m_band) { | ||||
| 			m_band = req_band; | ||||
| 			assign_band_desc(m_band); | ||||
| 		} | ||||
| 		band_ass_curr_sess = true; | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
|     public: | ||||
| 	band_manager(const devkeyt &dev_type, const powermapt &power_map, const devmapt &dev_map, powerkeyt fallback) | ||||
| 		: m_dev_type(dev_type), m_power_map(power_map), m_dev_map(dev_map), m_fallback(fallback), | ||||
| 		  m_band((enum gsm_band)0) | ||||
| 	{ | ||||
| 	} | ||||
| 	band_manager(const powermapt &power_map, const devmapt &dev_map) | ||||
| 		: m_dev_type(dev_map.begin()->first), m_power_map(power_map), m_dev_map(dev_map), | ||||
| 		  m_fallback(m_power_map.begin()->first), m_band((enum gsm_band)0) | ||||
| 	{ | ||||
| 	} | ||||
| 	void band_reset() | ||||
| 	{ | ||||
| 		band_ass_curr_sess = false; | ||||
| 	} | ||||
|  | ||||
| 	void get_dev_band_desc(powermappedt &desc) | ||||
| 	{ | ||||
| 		if (m_band == 0) { | ||||
| 			LOGC(DDEV, ERROR) | ||||
| 				<< "Power parameters requested before Tx Frequency was set! Providing band 900 by default..."; | ||||
| 			assign_band_desc(GSM_BAND_900); | ||||
| 		} | ||||
| 		desc = m_band_desc; | ||||
| 	} | ||||
|  | ||||
| 	bool update_band_from_freq(double wFreq, int chan, bool is_tx) | ||||
| 	{ | ||||
| 		enum gsm_band req_band; | ||||
| 		auto dirstr = is_tx ? "Tx" : "Rx"; | ||||
| 		auto req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, !is_tx); | ||||
| 		if (req_arfcn == 0xffff) { | ||||
| 			LOGCHAN(chan, DDEV, ALERT) | ||||
| 				<< "Unknown ARFCN for " << dirstr << " Frequency " << wFreq / 1000 << " kHz"; | ||||
| 			return false; | ||||
| 		} | ||||
| 		if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { | ||||
| 			LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for " << dirstr << " Frequency " << wFreq | ||||
| 						   << " Hz (ARFCN " << req_arfcn << " )"; | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		return set_band(req_band); | ||||
| 	} | ||||
| }; | ||||
| @@ -51,13 +51,10 @@ class RadioDevice { | ||||
|     MULTI_ARFCN, | ||||
|   }; | ||||
|  | ||||
|   static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type, | ||||
|                            size_t chans = 1, double offset = 0.0, | ||||
|                            const std::vector<std::string>& tx_paths = std::vector<std::string>(1, ""), | ||||
|                            const std::vector<std::string>& rx_paths = std::vector<std::string>(1, "")); | ||||
|   static RadioDevice *make(InterfaceType type, const struct trx_cfg *cfg); | ||||
|  | ||||
|   /** Initialize the USRP */ | ||||
|   virtual int open(const std::string &args, int ref, bool swap_channels)=0; | ||||
|   virtual int open() = 0; | ||||
|  | ||||
|   virtual ~RadioDevice() { } | ||||
|  | ||||
| @@ -164,24 +161,30 @@ class RadioDevice { | ||||
|   double lo_offset; | ||||
|   std::vector<std::string> tx_paths, rx_paths; | ||||
|   std::vector<struct device_counters> m_ctr; | ||||
|   const struct trx_cfg *cfg; | ||||
|  | ||||
|   RadioDevice(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): | ||||
| 		tx_sps(tx_sps), rx_sps(rx_sps), iface(type), chans(chan_num), lo_offset(offset), | ||||
| 		tx_paths(tx_paths), rx_paths(rx_paths) | ||||
| 	{ | ||||
| 		if (iface == MULTI_ARFCN) { | ||||
| 			LOGC(DDEV, INFO) << "Multi-ARFCN: "<< chan_num << " logical chans -> 1 physical chans"; | ||||
| 			chans = 1; | ||||
| 		} | ||||
| #define charp2str(a) ((a) ? std::string(a) : std::string("")) | ||||
|  | ||||
| 		m_ctr.resize(chans); | ||||
| 		for (size_t i = 0; i < chans; i++) { | ||||
| 			memset(&m_ctr[i], 0, sizeof(m_ctr[i])); | ||||
| 			m_ctr[i].chan = i; | ||||
| 		} | ||||
| 	} | ||||
|   RadioDevice(InterfaceType type, const struct trx_cfg *cfg) | ||||
| 	  : tx_sps(cfg->tx_sps), rx_sps(cfg->rx_sps), iface(type), chans(cfg->num_chans), lo_offset(cfg->offset), | ||||
| 	    m_ctr(chans), cfg(cfg) | ||||
|   { | ||||
| 	  /* Generate vector of rx/tx_path: */ | ||||
| 	  for (unsigned int i = 0; i < cfg->num_chans; i++) { | ||||
| 		  rx_paths.push_back(charp2str(cfg->chans[i].rx_path)); | ||||
| 		  tx_paths.push_back(charp2str(cfg->chans[i].tx_path)); | ||||
| 	  } | ||||
|  | ||||
| 	  if (iface == MULTI_ARFCN) { | ||||
| 		  LOGC(DDEV, INFO) << "Multi-ARFCN: " << chans << " logical chans -> 1 physical chans"; | ||||
| 		  chans = 1; | ||||
| 	  } | ||||
|  | ||||
| 	  for (size_t i = 0; i < chans; i++) { | ||||
| 		  memset(&m_ctr[i], 0, sizeof(m_ctr[i])); | ||||
| 		  m_ctr[i].chan = i; | ||||
| 	  } | ||||
|   } | ||||
|  | ||||
|   bool set_antennas() { | ||||
| 	unsigned int i; | ||||
|   | ||||
| @@ -56,20 +56,13 @@ using namespace std; | ||||
|  | ||||
| static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags); | ||||
|  | ||||
| IPCDevice::IPCDevice(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), tx_attenuation(), | ||||
| 	  tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), started(false) | ||||
| IPCDevice::IPCDevice(InterfaceType iface, const struct trx_cfg *cfg) | ||||
| 	: RadioDevice(iface, cfg), sk_chan_state(chans, ipc_per_trx_sock_state()), tx_attenuation(), | ||||
| 	  tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), rx_buffers(chans), started(false), tx_gains(chans), | ||||
| 	  rx_gains(chans) | ||||
| { | ||||
| 	LOGC(DDEV, INFO) << "creating IPC device..."; | ||||
|  | ||||
| 	rx_gains.resize(chans); | ||||
| 	tx_gains.resize(chans); | ||||
|  | ||||
| 	rx_buffers.resize(chans); | ||||
|  | ||||
| 	sk_chan_state.resize(chans, ipc_per_trx_sock_state()); | ||||
|  | ||||
| 	/* 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)); | ||||
| @@ -543,9 +536,9 @@ void IPCDevice::ipc_sock_close(struct ipc_per_trx_sock_state *state) | ||||
|  | ||||
| 	LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n"); | ||||
|  | ||||
| 	osmo_fd_unregister(bfd); | ||||
| 	close(bfd->fd); | ||||
| 	bfd->fd = -1; | ||||
| 	osmo_fd_unregister(bfd); | ||||
|  | ||||
| 	/* flush the queue */ | ||||
| 	while (!llist_empty(&state->upqueue)) { | ||||
| @@ -777,11 +770,12 @@ static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) | ||||
| 	return rc; | ||||
| } | ||||
|  | ||||
| int IPCDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
| int IPCDevice::open() | ||||
| { | ||||
| 	std::string k, v; | ||||
| 	std::string::size_type keyend; | ||||
| 	int rc; | ||||
| 	std::string args(cfg->dev_args); | ||||
|  | ||||
| 	if ((keyend = args.find('=')) != std::string::npos) { | ||||
| 		k = args.substr(0, keyend++); | ||||
| @@ -816,7 +810,7 @@ int IPCDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	while (tmp_state != IPC_IF_MSG_INFO_CNF) | ||||
| 		osmo_select_main(0); | ||||
|  | ||||
| 	ipc_tx_open_req(&master_sk_state, chans, ref); | ||||
| 	ipc_tx_open_req(&master_sk_state, chans, cfg->clock_ref); | ||||
| 	/* Wait until confirmation is recieved */ | ||||
| 	while (tmp_state != IPC_IF_MSG_OPEN_CNF) | ||||
| 		osmo_select_main(0); | ||||
| @@ -1265,16 +1259,15 @@ bool IPCDevice::setRxFreq(double wFreq, size_t chan) | ||||
| 	return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF); | ||||
| } | ||||
|  | ||||
| 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(InterfaceType type, const struct trx_cfg *cfg) | ||||
| { | ||||
| 	if (tx_sps != rx_sps) { | ||||
| 	if (cfg->tx_sps != cfg->rx_sps) { | ||||
| 		LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	if (lo_offset != 0.0) { | ||||
| 	if (cfg->offset != 0.0) { | ||||
| 		LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return new IPCDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); | ||||
| 	return new IPCDevice(type, cfg); | ||||
| } | ||||
|   | ||||
| @@ -115,12 +115,11 @@ class IPCDevice : public RadioDevice { | ||||
| 	int ipc_chan_sock_write(osmo_fd *bfd); | ||||
|  | ||||
| 	/** Object constructor */ | ||||
| 	IPCDevice(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); | ||||
| 	IPCDevice(InterfaceType iface, const struct trx_cfg *cfg); | ||||
| 	virtual ~IPCDevice() override; | ||||
|  | ||||
| 	/** Instantiate the IPC */ | ||||
| 	virtual int open(const std::string &args, int ref, bool swap_channels) override; | ||||
| 	virtual int open() override; | ||||
|  | ||||
| 	/** Start the IPC */ | ||||
| 	virtual bool start() override; | ||||
| @@ -199,7 +198,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; | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| 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 | ||||
| AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common | ||||
| AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS) | ||||
| AM_CXXFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS) | ||||
|  | ||||
| noinst_HEADERS = IPCDevice.h shm.h ipc_shm.h ipc_chan.h ipc_sock.h | ||||
|  | ||||
| @@ -14,30 +13,22 @@ endif | ||||
| noinst_LTLIBRARIES = libdevice.la | ||||
|  | ||||
| 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 | ||||
| libdevice_la_CPPFLAGS = $(AM_CPPFLAGS) -DIPCMAGIC | ||||
| libdevice_la_LIBADD = \ | ||||
| 	$(top_builddir)/Transceiver52M/device/common/libdevice_common.la \ | ||||
| 	-lpthread \ | ||||
| 	-lrt \ | ||||
| 	$(NULL) | ||||
|  | ||||
| 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 ../uhd/UHDDevice.cpp | ||||
| ipc_driver_test_LDADD = \ | ||||
|         shm.lo \ | ||||
| 	$(top_builddir)/Transceiver52M/device/common/libdevice_common.la \ | ||||
| 	$(COMMON_LA) | ||||
| 	libdevice.la \ | ||||
| 	$(COMMON_LA) \ | ||||
| 	$(LIBOSMOCORE_LIBS) \ | ||||
| 	$(UHD_LIBS) \ | ||||
| 	$(NULL) | ||||
| ipc_driver_test_CXXFLAGS = $(AM_CXXFLAGS) $(UHD_CFLAGS) | ||||
| ipc_driver_test_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) | ||||
| ipc_driver_test_CFLAGS  = $(AM_CFLAGS) $(UHD_CFLAGS) | ||||
| ipc_driver_test_LDFLAGS  = $(AM_LDFLAGS) $(UHD_LIBS) | ||||
| endif | ||||
|   | ||||
| @@ -97,9 +97,9 @@ void ipc_sock_close(struct ipc_sock_state *state) | ||||
|  | ||||
| 	ipc_exit_requested = 1; | ||||
|  | ||||
| 	osmo_fd_unregister(bfd); | ||||
| 	close(bfd->fd); | ||||
| 	bfd->fd = -1; | ||||
| 	osmo_fd_unregister(bfd); | ||||
|  | ||||
| 	/* re-enable the generation of ACCEPT for new connections */ | ||||
| 	osmo_fd_read_enable(&state->listen_bfd); | ||||
|   | ||||
| @@ -35,9 +35,12 @@ extern "C" { | ||||
| #include "Threads.h" | ||||
| #include "Utils.h" | ||||
|  | ||||
| int uhd_wrap::open(const std::string &args, int ref, bool swap_channels) | ||||
| // no vty source for cfg params here, so we have to build our own | ||||
| static struct trx_cfg actual_cfg = {}; | ||||
|  | ||||
| int uhd_wrap::open() | ||||
| { | ||||
| 	int rv = uhd_device::open(args, ref, swap_channels); | ||||
| 	int rv = uhd_device::open(); | ||||
| 	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(); | ||||
| @@ -84,36 +87,33 @@ int uhd_wrap::wrap_read(TIMESTAMP *timestamp) | ||||
|  | ||||
| extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req) | ||||
| { | ||||
| 	unsigned int rx_sps, tx_sps; | ||||
| 	actual_cfg.num_chans = open_req->num_chans; | ||||
| 	actual_cfg.swap_channels = false; | ||||
| 	/* FIXME: this is actually the sps value, not the sample rate! | ||||
| 	 * sample rate is looked up according to the sps rate by uhd backend */ | ||||
| 	actual_cfg.rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den; | ||||
| 	actual_cfg.tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den; | ||||
|  | ||||
| 	/* 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; | ||||
| 		actual_cfg.clock_ref = ReferenceType::REF_EXTERNAL; | ||||
| 		break; | ||||
| 	case FEATURE_MASK_CLOCKREF_INTERNAL: | ||||
| 	default: | ||||
| 		cref = ReferenceType::REF_INTERNAL; | ||||
| 		actual_cfg.clock_ref = 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); | ||||
| 		actual_cfg.chans[i].rx_path = open_req->chan_info[i].tx_path; | ||||
| 		actual_cfg.chans[i].tx_path = 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); | ||||
| 	uhd_wrap *uhd_wrap_dev = new uhd_wrap(RadioDevice::NORMAL, &actual_cfg); | ||||
| 	uhd_wrap_dev->open(); | ||||
|  | ||||
| 	return uhd_wrap_dev; | ||||
| } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class uhd_wrap : public uhd_device { | ||||
|  | ||||
| 	//    void ipc_sock_close() override {}; | ||||
| 	int wrap_read(TIMESTAMP *timestamp); | ||||
| 	virtual int open(const std::string &args, int ref, bool swap_channels) override; | ||||
| 	virtual int open() override; | ||||
|  | ||||
| 	//	bool start() override; | ||||
| 	//	bool stop() override; | ||||
|   | ||||
| @@ -1,318 +0,0 @@ | ||||
| /* | ||||
| * Copyright 2022 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||
| * Author: Eric Wild <ewild@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) { | ||||
| 	#if 1 | ||||
| 	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; | ||||
| 	#else | ||||
| 	usleep(ONE_SAMPLE_DURATION_US * 625); | ||||
| 	#endif | ||||
| } | ||||
|  | ||||
| // 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 len; | ||||
| } | ||||
|  | ||||
| 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 2022 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||
| * Author: Eric Wild <ewild@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,387 +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 <deque> | ||||
| #include <mutex> | ||||
| #include <vector> | ||||
|  | ||||
| #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::sema r; | ||||
| 		shm::sema w; | ||||
| 		std::atomic<uint64_t> ts; | ||||
| 		std::atomic<uint64_t> ts_req; | ||||
| 		std::atomic<size_t> len_written_sps; // -> | ||||
| 		sample_t buffer[max_ul_rdlen]; | ||||
| 	} ul; | ||||
| 	struct { | ||||
| 		shm::sema r; | ||||
| 		shm::sema w; | ||||
| 		std::atomic<uint64_t> ts; | ||||
| 		std::atomic<uint64_t> ts_req; | ||||
| 		std::atomic<size_t> len_written_sps; | ||||
| 		sample_t buffer[max_dl_rdlen]; | ||||
| 	} dl; | ||||
| }; | ||||
|  | ||||
| // unique up to signed_type/2 diff | ||||
| // ex: uint8/int8 (250, 0) = -6 | ||||
| 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); | ||||
| }; | ||||
|  | ||||
| constexpr inline int samp2byte(int v) | ||||
| { | ||||
| 	return v * sizeof(sample_t); | ||||
| } | ||||
| constexpr inline int byte2samp(int v) | ||||
| { | ||||
| 	return v / sizeof(sample_t); | ||||
| } | ||||
|  | ||||
| struct ulentry { | ||||
| 	bool done; | ||||
| 	uint64_t ts; | ||||
| 	unsigned int len_in_sps; | ||||
| 	unsigned int read_pos_in_sps; | ||||
| 	sample_t buf[1000]; | ||||
| }; | ||||
| /* | ||||
| 		write: find read index +.. until marked free = "end" of current list | ||||
|  | ||||
| 		check: | ||||
| 		within begin, end AND not free? | ||||
| 			y: | ||||
| 			copy (chunk) | ||||
| 				if chunk advance burst buf ptr | ||||
| 			n: next, advance, remove old. | ||||
| 		*/ | ||||
| template <unsigned int num_bursts> class ulburstprovider { | ||||
| 	std::mutex ul_q_m; | ||||
| 	// std::deque<ulentry> ul_q; | ||||
|  | ||||
| 	// classic circular buffer | ||||
| 	ulentry foo[num_bursts]; | ||||
| 	int current_index; // % num_bursts | ||||
|  | ||||
| 	void cur_buf_done() | ||||
| 	{ | ||||
| 		foo[current_index].done = true; | ||||
| 		current_index = current_index + 1 % num_bursts; | ||||
| 	} | ||||
| 	bool is_empty() | ||||
| 	{ | ||||
| 		return foo[current_index].done = true; | ||||
| 	} | ||||
| 	void reset() | ||||
| 	{ | ||||
| 		for (auto &i : foo) | ||||
| 			i = {}; | ||||
| 		current_index = 0; | ||||
| 	} | ||||
| 	ulentry &find_free_at_end() | ||||
| 	{ | ||||
| 		for (int i = current_index, max_to_search = 0; max_to_search < num_bursts; | ||||
| 		     i = (i + 1 % num_bursts), max_to_search++) { | ||||
| 			if (foo[i].done) | ||||
| 				return foo[i]; | ||||
| 		} | ||||
| 		return foo[0]; // FIXME actually broken, q full, wat do? | ||||
| 	} | ||||
|  | ||||
| 	void push_back(ulentry &e) | ||||
| 	{ | ||||
| 		auto free_buf = find_free_at_end(); | ||||
| 		free_buf = e; | ||||
| 		e.done = false; | ||||
| 	} | ||||
|  | ||||
|     public: | ||||
| 	void add(ulentry &e) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> foo(ul_q_m); | ||||
| 		push_back(e); | ||||
| 	} | ||||
| 	void get(uint64_t requested_ts, unsigned int req_len_in_sps, sample_t *buf, unsigned int max_buf_write_len) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(ul_q_m); | ||||
|  | ||||
| 		/* | ||||
| 		1) if empty return | ||||
| 		2) if not empty prune stale bursts | ||||
| 		3) if only future bursts also return and zero buf | ||||
| 		*/ | ||||
| 		for (int i = current_index, max_to_search = 0; max_to_search < num_bursts; | ||||
| 		     i = (i + 1 % num_bursts), max_to_search++) { | ||||
| 			auto cur_entry = foo[i]; | ||||
| 			if (is_empty()) { // might be empty due to advance below! | ||||
| 				memset(buf, 0, samp2byte(req_len_in_sps)); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if (cur_entry.ts + cur_entry.len_in_sps < requested_ts) { // remove late bursts | ||||
| 				if (i == current_index) // only advance if we are at the front | ||||
| 					cur_buf_done(); | ||||
| 				else | ||||
| 					assert(true); | ||||
| 			} else if (cur_entry.ts >= requested_ts + byte2samp(max_buf_write_len)) { // not in range | ||||
| 				memset(buf, 0, samp2byte(req_len_in_sps)); | ||||
| 				return; | ||||
|  | ||||
| 				// FIXME: what about requested_ts <= entry.ts <= ts + reqlen? | ||||
| 			} else { | ||||
| 				// requested_ts <= cur_entry.ts <= requested_ts + byte2samp(max_write_len) | ||||
|  | ||||
| 				auto before_sps = unsigned_diff(cur_entry.ts, requested_ts); | ||||
|  | ||||
| 				// at least one whole buffer before our most recent "head" burst? | ||||
| 				// set 0, return. | ||||
| 				if (-before_sps >= byte2samp(max_buf_write_len)) { | ||||
| 					memset(buf, 0, samp2byte(req_len_in_sps)); | ||||
| 					return; | ||||
| 				} | ||||
| 				// less than one full buffer before: pad 0 | ||||
| 				auto to_pad_sps = -before_sps; | ||||
| 				memset(buf, 0, samp2byte(to_pad_sps)); | ||||
| 				requested_ts += to_pad_sps; | ||||
| 				req_len_in_sps -= to_pad_sps; | ||||
|  | ||||
| 				if (!req_len_in_sps) | ||||
| 					return; | ||||
|  | ||||
| 				// actual burst data after possible 0 pad | ||||
| 				auto max_sps_to_write = std::min(cur_entry.len_in_sps, req_len_in_sps); | ||||
| 				memcpy(&buf[samp2byte(to_pad_sps)], cur_entry.buf, samp2byte(max_sps_to_write)); | ||||
| 				requested_ts += max_sps_to_write; | ||||
| 				req_len_in_sps -= max_sps_to_write; | ||||
| 				cur_entry.read_pos_in_sps += max_sps_to_write; | ||||
|  | ||||
| 				//this buf is done... | ||||
| 				if (cur_entry.read_pos_in_sps == cur_entry.len_in_sps) { | ||||
| 					cur_buf_done(); | ||||
| 				} | ||||
|  | ||||
| 				if (!req_len_in_sps) | ||||
| 					return; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class trxmsif { | ||||
| 	shm::shm<shm_if> m; | ||||
| 	shm_if *ptr; | ||||
|  | ||||
| 	ulburstprovider<10> p; | ||||
|  | ||||
| 	template <typename T> void read(T &direction, size_t howmany_sps, uint64_t *read_ts, sample_t *outbuf) | ||||
| 	{ | ||||
| 		static int readoffset_sps; | ||||
| 		// auto &direction = ptr->dl; | ||||
| 		auto buf = &direction.buffer[0]; | ||||
| 		size_t len_avail_sps = direction.len_written_sps.load(); | ||||
|  | ||||
| 		auto left_to_read = len_avail_sps - readoffset_sps; | ||||
|  | ||||
| 		shm::mtx_log::print_guard() << "\tr @" << direction.ts.load() << " " << readoffset_sps << std::endl; | ||||
|  | ||||
| 		// no data, wait for new buffer, maybe some data left afterwards | ||||
| 		if (!left_to_read) { | ||||
| 			assert(readoffset_sps == len_avail_sps); | ||||
| 			readoffset_sps = 0; | ||||
| 			direction.r.reset_unsafe(); | ||||
| 			direction.ts_req = (*read_ts); | ||||
| 			direction.w.set(1); | ||||
| 			direction.r.wait_and_reset(1); | ||||
| 			assert(*read_ts != direction.ts.load()); | ||||
| 			// shm::sema_guard g(dl.r, dl.w); | ||||
| 			*read_ts = direction.ts.load(); | ||||
| 			len_avail_sps = direction.len_written_sps.load(); | ||||
| 			readoffset_sps += howmany_sps; | ||||
| 			assert(len_avail_sps >= howmany_sps); | ||||
| 			memcpy(outbuf, buf, samp2byte(howmany_sps)); | ||||
|  | ||||
| 			shm::mtx_log::print_guard() << "\tr+ " << *read_ts << " " << howmany_sps << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		*read_ts = direction.ts.load() + readoffset_sps; | ||||
| 		left_to_read = len_avail_sps - readoffset_sps; | ||||
|  | ||||
| 		// data left from prev read | ||||
| 		if (left_to_read >= howmany_sps) { | ||||
| 			memcpy(outbuf, &buf[readoffset_sps], samp2byte(howmany_sps)); | ||||
| 			readoffset_sps += howmany_sps; | ||||
|  | ||||
| 			shm::mtx_log::print_guard() << "\tr++ " << *read_ts << " " << howmany_sps << std::endl; | ||||
| 			return; | ||||
| 		} else { | ||||
| 			memcpy(outbuf, &buf[readoffset_sps], samp2byte(left_to_read)); | ||||
| 			readoffset_sps = 0; | ||||
| 			auto still_left_to_read = howmany_sps - left_to_read; | ||||
| 			{ | ||||
| 				direction.r.reset_unsafe(); | ||||
| 				direction.ts_req = (*read_ts); | ||||
| 				direction.w.set(1); | ||||
| 				direction.r.wait_and_reset(1); | ||||
| 				assert(*read_ts != direction.ts.load()); | ||||
| 				len_avail_sps = direction.len_written_sps.load(); | ||||
| 				assert(len_avail_sps >= still_left_to_read); | ||||
| 				memcpy(&outbuf[left_to_read], buf, samp2byte(still_left_to_read)); | ||||
| 				readoffset_sps += still_left_to_read; | ||||
| 				shm::mtx_log::print_guard() | ||||
| 					<< "\tr+++2 " << *read_ts << " " << howmany_sps << " " << still_left_to_read | ||||
| 					<< " new @" << direction.ts.load() << std::endl; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|     public: | ||||
| 	trxmsif() : m("trx-ms-if") | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	bool create() | ||||
| 	{ | ||||
| 		m.create(); | ||||
| 		ptr = m.p(); | ||||
| 		return m.isgood(); | ||||
| 	} | ||||
| 	bool connect() | ||||
| 	{ | ||||
| 		m.open(); | ||||
| 		ptr = m.p(); | ||||
| 		ptr->ms_connected = true; | ||||
| 		ptr->dl.w.set(1); | ||||
| 		return m.isgood(); | ||||
| 	} | ||||
| 	bool good() | ||||
| 	{ | ||||
| 		return m.isgood(); | ||||
| 	} | ||||
| 	bool is_connected() | ||||
| 	{ | ||||
| 		return ptr->ms_connected == true; | ||||
| 	} | ||||
|  | ||||
| 	/* is being read from ms side */ | ||||
| 	void read_dl(size_t howmany_sps, uint64_t *read_ts, sample_t *outbuf) | ||||
| 	{ | ||||
| 		return read(ptr->dl, howmany_sps, read_ts, outbuf); | ||||
| 	} | ||||
|  | ||||
| 	/* is being read from trx/network side */ | ||||
| 	void read_ul(size_t howmany_sps, uint64_t *read_ts, sample_t *outbuf) | ||||
| 	{ | ||||
| 		// if (ptr->ms_connected != true) { | ||||
| 			memset(outbuf, 0, samp2byte(howmany_sps)); | ||||
| 		// 	return; | ||||
| 		// } | ||||
| 		// return read(ptr->ul, howmany_sps, read_ts, outbuf); | ||||
| 	} | ||||
|  | ||||
| 	void write_dl(size_t howmany_sps, 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_sps)); | ||||
| 		// print_guard() << "####w " << std::endl; | ||||
|  | ||||
| 		{ | ||||
| 			shm::sema_wait_guard g(dl.w, dl.r); | ||||
|  | ||||
| 			memcpy(buf, inbuf, samp2byte(howmany_sps)); | ||||
| 			dl.ts.store(write_ts); | ||||
| 			dl.len_written_sps.store(howmany_sps); | ||||
| 		} | ||||
| 		shm::mtx_log::print_guard() << std::endl | ||||
| 					    << "####w+ " << write_ts << " " << howmany_sps << std::endl | ||||
| 					    << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	void write_ul(size_t howmany_sps_sps, uint64_t write_ts, sample_t *inbuf) | ||||
| 	{ | ||||
| 		auto &ul = ptr->ul; | ||||
| 		assert(sizeof(ul.buffer) >= samp2byte(howmany_sps_sps)); | ||||
| 		// print_guard() << "####w " << std::endl; | ||||
|  | ||||
| 		ulentry e; | ||||
| 		e.ts = write_ts; | ||||
| 		e.len_in_sps = howmany_sps_sps; | ||||
| 		e.done = false; | ||||
| 		e.read_pos_in_sps = 0; | ||||
| 		assert(sizeof(e.buf) >= samp2byte(howmany_sps_sps)); | ||||
| 		memcpy(e.buf, inbuf, samp2byte(howmany_sps_sps)); | ||||
| 		p.add(e); | ||||
|  | ||||
| 		shm::mtx_log::print_guard() << std::endl | ||||
| 					    << "####q+ " << write_ts << " " << howmany_sps_sps << std::endl | ||||
| 					    << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	void drive_tx() | ||||
| 	{ | ||||
| 		auto &ul = ptr->ul; | ||||
| 		auto buf = &ul.buffer[0]; | ||||
| 		const auto max_write_len = sizeof(ul.buffer); | ||||
|  | ||||
| 		// ul_q_m.lock(); | ||||
| 		// ul_q.push_front(e); | ||||
| 		// ul_q_m.unlock(); | ||||
| 		// ul.w.wait_and_reset(); | ||||
|  | ||||
| 		// no read waiting for a write | ||||
| 		if (!ul.w.check_unsafe(1)) | ||||
| 			return; | ||||
|  | ||||
| 		// FIXME: store written, notify after get! | ||||
|  | ||||
| 		auto requested_ts = ul.ts_req.load(); | ||||
|  | ||||
| 		p.get(requested_ts, byte2samp(max_write_len), buf, max_write_len); | ||||
|  | ||||
| 		// memset(buf, 0, max_write_len); | ||||
| 		ul.ts.store(requested_ts); | ||||
| 		ul.len_written_sps.store(byte2samp(max_write_len)); | ||||
| 		ul.w.reset_unsafe(); | ||||
| 		ul.r.set(1); | ||||
| 	} | ||||
|  | ||||
| 	void signal_read_start() | ||||
| 	{ /* nop */ | ||||
| 	} | ||||
| }; | ||||
| @@ -1,375 +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 <iostream> | ||||
| #include <cassert> | ||||
| #include <cstring> | ||||
| #include <mutex> | ||||
| #include <sstream> | ||||
| #include <unistd.h> | ||||
| #include <sys/mman.h> | ||||
| #include <sys/stat.h> | ||||
| #include <fcntl.h> | ||||
| #include <pthread.h> | ||||
| #include <cerrno> | ||||
|  | ||||
| namespace shm | ||||
| { | ||||
|  | ||||
| namespace mtx_log | ||||
| { | ||||
| #if defined(MTX_LOG_ENABLED) | ||||
| 	class print_guard : public std::ostringstream { | ||||
| 		static std::mutex thread_print_lock; | ||||
|  | ||||
| 	    public: | ||||
| 		~print_guard() | ||||
| 		{ | ||||
| 			std::lock_guard<std::mutex> guard(thread_print_lock); | ||||
| 			std::cerr << str(); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| #else | ||||
| 	struct print_guard {}; | ||||
|  | ||||
| 	template <typename T> constexpr print_guard operator<<(const print_guard dummy, T &&value) | ||||
| 	{ | ||||
| 		return dummy; | ||||
| 	} | ||||
|  | ||||
| 	constexpr print_guard operator<<(const print_guard &dummy, std::ostream &(*f)(std::ostream &)) | ||||
| 	{ | ||||
| 		return dummy; | ||||
| 	} | ||||
|  | ||||
| #endif | ||||
| } // namespace mtx_log | ||||
|  | ||||
| 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; | ||||
| 	} | ||||
| 	shmmutex(const shmmutex &) = delete; | ||||
| 	shmmutex &operator=(const shmmutex &) = delete; | ||||
| }; | ||||
|  | ||||
| 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); | ||||
| 	} | ||||
| 	shmcond(const shmcond &) = delete; | ||||
| 	shmcond &operator=(const shmcond &) = delete; | ||||
| }; | ||||
|  | ||||
| class signal_guard { | ||||
| 	shmmutex &m; | ||||
| 	shmcond &s; | ||||
|  | ||||
|     public: | ||||
| 	signal_guard() = delete; | ||||
| 	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(); | ||||
| 	} | ||||
| 	signal_guard(const signal_guard &) = delete; | ||||
| 	signal_guard &operator=(const signal_guard &) = delete; | ||||
| }; | ||||
|  | ||||
| class mutex_guard { | ||||
| 	shmmutex &m; | ||||
|  | ||||
|     public: | ||||
| 	mutex_guard() = delete; | ||||
| 	explicit mutex_guard(shmmutex &m) : m(m) | ||||
| 	{ | ||||
| 		m.lock(); | ||||
| 	} | ||||
| 	~mutex_guard() | ||||
| 	{ | ||||
| 		m.unlock(); | ||||
| 	} | ||||
| 	mutex_guard(const mutex_guard &) = delete; | ||||
| 	mutex_guard &operator=(const mutex_guard &) = delete; | ||||
| }; | ||||
|  | ||||
| class sema { | ||||
| 	std::atomic<int> value; | ||||
| 	shmmutex m; | ||||
| 	shmcond c; | ||||
|  | ||||
|     public: | ||||
| 	sema() : value(0) | ||||
| 	{ | ||||
| 	} | ||||
| 	explicit sema(int v) : value(v) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	void wait() | ||||
| 	{ | ||||
| 		wait(1); | ||||
| 	} | ||||
| 	void wait(int v) | ||||
| 	{ | ||||
| 		mtx_log::print_guard() << __FUNCTION__ << value << std::endl; | ||||
| 		mutex_guard g(m); | ||||
| 		assert(value <= v); | ||||
| 		while (value != v) | ||||
| 			c.wait(&m); | ||||
| 	} | ||||
| 	void wait_and_reset() | ||||
| 	{ | ||||
| 		wait_and_reset(1); | ||||
| 	} | ||||
| 	void wait_and_reset(int v) | ||||
| 	{ | ||||
| 		mtx_log::print_guard() << __FUNCTION__ << value << std::endl; | ||||
| 		mutex_guard g(m); | ||||
| 		assert(value <= v); | ||||
| 		while (value != v) | ||||
| 			c.wait(&m); | ||||
| 		value = 0; | ||||
| 	} | ||||
| 	void set() | ||||
| 	{ | ||||
| 		set(1); | ||||
| 	} | ||||
| 	void set(int v) | ||||
| 	{ | ||||
| 		mtx_log::print_guard() << __FUNCTION__ << value << std::endl; | ||||
| 		mutex_guard g(m); | ||||
| 		value = v; | ||||
| 		c.signal(); | ||||
| 	} | ||||
| 	void reset_unsafe() | ||||
| 	{ | ||||
| 		value = 0; | ||||
| 	} | ||||
| 	bool check_unsafe(int v) | ||||
| 	{ | ||||
| 		return value == v; | ||||
| 	} | ||||
| 	sema(const sema &) = delete; | ||||
| 	sema &operator=(const sema &) = delete; | ||||
| }; | ||||
|  | ||||
| class sema_wait_guard { | ||||
| 	sema &a; | ||||
| 	sema &b; | ||||
|  | ||||
|     public: | ||||
| 	sema_wait_guard() = delete; | ||||
| 	explicit sema_wait_guard(sema &wait, sema &signal) : a(wait), b(signal) | ||||
| 	{ | ||||
| 		a.wait_and_reset(1); | ||||
| 	} | ||||
| 	~sema_wait_guard() | ||||
| 	{ | ||||
| 		b.set(1); | ||||
| 	} | ||||
| 	sema_wait_guard(const sema_wait_guard &) = delete; | ||||
| 	sema_wait_guard &operator=(const sema_wait_guard &) = delete; | ||||
| }; | ||||
|  | ||||
| class sema_signal_guard { | ||||
| 	sema &a; | ||||
| 	sema &b; | ||||
|  | ||||
|     public: | ||||
| 	sema_signal_guard() = delete; | ||||
| 	explicit sema_signal_guard(sema &wait, sema &signal) : a(wait), b(signal) | ||||
| 	{ | ||||
| 		a.wait_and_reset(1); | ||||
| 	} | ||||
| 	~sema_signal_guard() | ||||
| 	{ | ||||
| 		b.set(1); | ||||
| 	} | ||||
| 	sema_signal_guard(const sema_signal_guard &) = delete; | ||||
| 	sema_signal_guard &operator=(const sema_signal_guard &) = delete; | ||||
| }; | ||||
|  | ||||
| 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; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } // namespace shm | ||||
| @@ -52,39 +52,16 @@ extern "C" { | ||||
| #define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini" | ||||
| #define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro" | ||||
|  | ||||
| /* Device parameter descriptor */ | ||||
| struct dev_desc { | ||||
| 	/* Does LimeSuite allow switching the clock source for this device? | ||||
| 	 * LimeSDR-Mini does not have switches but needs soldering to select | ||||
| 	 * external/internal clock. Any call to LMS_SetClockFreq() will fail. | ||||
| 	 */ | ||||
| 	bool clock_src_switchable; | ||||
| 	/* Does LimeSuite allow using REF_INTERNAL for this device? | ||||
| 	 * LimeNET-Micro does not like selecting internal clock | ||||
| 	 */ | ||||
| 	bool clock_src_int_usable; | ||||
| 	/* Sample rate coef (without having TX/RX samples per symbol into account) */ | ||||
| 	double rate; | ||||
| 	/* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */ | ||||
| 	double rate_multiarfcn; | ||||
| 	/* Coefficient multiplied by TX sample rate in order to shift Tx time */ | ||||
| 	double ts_offset_coef; | ||||
| 	/* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */ | ||||
| 	double ts_offset_coef_multiarfcn; | ||||
| 	/* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */ | ||||
| 	std::string name_prefix; | ||||
| }; | ||||
|  | ||||
| static const std::map<enum lms_dev_type, struct dev_desc> dev_param_map { | ||||
|  | ||||
| static const dev_map_t dev_param_map { | ||||
| 	{ LMS_DEV_SDR_USB,   { true,  true,  GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } }, | ||||
| 	{ LMS_DEV_SDR_MINI,  { false, true,  GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } }, | ||||
| 	{ LMS_DEV_NET_MICRO, { true,  false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } }, | ||||
| 	{ LMS_DEV_UNKNOWN,   { true,  true,  GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } }, | ||||
| }; | ||||
|  | ||||
| typedef std::tuple<lms_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 { | ||||
| static const power_map_t dev_band_nom_power_param_map { | ||||
| 	{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850),	{ 73.0, 11.2,  -6.0  } }, | ||||
| 	{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900),	{ 73.0, 10.8,  -6.0  } }, | ||||
| 	{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800),	{ 65.0, -3.5,  -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */ | ||||
| @@ -122,8 +99,8 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev) | ||||
| 		enum lms_dev_type dev_type = it->first; | ||||
| 		struct dev_desc desc = it->second; | ||||
|  | ||||
| 		if (strncmp(device_info->deviceName, desc.name_prefix.c_str(), desc.name_prefix.length()) == 0) { | ||||
| 			LOGC(DDEV, INFO) << "Device identified as " << desc.name_prefix; | ||||
| 		if (strncmp(device_info->deviceName, desc.desc_str.c_str(), desc.desc_str.length()) == 0) { | ||||
| 			LOGC(DDEV, INFO) << "Device identified as " << desc.desc_str; | ||||
| 			return dev_type; | ||||
| 		} | ||||
| 		it++; | ||||
| @@ -131,12 +108,10 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev) | ||||
| 	return LMS_DEV_UNKNOWN; | ||||
| } | ||||
|  | ||||
| LMSDevice::LMSDevice(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), | ||||
| 		     m_lms_dev(NULL), started(false), band_ass_curr_sess(false), band((enum gsm_band)0), | ||||
| 		     m_dev_type(LMS_DEV_UNKNOWN) | ||||
| LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg) | ||||
| 	: RadioDevice(iface, cfg), | ||||
| 	  band_manager(m_dev_type, dev_band_nom_power_param_map, dev_param_map, {LMS_DEV_SDR_USB, GSM_BAND_850}), m_lms_dev(NULL), | ||||
| 	  started(false), m_dev_type(LMS_DEV_UNKNOWN) | ||||
| { | ||||
| 	LOGC(DDEV, INFO) << "creating LMS device..."; | ||||
|  | ||||
| @@ -223,48 +198,7 @@ int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::str | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| void LMSDevice::assign_band_desc(enum gsm_band req_band) | ||||
| { | ||||
| 	dev_band_map_it it; | ||||
|  | ||||
| 	it = dev_band_nom_power_param_map.find(dev_band_key(m_dev_type, req_band)); | ||||
| 	if (it == dev_band_nom_power_param_map.end()) { | ||||
| 		dev_desc desc = dev_param_map.at(m_dev_type); | ||||
| 		LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " | ||||
| 				    << desc.name_prefix << " on band " << gsm_band_name(req_band) | ||||
| 				    << ", using LimeSDR-USB ones as fallback"; | ||||
| 		it = dev_band_nom_power_param_map.find(dev_band_key(LMS_DEV_SDR_USB, req_band)); | ||||
| 	} | ||||
| 	OSMO_ASSERT(it != dev_band_nom_power_param_map.end()); | ||||
| 	band_desc = it->second; | ||||
| } | ||||
|  | ||||
| bool LMSDevice::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); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (req_band != band) { | ||||
| 		band = req_band; | ||||
| 		assign_band_desc(band); | ||||
| 	} | ||||
| 	band_ass_curr_sess = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void LMSDevice::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..."; | ||||
| 		assign_band_desc(GSM_BAND_900); | ||||
| 	} | ||||
| 	desc = band_desc; | ||||
| } | ||||
|  | ||||
| int LMSDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
| int LMSDevice::open() | ||||
| { | ||||
| 	lms_info_str_t* info_list; | ||||
| 	lms_range_t range_sr; | ||||
| @@ -292,9 +226,9 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	for (i = 0; i < n; i++) | ||||
| 		LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i]; | ||||
|  | ||||
| 	dev_id = info_list_find(info_list, n, args); | ||||
| 	dev_id = info_list_find(info_list, n, cfg->dev_args); | ||||
| 	if (dev_id == -1) { | ||||
| 		LOGC(DDEV, ERROR) << "No LMS device found with address '" << args << "'"; | ||||
| 		LOGC(DDEV, ERROR) << "No LMS device found with address '" << cfg->dev_args << "'"; | ||||
| 		delete[] info_list; | ||||
| 		return -1; | ||||
| 	} | ||||
| @@ -312,13 +246,13 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	m_dev_type = parse_dev_type(m_lms_dev); | ||||
| 	dev_desc = dev_param_map.at(m_dev_type); | ||||
|  | ||||
| 	if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL)){ | ||||
| 	if ((cfg->clock_ref != REF_EXTERNAL) && (cfg->clock_ref != REF_INTERNAL)) { | ||||
| 		LOGC(DDEV, ERROR) << "Invalid reference type"; | ||||
| 		goto out_close; | ||||
| 	} | ||||
|  | ||||
| 	/* if reference clock is external, setup must happen _before_ calling LMS_Init */ | ||||
| 	if (ref == REF_EXTERNAL) { | ||||
| 	if (cfg->clock_ref == REF_EXTERNAL) { | ||||
| 		LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz"; | ||||
| 		/* FIXME: Assume an external 10 MHz reference clock. make | ||||
| 		   external reference frequency configurable */ | ||||
| @@ -333,7 +267,7 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	} | ||||
|  | ||||
| 	/* if reference clock is internal, setup must happen _after_ calling LMS_Init */ | ||||
| 	if (ref == REF_INTERNAL) { | ||||
| 	if (cfg->clock_ref == REF_INTERNAL) { | ||||
| 		LOGC(DDEV, INFO) << "Setting Internal clock reference"; | ||||
| 		/* Internal freq param is not used */ | ||||
| 		if (!do_clock_src_freq(REF_INTERNAL, 0)) | ||||
| @@ -469,7 +403,7 @@ bool LMSDevice::stop() | ||||
| 		LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]); | ||||
| 	} | ||||
|  | ||||
| 	band_ass_curr_sess = false; | ||||
| 	band_reset(); | ||||
|  | ||||
| 	started = false; | ||||
| 	return true; | ||||
| @@ -486,8 +420,8 @@ bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq) | ||||
| 		break; | ||||
| 	case REF_INTERNAL: | ||||
| 		if (!dev_desc.clock_src_int_usable) { | ||||
| 			LOGC(DDEV, ERROR) << "Device type " << dev_desc.name_prefix | ||||
| 					  << " doesn't support internal reference clock"; | ||||
| 			LOGC(DDEV, ERROR) | ||||
| 				<< "Device type " << dev_desc.desc_str << " doesn't support internal reference clock"; | ||||
| 			return false; | ||||
| 		} | ||||
| 		/* According to lms using LMS_CLOCK_EXTREF with a | ||||
| @@ -505,8 +439,8 @@ bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq) | ||||
| 		if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0) | ||||
| 			return false; | ||||
| 	} else { | ||||
| 		LOGC(DDEV, INFO) << "Device type " << dev_desc.name_prefix | ||||
| 				 << " doesn't support switching clock source through SW"; | ||||
| 		LOGC(DDEV, INFO) | ||||
| 			<< "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW"; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| @@ -999,9 +933,6 @@ bool LMSDevice::updateAlignment(TIMESTAMP timestamp) | ||||
|  | ||||
| bool LMSDevice::setTxFreq(double wFreq, size_t chan) | ||||
| { | ||||
| 	uint16_t req_arfcn; | ||||
| 	enum gsm_band req_band; | ||||
|  | ||||
| 	if (chan >= chans) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return false; | ||||
| @@ -1009,18 +940,7 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan) | ||||
|  | ||||
| 	LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; | ||||
|  | ||||
| 	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 << " )"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!set_band(req_band)) | ||||
| 	if (!update_band_from_freq(wFreq, chan, true)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) { | ||||
| @@ -1033,23 +953,9 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan) | ||||
|  | ||||
| bool LMSDevice::setRxFreq(double wFreq, size_t chan) | ||||
| { | ||||
| 	uint16_t req_arfcn; | ||||
| 	enum gsm_band req_band; | ||||
|  | ||||
| 	LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; | ||||
|  | ||||
| 	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1); | ||||
| 	if (req_arfcn == 0xffff) { | ||||
| 		LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz"; | ||||
| 		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 << " )"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!set_band(req_band)) | ||||
| 	if (!update_band_from_freq(wFreq, chan, false)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) { | ||||
| @@ -1060,18 +966,15 @@ bool LMSDevice::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) | ||||
| RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) | ||||
| { | ||||
| 	if (tx_sps != rx_sps) { | ||||
| 		LOGC(DDEV, ERROR) << "LMS Requires tx_sps == rx_sps"; | ||||
| 	if (cfg->tx_sps != cfg->rx_sps) { | ||||
| 		LOGC(DDEV, ERROR) << "LMS requires tx_sps == rx_sps"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	if (lo_offset != 0.0) { | ||||
| 	if (cfg->offset != 0.0) { | ||||
| 		LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return new LMSDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); | ||||
| 	return new LMSDevice(type, cfg); | ||||
| } | ||||
|   | ||||
| @@ -18,11 +18,13 @@ | ||||
| #ifndef _LMS_DEVICE_H_ | ||||
| #define _LMS_DEVICE_H_ | ||||
|  | ||||
| #include <map> | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
|  | ||||
| #include "radioDevice.h" | ||||
| #include "bandmanager.h" | ||||
| #include "smpl_buf.h" | ||||
|  | ||||
| #include <sys/time.h> | ||||
| @@ -69,8 +71,35 @@ struct dev_band_desc { | ||||
| 	double rxgain2rssioffset_rel; /* dB */ | ||||
| }; | ||||
|  | ||||
| /* Device parameter descriptor */ | ||||
| struct dev_desc { | ||||
| 	/* Does LimeSuite allow switching the clock source for this device? | ||||
| 	 * LimeSDR-Mini does not have switches but needs soldering to select | ||||
| 	 * external/internal clock. Any call to LMS_SetClockFreq() will fail. | ||||
| 	 */ | ||||
| 	bool clock_src_switchable; | ||||
| 	/* Does LimeSuite allow using REF_INTERNAL for this device? | ||||
| 	 * LimeNET-Micro does not like selecting internal clock | ||||
| 	 */ | ||||
| 	bool clock_src_int_usable; | ||||
| 	/* Sample rate coef (without having TX/RX samples per symbol into account) */ | ||||
| 	double rate; | ||||
| 	/* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */ | ||||
| 	double rate_multiarfcn; | ||||
| 	/* Coefficient multiplied by TX sample rate in order to shift Tx time */ | ||||
| 	double ts_offset_coef; | ||||
| 	/* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */ | ||||
| 	double ts_offset_coef_multiarfcn; | ||||
| 	/* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */ | ||||
| 	std::string desc_str; | ||||
| }; | ||||
|  | ||||
| using dev_band_key_t = std::tuple<lms_dev_type, gsm_band>; | ||||
| using power_map_t = std::map<dev_band_key_t, dev_band_desc>; | ||||
| using dev_map_t = std::map<lms_dev_type, struct dev_desc>; | ||||
|  | ||||
| /** A class to handle a LimeSuite supported device */ | ||||
| class LMSDevice:public RadioDevice { | ||||
| class LMSDevice:public RadioDevice, public band_manager<power_map_t, dev_map_t> { | ||||
|  | ||||
| private: | ||||
| 	lms_device_t *m_lms_dev; | ||||
| @@ -87,9 +116,6 @@ private: | ||||
| 	TIMESTAMP ts_initial, ts_offset; | ||||
|  | ||||
| 	std::vector<double> tx_gains, rx_gains; | ||||
| 	bool band_ass_curr_sess; /* true if  "band" was set after last POWEROFF */ | ||||
| 	enum gsm_band band; | ||||
| 	struct dev_band_desc band_desc; | ||||
|  | ||||
| 	enum lms_dev_type m_dev_type; | ||||
|  | ||||
| @@ -101,29 +127,25 @@ private: | ||||
| 	void update_stream_stats_rx(size_t chan, bool *overrun); | ||||
| 	void update_stream_stats_tx(size_t chan, bool *underrun); | ||||
| 	bool do_clock_src_freq(enum ReferenceType ref, double freq); | ||||
| 	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); | ||||
| public: | ||||
|  | ||||
| 	/** Object constructor */ | ||||
| 	LMSDevice(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); | ||||
| 	~LMSDevice(); | ||||
|     LMSDevice(InterfaceType iface, const struct trx_cfg *cfg); | ||||
|     ~LMSDevice(); | ||||
|  | ||||
| 	/** Instantiate the LMS */ | ||||
| 	int open(const std::string &args, int ref, bool swap_channels); | ||||
|     /** Instantiate the LMS */ | ||||
|     int open(); | ||||
|  | ||||
| 	/** Start the LMS */ | ||||
| 	bool start(); | ||||
|     /** Start the LMS */ | ||||
|     bool start(); | ||||
|  | ||||
| 	/** Stop the LMS */ | ||||
| 	bool stop(); | ||||
|     /** Stop the LMS */ | ||||
|     bool stop(); | ||||
|  | ||||
| 	enum TxWindowType getWindowType() { | ||||
| 		return TX_WINDOW_LMS1; | ||||
| 	} | ||||
|     enum TxWindowType getWindowType() | ||||
|     { | ||||
| 	    return TX_WINDOW_LMS1; | ||||
|     } | ||||
|  | ||||
| 	/** | ||||
| 	Read samples from the LMS. | ||||
|   | ||||
| @@ -91,19 +91,7 @@ extern "C" { | ||||
|  *   USRP1 with timestamps is not supported by UHD. | ||||
|  */ | ||||
|  | ||||
| /* Device Type, Tx-SPS, Rx-SPS */ | ||||
| typedef std::tuple<uhd_dev_type, int, int> dev_key; | ||||
|  | ||||
| /* Device parameter descriptor */ | ||||
| struct dev_desc { | ||||
| 	unsigned channels; | ||||
| 	double mcr; | ||||
| 	double rate; | ||||
| 	double offset; | ||||
| 	std::string str; | ||||
| }; | ||||
|  | ||||
| static const std::map<dev_key, dev_desc> dev_param_map { | ||||
| static const dev_map_t dev_param_map { | ||||
| 	{ std::make_tuple(USRP2, 1, 1), { 1, 0.0,  390625,  1.2184e-4,  "N2XX 1 SPS"         } }, | ||||
| 	{ std::make_tuple(USRP2, 4, 1), { 1, 0.0,  390625,  7.6547e-5,  "N2XX 4/1 Tx/Rx SPS" } }, | ||||
| 	{ std::make_tuple(USRP2, 4, 4), { 1, 0.0,  390625,  4.6080e-5,  "N2XX 4 SPS"         } }, | ||||
| @@ -129,9 +117,7 @@ static const std::map<dev_key, dev_desc> dev_param_map { | ||||
| 	{ std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } }, | ||||
| }; | ||||
|  | ||||
| typedef std::tuple<uhd_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 { | ||||
| static const power_map_t dev_band_nom_power_param_map { | ||||
| 	{ std::make_tuple(B200, GSM_BAND_850),	{ 89.75, 13.3, -7.5  } }, | ||||
| 	{ std::make_tuple(B200, GSM_BAND_900),	{ 89.75, 13.3, -7.5  } }, | ||||
| 	{ std::make_tuple(B200, GSM_BAND_1800),	{ 89.75, 7.5,  -11.0 } }, | ||||
| @@ -220,15 +206,10 @@ 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); | ||||
| } | ||||
|  | ||||
| uhd_device::uhd_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), | ||||
| 	  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,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL) | ||||
| uhd_device::uhd_device(InterfaceType iface, const struct trx_cfg *cfg) | ||||
| 	: RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), rx_gain_min(0.0), | ||||
| 	  rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), prev_ts(0, 0), | ||||
| 	  ts_initial(0), ts_offset(0), async_event_thrd(NULL) | ||||
| { | ||||
| } | ||||
|  | ||||
| @@ -240,47 +221,6 @@ uhd_device::~uhd_device() | ||||
| 		delete rx_buffers[i]; | ||||
| } | ||||
|  | ||||
| void uhd_device::assign_band_desc(enum gsm_band req_band) | ||||
| { | ||||
| 	dev_band_map_it it; | ||||
|  | ||||
| 	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"; | ||||
| 		it = dev_band_nom_power_param_map.find(dev_band_key(B210, req_band)); | ||||
| 	} | ||||
| 	OSMO_ASSERT(it != dev_band_nom_power_param_map.end()) | ||||
| 	band_desc = it->second; | ||||
| } | ||||
|  | ||||
| bool uhd_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); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (req_band != band) { | ||||
| 		band = req_band; | ||||
| 		assign_band_desc(band); | ||||
| 	} | ||||
| 	band_ass_curr_sess = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void uhd_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..."; | ||||
| 		assign_band_desc(GSM_BAND_900); | ||||
| 	} | ||||
| 	desc = band_desc; | ||||
| } | ||||
|  | ||||
| void uhd_device::init_gains() | ||||
| { | ||||
| 	double tx_gain_min, tx_gain_max; | ||||
| @@ -345,7 +285,7 @@ void uhd_device::set_rates() | ||||
| 	rx_rate = usrp_dev->get_rx_rate(); | ||||
|  | ||||
| 	ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate); | ||||
| 	LOGC(DDEV, INFO) << "Rates configured for " << desc.str; | ||||
| 	LOGC(DDEV, INFO) << "Rates configured for " << desc.desc_str; | ||||
| } | ||||
|  | ||||
| double uhd_device::setRxGain(double db, size_t chan) | ||||
| @@ -355,6 +295,9 @@ double uhd_device::setRxGain(double db, size_t chan) | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	if (cfg->overrides.ul_gain_override) | ||||
| 		return rx_gains[chan]; | ||||
|  | ||||
| 	usrp_dev->set_rx_gain(db, chan); | ||||
| 	rx_gains[chan] = usrp_dev->get_rx_gain(chan); | ||||
|  | ||||
| @@ -397,6 +340,9 @@ double uhd_device::setPowerAttenuation(int atten, size_t chan) { | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	if (cfg->overrides.dl_gain_override) | ||||
| 		return atten; // ensures caller does not apply digital attenuation | ||||
|  | ||||
| 	get_dev_band_desc(desc); | ||||
| 	tx_power = desc.nom_out_tx_power - atten; | ||||
| 	db = TxPower2TxGain(desc, tx_power); | ||||
| @@ -548,7 +494,7 @@ void uhd_device::set_channels(bool swap) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| int uhd_device::open() | ||||
| { | ||||
| 	const char *refstr; | ||||
| 	int clock_lock_attempts = 15; | ||||
| @@ -564,10 +510,10 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| #endif | ||||
|  | ||||
| 	// Find UHD devices | ||||
| 	uhd::device_addr_t addr(args); | ||||
| 	uhd::device_addr_t addr(cfg->dev_args); | ||||
| 	uhd::device_addrs_t dev_addrs = uhd::device::find(addr); | ||||
| 	if (dev_addrs.size() == 0) { | ||||
| 		LOGC(DDEV, ALERT) << "No UHD devices found with address '" << args << "'"; | ||||
| 		LOGC(DDEV, ALERT) << "No UHD devices found with address '" << cfg->dev_args << "'"; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| @@ -576,7 +522,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	try { | ||||
| 		usrp_dev = uhd::usrp::multi_usrp::make(addr); | ||||
| 	} catch(uhd::key_error::exception &e) { | ||||
| 		LOGC(DDEV, ALERT) << "UHD make failed, device " << args << ", exception:\n" << e.what(); | ||||
| 		LOGC(DDEV, ALERT) << "UHD make failed, device " << cfg->dev_args << ", exception:\n" << e.what(); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| @@ -590,8 +536,8 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	} | ||||
|  | ||||
| 	try { | ||||
| 		set_channels(swap_channels); | ||||
|         } catch (const std::exception &e) { | ||||
| 		set_channels(cfg->swap_channels); | ||||
| 	} catch (const std::exception &e) { | ||||
| 		LOGC(DDEV, ALERT) << "Channel setting failed - " << e.what(); | ||||
| 		return -1; | ||||
| 	} | ||||
| @@ -607,7 +553,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	rx_gains.resize(chans); | ||||
| 	rx_buffers.resize(chans); | ||||
|  | ||||
| 	switch (ref) { | ||||
| 	switch (cfg->clock_ref) { | ||||
| 	case REF_INTERNAL: | ||||
| 		refstr = "internal"; | ||||
| 		break; | ||||
| @@ -686,6 +632,32 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| 	// Print configuration | ||||
| 	LOGC(DDEV, INFO) << "Device configuration: " << usrp_dev->get_pp_string(); | ||||
|  | ||||
| 	if (cfg->overrides.dl_freq_override) { | ||||
| 		uhd::tune_request_t treq_tx = uhd::tune_request_t(cfg->overrides.dl_freq, 0); | ||||
| 		auto tres = usrp_dev->set_tx_freq(treq_tx, 0); | ||||
| 		tx_freqs[0] = usrp_dev->get_tx_freq(0); | ||||
| 		LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << tx_freqs[0] << ", TX): " << tres.to_pp_string() << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	if (cfg->overrides.ul_freq_override) { | ||||
| 		uhd::tune_request_t treq_rx = uhd::tune_request_t(cfg->overrides.ul_freq, 0); | ||||
| 		auto tres = usrp_dev->set_rx_freq(treq_rx, 0); | ||||
| 		rx_freqs[0] = usrp_dev->get_rx_freq(0); | ||||
| 		LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << rx_freqs[0] << ", RX): " << tres.to_pp_string() << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	if (cfg->overrides.ul_gain_override) { | ||||
| 		usrp_dev->set_rx_gain(cfg->overrides.ul_gain, 0); | ||||
| 		rx_gains[0] = usrp_dev->get_rx_gain(0); | ||||
| 		LOGCHAN(0, DDEV, INFO) << " OVERRIDE RX gain:" << rx_gains[0] << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	if (cfg->overrides.dl_gain_override) { | ||||
| 		usrp_dev->set_tx_gain(cfg->overrides.dl_gain, 0); | ||||
| 		tx_gains[0] = usrp_dev->get_tx_gain(0); | ||||
| 		LOGCHAN(0, DDEV, INFO) << " OVERRIDE TX gain:" << tx_gains[0] << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	if (iface == MULTI_ARFCN) | ||||
| 		return MULTI_ARFCN; | ||||
|  | ||||
| @@ -796,7 +768,7 @@ bool uhd_device::stop() | ||||
| 	for (size_t i = 0; i < rx_buffers.size(); i++) | ||||
| 		rx_buffers[i]->reset(); | ||||
|  | ||||
| 	band_ass_curr_sess = false; | ||||
| 	band_reset(); | ||||
|  | ||||
| 	started = false; | ||||
| 	return true; | ||||
| @@ -1036,17 +1008,22 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) | ||||
| { | ||||
| 	std::vector<double> freqs; | ||||
| 	uhd::tune_result_t tres; | ||||
| 	std::string str_dir = tx ? "Tx" : "Rx"; | ||||
|  | ||||
| 	if (cfg->overrides.dl_freq_override || cfg->overrides.ul_freq_override) | ||||
| 		return true; | ||||
|  | ||||
| 	if (!update_band_from_freq(freq, chan, tx)) | ||||
| 		return false; | ||||
|  | ||||
| 	uhd::tune_request_t treq = select_freq(freq, chan, tx); | ||||
| 	std::string str_dir; | ||||
|  | ||||
| 	if (tx) { | ||||
| 		tres = usrp_dev->set_tx_freq(treq, chan); | ||||
| 		tx_freqs[chan] = usrp_dev->get_tx_freq(chan); | ||||
| 		str_dir = "Tx"; | ||||
| 	} else { | ||||
| 		tres = usrp_dev->set_rx_freq(treq, chan); | ||||
| 		rx_freqs[chan] = usrp_dev->get_rx_freq(chan); | ||||
| 		str_dir = "Rx"; | ||||
| 	} | ||||
| 	LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl; | ||||
|  | ||||
| @@ -1076,59 +1053,20 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) | ||||
|  | ||||
| bool uhd_device::setTxFreq(double wFreq, size_t chan) | ||||
| { | ||||
| 	uint16_t req_arfcn; | ||||
| 	enum gsm_band req_band; | ||||
|  | ||||
| 	if (chan >= tx_freqs.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return false; | ||||
| 	} | ||||
| 	ScopedLock lock(tune_lock); | ||||
|  | ||||
| 	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 << " )"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!set_band(req_band)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!set_freq(wFreq, chan, true)) | ||||
| 		return false; | ||||
|  | ||||
| 	return true; | ||||
| 	return set_freq(wFreq, chan, true); | ||||
| } | ||||
|  | ||||
| bool uhd_device::setRxFreq(double wFreq, size_t chan) | ||||
| { | ||||
| 	uint16_t req_arfcn; | ||||
| 	enum gsm_band req_band; | ||||
|  | ||||
| 	if (chan >= rx_freqs.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return false; | ||||
| 	} | ||||
| 	ScopedLock lock(tune_lock); | ||||
|  | ||||
| 	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1); | ||||
| 	if (req_arfcn == 0xffff) { | ||||
| 		LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz"; | ||||
| 		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 << " )"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!set_band(req_band)) | ||||
| 		return false; | ||||
|  | ||||
| 	return set_freq(wFreq, chan, false); | ||||
| } | ||||
| @@ -1378,11 +1316,8 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata) | ||||
| } | ||||
|  | ||||
| #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(InterfaceType type, const struct trx_cfg *cfg) | ||||
| { | ||||
| 	return new uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); | ||||
| 	return new uhd_device(type, cfg); | ||||
| } | ||||
| #endif | ||||
|   | ||||
| @@ -30,6 +30,7 @@ | ||||
| #include "config.h" | ||||
| #endif | ||||
|  | ||||
| #include "bandmanager.h" | ||||
| #include "radioDevice.h" | ||||
| #include "smpl_buf.h" | ||||
|  | ||||
| @@ -71,6 +72,19 @@ struct dev_band_desc { | ||||
| 	double rxgain2rssioffset_rel; /* dB */ | ||||
| }; | ||||
|  | ||||
| struct dev_desc { | ||||
| 	unsigned channels; | ||||
| 	double mcr; | ||||
| 	double rate; | ||||
| 	double offset; | ||||
| 	std::string desc_str; | ||||
| }; | ||||
|  | ||||
| using dev_key = std::tuple<uhd_dev_type, int, int>; | ||||
| using dev_band_key = std::tuple<uhd_dev_type, enum gsm_band>; | ||||
| using power_map_t = std::map<dev_band_key, dev_band_desc>; | ||||
| using dev_map_t = std::map<dev_key, dev_desc>; | ||||
|  | ||||
| /* | ||||
|     uhd_device - UHD implementation of the Device interface. Timestamped samples | ||||
|                 are sent to and received from the device. An intermediate buffer | ||||
| @@ -78,19 +92,19 @@ struct dev_band_desc { | ||||
|                 Events and errors such as underruns are reported asynchronously | ||||
|                 by the device and received in a separate thread. | ||||
| */ | ||||
| class uhd_device : public RadioDevice { | ||||
| class uhd_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> { | ||||
| public: | ||||
| 	uhd_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); | ||||
| 	~uhd_device(); | ||||
|     uhd_device(InterfaceType iface, const struct trx_cfg *cfg); | ||||
|     ~uhd_device(); | ||||
|  | ||||
| 	int open(const std::string &args, int ref, bool swap_channels); | ||||
| 	bool start(); | ||||
| 	bool stop(); | ||||
| 	bool restart(); | ||||
| 	enum TxWindowType getWindowType() { return tx_window; } | ||||
|     int open(); | ||||
|     bool start(); | ||||
|     bool stop(); | ||||
|     bool restart(); | ||||
|     enum TxWindowType getWindowType() | ||||
|     { | ||||
| 	    return tx_window; | ||||
|     } | ||||
|  | ||||
| 	int readSamples(std::vector<short *> &bufs, int len, bool *overrun, | ||||
| 			TIMESTAMP timestamp, bool *underrun); | ||||
| @@ -160,9 +174,6 @@ protected: | ||||
|  | ||||
| 	std::vector<double> tx_gains, rx_gains; | ||||
| 	std::vector<double> tx_freqs, rx_freqs; | ||||
| 	bool band_ass_curr_sess; /* true if  "band" was set after last POWEROFF */ | ||||
| 	enum gsm_band band; | ||||
| 	struct dev_band_desc band_desc; | ||||
| 	size_t tx_spp, rx_spp; | ||||
|  | ||||
| 	bool started; | ||||
| @@ -191,10 +202,6 @@ protected: | ||||
|  | ||||
| 	uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx); | ||||
| 	bool set_freq(double freq, size_t chan, bool tx); | ||||
| 	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); | ||||
|  | ||||
| 	Thread *async_event_thrd; | ||||
| 	Mutex tune_lock; | ||||
| }; | ||||
|   | ||||
| @@ -60,11 +60,7 @@ const dboardConfigType dboardConfig = TXA_RXB; | ||||
|  | ||||
| const double USRPDevice::masterClockRate = 52.0e6; | ||||
|  | ||||
| USRPDevice::USRPDevice(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) | ||||
| USRPDevice::USRPDevice(InterfaceType iface, const struct trx_cfg *cfg) : RadioDevice(iface, cfg) | ||||
| { | ||||
|   LOGC(DDEV, INFO) << "creating USRP device..."; | ||||
|  | ||||
| @@ -94,7 +90,7 @@ USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, | ||||
| #endif | ||||
| } | ||||
|  | ||||
| int USRPDevice::open(const std::string &, int, bool) | ||||
| int USRPDevice::open() | ||||
| { | ||||
|   writeLock.unlock(); | ||||
|  | ||||
| @@ -587,8 +583,7 @@ bool USRPDevice::updateAlignment(TIMESTAMP timestamp) | ||||
| { | ||||
| #ifndef SWLOOPBACK | ||||
|   short data[] = {0x00,0x02,0x00,0x00}; | ||||
|   uint32_t *wordPtr = (uint32_t *) data; | ||||
|   *wordPtr = host_to_usrp_u32(*wordPtr); | ||||
|   /* FIXME: big endian */ | ||||
|   bool tmpUnderrun; | ||||
|  | ||||
|   std::vector<short *> buf(1, data); | ||||
| @@ -659,22 +654,19 @@ bool USRPDevice::setTxFreq(double wFreq) { return true;}; | ||||
| bool USRPDevice::setRxFreq(double wFreq) { return true;}; | ||||
| #endif | ||||
|  | ||||
| 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(InterfaceType type, const struct trx_cfg *cfg) | ||||
| { | ||||
| 	if (tx_sps != rx_sps) { | ||||
| 		LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	if (chans != 1) { | ||||
| 		LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	if (lo_offset != 0.0) { | ||||
| 		LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset"; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return new USRPDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); | ||||
|   if (cfg->tx_sps != cfg->rx_sps) { | ||||
|     LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps"; | ||||
|     return NULL; | ||||
|   } | ||||
|   if (cfg->num_chans != 1) { | ||||
|     LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel"; | ||||
|     return NULL; | ||||
|   } | ||||
|   if (cfg->offset != 0.0) { | ||||
|     LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset"; | ||||
|     return NULL; | ||||
|   } | ||||
|   return new USRPDevice(type, cfg); | ||||
| } | ||||
|   | ||||
| @@ -104,20 +104,21 @@ private: | ||||
|  public: | ||||
|  | ||||
|   /** Object constructor */ | ||||
|   USRPDevice(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); | ||||
|      USRPDevice(InterfaceType iface, const struct trx_cfg *cfg); | ||||
|  | ||||
|   /** Instantiate the USRP */ | ||||
|   int open(const std::string &, int, bool); | ||||
|      /** Instantiate the USRP */ | ||||
|      int open(); | ||||
|  | ||||
|   /** Start the USRP */ | ||||
|   bool start(); | ||||
|      /** Start the USRP */ | ||||
|      bool start(); | ||||
|  | ||||
|   /** Stop the USRP */ | ||||
|   bool stop(); | ||||
|      /** Stop the USRP */ | ||||
|      bool stop(); | ||||
|  | ||||
|   enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; } | ||||
|      enum TxWindowType getWindowType() | ||||
|      { | ||||
| 	     return TX_WINDOW_USRP1; | ||||
|      } | ||||
|  | ||||
|   /** | ||||
| 	Read samples from the USRP. | ||||
|   | ||||
| @@ -1,142 +1,149 @@ | ||||
| #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> | ||||
|  | ||||
| #define gr_complex std::complex<float> | ||||
|  | ||||
|  | ||||
| #define GSM_SYMBOL_RATE  (1625000.0/6.0) //symbols per second | ||||
| #define GSM_SYMBOL_PERIOD (1.0/GSM_SYMBOL_RATE) //seconds per symbol | ||||
|  | ||||
| //Burst timing | ||||
| #define TAIL_BITS         3 | ||||
| #define GUARD_BITS        8 | ||||
| #define GUARD_FRACTIONAL  0.25 //fractional part of guard period | ||||
| #define GUARD_PERIOD      GUARD_BITS + GUARD_FRACTIONAL | ||||
| #define DATA_BITS         57   //size of 1 data block in normal burst | ||||
| #define STEALING_BIT      1 | ||||
| #define N_TRAIN_BITS      26 | ||||
| #define N_SYNC_BITS       64 | ||||
| #define USEFUL_BITS       142  //(2*(DATA_BITS+STEALING_BIT) + N_TRAIN_BITS ) | ||||
| #define FCCH_BITS         USEFUL_BITS | ||||
| #define BURST_SIZE        (USEFUL_BITS+2*TAIL_BITS) | ||||
| #define ACCESS_BURST_SIZE 88 | ||||
| #define PROCESSED_CHUNK   BURST_SIZE+2*GUARD_PERIOD | ||||
|  | ||||
| #define SCH_DATA_LEN      39 | ||||
| #define TS_BITS           (TAIL_BITS+USEFUL_BITS+TAIL_BITS+GUARD_BITS)  //a full TS (156 bits) | ||||
| #define TS_PER_FRAME      8 | ||||
| #define FRAME_BITS        (TS_PER_FRAME * TS_BITS + 2) // 156.25 * 8 | ||||
| #define FCCH_POS          TAIL_BITS | ||||
| #define SYNC_POS          (TAIL_BITS + 39) | ||||
| #define TRAIN_POS         ( TAIL_BITS + (DATA_BITS+STEALING_BIT) + 5) //first 5 bits of a training sequence | ||||
| 													   //aren't used for channel impulse response estimation | ||||
| #define TRAIN_BEGINNING   5 | ||||
| #define SAFETY_MARGIN     6   // | ||||
|  | ||||
| #define FCCH_HITS_NEEDED        (USEFUL_BITS - 4) | ||||
| #define FCCH_MAX_MISSES         1 | ||||
| #define FCCH_MAX_FREQ_OFFSET    100 | ||||
|  | ||||
| #define CHAN_IMP_RESP_LENGTH  5 | ||||
|  | ||||
| #define MAX_SCH_ERRORS    10  //maximum number of subsequent sch errors after which gsm receiver goes to find_next_fcch state | ||||
|  | ||||
| typedef enum { empty, fcch_burst, sch_burst, normal_burst, rach_burst, dummy, dummy_or_normal, normal_or_noise } burst_type; | ||||
| typedef enum { unknown, multiframe_26, multiframe_51 } multiframe_type; | ||||
|  | ||||
| static const unsigned char SYNC_BITS[] = { | ||||
|   1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, | ||||
|   0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, | ||||
|   0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, | ||||
|   0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 | ||||
| }; | ||||
|  | ||||
| const unsigned FCCH_FRAMES[] = { 0, 10, 20, 30, 40 }; | ||||
| const unsigned SCH_FRAMES[] = { 1, 11, 21, 31, 41 }; | ||||
|  | ||||
| const unsigned BCCH_FRAMES[] = { 2, 3, 4, 5 };          //!!the receiver shouldn't care about logical | ||||
| 													  //!!channels so this will be removed from this header | ||||
| const unsigned TEST_CCH_FRAMES[] = { 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 44, 45, 46, 47, 48, 49 }; | ||||
| const unsigned TRAFFIC_CHANNEL_F[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; | ||||
| const unsigned TEST51[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }; | ||||
|  | ||||
|  | ||||
| #define TSC0  0 | ||||
| #define TSC1  1 | ||||
| #define TSC2  2 | ||||
| #define TSC3  3 | ||||
| #define TSC4  4 | ||||
| #define TSC5  5 | ||||
| #define TSC6  6 | ||||
| #define TSC7  7 | ||||
| #define TS_DUMMY 8 | ||||
|  | ||||
| #define TRAIN_SEQ_NUM 9 | ||||
|  | ||||
| #define TIMESLOT0  0 | ||||
| #define TIMESLOT1  1 | ||||
| #define TIMESLOT2  2 | ||||
| #define TIMESLOT3  3 | ||||
| #define TIMESLOT4  4 | ||||
| #define TIMESLOT5  5 | ||||
| #define TIMESLOT6  6 | ||||
| #define TIMESLOT7  7 | ||||
|  | ||||
|  | ||||
| static const unsigned char train_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS] = { | ||||
|   {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, 0, 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}, | ||||
|   {0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1} // DUMMY | ||||
| }; | ||||
|  | ||||
|  | ||||
| //Dummy burst 0xFB 76 0A 4E 09 10 1F 1C 5C 5C 57 4A 33 39 E9 F1 2F A8 | ||||
| static const unsigned char dummy_burst[] = { | ||||
|   0, 0, 0, | ||||
|   1, 1, 1, 1, 1, 0, 1, 1, 0, 1, | ||||
|   1, 1, 0, 1, 1, 0, 0, 0, 0, 0, | ||||
|   1, 0, 1, 0, 0, 1, 0, 0, 1, 1, | ||||
|   1, 0, 0, 0, 0, 0, 1, 0, 0, 1, | ||||
|   0, 0, 0, 1, 0, 0, 0, 0, 0, 0, | ||||
|   0, 1, 1, 1, 1, 1, 0, 0, | ||||
|  | ||||
|   0, 1, 1, 1, 0, 0, 0, 1, 0, 1, | ||||
|   1, 1, 0, 0, 0, 1, 0, 1, 1, 1, | ||||
|   0, 0, 0, 1, 0, 1, | ||||
|  | ||||
|   0, 1, 1, 1, 0, 1, 0, 0, 1, 0, | ||||
|   1, 0, 0, 0, 1, 1, 0, 0, 1, 1, | ||||
|   0, 0, 1, 1, 1, 0, 0, 1, 1, 1, | ||||
|   1, 0, 1, 0, 0, 1, 1, 1, 1, 1, | ||||
|   0, 0, 0, 1, 0, 0, 1, 0, 1, 1, | ||||
|   1, 1, 1, 0, 1, 0, 1, 0, | ||||
|   0, 0, 0 | ||||
| }; | ||||
| #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> | ||||
|  | ||||
| #define gr_complex std::complex<float> | ||||
|  | ||||
|  | ||||
| #define GSM_SYMBOL_RATE  (1625000.0/6.0) //symbols per second | ||||
| #define GSM_SYMBOL_PERIOD (1.0/GSM_SYMBOL_RATE) //seconds per symbol | ||||
|  | ||||
| //Burst timing | ||||
| #define TAIL_BITS         3 | ||||
| #define GUARD_BITS        8 | ||||
| #define GUARD_FRACTIONAL  0.25 //fractional part of guard period | ||||
| #define GUARD_PERIOD      GUARD_BITS + GUARD_FRACTIONAL | ||||
| #define DATA_BITS         57   //size of 1 data block in normal burst | ||||
| #define STEALING_BIT      1 | ||||
| #define N_TRAIN_BITS      26 | ||||
| #define N_SYNC_BITS       64 | ||||
| #define N_ACCESS_BITS     41 | ||||
| #define USEFUL_BITS       142  //(2*(DATA_BITS+STEALING_BIT) + N_TRAIN_BITS ) | ||||
| #define FCCH_BITS         USEFUL_BITS | ||||
| #define BURST_SIZE        (USEFUL_BITS+2*TAIL_BITS) | ||||
| #define ACCESS_BURST_SIZE 88 | ||||
| #define PROCESSED_CHUNK   BURST_SIZE+2*GUARD_PERIOD | ||||
|  | ||||
| #define SCH_DATA_LEN      39 | ||||
| #define TS_BITS           (TAIL_BITS+USEFUL_BITS+TAIL_BITS+GUARD_BITS)  //a full TS (156 bits) | ||||
| #define TS_PER_FRAME      8 | ||||
| #define FRAME_BITS        (TS_PER_FRAME * TS_BITS + 2) // 156.25 * 8 | ||||
| #define FCCH_POS          TAIL_BITS | ||||
| #define SYNC_POS          (TAIL_BITS + 39) | ||||
| #define TRAIN_POS         ( TAIL_BITS + (DATA_BITS+STEALING_BIT) + 5) //first 5 bits of a training sequence | ||||
| 													   //aren't used for channel impulse response estimation | ||||
| #define TRAIN_BEGINNING   5 | ||||
| #define SAFETY_MARGIN     6   // | ||||
|  | ||||
| #define FCCH_HITS_NEEDED        (USEFUL_BITS - 4) | ||||
| #define FCCH_MAX_MISSES         1 | ||||
| #define FCCH_MAX_FREQ_OFFSET    100 | ||||
|  | ||||
| #define CHAN_IMP_RESP_LENGTH  5 | ||||
|  | ||||
| #define MAX_SCH_ERRORS    10  //maximum number of subsequent sch errors after which gsm receiver goes to find_next_fcch state | ||||
|  | ||||
| typedef enum { empty, fcch_burst, sch_burst, normal_burst, rach_burst, dummy, dummy_or_normal, normal_or_noise } burst_type; | ||||
| typedef enum { unknown, multiframe_26, multiframe_51 } multiframe_type; | ||||
|  | ||||
| static const unsigned char SYNC_BITS[] = { | ||||
|   1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, | ||||
|   0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, | ||||
|   0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, | ||||
|   0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 | ||||
| }; | ||||
|  | ||||
| static const unsigned char ACCESS_BITS [] = { | ||||
|  0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, | ||||
|  0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, | ||||
|  0, 1, 1, 1, 1, 0, 0, 0 | ||||
| }; | ||||
|  | ||||
| const unsigned FCCH_FRAMES[] = { 0, 10, 20, 30, 40 }; | ||||
| const unsigned SCH_FRAMES[] = { 1, 11, 21, 31, 41 }; | ||||
|  | ||||
| const unsigned BCCH_FRAMES[] = { 2, 3, 4, 5 };          //!!the receiver shouldn't care about logical | ||||
| 													  //!!channels so this will be removed from this header | ||||
| const unsigned TEST_CCH_FRAMES[] = { 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 44, 45, 46, 47, 48, 49 }; | ||||
| const unsigned TRAFFIC_CHANNEL_F[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; | ||||
| const unsigned TEST51[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }; | ||||
|  | ||||
|  | ||||
| #define TSC0  0 | ||||
| #define TSC1  1 | ||||
| #define TSC2  2 | ||||
| #define TSC3  3 | ||||
| #define TSC4  4 | ||||
| #define TSC5  5 | ||||
| #define TSC6  6 | ||||
| #define TSC7  7 | ||||
| #define TS_DUMMY 8 | ||||
|  | ||||
| #define TRAIN_SEQ_NUM 9 | ||||
|  | ||||
| #define TIMESLOT0  0 | ||||
| #define TIMESLOT1  1 | ||||
| #define TIMESLOT2  2 | ||||
| #define TIMESLOT3  3 | ||||
| #define TIMESLOT4  4 | ||||
| #define TIMESLOT5  5 | ||||
| #define TIMESLOT6  6 | ||||
| #define TIMESLOT7  7 | ||||
|  | ||||
|  | ||||
| static const unsigned char train_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS] = { | ||||
|   {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, 0, 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}, | ||||
|   {0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1} // DUMMY | ||||
| }; | ||||
|  | ||||
|  | ||||
| //Dummy burst 0xFB 76 0A 4E 09 10 1F 1C 5C 5C 57 4A 33 39 E9 F1 2F A8 | ||||
| static const unsigned char dummy_burst[] = { | ||||
|   0, 0, 0, | ||||
|   1, 1, 1, 1, 1, 0, 1, 1, 0, 1, | ||||
|   1, 1, 0, 1, 1, 0, 0, 0, 0, 0, | ||||
|   1, 0, 1, 0, 0, 1, 0, 0, 1, 1, | ||||
|   1, 0, 0, 0, 0, 0, 1, 0, 0, 1, | ||||
|   0, 0, 0, 1, 0, 0, 0, 0, 0, 0, | ||||
|   0, 1, 1, 1, 1, 1, 0, 0, | ||||
|  | ||||
|   0, 1, 1, 1, 0, 0, 0, 1, 0, 1, | ||||
|   1, 1, 0, 0, 0, 1, 0, 1, 1, 1, | ||||
|   0, 0, 0, 1, 0, 1, | ||||
|  | ||||
|   0, 1, 1, 1, 0, 1, 0, 0, 1, 0, | ||||
|   1, 0, 0, 0, 1, 1, 0, 0, 1, 1, | ||||
|   0, 0, 1, 1, 1, 0, 0, 1, 1, 1, | ||||
|   1, 0, 1, 0, 0, 1, 1, 1, 1, 1, | ||||
|   0, 0, 0, 1, 0, 0, 1, 0, 1, 1, | ||||
|   1, 1, 1, 0, 1, 0, 1, 0, | ||||
|   0, 0, 0 | ||||
| }; | ||||
|   | ||||
| @@ -1,299 +1,305 @@ | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @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 | ||||
|  * 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 "constants.h" | ||||
|  | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
| #include <complex> | ||||
|  | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <string.h> | ||||
| #include <iostream> | ||||
| #include <numeric> | ||||
| #include <vector> | ||||
| #include <fstream> | ||||
|  | ||||
| #include "viterbi_detector.h" | ||||
| #include "grgsm_vitac.h" | ||||
|  | ||||
| //signalVector mChanResp; | ||||
| gr_complex d_sch_training_seq[N_SYNC_BITS]; ///<encoded training sequence of a SCH burst | ||||
| gr_complex d_norm_training_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS]; ///<encoded training sequences of a normal and dummy burst | ||||
| const int d_chan_imp_length = CHAN_IMP_RESP_LENGTH; | ||||
|  | ||||
| void initvita() { | ||||
|  | ||||
| 	/** | ||||
| 	 * Prepare SCH sequence bits | ||||
| 	 * | ||||
| 	 * (TS_BITS + 2 * GUARD_PERIOD) | ||||
| 	 * Burst and two guard periods | ||||
| 	 * (one guard period is an arbitrary overlap) | ||||
| 	 */ | ||||
| 	gmsk_mapper(SYNC_BITS, N_SYNC_BITS, | ||||
| 		d_sch_training_seq, gr_complex(0.0, -1.0)); | ||||
| 	for (auto &i : d_sch_training_seq) | ||||
| 		i = conj(i); | ||||
|  | ||||
| 	/* Prepare bits of training sequences */ | ||||
| 	for (int i = 0; i < TRAIN_SEQ_NUM; i++) { | ||||
| 		/** | ||||
| 		 * If first bit of the sequence is 0 | ||||
| 		 * => first symbol is 1, else -1 | ||||
| 		 */ | ||||
| 		gr_complex startpoint = train_seq[i][0] == 0 ? | ||||
| 			gr_complex(1.0, 0.0) : gr_complex(-1.0, 0.0); | ||||
| 		gmsk_mapper(train_seq[i], N_TRAIN_BITS, | ||||
| 			d_norm_training_seq[i], startpoint); | ||||
| 		for (auto &i : d_norm_training_seq[i]) | ||||
| 			i = conj(i); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| MULTI_VER_TARGET_ATTR | ||||
| 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 }; | ||||
| 	gr_complex filtered_burst[BURST_SIZE]; | ||||
| 	gr_complex rhh[CHAN_IMP_RESP_LENGTH]; | ||||
| 	float output[BURST_SIZE]; | ||||
| 	int start_state = 3; | ||||
|  | ||||
| 	// if(burst_start < 0 ||burst_start > 10) | ||||
| 	// 	fprintf(stderr, "bo %d\n", burst_start); | ||||
|  | ||||
| 	// burst_start = burst_start >= 0 ? burst_start : 0; | ||||
|  | ||||
| 	autocorrelation(chan_imp_resp, &rhh_temp[0], d_chan_imp_length * d_OSR); | ||||
| 	for (int ii = 0; ii < d_chan_imp_length; ii++) | ||||
| 		rhh[ii] = conj(rhh_temp[ii * d_OSR]); | ||||
|  | ||||
| 	mafi(&input[burst_start], BURST_SIZE, chan_imp_resp, | ||||
| 		d_chan_imp_length * d_OSR, filtered_burst); | ||||
|  | ||||
| 	viterbi_detector(filtered_burst, BURST_SIZE, rhh, | ||||
| 		start_state, stop_states, 2, output); | ||||
|  | ||||
| 	for (int i = 0; i < BURST_SIZE; i++) | ||||
| 		output_binary[i] = output[i] * -127; // pre flip bits! | ||||
| } | ||||
|  | ||||
| void | ||||
| gmsk_mapper(const unsigned char* input, | ||||
| 	int nitems, gr_complex* gmsk_output, gr_complex start_point) | ||||
| { | ||||
| 	gr_complex j = gr_complex(0.0, 1.0); | ||||
| 	gmsk_output[0] = start_point; | ||||
|  | ||||
| 	int previous_symbol = 2 * input[0] - 1; | ||||
| 	int current_symbol; | ||||
| 	int encoded_symbol; | ||||
|  | ||||
| 	for (int i = 1; i < nitems; i++) { | ||||
| 		/* Change bits representation to NRZ */ | ||||
| 		current_symbol = 2 * input[i] - 1; | ||||
|  | ||||
| 		/* Differentially encode */ | ||||
| 		encoded_symbol = current_symbol * previous_symbol; | ||||
|  | ||||
| 		/* And do GMSK mapping */ | ||||
| 		gmsk_output[i] = j * gr_complex(encoded_symbol, 0.0) | ||||
| 			* gmsk_output[i - 1]; | ||||
|  | ||||
| 		previous_symbol = current_symbol; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| gr_complex | ||||
| correlate_sequence(const gr_complex* sequence, | ||||
| 	int length, const gr_complex* input) | ||||
| { | ||||
| 	gr_complex result(0.0, 0.0); | ||||
|  | ||||
| 	for (int ii = 0; ii < length; ii++) | ||||
| 		result += sequence[ii] * input[ii * d_OSR]; | ||||
|  | ||||
| 	return conj(result) / gr_complex(length, 0); | ||||
| } | ||||
|  | ||||
| /* Computes autocorrelation for positive arguments */ | ||||
| inline void | ||||
| autocorrelation(const gr_complex* input, | ||||
| 	gr_complex* out, int nitems) | ||||
| { | ||||
| 	for (int k = nitems - 1; k >= 0; k--) { | ||||
| 		out[k] = gr_complex(0, 0); | ||||
| 		for (int i = k; i < nitems; i++) | ||||
| 			out[k] += input[i] * conj(input[i - k]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| inline void | ||||
| mafi(const gr_complex* input, int nitems, | ||||
| 	gr_complex* filter, int filter_length, gr_complex* output) | ||||
| { | ||||
| 	for (int n = 0; n < nitems; n++) { | ||||
| 		int a = n * d_OSR; | ||||
| 		output[n] = 0; | ||||
|  | ||||
| 		for (int ii = 0; ii < filter_length; ii++) { | ||||
| 			if ((a + ii) >= nitems * d_OSR) | ||||
| 				break; | ||||
|  | ||||
| 			output[n] += input[a + ii] * filter[ii]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int get_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, int search_center, int search_start_pos, | ||||
| 		      int search_stop_pos, gr_complex *tseq, int tseqlen, float *corr_max) | ||||
| { | ||||
| 	std::vector<gr_complex> correlation_buffer; | ||||
| 	std::vector<float> window_energy_buffer; | ||||
| 	std::vector<float> power_buffer; | ||||
|  | ||||
| 	for (int ii = search_start_pos; ii < search_stop_pos; ii++) { | ||||
| 		gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[ii]); | ||||
| 		correlation_buffer.push_back(correlation); | ||||
| 		power_buffer.push_back(std::pow(abs(correlation), 2)); | ||||
| 	} | ||||
|  | ||||
| 	int strongest_corr_nr = max_element(power_buffer.begin(), power_buffer.end()) - power_buffer.begin(); | ||||
|  | ||||
| 	/* Compute window energies */ | ||||
| 	auto window_energy_start_offset = strongest_corr_nr - 6 * d_OSR; | ||||
| 	window_energy_start_offset = window_energy_start_offset < 0 ? 0 : window_energy_start_offset; //can end up out of range.. | ||||
| 	auto window_energy_end_offset = strongest_corr_nr + 6 * d_OSR + d_chan_imp_length * d_OSR; | ||||
| 	auto iter = power_buffer.begin() + window_energy_start_offset; | ||||
| 	auto iter_end = power_buffer.begin() + window_energy_end_offset; | ||||
| 	while (iter != iter_end) { | ||||
| 		std::vector<float>::iterator iter_ii = iter; | ||||
| 		bool loop_end = false; | ||||
| 		float energy = 0; | ||||
|  | ||||
| 		int len = d_chan_imp_length * d_OSR; | ||||
| 		for (int ii = 0; ii < len; ii++, iter_ii++) { | ||||
| 			if (iter_ii == power_buffer.end()) { | ||||
| 				loop_end = true; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			energy += (*iter_ii); | ||||
| 		} | ||||
|  | ||||
| 		if (loop_end) | ||||
| 			break; | ||||
|  | ||||
| 		window_energy_buffer.push_back(energy); | ||||
| 		iter++; | ||||
| 	} | ||||
|  | ||||
| 	/* Calculate the strongest window number */ | ||||
| 	int strongest_window_nr = window_energy_start_offset + | ||||
| 				  max_element(window_energy_buffer.begin(), window_energy_buffer.end()) - | ||||
| 				  window_energy_buffer.begin(); | ||||
|  | ||||
| 	// auto window_search_start = window_energy_buffer.begin() + strongest_corr_nr - 5* d_OSR; | ||||
| 	// auto window_search_end = window_energy_buffer.begin() + strongest_corr_nr + 10* d_OSR; | ||||
| 	// window_search_end = window_search_end >= window_energy_buffer.end() ? window_energy_buffer.end() : window_search_end; | ||||
|  | ||||
| 	// /* Calculate the strongest window number */ | ||||
| 	// int strongest_window_nr = max_element(window_search_start, window_search_end /* - d_chan_imp_length * d_OSR*/) - window_energy_buffer.begin(); | ||||
|  | ||||
| 	// if (strongest_window_nr < 0) | ||||
| 	// 	strongest_window_nr = 0; | ||||
|  | ||||
| 	float max_correlation = 0; | ||||
| 	for (int ii = 0; ii < d_chan_imp_length * d_OSR; ii++) { | ||||
| 		gr_complex correlation = correlation_buffer[strongest_window_nr + ii]; | ||||
| 		if (abs(correlation) > max_correlation) | ||||
| 			max_correlation = abs(correlation); | ||||
| 		chan_imp_resp[ii] = correlation; | ||||
| 	} | ||||
|  | ||||
| 	*corr_max = max_correlation; | ||||
|  | ||||
| 	/** | ||||
| 	 * Compute first sample position, which corresponds | ||||
| 	 * to the first sample of the impulse response | ||||
| 	 */ | ||||
| 	return search_start_pos + strongest_window_nr - search_center * d_OSR; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  | ||||
| 3 + 57 + 1 + 26 + 1 + 57 + 3 + 8.25 | ||||
|  | ||||
| search center = 3 + 57 + 1 + 5 (due to tsc 5+16+5 split) | ||||
| this is +-5 samples around (+5 beginning) of truncated t16 tsc | ||||
|  | ||||
| */ | ||||
| int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc) | ||||
| { | ||||
| 	const int search_center = TRAIN_POS; | ||||
| 	const int search_start_pos = (search_center - 5) * d_OSR + 1; | ||||
| 	const int search_stop_pos = (search_center + 5 + d_chan_imp_length) * d_OSR; | ||||
| 	const auto tseq = &d_norm_training_seq[bcc][TRAIN_BEGINNING]; | ||||
| 	const auto tseqlen = N_TRAIN_BITS - (2 * TRAIN_BEGINNING); | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_center, search_start_pos, search_stop_pos, tseq, tseqlen, | ||||
| 				 corr_max); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  | ||||
| 3 tail | 39 data | 64 tsc | 39 data | 3 tail | 8.25 guard | ||||
| start 3+39 - 10 | ||||
| end 3+39 + SYNC_SEARCH_RANGE | ||||
|  | ||||
| */ | ||||
| int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp) | ||||
| { | ||||
| 	const int search_center = SYNC_POS + TRAIN_BEGINNING; | ||||
| 	const int search_start_pos = (search_center - 10) * d_OSR; | ||||
| 	const int search_stop_pos = (search_center + SYNC_SEARCH_RANGE) * d_OSR; | ||||
| 	const auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; | ||||
| 	const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); | ||||
|  | ||||
| 	// strongest_window_nr + chan_imp_resp_center + SYNC_POS *d_OSR - 48 * d_OSR - 2 * d_OSR + 2 ; | ||||
| 	float corr_max; | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_center, search_start_pos, search_stop_pos, tseq, tseqlen, | ||||
| 				 &corr_max); | ||||
| } | ||||
|  | ||||
| int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max) | ||||
| { | ||||
| 	const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); | ||||
| 	const int search_center = SYNC_POS + TRAIN_BEGINNING; | ||||
| 	const int search_start_pos = 0; | ||||
| 	// FIXME: proper end offset | ||||
| 	const int search_stop_pos = len - (N_SYNC_BITS*8); | ||||
| 	auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; | ||||
|  | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_center, search_start_pos, search_stop_pos, tseq, tseqlen, | ||||
| 				 corr_max); | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @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 | ||||
|  * 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 "constants.h" | ||||
|  | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
| #include <complex> | ||||
|  | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <string.h> | ||||
| #include <iostream> | ||||
| #include <numeric> | ||||
| #include <vector> | ||||
| #include <fstream> | ||||
|  | ||||
| #include "viterbi_detector.h" | ||||
| #include "grgsm_vitac.h" | ||||
|  | ||||
| gr_complex d_acc_training_seq[N_ACCESS_BITS]; ///<encoded training sequence of a RACH burst | ||||
| gr_complex d_sch_training_seq[N_SYNC_BITS]; ///<encoded training sequence of a SCH burst | ||||
| gr_complex d_norm_training_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS]; ///<encoded training sequences of a normal and dummy burst | ||||
| const int d_chan_imp_length = CHAN_IMP_RESP_LENGTH; | ||||
|  | ||||
| void initvita() | ||||
| { | ||||
| 	/** | ||||
| 	 * Prepare SCH sequence bits | ||||
| 	 * | ||||
| 	 * (TS_BITS + 2 * GUARD_PERIOD) | ||||
| 	 * Burst and two guard periods | ||||
| 	 * (one guard period is an arbitrary overlap) | ||||
| 	 */ | ||||
| 	gmsk_mapper(SYNC_BITS, N_SYNC_BITS, d_sch_training_seq, gr_complex(0.0, -1.0)); | ||||
| 	for (auto &i : d_sch_training_seq) | ||||
| 		i = conj(i); | ||||
|  | ||||
| 	/* ab */ | ||||
| 	gmsk_mapper(ACCESS_BITS, N_ACCESS_BITS, d_acc_training_seq, gr_complex(0.0, -1.0)); | ||||
| 	for (auto &i : d_acc_training_seq) | ||||
| 		i = conj(i); | ||||
|  | ||||
| 	/* Prepare bits of training sequences */ | ||||
| 	for (int i = 0; i < TRAIN_SEQ_NUM; i++) { | ||||
| 		/** | ||||
| 		 * If first bit of the sequence is 0 | ||||
| 		 * => first symbol is 1, else -1 | ||||
| 		 */ | ||||
| 		gr_complex startpoint = train_seq[i][0] == 0 ? gr_complex(1.0, 0.0) : gr_complex(-1.0, 0.0); | ||||
| 		gmsk_mapper(train_seq[i], N_TRAIN_BITS, d_norm_training_seq[i], startpoint); | ||||
| 		for (auto &i : d_norm_training_seq[i]) | ||||
| 			i = conj(i); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| template <unsigned int burst_size> | ||||
| NO_UBSAN static void detect_burst_generic(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, | ||||
| 					  char *output_binary, int ss) | ||||
| { | ||||
| 	std::vector<gr_complex> rhh_temp(CHAN_IMP_RESP_LENGTH * d_OSR); | ||||
| 	unsigned int stop_states[2] = { 4, 12 }; | ||||
| 	gr_complex filtered_burst[burst_size]; | ||||
| 	gr_complex rhh[CHAN_IMP_RESP_LENGTH]; | ||||
| 	float output[burst_size]; | ||||
| 	int start_state = ss; | ||||
|  | ||||
| 	autocorrelation(chan_imp_resp, &rhh_temp[0], d_chan_imp_length * d_OSR); | ||||
| 	for (int ii = 0; ii < d_chan_imp_length; ii++) | ||||
| 		rhh[ii] = conj(rhh_temp[ii * d_OSR]); | ||||
|  | ||||
| 	mafi(&input[burst_start], burst_size, chan_imp_resp, d_chan_imp_length * d_OSR, filtered_burst); | ||||
|  | ||||
| 	viterbi_detector(filtered_burst, burst_size, rhh, start_state, stop_states, 2, output); | ||||
|  | ||||
| 	for (unsigned int i = 0; i < burst_size; i++) | ||||
| 		output_binary[i] = (char)(output[i] * -127); // pre flip bits! | ||||
| } | ||||
|  | ||||
| NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, | ||||
| 			      int ss) | ||||
| { | ||||
| 	return detect_burst_generic<BURST_SIZE>(input, chan_imp_resp, burst_start, output_binary, ss); | ||||
| } | ||||
| NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, | ||||
| 			      int ss) | ||||
| { | ||||
| 	return detect_burst_generic<8 + 41 + 36 + 3>(input, chan_imp_resp, burst_start, output_binary, ss); | ||||
| } | ||||
|  | ||||
| NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary) | ||||
| { | ||||
| 	return detect_burst_nb(input, chan_imp_resp, burst_start, output_binary, 3); | ||||
| } | ||||
| NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary) | ||||
| { | ||||
| 	return detect_burst_ab(input, chan_imp_resp, burst_start, output_binary, 3); | ||||
| } | ||||
|  | ||||
| void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point) | ||||
| { | ||||
| 	gr_complex j = gr_complex(0.0, 1.0); | ||||
| 	gmsk_output[0] = start_point; | ||||
|  | ||||
| 	int previous_symbol = 2 * input[0] - 1; | ||||
| 	int current_symbol; | ||||
| 	int encoded_symbol; | ||||
|  | ||||
| 	for (int i = 1; i < nitems; i++) { | ||||
| 		/* Change bits representation to NRZ */ | ||||
| 		current_symbol = 2 * input[i] - 1; | ||||
|  | ||||
| 		/* Differentially encode */ | ||||
| 		encoded_symbol = current_symbol * previous_symbol; | ||||
|  | ||||
| 		/* And do GMSK mapping */ | ||||
| 		gmsk_output[i] = j * gr_complex(encoded_symbol, 0.0) * gmsk_output[i - 1]; | ||||
|  | ||||
| 		previous_symbol = current_symbol; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input) | ||||
| { | ||||
| 	gr_complex result(0.0, 0.0); | ||||
|  | ||||
| 	for (int ii = 0; ii < length; ii++) | ||||
| 		result += sequence[ii] * input[ii * d_OSR]; | ||||
|  | ||||
| 	return conj(result) / gr_complex(length, 0); | ||||
| } | ||||
|  | ||||
| /* Computes autocorrelation for positive arguments */ | ||||
| inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems) | ||||
| { | ||||
| 	for (int k = nitems - 1; k >= 0; k--) { | ||||
| 		out[k] = gr_complex(0, 0); | ||||
| 		for (int i = k; i < nitems; i++) | ||||
| 			out[k] += input[i] * conj(input[i - k]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output) | ||||
| { | ||||
| 	for (int n = 0; n < nitems; n++) { | ||||
| 		int a = n * d_OSR; | ||||
| 		output[n] = 0; | ||||
|  | ||||
| 		for (int ii = 0; ii < filter_length; ii++) { | ||||
| 			if ((a + ii) >= nitems * d_OSR) | ||||
| 				break; | ||||
|  | ||||
| 			output[n] += input[a + ii] * filter[ii]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int get_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, int search_start_pos, int search_stop_pos, | ||||
| 		      gr_complex *tseq, int tseqlen, float *corr_max) | ||||
| { | ||||
| 	const int num_search_windows = search_stop_pos - search_start_pos; | ||||
| 	const int power_search_window_len = d_chan_imp_length * d_OSR; | ||||
| 	std::vector<float> window_energy_buffer; | ||||
| 	std::vector<float> power_buffer; | ||||
| 	std::vector<gr_complex> correlation_buffer; | ||||
|  | ||||
| 	power_buffer.reserve(num_search_windows); | ||||
| 	correlation_buffer.reserve(num_search_windows); | ||||
| 	window_energy_buffer.reserve(num_search_windows); | ||||
|  | ||||
| 	for (int ii = 0; ii < num_search_windows; ii++) { | ||||
| 		gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[search_start_pos + ii]); | ||||
| 		correlation_buffer.push_back(correlation); | ||||
| 		power_buffer.push_back(std::pow(abs(correlation), 2)); | ||||
| 	} | ||||
|  | ||||
| 	/* Compute window energies */ | ||||
| 	float windowSum = 0; | ||||
|  | ||||
| 	// first window | ||||
| 	for (int i = 0; i < power_search_window_len; i++) { | ||||
| 		windowSum += power_buffer[i]; | ||||
| 	} | ||||
| 	window_energy_buffer.push_back(windowSum); | ||||
|  | ||||
| 	// slide windows | ||||
| 	for (int i = power_search_window_len; i < num_search_windows; i++) { | ||||
| 		windowSum += power_buffer[i] - power_buffer[i - power_search_window_len]; | ||||
| 		window_energy_buffer.push_back(windowSum); | ||||
| 	} | ||||
|  | ||||
| 	int strongest_window_nr = std::max_element(window_energy_buffer.begin(), window_energy_buffer.end()) - | ||||
| 				  window_energy_buffer.begin(); | ||||
|  | ||||
| 	float max_correlation = 0; | ||||
| 	for (int ii = 0; ii < power_search_window_len; ii++) { | ||||
| 		gr_complex correlation = correlation_buffer[strongest_window_nr + ii]; | ||||
| 		if (abs(correlation) > max_correlation) | ||||
| 			max_correlation = abs(correlation); | ||||
| 		chan_imp_resp[ii] = correlation; | ||||
| 	} | ||||
|  | ||||
| 	*corr_max = max_correlation; | ||||
|  | ||||
| 	/** | ||||
| 	 * Compute first sample position, which corresponds | ||||
| 	 * to the first sample of the impulse response | ||||
| 	 */ | ||||
| 	return search_start_pos + strongest_window_nr; | ||||
| } | ||||
|  | ||||
| /* | ||||
| 8 ext tail bits | ||||
| 41 sync seq | ||||
| 36 encrypted bits | ||||
| 3 tail bits | ||||
| 68.25 extended tail bits (!) | ||||
|  | ||||
| center at 8+5 (actually known tb -> known isi, start at 8?) FIXME | ||||
| */ | ||||
| int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay) | ||||
| { | ||||
| 	const int search_center = 8 + 5; | ||||
| 	const int search_start_pos = (search_center - 5) * d_OSR + 1; | ||||
| 	const int search_stop_pos = (search_center + 5 + d_chan_imp_length + max_delay) * d_OSR; | ||||
| 	const auto tseq = &d_acc_training_seq[TRAIN_BEGINNING]; | ||||
| 	const auto tseqlen = N_ACCESS_BITS - (2 * TRAIN_BEGINNING); | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - | ||||
| 	       search_center * d_OSR; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  | ||||
| 3 + 57 + 1 + 26 + 1 + 57 + 3 + 8.25 | ||||
|  | ||||
| search center = 3 + 57 + 1 + 5 (due to tsc 5+16+5 split) | ||||
| this is +-5 samples around (+5 beginning) of truncated t16 tsc | ||||
|  | ||||
| */ | ||||
| int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc) | ||||
| { | ||||
| 	const int search_center = TRAIN_POS; | ||||
| 	const int search_start_pos = (search_center - 5) * d_OSR + 1; | ||||
| 	const int search_stop_pos = (search_center + 5 + d_chan_imp_length) * d_OSR; | ||||
| 	const auto tseq = &d_norm_training_seq[bcc][TRAIN_BEGINNING]; | ||||
| 	const auto tseqlen = N_TRAIN_BITS - (2 * TRAIN_BEGINNING); | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - | ||||
| 	       search_center * d_OSR; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  | ||||
| 3 tail | 39 data | 64 tsc | 39 data | 3 tail | 8.25 guard | ||||
| start 3+39 - 10 | ||||
| end 3+39 + SYNC_SEARCH_RANGE | ||||
|  | ||||
| */ | ||||
| int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp) | ||||
| { | ||||
| 	const int search_center = SYNC_POS + TRAIN_BEGINNING; | ||||
| 	const int search_start_pos = (search_center - 10) * d_OSR; | ||||
| 	const int search_stop_pos = (search_center + SYNC_SEARCH_RANGE) * d_OSR; | ||||
| 	const auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; | ||||
| 	const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); | ||||
|  | ||||
| 	// strongest_window_nr + chan_imp_resp_center + SYNC_POS *d_OSR - 48 * d_OSR - 2 * d_OSR + 2 ; | ||||
| 	float corr_max; | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, &corr_max) - | ||||
| 	       search_center * d_OSR; | ||||
| 	; | ||||
| } | ||||
|  | ||||
| int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max) | ||||
| { | ||||
| 	const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); | ||||
| 	const int search_center = SYNC_POS + TRAIN_BEGINNING; | ||||
| 	const int search_start_pos = 0; | ||||
| 	// FIXME: proper end offset | ||||
| 	const int search_stop_pos = len - (N_SYNC_BITS * 8); | ||||
| 	auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; | ||||
|  | ||||
| 	return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - | ||||
| 	       search_center * d_OSR; | ||||
| } | ||||
| @@ -24,6 +24,9 @@ | ||||
| #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) && true | ||||
| #define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default"))) | ||||
| @@ -32,6 +35,26 @@ | ||||
| #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 | ||||
|  | ||||
| /* ancient gcc < 8 has no attribute, clang always pretends to be gcc 4 */ | ||||
| #if !defined(__clang__) && __GNUC__ < 8 | ||||
| #define NO_UBSAN __attribute__((no_sanitize_undefined)) | ||||
| #else | ||||
| #if defined(__has_attribute) | ||||
| #if __has_attribute(no_sanitize) | ||||
| #define NO_UBSAN __attribute__((no_sanitize("undefined"))) | ||||
| #endif | ||||
| #else | ||||
| #define NO_UBSAN | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #define SYNC_SEARCH_RANGE 30 | ||||
| const int d_OSR(4); | ||||
|  | ||||
| @@ -40,8 +63,11 @@ 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, char *output_binary); | ||||
| void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss); | ||||
| void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss); | ||||
| void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary); | ||||
| void detect_burst_ab(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); | ||||
| @@ -49,6 +75,7 @@ inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int fi | ||||
| int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp); | ||||
| int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc); | ||||
| int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max); | ||||
| int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay); | ||||
|  | ||||
| enum class btype { NB, SCH }; | ||||
| struct fdata { | ||||
|   | ||||
| @@ -1,392 +1,392 @@ | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @file | ||||
|  * @author (C) 2009 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. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * viterbi_detector: | ||||
|  *           This part does the detection of received sequnece. | ||||
|  *           Employed algorithm is viterbi Maximum Likehood Sequence Estimation. | ||||
|  *           At this moment it gives hard decisions on the output, but | ||||
|  *           it was designed with soft decisions in mind. | ||||
|  * | ||||
|  * SYNTAX:   void viterbi_detector( | ||||
|  *                                  const gr_complex * input,  | ||||
|  *                                  unsigned int samples_num,  | ||||
|  *                                  gr_complex * rhh,  | ||||
|  *                                  unsigned int start_state,  | ||||
|  *                                  const unsigned int * stop_states,  | ||||
|  *                                  unsigned int stops_num,  | ||||
|  *                                  float * output) | ||||
|  * | ||||
|  * INPUT:    input:       Complex received signal afted matched filtering. | ||||
|  *           samples_num: Number of samples in the input table. | ||||
|  *           rhh:         The autocorrelation of the estimated channel  | ||||
|  *                        impulse response. | ||||
|  *           start_state: Number of the start point. In GSM each burst  | ||||
|  *                        starts with sequence of three bits (0,0,0) which  | ||||
|  *                        indicates start point of the algorithm. | ||||
|  *           stop_states: Table with numbers of possible stop states. | ||||
|  *           stops_num:   Number of possible stop states | ||||
|  *                      | ||||
|  * | ||||
|  * OUTPUT:   output:      Differentially decoded hard output of the algorithm:  | ||||
|  *                        -1 for logical "0" and 1 for logical "1" | ||||
|  * | ||||
|  * SUB_FUNC: none | ||||
|  * | ||||
|  * TEST(S):  Tested with real world normal burst. | ||||
|  */ | ||||
|  | ||||
| #include "constants.h" | ||||
| #include <cmath> | ||||
|  | ||||
| #define PATHS_NUM (1 << (CHAN_IMP_RESP_LENGTH-1)) | ||||
|  | ||||
| void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output) | ||||
| { | ||||
|    float increment[8]; | ||||
|    float path_metrics1[16]; | ||||
|    float path_metrics2[16]; | ||||
|    float paths_difference; | ||||
|    float * new_path_metrics; | ||||
|    float * old_path_metrics; | ||||
|    float * tmp; | ||||
|    float trans_table[BURST_SIZE][16]; | ||||
|    float pm_candidate1, pm_candidate2; | ||||
|    bool real_imag; | ||||
|    float input_symbol_real, input_symbol_imag; | ||||
|    unsigned int i, sample_nr; | ||||
|  | ||||
| /* | ||||
| * Setup first path metrics, so only state pointed by start_state is possible. | ||||
| * Start_state metric is equal to zero, the rest is written with some very low value, | ||||
| * which makes them practically impossible to occur. | ||||
| */ | ||||
|    for(i=0; i<PATHS_NUM; i++){ | ||||
|       path_metrics1[i]=(-10e30); | ||||
|    } | ||||
|    path_metrics1[start_state]=0; | ||||
|  | ||||
| /* | ||||
| * Compute Increment - a table of values which does not change for subsequent input samples. | ||||
| * Increment is table of reference levels for computation of branch metrics: | ||||
| *    branch metric = (+/-)received_sample (+/-) reference_level | ||||
| */ | ||||
|    increment[0] = -rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[1] = rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[2] = -rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[3] = rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[4] = -rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|    increment[5] = rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|    increment[6] = -rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|    increment[7] = rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|  | ||||
|  | ||||
| /* | ||||
| * Computation of path metrics and decisions (Add-Compare-Select). | ||||
| * It's composed of two parts: one for odd input samples (imaginary numbers) | ||||
| * and one for even samples (real numbers). | ||||
| * Each part is composed of independent (parallelisable) statements like   | ||||
| * this one: | ||||
| *      pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; | ||||
| *      pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; | ||||
| *      paths_difference=pm_candidate2-pm_candidate1; | ||||
| *      new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
| *      trans_table[sample_nr][1] = paths_difference; | ||||
| * This is very good point for optimisations (SIMD or OpenMP) as it's most time  | ||||
| * consuming part of this function.  | ||||
| */ | ||||
|    sample_nr=0; | ||||
|    old_path_metrics=path_metrics1; | ||||
|    new_path_metrics=path_metrics2; | ||||
|    while(sample_nr<samples_num){ | ||||
|       //Processing imag states | ||||
|       real_imag=1; | ||||
|       input_symbol_imag = input[sample_nr].imag(); | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] +input_symbol_imag -increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[8] +input_symbol_imag +increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][0] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][1] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] +input_symbol_imag -increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[9] +input_symbol_imag +increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][2] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] -input_symbol_imag +increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[9] -input_symbol_imag -increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][3] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] +input_symbol_imag -increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[10] +input_symbol_imag +increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][4] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] -input_symbol_imag +increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[10] -input_symbol_imag -increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][5] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] +input_symbol_imag -increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[11] +input_symbol_imag +increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][6] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] -input_symbol_imag +increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[11] -input_symbol_imag -increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][7] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] +input_symbol_imag -increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[12] +input_symbol_imag +increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][8] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] -input_symbol_imag +increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[12] -input_symbol_imag -increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][9] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] +input_symbol_imag -increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[13] +input_symbol_imag +increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][10] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] -input_symbol_imag +increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[13] -input_symbol_imag -increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][11] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] +input_symbol_imag -increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[14] +input_symbol_imag +increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][12] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] -input_symbol_imag +increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[14] -input_symbol_imag -increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][13] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] +input_symbol_imag -increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[15] +input_symbol_imag +increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][14] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] -input_symbol_imag +increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[15] -input_symbol_imag -increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][15] = paths_difference; | ||||
|       tmp=old_path_metrics; | ||||
|       old_path_metrics=new_path_metrics; | ||||
|       new_path_metrics=tmp; | ||||
|  | ||||
|       sample_nr++; | ||||
|       if(sample_nr==samples_num) | ||||
|          break; | ||||
|  | ||||
|       //Processing real states | ||||
|       real_imag=0; | ||||
|       input_symbol_real = input[sample_nr].real(); | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] -input_symbol_real -increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[8] -input_symbol_real +increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][0] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] +input_symbol_real +increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[8] +input_symbol_real -increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][1] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] -input_symbol_real -increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[9] -input_symbol_real +increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][2] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] +input_symbol_real +increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[9] +input_symbol_real -increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][3] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] -input_symbol_real -increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[10] -input_symbol_real +increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][4] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] +input_symbol_real +increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[10] +input_symbol_real -increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][5] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] -input_symbol_real -increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[11] -input_symbol_real +increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][6] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] +input_symbol_real +increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[11] +input_symbol_real -increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][7] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] -input_symbol_real -increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[12] -input_symbol_real +increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][8] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] +input_symbol_real +increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[12] +input_symbol_real -increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][9] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] -input_symbol_real -increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[13] -input_symbol_real +increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][10] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] +input_symbol_real +increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[13] +input_symbol_real -increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][11] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] -input_symbol_real -increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[14] -input_symbol_real +increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][12] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] +input_symbol_real +increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[14] +input_symbol_real -increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][13] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] -input_symbol_real -increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[15] -input_symbol_real +increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][14] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] +input_symbol_real +increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[15] +input_symbol_real -increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][15] = paths_difference; | ||||
|  | ||||
|       tmp=old_path_metrics; | ||||
|       old_path_metrics=new_path_metrics; | ||||
|       new_path_metrics=tmp; | ||||
|  | ||||
|       sample_nr++; | ||||
|    } | ||||
|  | ||||
| /* | ||||
| * Find the best from the stop states by comparing their path metrics. | ||||
| * Not every stop state is always possible, so we are searching in | ||||
| * a subset of them. | ||||
| */ | ||||
|    unsigned int best_stop_state; | ||||
|    float stop_state_metric, max_stop_state_metric; | ||||
|    best_stop_state = stop_states[0]; | ||||
|    max_stop_state_metric = old_path_metrics[best_stop_state]; | ||||
|    for(i=1; i< stops_num; i++){ | ||||
|       stop_state_metric = old_path_metrics[stop_states[i]]; | ||||
|       if(stop_state_metric > max_stop_state_metric){ | ||||
|          max_stop_state_metric = stop_state_metric; | ||||
|          best_stop_state = stop_states[i]; | ||||
|       } | ||||
|    } | ||||
|  | ||||
| /* | ||||
| * This table was generated with hope that it gives a litle speedup during | ||||
| * traceback stage.  | ||||
| * Received bit is related to the number of state in the trellis. | ||||
| * I've numbered states so their parity (number of ones) is related | ||||
| * to a received bit.  | ||||
| */ | ||||
|    static const unsigned int parity_table[PATHS_NUM] = { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,  }; | ||||
|  | ||||
| /* | ||||
| * Table of previous states in the trellis diagram. | ||||
| * For GMSK modulation every state has two previous states. | ||||
| * Example: | ||||
| *   previous_state_nr1 = prev_table[current_state_nr][0] | ||||
| *   previous_state_nr2 = prev_table[current_state_nr][1] | ||||
| */ | ||||
|    static const unsigned int prev_table[PATHS_NUM][2] = { {0,8}, {0,8}, {1,9}, {1,9}, {2,10}, {2,10}, {3,11}, {3,11}, {4,12}, {4,12}, {5,13}, {5,13}, {6,14}, {6,14}, {7,15}, {7,15},  }; | ||||
|  | ||||
| /* | ||||
| * Traceback and differential decoding of received sequence. | ||||
| * Decisions stored in trans_table are used to restore best path in the trellis. | ||||
| */ | ||||
|    sample_nr=samples_num; | ||||
|    unsigned int state_nr=best_stop_state; | ||||
|    unsigned int decision; | ||||
|    bool out_bit=0; | ||||
|  | ||||
|    while(sample_nr>0){ | ||||
|       sample_nr--; | ||||
|       decision = (trans_table[sample_nr][state_nr]>0); | ||||
|  | ||||
|       if(decision != out_bit) | ||||
|          output[sample_nr]=-trans_table[sample_nr][state_nr]; | ||||
|       else | ||||
|          output[sample_nr]=trans_table[sample_nr][state_nr]; | ||||
|  | ||||
|       out_bit = out_bit ^ real_imag ^ parity_table[state_nr]; | ||||
|       state_nr = prev_table[state_nr][decision]; | ||||
|       real_imag = !real_imag; | ||||
|    } | ||||
| } | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @file | ||||
|  * @author (C) 2009 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. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * viterbi_detector: | ||||
|  *           This part does the detection of received sequnece. | ||||
|  *           Employed algorithm is viterbi Maximum Likehood Sequence Estimation. | ||||
|  *           At this moment it gives hard decisions on the output, but | ||||
|  *           it was designed with soft decisions in mind. | ||||
|  * | ||||
|  * SYNTAX:   void viterbi_detector( | ||||
|  *                                  const gr_complex * input,  | ||||
|  *                                  unsigned int samples_num,  | ||||
|  *                                  gr_complex * rhh,  | ||||
|  *                                  unsigned int start_state,  | ||||
|  *                                  const unsigned int * stop_states,  | ||||
|  *                                  unsigned int stops_num,  | ||||
|  *                                  float * output) | ||||
|  * | ||||
|  * INPUT:    input:       Complex received signal afted matched filtering. | ||||
|  *           samples_num: Number of samples in the input table. | ||||
|  *           rhh:         The autocorrelation of the estimated channel  | ||||
|  *                        impulse response. | ||||
|  *           start_state: Number of the start point. In GSM each burst  | ||||
|  *                        starts with sequence of three bits (0,0,0) which  | ||||
|  *                        indicates start point of the algorithm. | ||||
|  *           stop_states: Table with numbers of possible stop states. | ||||
|  *           stops_num:   Number of possible stop states | ||||
|  *                      | ||||
|  * | ||||
|  * OUTPUT:   output:      Differentially decoded hard output of the algorithm:  | ||||
|  *                        -1 for logical "0" and 1 for logical "1" | ||||
|  * | ||||
|  * SUB_FUNC: none | ||||
|  * | ||||
|  * TEST(S):  Tested with real world normal burst. | ||||
|  */ | ||||
|  | ||||
| #include "constants.h" | ||||
| #include <cmath> | ||||
|  | ||||
| #define PATHS_NUM (1 << (CHAN_IMP_RESP_LENGTH-1)) | ||||
|  | ||||
| void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output) | ||||
| { | ||||
|    float increment[8]; | ||||
|    float path_metrics1[16]; | ||||
|    float path_metrics2[16]; | ||||
|    float paths_difference; | ||||
|    float * new_path_metrics; | ||||
|    float * old_path_metrics; | ||||
|    float * tmp; | ||||
|    float trans_table[BURST_SIZE][16]; | ||||
|    float pm_candidate1, pm_candidate2; | ||||
|    bool real_imag; | ||||
|    float input_symbol_real, input_symbol_imag; | ||||
|    unsigned int i, sample_nr; | ||||
|  | ||||
| /* | ||||
| * Setup first path metrics, so only state pointed by start_state is possible. | ||||
| * Start_state metric is equal to zero, the rest is written with some very low value, | ||||
| * which makes them practically impossible to occur. | ||||
| */ | ||||
|    for(i=0; i<PATHS_NUM; i++){ | ||||
|       path_metrics1[i]=(-10e30); | ||||
|    } | ||||
|    path_metrics1[start_state]=0; | ||||
|  | ||||
| /* | ||||
| * Compute Increment - a table of values which does not change for subsequent input samples. | ||||
| * Increment is table of reference levels for computation of branch metrics: | ||||
| *    branch metric = (+/-)received_sample (+/-) reference_level | ||||
| */ | ||||
|    increment[0] = -rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[1] = rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[2] = -rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[3] = rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); | ||||
|    increment[4] = -rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|    increment[5] = rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|    increment[6] = -rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|    increment[7] = rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); | ||||
|  | ||||
|  | ||||
| /* | ||||
| * Computation of path metrics and decisions (Add-Compare-Select). | ||||
| * It's composed of two parts: one for odd input samples (imaginary numbers) | ||||
| * and one for even samples (real numbers). | ||||
| * Each part is composed of independent (parallelisable) statements like   | ||||
| * this one: | ||||
| *      pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; | ||||
| *      pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; | ||||
| *      paths_difference=pm_candidate2-pm_candidate1; | ||||
| *      new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
| *      trans_table[sample_nr][1] = paths_difference; | ||||
| * This is very good point for optimisations (SIMD or OpenMP) as it's most time  | ||||
| * consuming part of this function.  | ||||
| */ | ||||
|    sample_nr=0; | ||||
|    old_path_metrics=path_metrics1; | ||||
|    new_path_metrics=path_metrics2; | ||||
|    while(sample_nr<samples_num){ | ||||
|       //Processing imag states | ||||
|       real_imag=1; | ||||
|       input_symbol_imag = input[sample_nr].imag(); | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] +input_symbol_imag -increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[8] +input_symbol_imag +increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][0] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][1] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] +input_symbol_imag -increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[9] +input_symbol_imag +increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][2] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] -input_symbol_imag +increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[9] -input_symbol_imag -increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][3] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] +input_symbol_imag -increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[10] +input_symbol_imag +increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][4] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] -input_symbol_imag +increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[10] -input_symbol_imag -increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][5] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] +input_symbol_imag -increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[11] +input_symbol_imag +increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][6] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] -input_symbol_imag +increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[11] -input_symbol_imag -increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][7] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] +input_symbol_imag -increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[12] +input_symbol_imag +increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][8] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] -input_symbol_imag +increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[12] -input_symbol_imag -increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][9] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] +input_symbol_imag -increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[13] +input_symbol_imag +increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][10] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] -input_symbol_imag +increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[13] -input_symbol_imag -increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][11] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] +input_symbol_imag -increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[14] +input_symbol_imag +increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][12] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] -input_symbol_imag +increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[14] -input_symbol_imag -increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][13] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] +input_symbol_imag -increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[15] +input_symbol_imag +increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][14] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] -input_symbol_imag +increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[15] -input_symbol_imag -increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][15] = paths_difference; | ||||
|       tmp=old_path_metrics; | ||||
|       old_path_metrics=new_path_metrics; | ||||
|       new_path_metrics=tmp; | ||||
|  | ||||
|       sample_nr++; | ||||
|       if(sample_nr==samples_num) | ||||
|          break; | ||||
|  | ||||
|       //Processing real states | ||||
|       real_imag=0; | ||||
|       input_symbol_real = input[sample_nr].real(); | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] -input_symbol_real -increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[8] -input_symbol_real +increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][0] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[0] +input_symbol_real +increment[7]; | ||||
|       pm_candidate2 = old_path_metrics[8] +input_symbol_real -increment[0]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][1] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] -input_symbol_real -increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[9] -input_symbol_real +increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][2] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[1] +input_symbol_real +increment[6]; | ||||
|       pm_candidate2 = old_path_metrics[9] +input_symbol_real -increment[1]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][3] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] -input_symbol_real -increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[10] -input_symbol_real +increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][4] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[2] +input_symbol_real +increment[5]; | ||||
|       pm_candidate2 = old_path_metrics[10] +input_symbol_real -increment[2]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][5] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] -input_symbol_real -increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[11] -input_symbol_real +increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][6] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[3] +input_symbol_real +increment[4]; | ||||
|       pm_candidate2 = old_path_metrics[11] +input_symbol_real -increment[3]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][7] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] -input_symbol_real -increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[12] -input_symbol_real +increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][8] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[4] +input_symbol_real +increment[3]; | ||||
|       pm_candidate2 = old_path_metrics[12] +input_symbol_real -increment[4]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][9] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] -input_symbol_real -increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[13] -input_symbol_real +increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][10] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[5] +input_symbol_real +increment[2]; | ||||
|       pm_candidate2 = old_path_metrics[13] +input_symbol_real -increment[5]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][11] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] -input_symbol_real -increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[14] -input_symbol_real +increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][12] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[6] +input_symbol_real +increment[1]; | ||||
|       pm_candidate2 = old_path_metrics[14] +input_symbol_real -increment[6]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][13] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] -input_symbol_real -increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[15] -input_symbol_real +increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][14] = paths_difference; | ||||
|  | ||||
|       pm_candidate1 = old_path_metrics[7] +input_symbol_real +increment[0]; | ||||
|       pm_candidate2 = old_path_metrics[15] +input_symbol_real -increment[7]; | ||||
|       paths_difference=pm_candidate2-pm_candidate1; | ||||
|       new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; | ||||
|       trans_table[sample_nr][15] = paths_difference; | ||||
|  | ||||
|       tmp=old_path_metrics; | ||||
|       old_path_metrics=new_path_metrics; | ||||
|       new_path_metrics=tmp; | ||||
|  | ||||
|       sample_nr++; | ||||
|    } | ||||
|  | ||||
| /* | ||||
| * Find the best from the stop states by comparing their path metrics. | ||||
| * Not every stop state is always possible, so we are searching in | ||||
| * a subset of them. | ||||
| */ | ||||
|    unsigned int best_stop_state; | ||||
|    float stop_state_metric, max_stop_state_metric; | ||||
|    best_stop_state = stop_states[0]; | ||||
|    max_stop_state_metric = old_path_metrics[best_stop_state]; | ||||
|    for(i=1; i< stops_num; i++){ | ||||
|       stop_state_metric = old_path_metrics[stop_states[i]]; | ||||
|       if(stop_state_metric > max_stop_state_metric){ | ||||
|          max_stop_state_metric = stop_state_metric; | ||||
|          best_stop_state = stop_states[i]; | ||||
|       } | ||||
|    } | ||||
|  | ||||
| /* | ||||
| * This table was generated with hope that it gives a litle speedup during | ||||
| * traceback stage.  | ||||
| * Received bit is related to the number of state in the trellis. | ||||
| * I've numbered states so their parity (number of ones) is related | ||||
| * to a received bit.  | ||||
| */ | ||||
|    static const unsigned int parity_table[PATHS_NUM] = { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,  }; | ||||
|  | ||||
| /* | ||||
| * Table of previous states in the trellis diagram. | ||||
| * For GMSK modulation every state has two previous states. | ||||
| * Example: | ||||
| *   previous_state_nr1 = prev_table[current_state_nr][0] | ||||
| *   previous_state_nr2 = prev_table[current_state_nr][1] | ||||
| */ | ||||
|    static const unsigned int prev_table[PATHS_NUM][2] = { {0,8}, {0,8}, {1,9}, {1,9}, {2,10}, {2,10}, {3,11}, {3,11}, {4,12}, {4,12}, {5,13}, {5,13}, {6,14}, {6,14}, {7,15}, {7,15},  }; | ||||
|  | ||||
| /* | ||||
| * Traceback and differential decoding of received sequence. | ||||
| * Decisions stored in trans_table are used to restore best path in the trellis. | ||||
| */ | ||||
|    sample_nr=samples_num; | ||||
|    unsigned int state_nr=best_stop_state; | ||||
|    unsigned int decision; | ||||
|    bool out_bit=0; | ||||
|  | ||||
|    while(sample_nr>0){ | ||||
|       sample_nr--; | ||||
|       decision = (trans_table[sample_nr][state_nr]>0); | ||||
|  | ||||
|       if(decision != out_bit) | ||||
|          output[sample_nr]=-trans_table[sample_nr][state_nr]; | ||||
|       else | ||||
|          output[sample_nr]=trans_table[sample_nr][state_nr]; | ||||
|  | ||||
|       out_bit = out_bit ^ real_imag ^ parity_table[state_nr]; | ||||
|       state_nr = prev_table[state_nr][decision]; | ||||
|       real_imag = !real_imag; | ||||
|    } | ||||
| } | ||||
|   | ||||
| @@ -1,64 +1,64 @@ | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @file | ||||
|  * @author (C) 2009 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. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * viterbi_detector: | ||||
|  *           This part does the detection of received sequnece. | ||||
|  *           Employed algorithm is viterbi Maximum Likehood Sequence Estimation. | ||||
|  *           At this moment it gives hard decisions on the output, but | ||||
|  *           it was designed with soft decisions in mind. | ||||
|  * | ||||
|  * SYNTAX:   void viterbi_detector( | ||||
|  *                                  const gr_complex * input,  | ||||
|  *                                  unsigned int samples_num,  | ||||
|  *                                  gr_complex * rhh,  | ||||
|  *                                  unsigned int start_state,  | ||||
|  *                                  const unsigned int * stop_states,  | ||||
|  *                                  unsigned int stops_num,  | ||||
|  *                                  float * output) | ||||
|  * | ||||
|  * INPUT:    input:       Complex received signal afted matched filtering. | ||||
|  *           samples_num: Number of samples in the input table. | ||||
|  *           rhh:         The autocorrelation of the estimated channel  | ||||
|  *                        impulse response. | ||||
|  *           start_state: Number of the start point. In GSM each burst  | ||||
|  *                        starts with sequence of three bits (0,0,0) which  | ||||
|  *                        indicates start point of the algorithm. | ||||
|  *           stop_states: Table with numbers of possible stop states. | ||||
|  *           stops_num:   Number of possible stop states | ||||
|  *                      | ||||
|  * | ||||
|  * OUTPUT:   output:      Differentially decoded hard output of the algorithm:  | ||||
|  *                        -1 for logical "0" and 1 for logical "1" | ||||
|  * | ||||
|  * SUB_FUNC: none | ||||
|  * | ||||
|  * TEST(S):  Tested with real world normal burst. | ||||
|  */ | ||||
|  | ||||
| #ifndef INCLUDED_VITERBI_DETECTOR_H | ||||
| #define INCLUDED_VITERBI_DETECTOR_H | ||||
| #include "constants.h" | ||||
|  | ||||
| void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output); | ||||
|  | ||||
| #endif /* INCLUDED_VITERBI_DETECTOR_H */ | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @file | ||||
|  * @author (C) 2009 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. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * viterbi_detector: | ||||
|  *           This part does the detection of received sequnece. | ||||
|  *           Employed algorithm is viterbi Maximum Likehood Sequence Estimation. | ||||
|  *           At this moment it gives hard decisions on the output, but | ||||
|  *           it was designed with soft decisions in mind. | ||||
|  * | ||||
|  * SYNTAX:   void viterbi_detector( | ||||
|  *                                  const gr_complex * input,  | ||||
|  *                                  unsigned int samples_num,  | ||||
|  *                                  gr_complex * rhh,  | ||||
|  *                                  unsigned int start_state,  | ||||
|  *                                  const unsigned int * stop_states,  | ||||
|  *                                  unsigned int stops_num,  | ||||
|  *                                  float * output) | ||||
|  * | ||||
|  * INPUT:    input:       Complex received signal afted matched filtering. | ||||
|  *           samples_num: Number of samples in the input table. | ||||
|  *           rhh:         The autocorrelation of the estimated channel  | ||||
|  *                        impulse response. | ||||
|  *           start_state: Number of the start point. In GSM each burst  | ||||
|  *                        starts with sequence of three bits (0,0,0) which  | ||||
|  *                        indicates start point of the algorithm. | ||||
|  *           stop_states: Table with numbers of possible stop states. | ||||
|  *           stops_num:   Number of possible stop states | ||||
|  *                      | ||||
|  * | ||||
|  * OUTPUT:   output:      Differentially decoded hard output of the algorithm:  | ||||
|  *                        -1 for logical "0" and 1 for logical "1" | ||||
|  * | ||||
|  * SUB_FUNC: none | ||||
|  * | ||||
|  * TEST(S):  Tested with real world normal burst. | ||||
|  */ | ||||
|  | ||||
| #ifndef INCLUDED_VITERBI_DETECTOR_H | ||||
| #define INCLUDED_VITERBI_DETECTOR_H | ||||
| #include "constants.h" | ||||
|  | ||||
| void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output); | ||||
|  | ||||
| #endif /* INCLUDED_VITERBI_DETECTOR_H */ | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #pragma once | ||||
|  | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
| @@ -39,16 +38,18 @@ 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 expand_args(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); | ||||
| 	(void)(int[]){ 0, (void((out << "," << std::forward<Args>(args))), 0)... }; | ||||
| 	out << ')' << std::endl; | ||||
| } | ||||
|  | ||||
| template <class R, class... Args> using RvalFunc = R (*)(Args...); | ||||
| 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) | ||||
| @@ -68,7 +69,8 @@ R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finam | ||||
| #pragma pack(push, 1) | ||||
| using blade_sample_type = std::complex<int16_t>; | ||||
| enum class blade_speed_buffer_type { HS, SS }; | ||||
| template <blade_speed_buffer_type T> struct blade_usb_message { | ||||
| template <blade_speed_buffer_type T> | ||||
| struct blade_usb_message { | ||||
| 	uint32_t reserved; | ||||
| 	uint64_t ts; | ||||
| 	uint32_t meta_flags; | ||||
| @@ -77,7 +79,8 @@ template <blade_speed_buffer_type T> struct blade_usb_message { | ||||
|  | ||||
| static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::SS>) == 2048, "blade buffer mismatch!"); | ||||
| static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::HS>) == 1024, "blade buffer mismatch!"); | ||||
| template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer { | ||||
| template <unsigned int SZ, blade_speed_buffer_type T> | ||||
| struct blade_otw_buffer { | ||||
| 	static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!"); | ||||
| 	blade_usb_message<T> m[SZ]; | ||||
| 	int actual_samples_per_msg() | ||||
| @@ -109,7 +112,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(); | ||||
| 		} | ||||
| @@ -157,7 +160,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) { | ||||
| @@ -173,7 +176,8 @@ template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer { | ||||
| }; | ||||
| #pragma pack(pop) | ||||
|  | ||||
| template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer_helper { | ||||
| template <unsigned int SZ, blade_speed_buffer_type T> | ||||
| struct blade_otw_buffer_helper { | ||||
| 	static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!"); | ||||
| 	static blade_otw_buffer<SZ / 512, T> x; | ||||
| }; | ||||
| @@ -182,16 +186,19 @@ using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_ | ||||
| // using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>; | ||||
| using bh_fn_t = std::function<int(dev_buf_t *)>; | ||||
|  | ||||
| template <typename T> struct blade_hw { | ||||
| template <typename T> | ||||
| struct blade_hw { | ||||
| 	struct bladerf *dev; | ||||
| 	struct bladerf_stream *rx_stream; | ||||
| 	struct bladerf_stream *tx_stream; | ||||
| 	// using pkt2buf = blade_otw_buffer<2, blade_speed_buffer_type::SS>; | ||||
| 	using tx_buf_q_type = spsc_cond<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>; | ||||
| 	using tx_buf_q_type = spsc_cond_timeout<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>; | ||||
| 	const unsigned int rxFullScale, txFullScale; | ||||
| 	const int rxtxdelay; | ||||
|  | ||||
| 	float rxgain, txgain; | ||||
| 	static std::atomic<bool> stop_lower_threads_flag; | ||||
| 	double rxfreq_cache, txfreq_cache; | ||||
|  | ||||
| 	struct ms_trx_config { | ||||
| 		int tx_freq; | ||||
| @@ -216,21 +223,23 @@ template <typename T> struct blade_hw { | ||||
| 	{ | ||||
| 		close_device(); | ||||
| 	} | ||||
| 	blade_hw() : rxFullScale(2047), txFullScale(2047), rxtxdelay(-60) | ||||
| 	blade_hw() | ||||
| 		: rxFullScale(2047), txFullScale(2047), rxtxdelay(-60), rxgain(30), txgain(30), rxfreq_cache(0), | ||||
| 		  txfreq_cache(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	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); | ||||
|  | ||||
| @@ -284,46 +293,51 @@ 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)30); | ||||
| 		usleep(1000); | ||||
| 		blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true); | ||||
| 		usleep(1000); | ||||
| 		blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true); | ||||
| 		setRxGain(rxgain, 0); | ||||
| 		setTxGain(txgain, 0); | ||||
| 		usleep(1000); | ||||
|  | ||||
| 		bladerf_set_stream_timeout(dev, BLADERF_TX, 10); | ||||
| 		bladerf_set_stream_timeout(dev, BLADERF_RX, 10); | ||||
|  | ||||
| 		blade_check(bladerf_init_stream, &rx_stream, dev, getrxcb(rxh), &buf_mgmt.rx_samples, BLADE_NUM_BUFFERS, | ||||
| 			    BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this); | ||||
|  | ||||
| 		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]); | ||||
| 		} | ||||
|  | ||||
| 		setRxGain(20); | ||||
| 		setTxGain(30); | ||||
|  | ||||
| 		usleep(1000); | ||||
|  | ||||
| 		// bladerf_set_stream_timeout(dev, BLADERF_TX, 4); | ||||
| 		// bladerf_set_stream_timeout(dev, BLADERF_RX, 4); | ||||
|  | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	void actually_enable_streams() | ||||
| 	{ | ||||
| 		blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true); | ||||
| 		usleep(1000); | ||||
| 		blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true); | ||||
| 	} | ||||
|  | ||||
| 	bool tuneTx(double freq, size_t chan = 0) | ||||
| 	{ | ||||
| 		if (txfreq_cache == freq) | ||||
| 			return true; | ||||
| 		msleep(15); | ||||
| 		blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq); | ||||
| 		txfreq_cache = freq; | ||||
| 		msleep(15); | ||||
| 		return true; | ||||
| 	}; | ||||
| 	bool tuneRx(double freq, size_t chan = 0) | ||||
| 	{ | ||||
| 		if (rxfreq_cache == freq) | ||||
| 			return true; | ||||
| 		msleep(15); | ||||
| 		blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq); | ||||
| 		rxfreq_cache = freq; | ||||
| 		msleep(15); | ||||
| 		return true; | ||||
| 	}; | ||||
| @@ -379,6 +393,9 @@ template <typename T> struct blade_hw { | ||||
| 			static int to_skip = 0; | ||||
| 			dev_buf_t *rcd = (dev_buf_t *)samples; | ||||
|  | ||||
| 			if (stop_lower_threads_flag) | ||||
| 				return BLADERF_STREAM_SHUTDOWN; | ||||
|  | ||||
| 			if (to_skip < 120) // prevents weird overflows on startup | ||||
| 				to_skip++; | ||||
| 			else { | ||||
| @@ -401,6 +418,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_lower_threads_flag) | ||||
| 				return BLADERF_STREAM_SHUTDOWN; | ||||
|  | ||||
| 			return BLADERF_STREAM_NO_DATA; | ||||
| 		}; | ||||
| 	} | ||||
| @@ -408,33 +428,41 @@ template <typename T> struct blade_hw { | ||||
| 	auto get_rx_burst_handler_fn(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		auto fn = [this] { | ||||
| 			int status; | ||||
| 			status = bladerf_stream(rx_stream, BLADERF_RX_X1); | ||||
| 			int status = 0; | ||||
| 			if (!stop_lower_threads_flag) | ||||
| 				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; | ||||
| 	} | ||||
| 	auto get_tx_burst_handler_fn(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		auto fn = [this] { | ||||
| 			int status; | ||||
| 			status = bladerf_stream(tx_stream, BLADERF_TX_X1); | ||||
| 			int status = 0; | ||||
| 			if (!stop_lower_threads_flag) | ||||
| 				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; | ||||
| 	} | ||||
|  | ||||
| 	void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts) | ||||
| 	{ | ||||
| 		//get empty bufer from list | ||||
| 		tx_buf_q_type::elem_t rcd; | ||||
|  | ||||
| 		// exit by submitting a dummy buffer to assure the libbladerf stream mutex is happy (thread!) | ||||
| 		if (!buffer) { | ||||
| 			bladerf_submit_stream_buffer(tx_stream, (void *)BLADERF_STREAM_SHUTDOWN, 1000); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		//get empty bufer from list | ||||
| 		while (!buf_mgmt.bufptrqueue.spsc_pop(&rcd)) | ||||
| 			buf_mgmt.bufptrqueue.spsc_prep_pop(); | ||||
| 		assert(rcd != nullptr); | ||||
|   | ||||
| @@ -1,197 +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 <cassert> | ||||
| #include <complex> | ||||
| #include <cstring> | ||||
| #include <functional> | ||||
| #include <iostream> | ||||
| #include <thread> | ||||
|  | ||||
| #include <Timeval.h> | ||||
| #include <vector> | ||||
|  | ||||
| // #define MTX_LOG_ENABLED | ||||
| #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(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	{ | ||||
| 		return m.connect() ? 0 : -1; | ||||
| 	} | ||||
|  | ||||
| 	void *rx_cb(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		void *ret; | ||||
| 		static int to_skip = 0; | ||||
| 		static uint64_t last_ts; | ||||
|  | ||||
| 		blade_sample_type pbuf[508 * 2]; | ||||
|  | ||||
| 		uint64_t t; | ||||
|  | ||||
| 		int len = 508 * 2; | ||||
| 		m.read_dl(508 * 2, &t, pbuf); | ||||
| 		dev_buf_t rcd = { t, static_cast<uint32_t>(len), pbuf }; | ||||
|  | ||||
| 		if (to_skip < 120) // prevents weird overflows on startup | ||||
| 			to_skip++; | ||||
| 		else { | ||||
| 			assert(last_ts != rcd.get_first_ts()); | ||||
| 			burst_handler(&rcd); | ||||
| 			last_ts = rcd.get_first_ts(); | ||||
| 		} | ||||
|  | ||||
| 		m.drive_tx(); | ||||
|  | ||||
| 		return ret; | ||||
| 	} | ||||
|  | ||||
| 	auto get_rx_burst_handler_fn(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		auto fn = [this, burst_handler] { | ||||
| 			pthread_setname_np(pthread_self(), "rxrun"); | ||||
|  | ||||
| 			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) | ||||
| 	{ | ||||
| 		// FIXME: missing | ||||
| 	} | ||||
|  | ||||
| 	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio) | ||||
| 	{ | ||||
| 		pthread_setname_np(pthread_self(), name); | ||||
|  | ||||
| 	} | ||||
| 	void signal_start() | ||||
| 	{ | ||||
| 		m.signal_read_start(); | ||||
| 	} | ||||
| }; | ||||
| @@ -27,28 +27,60 @@ | ||||
| #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 { | ||||
| template <bool block_read, bool block_write> | ||||
| class spsc_cond_timeout_detail { | ||||
| 	std::condition_variable cond_r, cond_w; | ||||
| 	std::mutex lr, lw; | ||||
| 	std::atomic_int r_flag, w_flag; | ||||
| 	const int timeout_ms = 200; | ||||
|  | ||||
|     public: | ||||
| 	explicit spsc_cond_timeout_detail() : r_flag(0), w_flag(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	~spsc_cond_timeout_detail() | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	ssize_t spsc_check_r() | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> lk(lr); | ||||
| 		if (cond_r.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return r_flag != 0; })) { | ||||
| 			r_flag--; | ||||
| 			return 1; | ||||
| 		} else { | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
| 	ssize_t spsc_check_w() | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> lk(lw); | ||||
| 		if (cond_w.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return w_flag != 0; })) { | ||||
| 			w_flag--; | ||||
| 			return 1; | ||||
| 		} else { | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
| 	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(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| template <bool block_read, bool block_write> | ||||
| class spsc_cond_detail { | ||||
| 	std::condition_variable cond_r, cond_w; | ||||
| 	std::mutex lr, lw; | ||||
| 	std::atomic_int r_flag, w_flag; | ||||
| @@ -93,7 +125,8 @@ template <bool block_read, bool block_write> class spsc_cond_detail { | ||||
| }; | ||||
|  | ||||
| // originally designed for select loop integration | ||||
| template <bool block_read, bool block_write> class spsc_efd_detail { | ||||
| template <bool block_read, bool block_write> | ||||
| class spsc_efd_detail { | ||||
| 	int efd_r, efd_w; /* eventfds used to block/notify readers/writers */ | ||||
|  | ||||
|     public: | ||||
| @@ -210,4 +243,7 @@ class spsc : public T<block_read, block_write> { | ||||
| template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> | ||||
| class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {}; | ||||
| template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> | ||||
| class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {}; | ||||
| class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {}; | ||||
| template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> | ||||
| class spsc_cond_timeout | ||||
| 	: public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_timeout_detail> {}; | ||||
| @@ -24,13 +24,10 @@ extern "C" { | ||||
| #include <osmocom/bb/trxcon/trxcon_fsm.h> | ||||
| #include <osmocom/bb/trxcon/l1ctl_server.h> | ||||
| } | ||||
| #include "ms_trxcon_if.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) | ||||
| { | ||||
| @@ -41,9 +38,9 @@ static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *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; | ||||
| 	l1c->log_prefix = talloc_strdup(l1c, g_trxcon->log_prefix); | ||||
| 	l1c->priv = g_trxcon; | ||||
| 	g_trxcon->l2if = l1c; | ||||
| } | ||||
|  | ||||
| static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) | ||||
| @@ -56,22 +53,19 @@ static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) | ||||
| 	osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); | ||||
| } | ||||
|  | ||||
| namespace trxcon | ||||
| { | ||||
| void trxc_l1ctl_init(void *tallctx) | ||||
| bool trxc_l1ctl_init(void *tallctx) | ||||
| { | ||||
| 	/* Start the L1CTL server */ | ||||
| 	server_cfg = (struct l1ctl_server_cfg){ | ||||
| 		.sock_path = "/tmp/osmocom_l2", | ||||
| 		.num_clients_max = 1, | ||||
| 		.conn_read_cb = &l1ctl_rx_cb, | ||||
| 		.conn_accept_cb = &l1ctl_conn_accept_cb, | ||||
| 		/* 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; | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| } // namespace trxcon | ||||
| @@ -1,103 +1,98 @@ | ||||
| /*
 | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * OsmocomBB <-> SDR connection bridge | ||||
|  * | ||||
|  * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> | ||||
|  * | ||||
|  * 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 | ||||
|  * 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 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/>.
 | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <osmocom/core/application.h> | ||||
| #include <osmocom/bb/trxcon/logging.h> | ||||
| #include <osmocom/bb/trxcon/trxcon.h> | ||||
| #include <osmocom/bb/l1sched/l1sched.h> | ||||
| #include <osmocom/core/logging.h> | ||||
| #include <osmocom/core/utils.h> | ||||
| 
 | ||||
| } | ||||
| 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, | ||||
| }; | ||||
| #include <osmocom/bb/trxcon/trxcon.h> | ||||
| #include <osmocom/bb/trxcon/logging.h> | ||||
| 
 | ||||
| static struct log_info_cat trxcon_log_info_cat[] = { | ||||
| 	[DAPP] = { | ||||
| 		.name = "DAPP", | ||||
| 		.color = "\033[1;35m", | ||||
| 		.description = "Application", | ||||
| 		.loglevel = LOGL_NOTICE, .enabled = 1, | ||||
| 		.color = "\033[1;35m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DL1C] = { | ||||
| 		.name = "DL1C", | ||||
| 		.color = "\033[1;31m", | ||||
| 		.description = "Layer 1 control interface", | ||||
| 		.loglevel = LOGL_NOTICE, .enabled = 1, | ||||
| 		.color = "\033[1;31m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DL1D] = { | ||||
| 		.name = "DL1D", | ||||
| 		.color = "\033[1;31m", | ||||
| 		.description = "Layer 1 data", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 1, | ||||
| 		.color = "\033[1;31m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DTRXC] = { | ||||
| 		.name = "DTRXC", | ||||
| 		.color = "\033[1;33m", | ||||
| 		.description = "Transceiver control interface", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 1, | ||||
| 		.color = "\033[1;33m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DTRXD] = { | ||||
| 		.name = "DTRXD", | ||||
| 		.color = "\033[1;33m", | ||||
| 		.description = "Transceiver data interface", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 1, | ||||
| 		.color = "\033[1;33m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DSCH] = { | ||||
| 		.name = "DSCH", | ||||
| 		.color = "\033[1;36m", | ||||
| 		.description = "Scheduler management", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 0, | ||||
| 		.color = "\033[1;36m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DSCHD] = { | ||||
| 		.name = "DSCHD", | ||||
| 		.color = "\033[1;36m", | ||||
| 		.description = "Scheduler data", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 0,  | ||||
| 		.color = "\033[1;36m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| 	[DGPRS] = { | ||||
| 		.name = "DGPRS", | ||||
| 		.description = "L1 GPRS (MAC layer)", | ||||
| 		.color = "\033[1;36m", | ||||
| 		.enabled = 1, .loglevel = LOGL_NOTICE, | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| static struct log_info trxcon_log_info = { | ||||
| static const struct log_info trxcon_log_info = { | ||||
| 	.cat = trxcon_log_info_cat, | ||||
| 	.num_cat = ARRAY_SIZE(trxcon_log_info_cat), | ||||
| }; | ||||
| 
 | ||||
| namespace trxcon | ||||
| { | ||||
| 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, | ||||
| 	[TRXCON_LOGC_GPRS] = DGPRS, | ||||
| }; | ||||
| 
 | ||||
| void trxc_log_init(void *tallctx) | ||||
| { | ||||
| 	osmo_init_logging2(tallctx, &trxcon_log_info); | ||||
| 	// log_parse_category_mask(osmo_stderr_target, "");
 | ||||
| 	log_target_file_switch_to_wqueue(osmo_stderr_target); | ||||
| 
 | ||||
| 	trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg)); | ||||
| } | ||||
| 
 | ||||
| } // namespace trxcon
 | ||||
| @@ -29,16 +29,10 @@ | ||||
| #include <thread> | ||||
| #include <fstream> | ||||
|  | ||||
| #include "sigProcLib.h" | ||||
|  | ||||
| #include "ms.h" | ||||
| #include "ms_rx_burst.h" | ||||
| #include "grgsm_vitac/grgsm_vitac.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include "sch.h" | ||||
| #include "convolve.h" | ||||
| #include "convert.h" | ||||
| } | ||||
|  | ||||
| dummylog ms_trx::dummy_log; | ||||
| @@ -49,196 +43,13 @@ const int offset_start = -15; | ||||
| static int offset_ctr = 0; | ||||
| #endif | ||||
|  | ||||
| void tx_test(ms_trx *t, ts_hitter_q_t *q, unsigned int *tsc) | ||||
| { | ||||
| 	sched_param sch_params; | ||||
| 	sch_params.sched_priority = sched_get_priority_max(SCHED_FIFO); | ||||
| 	pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch_params); | ||||
|  | ||||
| 	auto burst = genRandAccessBurst(0, 4, 0); | ||||
| 	scaleVector(*burst, t->txFullScale * 0.7); | ||||
|  | ||||
| 	// float -> int16 | ||||
| 	blade_sample_type burst_buf[burst->size()]; | ||||
| 	convert_and_scale<int16_t, float>(burst_buf, burst->begin(), burst->size() * 2, 1); | ||||
|  | ||||
| 	while (1) { | ||||
| 		GSM::Time target; | ||||
| 		while (!q->spsc_pop(&target)) { | ||||
| 			q->spsc_prep_pop(); | ||||
| 		} | ||||
|  | ||||
| 		std::cerr << std::endl << "\x1B[32m hitting " << target.FN() << "\033[0m" << std::endl; | ||||
|  | ||||
| 		int timing_advance = 0; | ||||
| 		int64_t now_ts; | ||||
| 		GSM::Time now_time; | ||||
| 		target.incTN(3); // ul dl offset | ||||
| 		int target_fn = target.FN(); | ||||
| 		int target_tn = target.TN(); | ||||
| 		t->timekeeper.get_both(&now_time, &now_ts); | ||||
|  | ||||
| 		auto diff_fn = GSM::FNDelta(target_fn, now_time.FN()); | ||||
| 		int diff_tn = (target_tn - (int)now_time.TN()) % 8; | ||||
| 		auto tosend = GSM::Time(diff_fn, 0); | ||||
|  | ||||
| 		if (diff_tn > 0) | ||||
| 			tosend.incTN(diff_tn); | ||||
| 		else if (diff_tn < 0) | ||||
| 			tosend.decTN(-diff_tn); | ||||
|  | ||||
| 		// in thory 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; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		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; | ||||
|  | ||||
| #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)); | ||||
| 		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); | ||||
| #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); | ||||
| #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 | ||||
| template <typename A> auto parsec(std::vector<std::string> &v, A &itr, std::string arg, bool *rv) | ||||
| { | ||||
| 	if (*itr == arg) { | ||||
| 		*rv = true; | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| template <typename A, typename B, typename C> | ||||
| bool parsec(std::vector<std::string> &v, A &itr, std::string arg, B f, C *rv) | ||||
| { | ||||
| 	if (*itr == arg) { | ||||
| 		itr++; | ||||
| 		if (itr != v.end()) { | ||||
| 			*rv = f(itr->c_str()); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, int *rv) | ||||
| { | ||||
| 	return parsec( | ||||
| 		v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv); | ||||
| } | ||||
| template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, unsigned int *rv) | ||||
| { | ||||
| 	return parsec( | ||||
| 		v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv); | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
| 	cpu_set_t cpuset; | ||||
|  | ||||
| 	CPU_ZERO(&cpuset); | ||||
| 	CPU_SET(2, &cpuset); | ||||
|  | ||||
| 	auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); | ||||
| 	if (rv < 0) { | ||||
| 		std::cerr << "affinity: errreur! " << std::strerror(errno); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	unsigned int default_tx_freq(881000 * 1000), default_rx_freq(926000 * 1000); | ||||
| 	unsigned int grx = 20, gtx = 20; | ||||
| 	bool tx_flag = false; | ||||
| 	pthread_setname_np(pthread_self(), "main"); | ||||
| 	convolve_init(); | ||||
| 	convert_init(); | ||||
| 	sigProcLibSetup(); | ||||
| 	initvita(); | ||||
|  | ||||
| 	int status = 0; | ||||
| 	auto trx = new ms_trx(); | ||||
| 	trx->do_auto_gain = true; | ||||
|  | ||||
| 	std::vector<std::string> args(argv + 1, argv + argc); | ||||
| 	for (auto i = args.begin(); i != args.end(); ++i) { | ||||
| 		parsec(args, i, "-r", 1000, &default_rx_freq); | ||||
| 		parsec(args, i, "-t", 1000, &default_tx_freq); | ||||
| 		parsec(args, i, "-gr", 1, &grx); | ||||
| 		parsec(args, i, "-gt", 1, >x); | ||||
| 		parsec(args, i, "-tx", &tx_flag); | ||||
| 	} | ||||
|  | ||||
| 	std::cerr << "usage: " << argv[0] << " <rxfreq in khz, i.e. 926000> [txfreq in khz, i.e. 881000] [TX]" | ||||
| 		  << std::endl | ||||
| 		  << "rx" << (argc == 1 ? " (default) " : " ") << default_rx_freq << "hz, tx " << default_tx_freq | ||||
| 		  << "hz" << std::endl | ||||
| 		  << "gain rx " << grx << " gain tx " << gtx << std::endl | ||||
| 		  << (tx_flag ? "##!!## RACH TX ACTIVE ##!!##" : "-- no rach tx --") << std::endl; | ||||
|  | ||||
| 	status = trx->init_dev_and_streams(); | ||||
| 	if (status < 0) | ||||
| 		return status; | ||||
| 	trx->tuneRx(default_rx_freq); | ||||
| 	trx->tuneTx(default_tx_freq); | ||||
| 	trx->setRxGain(grx); | ||||
| 	trx->setTxGain(gtx); | ||||
|  | ||||
| 	if (status == 0) { | ||||
| 		// FIXME: hacks! needs exit flag for detached threads! | ||||
|  | ||||
| 		std::thread(rcv_bursts_test, &trx->rxqueue, &trx->mTSC, trx->rxFullScale).detach(); | ||||
| 		if (tx_flag) | ||||
| 			std::thread(tx_test, trx, &trx->ts_hitter_q, &trx->mTSC).detach(); | ||||
| 		trx->start(); | ||||
| 		do { | ||||
| 			sleep(1); | ||||
| 		} while (1); | ||||
|  | ||||
| 		trx->stop_threads(); | ||||
| 	} | ||||
| 	delete trx; | ||||
|  | ||||
| 	return status; | ||||
| } | ||||
| #endif | ||||
| template <> | ||||
| std::atomic<bool> ms_trx::base::stop_lower_threads_flag(false); | ||||
|  | ||||
| int ms_trx::init_dev_and_streams() | ||||
| { | ||||
| 	int status = 0; | ||||
| 	status = base::init_device(rx_bh(), tx_bh()); | ||||
| 	status = init_device(rx_bh(), tx_bh()); | ||||
| 	if (status < 0) { | ||||
| 		std::cerr << "failed to init dev!" << std::endl; | ||||
| 		return -1; | ||||
| @@ -258,24 +69,29 @@ 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; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| void ms_trx::start() | ||||
| void ms_trx::start_lower_ms() | ||||
| { | ||||
| 	if (stop_lower_threads_flag) | ||||
| 		return; | ||||
| 	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); | ||||
| 	lower_rx_task = std::thread(fn); | ||||
| 	set_name_aff_sched(lower_rx_task.native_handle(), sched_params::thread_names::RXRUN); | ||||
|  | ||||
| 	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); | ||||
| 	lower_tx_task = std::thread(fn2); | ||||
| 	set_name_aff_sched(lower_tx_task.native_handle(), sched_params::thread_names::TXRUN); | ||||
|  | ||||
| 	actually_enable_streams(); | ||||
| } | ||||
|  | ||||
| void ms_trx::set_upper_ready(bool is_ready) | ||||
| @@ -285,10 +101,14 @@ void ms_trx::set_upper_ready(bool is_ready) | ||||
|  | ||||
| void ms_trx::stop_threads() | ||||
| { | ||||
| 	std::cerr << "killing threads...\r\n" << std::endl; | ||||
| 	std::cerr << "killing threads..." << std::endl; | ||||
| 	stop_lower_threads_flag = true; | ||||
| 	close_device(); | ||||
| 	rx_task.join(); | ||||
| 	tx_task.join(); | ||||
| 	std::cerr << "dev closed..." << std::endl; | ||||
| 	lower_rx_task.join(); | ||||
| 	std::cerr << "L rx dead..." << std::endl; | ||||
| 	lower_tx_task.join(); | ||||
| 	std::cerr << "L tx dead..." << std::endl; | ||||
| } | ||||
|  | ||||
| void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target) | ||||
| @@ -309,25 +129,26 @@ 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; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	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; | ||||
| #ifdef DBGXX | ||||
| 	auto check = now_time + tosend; | ||||
| 	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 | ||||
| #if 0 | ||||
| 	auto check = now_time + tosend; | ||||
| 	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,37 +34,92 @@ | ||||
| #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 | ||||
|  | ||||
| #include "Complex.h" | ||||
| #include "GSMCommon.h" | ||||
| #include "itrq.h" | ||||
| #include "threadpool.h" | ||||
|  | ||||
| const unsigned int ONE_TS_BURST_LEN = (3 + 58 + 26 + 58 + 3 + 8.25) * 4 /*sps*/; | ||||
| const unsigned int NUM_RXQ_FRAMES = 1; // rx thread <-> upper rx queue | ||||
| const unsigned int SCH_LEN_SPS = (ONE_TS_BURST_LEN * 8 /*ts*/ * 12 /*frames*/); | ||||
|  | ||||
| template <typename T> void clamp_array(T *start2, unsigned int len, T max) | ||||
| template <typename T> | ||||
| void clamp_array(T *start2, unsigned int len, T max) | ||||
| { | ||||
| 	for (int i = 0; i < len; i++) { | ||||
| 	for (unsigned int i = 0; i < len; i++) { | ||||
| 		const T t1 = start2[i] < -max ? -max : start2[i]; | ||||
| 		const T t2 = t1 > max ? max : t1; | ||||
| 		start2[i] = t2; | ||||
| 	} | ||||
| } | ||||
| template <typename DST_T, typename SRC_T, typename ST> | ||||
| void convert_and_scale(void *dst, void *src, unsigned int src_len, ST scale) | ||||
|  | ||||
| namespace cvt_internal | ||||
| { | ||||
|  | ||||
| template <typename SRC_T, typename ST> | ||||
| void convert_and_scale_i(float *dst, const SRC_T *src, unsigned int src_len, ST scale) | ||||
| { | ||||
| 	for (unsigned int i = 0; i < src_len; i++) | ||||
| 		reinterpret_cast<DST_T *>(dst)[i] = static_cast<DST_T>((reinterpret_cast<SRC_T *>(src)[i])) * scale; | ||||
| 		dst[i] = static_cast<float>(src[i]) * scale; | ||||
| } | ||||
| template <typename DST_T, typename SRC_T> void convert_and_scale_default(void *dst, void *src, unsigned int src_len) | ||||
|  | ||||
| template <typename DST_T, typename ST> | ||||
| void convert_and_scale_i(DST_T *dst, const float *src, unsigned int src_len, ST scale) | ||||
| { | ||||
| 	return convert_and_scale<DST_T, SRC_T>(dst, src, src_len, SAMPLE_SCALE_FACTOR); | ||||
| 	for (unsigned int i = 0; i < src_len; i++) | ||||
| 		dst[i] = static_cast<DST_T>(src[i] * scale); | ||||
| } | ||||
|  | ||||
| template <typename ST> | ||||
| void convert_and_scale_i(float *dst, const float *src, unsigned int src_len, ST scale) | ||||
| { | ||||
| 	for (unsigned int i = 0; i < src_len; i++) | ||||
| 		dst[i] = src[i] * scale; | ||||
| } | ||||
|  | ||||
| template <typename T> | ||||
| struct is_complex : std::false_type { | ||||
| 	using baset = T; | ||||
| 	static const unsigned int len_mul = 1; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| struct is_complex<std::complex<T>> : std::true_type { | ||||
| 	using baset = typename std::complex<T>::value_type; | ||||
| 	static const unsigned int len_mul = 2; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| struct is_complex<Complex<T>> : std::true_type { | ||||
| 	using baset = typename Complex<T>::value_type; | ||||
| 	static const unsigned int len_mul = 2; | ||||
| }; | ||||
|  | ||||
| } // namespace cvt_internal | ||||
|  | ||||
| template <typename DST_T, typename SRC_T, typename ST> | ||||
| void convert_and_scale(DST_T *dst, const SRC_T *src, unsigned int src_len, ST scale) | ||||
| { | ||||
| 	using vd = typename cvt_internal::is_complex<DST_T>::baset; | ||||
| 	using vs = typename cvt_internal::is_complex<SRC_T>::baset; | ||||
| 	return cvt_internal::convert_and_scale_i((vd *)dst, (vs *)src, src_len, scale); | ||||
| } | ||||
|  | ||||
| template <typename array_t> | ||||
| float normed_abs_sum(array_t *src, int len) | ||||
| { | ||||
| 	using vd = typename cvt_internal::is_complex<array_t>::baset; | ||||
| 	auto len_mul = cvt_internal::is_complex<array_t>::len_mul; | ||||
| 	auto ptr = reinterpret_cast<const vd *>(src); | ||||
| 	float sum = 0; | ||||
| 	for (unsigned int i = 0; i < len * len_mul; i++) | ||||
| 		sum += std::abs(ptr[i]); | ||||
| 	sum /= len * len_mul; | ||||
| 	return sum; | ||||
| } | ||||
|  | ||||
| struct one_burst { | ||||
| @@ -79,7 +133,7 @@ struct one_burst { | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| using rx_queue_t = spsc_cond<8 * NUM_RXQ_FRAMES, one_burst, true, true>; | ||||
| using rx_queue_t = spsc_cond_timeout<8 * NUM_RXQ_FRAMES, one_burst, true, false>; | ||||
|  | ||||
| enum class SCH_STATE { SEARCHING, FOUND }; | ||||
|  | ||||
| @@ -163,6 +217,40 @@ class time_keeper { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| static struct sched_params { | ||||
| 	enum thread_names { U_CTL = 0, U_RX, U_TX, SCH_SEARCH, MAIN, LEAKCHECK, RXRUN, TXRUN, _THRD_NAME_COUNT }; | ||||
| 	enum target { ODROID = 0, PI4 }; | ||||
| 	const char *name; | ||||
| 	int core; | ||||
| 	int schedtype; | ||||
| 	int prio; | ||||
| } schdp[][sched_params::_THRD_NAME_COUNT]{ | ||||
| 	{ | ||||
| 		{ "upper_ctrl", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) }, | ||||
| 		{ "upper_rx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 }, | ||||
| 		{ "upper_tx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 1 }, | ||||
|  | ||||
| 		{ "sch_search", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, | ||||
| 		{ "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, | ||||
| 		{ "leakcheck", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 }, | ||||
|  | ||||
| 		{ "rxrun", 4, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 }, | ||||
| 		{ "txrun", 5, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, | ||||
| 	}, | ||||
| 	{ | ||||
| 		{ "upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) }, | ||||
| 		{ "upper_rx", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 }, | ||||
| 		{ "upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, | ||||
|  | ||||
| 		{ "sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, | ||||
| 		{ "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, | ||||
| 		{ "leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 }, | ||||
|  | ||||
| 		{ "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 }, | ||||
| 		{ "txrun", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| using ts_hitter_q_t = spsc_cond<64, GSM::Time, true, false>; | ||||
|  | ||||
| struct ms_trx : public BASET { | ||||
| @@ -173,15 +261,12 @@ struct ms_trx : public BASET { | ||||
| 	int timing_advance; | ||||
| 	bool do_auto_gain; | ||||
|  | ||||
| 	std::thread rx_task; | ||||
| 	std::thread tx_task; | ||||
| 	std::thread *calcrval_task; | ||||
| 	std::thread lower_rx_task; | ||||
| 	std::thread lower_tx_task; | ||||
|  | ||||
| 	// provides bursts to upper rx thread | ||||
| 	rx_queue_t rxqueue; | ||||
| #ifdef SYNCTHINGONLY | ||||
| 	ts_hitter_q_t ts_hitter_q; | ||||
| #endif | ||||
|  | ||||
| 	blade_sample_type *first_sch_buf; | ||||
| 	blade_sample_type *burst_copy_buffer; | ||||
|  | ||||
| @@ -193,18 +278,20 @@ struct ms_trx : public BASET { | ||||
| 	int64_t first_sch_ts_start = -1; | ||||
|  | ||||
| 	time_keeper timekeeper; | ||||
| 	int hw_cpus; | ||||
| 	sched_params::target hw_target; | ||||
| 	single_thread_pool worker_thread; | ||||
|  | ||||
| 	void start(); | ||||
| 	void start_lower_ms(); | ||||
| 	std::atomic<bool> upper_is_ready; | ||||
| 	void set_upper_ready(bool is_ready); | ||||
|  | ||||
| 	bool handle_sch_or_nb(); | ||||
| 	bool handle_sch(bool first = false); | ||||
| 	bool decode_sch(float *bits, bool update_global_clock); | ||||
| 	bool decode_sch(char *bits, bool update_global_clock); | ||||
| 	SCH_STATE search_for_sch(dev_buf_t *rcd); | ||||
| 	void grab_bursts(dev_buf_t *rcd) __attribute__((optnone)); | ||||
| 	void grab_bursts(dev_buf_t *rcd); | ||||
|  | ||||
| 	int init_device(); | ||||
| 	int init_dev_and_streams(); | ||||
| 	void stop_threads(); | ||||
| 	void *rx_cb(ms_trx *t); | ||||
| @@ -212,9 +299,15 @@ struct ms_trx : public BASET { | ||||
| 	void maybe_update_gain(one_burst &brst); | ||||
|  | ||||
| 	ms_trx() | ||||
| 		: timing_advance(0), do_auto_gain(false), rxqueue(), first_sch_buf(new blade_sample_type[SCH_LEN_SPS]), | ||||
| 		  burst_copy_buffer(new blade_sample_type[ONE_TS_BURST_LEN]), rcv_done{ false }, sch_thread_done{ false } | ||||
| 		: mTSC(0), mBSIC(0), timing_advance(0), do_auto_gain(false), rxqueue(), | ||||
| 		  first_sch_buf(new blade_sample_type[SCH_LEN_SPS]), | ||||
| 		  burst_copy_buffer(new blade_sample_type[ONE_TS_BURST_LEN]), first_sch_buf_rcv_ts(0), | ||||
| 		  rcv_done{ false }, sch_thread_done{ false }, hw_cpus(std::thread::hardware_concurrency()), | ||||
| 		  hw_target(hw_cpus > 4 ? sched_params::target::ODROID : sched_params::target::PI4), | ||||
| 		  upper_is_ready(false) | ||||
| 	{ | ||||
| 		std::cerr << "scheduling for: " << (hw_cpus > 4 ? "odroid" : "pi4") << std::endl; | ||||
| 		set_name_aff_sched(worker_thread.get_handle(), sched_params::thread_names::SCH_SEARCH); | ||||
| 	} | ||||
|  | ||||
| 	virtual ~ms_trx() | ||||
| @@ -232,11 +325,28 @@ struct ms_trx : public BASET { | ||||
| 		timing_advance = val * 4; | ||||
| 	} | ||||
|  | ||||
| 	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio) | ||||
| 	void set_name_aff_sched(sched_params::thread_names name) | ||||
| 	{ | ||||
| 		set_name_aff_sched(pthread_self(), name, cpunum, schedtype, prio); | ||||
| 		set_name_aff_sched(pthread_self(), name); | ||||
| 	} | ||||
|  | ||||
| 	void set_name_aff_sched(std::thread::native_handle_type h, sched_params::thread_names name) | ||||
| 	{ | ||||
| 		auto tgt = schdp[hw_target][name]; | ||||
| 		// std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << std::endl; | ||||
| 		set_name_aff_sched(h, tgt.name, tgt.core, tgt.schedtype, tgt.prio); | ||||
| 	} | ||||
|  | ||||
| 	using pt_sig = void *(*)(void *); | ||||
|  | ||||
| 	pthread_t spawn_worker_thread(sched_params::thread_names name, pt_sig fun, void *arg) | ||||
| 	{ | ||||
| 		auto tgt = schdp[hw_target][name]; | ||||
| 		// std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << " prio:" << tgt.prio << std::endl; | ||||
| 		return do_spawn_thr(tgt.name, tgt.core, tgt.schedtype, tgt.prio, fun, arg); | ||||
| 	} | ||||
|  | ||||
|     private: | ||||
| 	void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype, | ||||
| 				int prio) | ||||
| 	{ | ||||
| @@ -247,18 +357,40 @@ struct ms_trx : public BASET { | ||||
| 		CPU_ZERO(&cpuset); | ||||
| 		CPU_SET(cpunum, &cpuset); | ||||
|  | ||||
| 		auto rv = pthread_setaffinity_np(h, sizeof(cpuset), &cpuset); | ||||
| 		if (rv < 0) { | ||||
| 		if (pthread_setaffinity_np(h, sizeof(cpuset), &cpuset) < 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) { | ||||
| 		if (pthread_setschedparam(h, schedtype, &sch_params) < 0) { | ||||
| 			std::cerr << name << " sched: errreur! " << std::strerror(errno); | ||||
| 			return exit(0); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pthread_t do_spawn_thr(const char *name, int cpunum, int schedtype, int prio, pt_sig fun, void *arg) | ||||
| 	{ | ||||
| 		pthread_t thread; | ||||
|  | ||||
| 		pthread_attr_t attr; | ||||
| 		pthread_attr_init(&attr); | ||||
|  | ||||
| 		sched_param sch_params; | ||||
| 		sch_params.sched_priority = prio; | ||||
| 		cpu_set_t cpuset; | ||||
| 		CPU_ZERO(&cpuset); | ||||
| 		CPU_SET(cpunum, &cpuset); | ||||
| 		auto a = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); | ||||
| 		a |= pthread_attr_setschedpolicy(&attr, schedtype); | ||||
| 		a |= pthread_attr_setschedparam(&attr, &sch_params); | ||||
| 		a |= pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); | ||||
| 		if(a) | ||||
| 			std::cerr << "thread arg rc:" << a << std::endl; | ||||
| 		pthread_create(&thread, &attr, fun, arg); | ||||
| 		pthread_setname_np(thread, name); | ||||
| 		pthread_attr_destroy(&attr); | ||||
| 		return thread; | ||||
| 	} | ||||
| }; | ||||
|   | ||||
| @@ -1,207 +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 "ms.h" | ||||
| #include "sigProcLib.h" | ||||
| #include "signalVector.h" | ||||
| #include "grgsm_vitac/grgsm_vitac.h" | ||||
| extern "C" { | ||||
| #include "sch.h" | ||||
| } | ||||
|  | ||||
| #if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG) | ||||
| #define DBGLG(...) ms_trx::dummy_log() | ||||
| #else | ||||
| #define DBGLG(...) std::cerr | ||||
| #endif | ||||
|  | ||||
| #if !defined(SYNCTHINGONLY) | ||||
| #define DBGLG2(...) ms_trx::dummy_log() | ||||
| #else | ||||
| #define DBGLG2(...) std::cerr | ||||
| #endif | ||||
|  | ||||
| static bool decode_sch(float *bits, bool update_global_clock) | ||||
| { | ||||
| 	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); | ||||
|  | ||||
| 	if (!gsm_sch_decode(info, data)) { | ||||
| 		gsm_sch_parse(info, &sch); | ||||
|  | ||||
| 		DBGLG() << "SCH : Decoded values" << std::endl; | ||||
| 		DBGLG() << "    BSIC: " << sch.bsic << std::endl; | ||||
| 		DBGLG() << "    TSC: " << (sch.bsic & 0x7) << std::endl; | ||||
| 		DBGLG() << "    T1  : " << sch.t1 << std::endl; | ||||
| 		DBGLG() << "    T2  : " << sch.t2 << std::endl; | ||||
| 		DBGLG() << "    T3p : " << sch.t3p << std::endl; | ||||
| 		DBGLG() << "    FN  : " << gsm_sch_to_fn(&sch) << std::endl; | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| static void check_rcv_fn(GSM::Time t, bool first, unsigned int &lastfn, unsigned int &fnbm) | ||||
| { | ||||
| 	if (first && t.TN() == 0) { | ||||
| 		lastfn = t.FN(); | ||||
| 		fnbm = 1 << 0; | ||||
| 		first = false; | ||||
| 	} | ||||
| 	if (!first && t.FN() != lastfn) { | ||||
| 		if (fnbm != 255) | ||||
| 			std::cerr << "rx " << lastfn << ":" << fnbm << " " << __builtin_popcount(fnbm) << std::endl; | ||||
| 		lastfn = t.FN(); | ||||
| 		fnbm = 1 << t.TN(); | ||||
| 	} | ||||
|  | ||||
| 	fnbm |= 1 << t.TN(); | ||||
| } | ||||
|  | ||||
| static void handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale) | ||||
| { | ||||
| 	memset(burst.begin(), 0, burst.size() * sizeof(std::complex<float>)); | ||||
| 	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) { | ||||
| 		char outbin[148]; | ||||
| 		convert_and_scale_default<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2); | ||||
| 		std::stringstream dbgout; | ||||
| #if 0 | ||||
| 		{ | ||||
| 			struct estim_burst_params ebp; | ||||
| 			auto rv2 = detectSCHBurst(burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp); | ||||
| 			auto bits = demodAnyBurst(burst, SCH, 4, &ebp); | ||||
| 			// clamp_array(bits->begin(), 148, 1.5f); | ||||
| 			for (auto &i : *bits) | ||||
| 				i = (i > 0 ? 1 : -1); | ||||
|  | ||||
| 			auto rv = decode_sch(bits->begin(), false); | ||||
| 			dbgout << "U DET@" << (rv2 ? "yes " : "   ") << "Timing offset     " << ebp.toa | ||||
| 			       << " symbols, DECODE: " << (rv ? "yes" : "---") << " "; | ||||
|  | ||||
| 			delete bits; | ||||
| 		} | ||||
| #endif | ||||
| 		{ | ||||
| 			convert_and_scale<float, float>(burst.begin(), burst.begin(), ONE_TS_BURST_LEN * 2, | ||||
| 							1.f / float(scale)); | ||||
|  | ||||
| 			std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; | ||||
| 			auto ss = reinterpret_cast<std::complex<float> *>(burst.begin()); | ||||
| 			int d_c0_burst_start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]); | ||||
| 			detect_burst(ss, &channel_imp_resp[0], d_c0_burst_start, outbin); | ||||
|  | ||||
| 			SoftVector bits; | ||||
| 			bits.resize(148); | ||||
| 			for (int i = 0; i < 148; i++) { | ||||
| 				bits[i] = (!outbin[i]); // < 1 ? -1 : 1; | ||||
| 			} | ||||
|  | ||||
| 			auto rv = decode_sch(bits.begin(), false); | ||||
| 			dbgout << "U SCH@" | ||||
| 			       << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << d_c0_burst_start | ||||
| 			       << " DECODE:" << (rv ? "yes" : "---") << std::endl; | ||||
| 		} | ||||
|  | ||||
| 		DBGLG() << dbgout.str(); | ||||
| 		return; | ||||
| 	} | ||||
| #if 1 | ||||
| 	convert_and_scale<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(scale)); | ||||
| 	// std::cerr << "@" << tsc << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << " " | ||||
| 	// 	  << std::endl; | ||||
|  | ||||
| 	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]; | ||||
| 	auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, tsc); | ||||
| 	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; | ||||
|  | ||||
| 	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); | ||||
| 	; | ||||
| #ifdef DBGXX | ||||
| 	// auto bits = SoftVector(148); | ||||
| 	// for (int i = 0; i < 148; i++) | ||||
| 	// 	(bits)[i] = outbin[i] < 1 ? -1 : 1; | ||||
| #endif | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale) | ||||
| { | ||||
| 	static bool first = true; | ||||
| 	unsigned int lastfn = 0; | ||||
| 	unsigned int fnbm = 0; | ||||
| 	signalVector burst(ONE_TS_BURST_LEN, 100, 100); | ||||
|  | ||||
| 	cpu_set_t cpuset; | ||||
|  | ||||
| 	CPU_ZERO(&cpuset); | ||||
| 	CPU_SET(1, &cpuset); | ||||
|  | ||||
| 	auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); | ||||
| 	if (rv < 0) { | ||||
| 		std::cerr << "affinity: errreur! " << std::strerror(errno); | ||||
| 		exit(0); | ||||
| 	} | ||||
|  | ||||
| 	int prio = sched_get_priority_max(SCHED_RR); | ||||
| 	struct sched_param param; | ||||
| 	param.sched_priority = prio; | ||||
| 	rv = sched_setscheduler(0, SCHED_RR, ¶m); | ||||
| 	if (rv < 0) { | ||||
| 		std::cerr << "scheduler: errreur! " << std::strerror(errno); | ||||
| 		exit(0); | ||||
| 	} | ||||
|  | ||||
| 	while (1) { | ||||
| 		one_burst e; | ||||
| 		while (!q->spsc_pop(&e)) { | ||||
| 			q->spsc_prep_pop(); | ||||
| 		} | ||||
|  | ||||
| 		check_rcv_fn(e.gsmts, first, lastfn, fnbm); | ||||
|  | ||||
| 		handle_it(e, burst, *tsc, scale); | ||||
| #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 | ||||
| 	} | ||||
| } | ||||
| @@ -19,8 +19,6 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "sigProcLib.h" | ||||
| #include "signalVector.h" | ||||
| #include <atomic> | ||||
| #include <cassert> | ||||
| #include <complex> | ||||
| @@ -30,6 +28,8 @@ | ||||
| #include "ms.h" | ||||
| #include "grgsm_vitac/grgsm_vitac.h" | ||||
|  | ||||
| #include "threadpool.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include "sch.h" | ||||
| } | ||||
| @@ -38,28 +38,29 @@ extern "C" { | ||||
| #undef LOG | ||||
| #endif | ||||
|  | ||||
| #if !defined(SYNCTHINGONLY) //|| !defined(NODAMNLOG) | ||||
| #if !defined(NODAMNLOG) | ||||
| #define DBGLG(...) ms_trx::dummy_log() | ||||
| #else | ||||
| #define DBGLG(...) std::cerr | ||||
| #endif | ||||
|  | ||||
| #if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG) | ||||
| #if !defined(NODAMNLOG) | ||||
| #define DBGLG2(...) ms_trx::dummy_log() | ||||
| #else | ||||
| #define DBGLG2(...) std::cerr | ||||
| #endif | ||||
|  | ||||
| #define PRINT_Q_OVERFLOW | ||||
| bool ms_trx::decode_sch(float *bits, bool update_global_clock) | ||||
|  | ||||
| bool ms_trx::decode_sch(char *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], 1, 39); | ||||
| 	float_to_sbit(&bits[106], &data[39], 1, 39); | ||||
| 	memcpy(&data[0], &bits[3], 39); | ||||
| 	memcpy(&data[39], &bits[106], 39); | ||||
|  | ||||
| 	if (!gsm_sch_decode(info, data)) { | ||||
| 		gsm_sch_parse(info, &sch); | ||||
| @@ -87,18 +88,6 @@ bool ms_trx::decode_sch(float *bits, bool update_global_clock) | ||||
| 			// global_time_keeper.FN(fn); | ||||
| 			// global_time_keeper.TN(0); | ||||
| 		} | ||||
| #ifdef SYNCTHINGONLY | ||||
| 		else { | ||||
| 			int t3 = sch.t3p * 10 + 1; | ||||
| 			if (t3 == 11) { | ||||
| 				// timeslot hitter attempt @ fn 21 in mf | ||||
| 				DBGLG2() << "sch @ " << t3 << std::endl; | ||||
| 				auto e = GSM::Time(fn, 0); | ||||
| 				e += 10; | ||||
| 				ts_hitter_q.spsc_push(&e); | ||||
| 			} | ||||
| 		} | ||||
| #endif | ||||
|  | ||||
| 		return true; | ||||
| 	} | ||||
| @@ -113,11 +102,7 @@ void ms_trx::maybe_update_gain(one_burst &brst) | ||||
| 	const unsigned int rx_max_cutoff = (rxFullScale * 2) / 3; | ||||
| 	static int gain_check = 0; | ||||
| 	static float runmean = 0; | ||||
| 	float sum = 0; | ||||
| 	for (auto i : brst.burst) | ||||
| 		sum += abs(i.real()) + abs(i.imag()); | ||||
| 	sum /= ONE_TS_BURST_LEN * 2; | ||||
|  | ||||
| 	float sum = normed_abs_sum(&brst.burst[0], ONE_TS_BURST_LEN); | ||||
| 	runmean = gain_check ? (runmean * (gain_check + 2) - 1 + sum) / (gain_check + 2) : sum; | ||||
|  | ||||
| 	if (gain_check == avgburst_num - 1) { | ||||
| @@ -126,8 +111,11 @@ void ms_trx::maybe_update_gain(one_burst &brst) | ||||
| 		gainoffset = runmean < (rxFullScale / 2 ? 2 : 1); | ||||
| 		float newgain = runmean < rx_max_cutoff ? rxgain + gainoffset : rxgain - gainoffset; | ||||
| 		// FIXME: gian cutoff | ||||
| 		if (newgain != rxgain && newgain <= 60) | ||||
| 			std::thread([this, newgain] { setRxGain(newgain); }).detach(); | ||||
| 		if (newgain != rxgain && newgain <= 60) { | ||||
| 			auto gain_fun = [this, newgain] { setRxGain(newgain); }; | ||||
| 			worker_thread.add_task(gain_fun); | ||||
| 		} | ||||
|  | ||||
| 		runmean = 0; | ||||
| 	} | ||||
| 	gain_check = (gain_check + 1) % avgburst_num; | ||||
| @@ -140,8 +128,6 @@ bool ms_trx::handle_sch_or_nb() | ||||
| 	one_burst brst; | ||||
| 	const auto current_gsm_time = timekeeper.gsmtime(); | ||||
| 	const auto is_sch = gsm_sch_check_ts(current_gsm_time.TN(), current_gsm_time.FN()); | ||||
| 	const auto is_fcch = gsm_fcch_check_ts(current_gsm_time.TN(), current_gsm_time.FN()); | ||||
| #pragma unused(is_fcch) | ||||
|  | ||||
| 	//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; | ||||
| @@ -152,14 +138,9 @@ bool ms_trx::handle_sch_or_nb() | ||||
| 		handle_sch(false); | ||||
| 		memcpy(brst.sch_bits, sch_demod_bits, sizeof(sch_demod_bits)); | ||||
| 	} | ||||
| #ifndef SYNCTHINGONLY | ||||
| 	if (upper_is_ready) { // this is blocking, so only submit if there is a reader - only if upper exists! | ||||
| #endif | ||||
| 		while (!rxqueue.spsc_push(&brst)) | ||||
| 			; | ||||
| #ifndef SYNCTHINGONLY | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| 	while (upper_is_ready && !rxqueue.spsc_push(&brst)) | ||||
| 		; | ||||
|  | ||||
| 	if (do_auto_gain) | ||||
| 		maybe_update_gain(brst); | ||||
| @@ -180,27 +161,18 @@ bool ms_trx::handle_sch(bool is_first_sch_acq) | ||||
|  | ||||
| 	int start; | ||||
| 	memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer)); | ||||
| 	convert_and_scale(which_out_buffer, which_in_buffer, buf_len * 2, 1.f / float(rxFullScale)); | ||||
| 	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); | ||||
| 		detect_burst(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits); | ||||
| 	} else { | ||||
| 		convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2, | ||||
| 						  1.f / float(rxFullScale)); | ||||
| 		start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]); | ||||
| 		start = start < 39 ? start : 39; | ||||
| 		start = start > -39 ? start : -39; | ||||
| 		detect_burst(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits); | ||||
| 	} | ||||
| 	detect_burst_nb(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits); | ||||
|  | ||||
| 	SoftVector bitss(148); | ||||
| 	for (int i = 0; i < 148; i++) { | ||||
| 		bitss[i] = (sch_demod_bits[i]); | ||||
| 	} | ||||
|  | ||||
| 	auto sch_decode_success = decode_sch(bitss.begin(), is_first_sch_acq); | ||||
| 	auto sch_decode_success = decode_sch(sch_demod_bits, is_first_sch_acq); | ||||
|  | ||||
| 	if (sch_decode_success) { | ||||
| 		const auto ts_offset_symb = 0; | ||||
| @@ -221,32 +193,38 @@ 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) | ||||
| /* | ||||
| accumulates a full big buffer consisting of 8*12 timeslots, then: | ||||
| either | ||||
| 1) adjusts gain if necessary and starts over | ||||
| 2) searches and finds SCH and is done | ||||
| */ | ||||
| SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd) | ||||
| { | ||||
| 	static unsigned int sch_pos = 0; | ||||
| 	auto to_copy = SCH_LEN_SPS - sch_pos; | ||||
|  | ||||
| 	if (sch_thread_done) | ||||
| 		return SCH_STATE::FOUND; | ||||
|  | ||||
| 	if (rcv_done) | ||||
| 		return SCH_STATE::SEARCHING; | ||||
|  | ||||
| 	auto to_copy = SCH_LEN_SPS - sch_pos; | ||||
|  | ||||
| 	if (SCH_LEN_SPS == to_copy) // first time | ||||
| 	if (sch_pos == 0) // keep first ts for time delta calc | ||||
| 		first_sch_buf_rcv_ts = rcd->get_first_ts(); | ||||
|  | ||||
| 	if (!to_copy) { | ||||
| 	if (to_copy) { | ||||
| 		auto spsmax = rcd->actual_samples_per_buffer(); | ||||
| 		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); | ||||
| 	} else { // (!to_copy) | ||||
| 		sch_pos = 0; | ||||
| 		rcv_done = true; | ||||
| 		std::thread([this] { | ||||
| 			set_name_aff_sched("sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5); | ||||
|  | ||||
| 			auto ptr = reinterpret_cast<const int16_t *>(first_sch_buf); | ||||
| 		auto sch_search_fun = [this] { | ||||
| 			const auto target_val = rxFullScale / 8; | ||||
| 			float sum = 0; | ||||
| 			for (int i = 0; i < SCH_LEN_SPS * 2; i++) | ||||
| 				sum += std::abs(ptr[i]); | ||||
| 			sum /= SCH_LEN_SPS * 2; | ||||
| 			float sum = normed_abs_sum(first_sch_buf, SCH_LEN_SPS); | ||||
|  | ||||
| 			//FIXME: arbitrary value, gain cutoff | ||||
| 			if (sum > target_val || rxgain >= 60) // enough ? | ||||
| @@ -259,16 +237,9 @@ __attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_ | ||||
|  | ||||
| 			if (!sch_thread_done) | ||||
| 				rcv_done = false; // retry! | ||||
| 			return (bool)sch_thread_done; | ||||
| 		}).detach(); | ||||
| 		}; | ||||
| 		worker_thread.add_task(sch_search_fun); | ||||
| 	} | ||||
|  | ||||
| 	auto spsmax = rcd->actual_samples_per_buffer(); | ||||
| 	if (to_copy > 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; | ||||
| } | ||||
|  | ||||
| @@ -286,7 +257,7 @@ 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) | ||||
| @@ -306,7 +277,7 @@ void ms_trx::grab_bursts(dev_buf_t *rcd) | ||||
| 	if (partial_rdofs) { | ||||
| 		auto first_remaining = ONE_TS_BURST_LEN - partial_rdofs; | ||||
| 		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; | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										78
									
								
								Transceiver52M/ms/ms_trxcon_if.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								Transceiver52M/ms/ms_trxcon_if.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| /* | ||||
|  * (C) 2023 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 <atomic> | ||||
| #include "ms_trxcon_if.h" | ||||
| extern "C" { | ||||
| #include <osmocom/bb/trxcon/trxcon.h> | ||||
| #include <osmocom/bb/trxcon/l1ctl_server.h> | ||||
| #include <osmocom/core/panic.h> | ||||
| } | ||||
|  | ||||
| extern tx_queue_t txq; | ||||
| extern cmd_queue_t cmdq_to_phy; | ||||
| extern cmdr_queue_t cmdq_from_phy; | ||||
| extern std::atomic<bool> g_exit_flag; | ||||
| // trxcon C call(back) if | ||||
| extern "C" { | ||||
| int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br) | ||||
| { | ||||
| 	if (br->burst_len == 0) // dummy/nope | ||||
| 		return 0; | ||||
| 	OSMO_ASSERT(br->burst != 0); | ||||
|  | ||||
| 	internal_q_tx_buf b(br); | ||||
| 	if (!g_exit_flag) | ||||
| 		txq.spsc_push(&b); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd) | ||||
| { | ||||
| #ifdef TXDEBUG | ||||
| 	DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl; | ||||
| #endif | ||||
| 	if (!g_exit_flag) | ||||
| 		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_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); | ||||
| 	l1ctl_client_conn_close((struct l1ctl_client *)trxcon->l2if); | ||||
| } | ||||
|  | ||||
| int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg) | ||||
| { | ||||
| 	struct l1ctl_client *l1c = (struct l1ctl_client *)trxcon->l2if; | ||||
|  | ||||
| 	return l1ctl_client_send(l1c, msg); | ||||
| } | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| #pragma once | ||||
| /*
 | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * Author: Eric Wild <ewild@sysmocom.de> | ||||
| @@ -21,5 +21,22 @@ | ||||
|  */ | ||||
| 
 | ||||
| #include "ms.h" | ||||
| extern "C" { | ||||
| #include <osmocom/bb/trxcon/phyif.h> | ||||
| } | ||||
| 
 | ||||
| void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale); | ||||
| extern struct trxcon_inst *g_trxcon; | ||||
| struct internal_q_tx_buf { | ||||
| 	trxcon_phyif_burst_req r; | ||||
| 	uint8_t buf[148]; | ||||
| 	internal_q_tx_buf() = default; | ||||
| 	internal_q_tx_buf(const internal_q_tx_buf &) = delete; | ||||
| 	internal_q_tx_buf &operator=(const internal_q_tx_buf &) = default; | ||||
| 	internal_q_tx_buf(const struct trxcon_phyif_burst_req *br) : r(*br) | ||||
| 	{ | ||||
| 		memcpy(buf, (void *)br->burst, br->burst_len); | ||||
| 	} | ||||
| }; | ||||
| using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>; | ||||
| using cmd_queue_t = spsc_cond_timeout<8 * 1, trxcon_phyif_cmd, true, false>; | ||||
| using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>; | ||||
| @@ -19,78 +19,43 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include <csignal> | ||||
| #include "sigProcLib.h" | ||||
| #include "ms.h" | ||||
| #include <signalVector.h> | ||||
| #include <radioVector.h> | ||||
| #include <radioInterface.h> | ||||
| #include "grgsm_vitac/grgsm_vitac.h" | ||||
| #include <grgsm_vitac/grgsm_vitac.h> | ||||
|  | ||||
| // #define TXDEBUG | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/core/select.h> | ||||
|  | ||||
| #include "sch.h" | ||||
| #include "convolve.h" | ||||
| #include "convert.h" | ||||
| #include "proto_trxd.h" | ||||
|  | ||||
| #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 <osmocom/core/application.h> | ||||
| #include <osmocom/gsm/gsm_utils.h> | ||||
|  | ||||
| #include <osmocom/bb/trxcon/trxcon.h> | ||||
| #include <osmocom/bb/trxcon/trxcon_fsm.h> | ||||
| #include <osmocom/bb/trxcon/l1ctl_server.h> | ||||
|  | ||||
| extern void trxc_log_init(void *tallctx); | ||||
| #ifdef LSANDEBUG | ||||
| void __lsan_do_recoverable_leak_check(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #include "ms_trxcon_if.h" | ||||
| #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/core/gsmtap_util.h> | ||||
| #include <osmocom/core/gsmtap.h> | ||||
|  | ||||
| // #include <osmocom/core/application.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/trx_if.h> | ||||
| #include <osmocom/bb/trxcon/l1ctl_server.h> | ||||
|  | ||||
| #include <osmocom/bb/l1sched/l1sched.h> | ||||
| // #include <osmocom/bb/l1sched/logging.h> | ||||
| } | ||||
| extern bool trxc_l1ctl_init(void *tallctx); | ||||
| 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 void trxc_log_init(void *tallctx); | ||||
| extern void trxc_l1ctl_init(void *tallctx); | ||||
|  | ||||
| } // namespace trxcon | ||||
| tx_queue_t txq; | ||||
| cmd_queue_t cmdq_to_phy; | ||||
| cmdr_queue_t cmdq_from_phy; | ||||
|  | ||||
| #ifdef LOG | ||||
| #undef LOG | ||||
| @@ -99,41 +64,64 @@ extern void trxc_l1ctl_init(void *tallctx); | ||||
|  | ||||
| #define DBGLG(...) upper_trx::dummy_log() | ||||
|  | ||||
| std::atomic<bool> g_exit_flag; | ||||
|  | ||||
| void upper_trx::stop_upper_threads() | ||||
| { | ||||
| 	g_exit_flag = true; | ||||
|  | ||||
| 	pthread_join(thr_control, NULL); | ||||
| 	pthread_join(thr_tx, NULL); | ||||
| } | ||||
|  | ||||
| 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(); | ||||
| 		} | ||||
| 	}); | ||||
| 	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(); | ||||
| 		} | ||||
| 	}); | ||||
| 	DBGLG(...) << "spawning threads.." << std::endl; | ||||
|  | ||||
| 	// atomic ensures data is not written to q until loop reads | ||||
| 	start_lower_ms(); | ||||
| 	thr_control = spawn_worker_thread( | ||||
| 		sched_params::thread_names::U_CTL, | ||||
| 		[](void *args) -> void * { | ||||
| 			upper_trx *t = reinterpret_cast<upper_trx *>(args); | ||||
| #ifdef TXDEBUG | ||||
| 			struct sched_param param; | ||||
| 			int policy; | ||||
| 			pthread_getschedparam(pthread_self(), &policy, ¶m); | ||||
| 			printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy, | ||||
| 			       param.sched_priority); | ||||
| #endif | ||||
| 			std::cerr << "started U control!" << std::endl; | ||||
| 			while (!g_exit_flag) { | ||||
| 				t->driveControl(); | ||||
| 			} | ||||
| 			std::cerr << "exit U control!" << std::endl; | ||||
|  | ||||
| 	set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5); | ||||
| 	while (1) { | ||||
| 		// set_upper_ready(true); | ||||
| 		driveReceiveFIFO(); | ||||
| 		osmo_select_main(1); | ||||
| 			return 0; | ||||
| 		}, | ||||
| 		this); | ||||
| 	thr_tx = spawn_worker_thread( | ||||
| 		sched_params::thread_names::U_TX, | ||||
| 		[](void *args) -> void * { | ||||
| 			upper_trx *t = reinterpret_cast<upper_trx *>(args); | ||||
| #ifdef TXDEBUG | ||||
| 			struct sched_param param; | ||||
| 			int policy; | ||||
| 			pthread_getschedparam(pthread_self(), &policy, ¶m); | ||||
| 			printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy, | ||||
| 			       param.sched_priority); | ||||
| #endif | ||||
| 			std::cerr << "started U tx!" << std::endl; | ||||
| 			while (!g_exit_flag) { | ||||
| 				t->driveTx(); | ||||
| 			} | ||||
| 			std::cerr << "exit U tx!" << std::endl; | ||||
|  | ||||
| 		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); | ||||
| 		} | ||||
| 	} | ||||
| 			return 0; | ||||
| 		}, | ||||
| 		this); | ||||
|  | ||||
| #ifdef LSANDEBUG | ||||
| 	std::thread([this] { | ||||
| 		set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10); | ||||
| 		set_name_aff_sched(sched_params::thread_names::LEAKCHECK); | ||||
|  | ||||
| 		while (1) { | ||||
| 			std::this_thread::sleep_for(std::chrono::seconds{ 5 }); | ||||
| @@ -143,19 +131,43 @@ void upper_trx::start_threads() | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void upper_trx::start_lower_ms() | ||||
| void upper_trx::main_loop() | ||||
| { | ||||
| 	ms_trx::start(); | ||||
| 	set_name_aff_sched(sched_params::thread_names::U_RX); | ||||
| 	set_upper_ready(true); | ||||
| 	while (!g_exit_flag) { | ||||
| 		driveReceiveFIFO(); | ||||
| 		osmo_select_main(1); | ||||
|  | ||||
| 		trxcon_phyif_rsp r; | ||||
| 		if (cmdq_from_phy.spsc_pop(&r)) { | ||||
| 			DBGLG() << "HAVE RESP:" << r.type << std::endl; | ||||
| 			trxcon_phyif_handle_rsp(g_trxcon, &r); | ||||
| 		} | ||||
| 	} | ||||
| 	set_upper_ready(false); | ||||
| 	std::cerr << "exit U rx!" << std::endl; | ||||
| 	mOn = false; | ||||
| } | ||||
|  | ||||
| // 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; | ||||
| 	static complex workbuf[40 + 625 + 40]; | ||||
| 	static signalVector sv(workbuf, 40, 625); | ||||
| 	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[40]); | ||||
| 	memset((void *)&workbuf[0], 0, sizeof(workbuf)); | ||||
| 	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)) { | ||||
| @@ -167,9 +179,9 @@ bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) | ||||
| 	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); | ||||
| 	trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) }; | ||||
| 	trxcon_phyif_rtr_rsp r = {}; | ||||
| 	trxcon_phyif_handle_rtr_ind(g_trxcon, &i, &r); | ||||
| 	if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE)) | ||||
| 		return false; | ||||
|  | ||||
| @@ -186,7 +198,7 @@ bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale)); | ||||
| 	convert_and_scale(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale)); | ||||
|  | ||||
| 	pow = energyDetect(sv, 20 * 4 /*sps*/); | ||||
| 	if (pow < -1) { | ||||
| @@ -213,13 +225,14 @@ bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) | ||||
| 		// 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); | ||||
| 		detect_burst_nb(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; | ||||
| @@ -235,34 +248,39 @@ void upper_trx::driveReceiveFIFO() | ||||
| 		return; | ||||
|  | ||||
| 	if (pullRadioVector(burstTime, RSSI, TOA)) { | ||||
| 		// trxcon::trx_data_rx_handler(trxcon::trxcon_instance, (uint8_t *)&response); | ||||
| 		trxcon::trxcon_phyif_burst_ind bi; | ||||
| 		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_clock_ind(trxcon::g_trxcon, bi.fn); | ||||
| 		trxcon_phyif_handle_burst_ind(trxcon::g_trxcon, &bi); | ||||
| 		trxcon_phyif_handle_burst_ind(g_trxcon, &bi); | ||||
| 	} | ||||
|  | ||||
| 	struct trxcon::trxcon_phyif_rts_ind rts { | ||||
| 	struct 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); | ||||
| 	trxcon_phyif_handle_rts_ind(g_trxcon, &rts); | ||||
| } | ||||
|  | ||||
| void upper_trx::driveTx() | ||||
| { | ||||
| 	trxcon::internal_q_tx_buf e; | ||||
| 	while (!trxcon::txq.spsc_pop(&e)) { | ||||
| 		trxcon::txq.spsc_prep_pop(); | ||||
| 	internal_q_tx_buf e; | ||||
| 	static BitVector newBurst(sizeof(e.buf)); | ||||
| 	while (!txq.spsc_pop(&e)) { | ||||
| 		txq.spsc_prep_pop(); | ||||
| 	} | ||||
|  | ||||
| 	trxcon::internal_q_tx_buf *burst = &e; | ||||
| 	// ensure our tx cb is tickled and can exit | ||||
| 	if (g_exit_flag) { | ||||
| 		submit_burst_ts(0, 1337, 1); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| #ifdef TXDEBUG | ||||
| 	internal_q_tx_buf *burst = &e; | ||||
|  | ||||
| #ifdef TXDEBUG2 | ||||
| 	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 | ||||
| @@ -270,7 +288,6 @@ void upper_trx::driveTx() | ||||
| 	auto currTime = GSM::Time(burst->r.fn, burst->r.tn); | ||||
| 	int RSSI = (int)burst->r.pwr; | ||||
|  | ||||
| 	static BitVector newBurst(gSlotLen); | ||||
| 	BitVector::iterator itr = newBurst.begin(); | ||||
| 	auto *bufferItr = burst->buf; | ||||
| 	while (itr < newBurst.end()) | ||||
| @@ -281,10 +298,10 @@ void upper_trx::driveTx() | ||||
|  | ||||
| 	// 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 | ||||
| 	convert_and_scale(burst_buf, txburst->begin(), txburst->size() * 2, 1); | ||||
| #ifdef TXDEBUG2 | ||||
| 	auto check = signalVector(txburst->size(), 40); | ||||
| 	convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2); | ||||
| 	convert_and_scale(check.begin(), burst_buf, txburst->size() * 2, 1); | ||||
| 	estim_burst_params ebp; | ||||
| 	auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp); | ||||
| 	if (d) | ||||
| @@ -298,139 +315,133 @@ void upper_trx::driveTx() | ||||
| 	delete txburst; | ||||
| } | ||||
|  | ||||
| static const char *cmd2str(trxcon::trxcon_phyif_cmd_type c) | ||||
| #ifdef TXDEBUG | ||||
| static const char *cmd2str(trxcon_phyif_cmd_type c) | ||||
| { | ||||
| 	switch (c) { | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_RESET: | ||||
| 	case TRXCON_PHYIF_CMDT_RESET: | ||||
| 		return "TRXCON_PHYIF_CMDT_RESET"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_POWERON: | ||||
| 	case TRXCON_PHYIF_CMDT_POWERON: | ||||
| 		return "TRXCON_PHYIF_CMDT_POWERON"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_POWEROFF: | ||||
| 	case TRXCON_PHYIF_CMDT_POWEROFF: | ||||
| 		return "TRXCON_PHYIF_CMDT_POWEROFF"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_MEASURE: | ||||
| 	case TRXCON_PHYIF_CMDT_MEASURE: | ||||
| 		return "TRXCON_PHYIF_CMDT_MEASURE"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0: | ||||
| 	case TRXCON_PHYIF_CMDT_SETFREQ_H0: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETFREQ_H0"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1: | ||||
| 	case TRXCON_PHYIF_CMDT_SETFREQ_H1: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETFREQ_H1"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETSLOT: | ||||
| 	case TRXCON_PHYIF_CMDT_SETSLOT: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETSLOT"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETTA: | ||||
| 	case TRXCON_PHYIF_CMDT_SETTA: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETTA"; | ||||
| 	default: | ||||
| 		return "UNKNOWN COMMAND!"; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void print_cmd(trxcon::trxcon_phyif_cmd_type c) | ||||
| static void print_cmd(trxcon_phyif_cmd_type c) | ||||
| { | ||||
| 	DBGLG() << cmd2str(c) << std::endl; | ||||
| 	DBGLG() << "handling " << 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(); | ||||
| 	trxcon_phyif_rsp r; | ||||
| 	trxcon_phyif_cmd cmd; | ||||
| 	while (!cmdq_to_phy.spsc_pop(&cmd)) { | ||||
| 		cmdq_to_phy.spsc_prep_pop(); | ||||
| 		if (g_exit_flag) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	if (g_exit_flag) | ||||
| 		return false; | ||||
|  | ||||
| #ifdef TXDEBUG | ||||
| 	print_cmd(cmd.type); | ||||
| #endif | ||||
|  | ||||
| 	switch (cmd.type) { | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_RESET: | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_POWERON: | ||||
|  | ||||
| 		if (!mOn) { | ||||
| 			// start_ms(); | ||||
| 			set_upper_ready(true); | ||||
| 			mOn = true; | ||||
| 		} | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_POWEROFF: | ||||
| 		// set_upper_ready(false); | ||||
| 	case TRXCON_PHYIF_CMDT_RESET: | ||||
| 		set_ta(0); | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_MEASURE: | ||||
| 		r.type = trxcon::trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE; | ||||
| 	case TRXCON_PHYIF_CMDT_POWERON: | ||||
| 		if (!mOn) { | ||||
| 			mOn = true; | ||||
| 			start_lower_ms(); | ||||
| 		} | ||||
| 		break; | ||||
| 	case TRXCON_PHYIF_CMDT_POWEROFF: | ||||
| 		break; | ||||
| 	case TRXCON_PHYIF_CMDT_MEASURE: | ||||
| 		r.type = 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); | ||||
| 		tuneRx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100); | ||||
| 		tuneTx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100); | ||||
| 		cmdq_from_phy.spsc_push(&r); | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0: | ||||
| 		// gsm_arfcn2band_rc(uint16_t arfcn, enum gsm_band *band) | ||||
| 		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); | ||||
|  | ||||
| 	case TRXCON_PHYIF_CMDT_SETFREQ_H0: | ||||
| 		tuneRx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100); | ||||
| 		tuneTx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100); | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1: | ||||
| 	case TRXCON_PHYIF_CMDT_SETFREQ_H1: | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETSLOT: | ||||
| 	case TRXCON_PHYIF_CMDT_SETSLOT: | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETTA: | ||||
| 	case 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) | ||||
| void sighandler(int sigset) | ||||
| { | ||||
| 	if (br->burst_len == 0) // dummy/nope | ||||
| 		return 0; | ||||
| 	assert(br->burst != 0); | ||||
| 	// we might get a sigpipe in case the l1ctl ud socket disconnects because mobile quits | ||||
| 	if (sigset == SIGPIPE || sigset == SIGINT) { | ||||
| 		g_exit_flag = true; | ||||
|  | ||||
| 	trxcon::internal_q_tx_buf b; | ||||
| 	b.r = *br; | ||||
| 	memcpy(b.buf, (void *)br->burst, br->burst_len); | ||||
| 	trxcon::txq.spsc_push(&b); | ||||
| 	return 0; | ||||
| } | ||||
| 		// 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_phyif_cmd cmd = {}; | ||||
| 		internal_q_tx_buf b = {}; | ||||
| 		txq.spsc_push(&b); | ||||
| 		cmdq_to_phy.spsc_push(&cmd); | ||||
| 		msleep(200); | ||||
|  | ||||
| int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon::trxcon_phyif_cmd *cmd) | ||||
| { | ||||
| 	DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl; | ||||
| 	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); | ||||
| } | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
| 	auto tall_trxcon_ctx = talloc_init("trxcon context"); | ||||
| 	trxcon::msgb_talloc_ctx_init(tall_trxcon_ctx, 0); | ||||
| 	trxcon::trxc_log_init(tall_trxcon_ctx); | ||||
| 	signal(SIGPIPE, sighandler); | ||||
| 	signal(SIGINT, sighandler); | ||||
|  | ||||
| 	trxcon::g_trxcon = trxcon::trxcon_inst_alloc(tall_trxcon_ctx, 0, 3); | ||||
| 	trxcon::g_trxcon->gsmtap = 0; | ||||
| 	trxcon::g_trxcon->phyif = (void *)0x1234; | ||||
| 	msgb_talloc_ctx_init(tall_trxcon_ctx, 0); | ||||
| 	trxc_log_init(tall_trxcon_ctx); | ||||
|  | ||||
| 	/* 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); | ||||
|  | ||||
| 	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); | ||||
| 	log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); | ||||
|  | ||||
| 	osmo_fsm_log_timeouts(true); | ||||
|  | ||||
| 	g_trxcon = trxcon_inst_alloc(tall_trxcon_ctx, 0); | ||||
| 	g_trxcon->gsmtap = nullptr; | ||||
| 	g_trxcon->phyif = nullptr; | ||||
| 	g_trxcon->phy_quirks.fbsb_extend_fns = 866; // 4 seconds, known to work. | ||||
|  | ||||
| 	pthread_setname_np(pthread_self(), "main_trxc"); | ||||
| 	convolve_init(); | ||||
| 	convert_init(); | ||||
| 	sigProcLibSetup(); | ||||
| @@ -441,11 +452,22 @@ int main(int argc, char *argv[]) | ||||
| 	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 (status < 0) { | ||||
| 		std::cerr << "Error initializing hardware, quitting.." << std::endl; | ||||
| 		return -1; | ||||
| 	} | ||||
| 	trx->set_name_aff_sched(sched_params::thread_names::MAIN); | ||||
|  | ||||
| 	trxcon::trxc_l1ctl_init(tall_trxcon_ctx); | ||||
| 	if (!trxc_l1ctl_init(tall_trxcon_ctx)) { | ||||
| 		std::cerr << "Error initializing l1ctl, quitting.." << std::endl; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	// blocking, will return when global exit is requested | ||||
| 	trx->start_threads(); | ||||
| 	trx->main_loop(); | ||||
| 	trx->stop_threads(); | ||||
| 	trx->stop_upper_threads(); | ||||
|  | ||||
| 	return status; | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
| @@ -26,32 +24,25 @@ | ||||
| #include <arpa/inet.h> | ||||
|  | ||||
| #include "GSMCommon.h" | ||||
| #include "radioClock.h" | ||||
| #include "ms.h" | ||||
|  | ||||
| namespace trxcon | ||||
| { | ||||
| extern "C" { | ||||
| #include <osmocom/bb/trxcon/phyif.h> | ||||
| #include <osmocom/bb/trxcon/trx_if.h> | ||||
| } | ||||
| } // namespace trxcon | ||||
| class upper_trx : public ms_trx { | ||||
| 	bool mOn; | ||||
| 	volatile bool mOn; | ||||
| 	char demodded_softbits[444]; | ||||
|  | ||||
| 	// void driveControl(); | ||||
| 	bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset); | ||||
|  | ||||
| 	pthread_t thr_control, thr_tx; | ||||
|  | ||||
|     public: | ||||
| 	void start_threads(); | ||||
| 	void main_loop(); | ||||
| 	void stop_upper_threads(); | ||||
|  | ||||
| 	bool driveControl(); | ||||
| 	void driveReceiveFIFO(); | ||||
| 	void driveTx(); | ||||
|  | ||||
| 	bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) __attribute__((optnone)); | ||||
|  | ||||
| 	std::thread thr_control, thr_rx, thr_tx; | ||||
|  | ||||
|     public: | ||||
| 	void start_threads(); | ||||
| 	void start_lower_ms(); | ||||
|  | ||||
| 	upper_trx(){}; | ||||
| 	upper_trx() : mOn(false){}; | ||||
| }; | ||||
|   | ||||
| @@ -34,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]; | ||||
| @@ -322,3 +325,5 @@ static __attribute__((constructor)) void init() | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| #pragma GCC diagnostic pop | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  * (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> | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|   | ||||
							
								
								
									
										92
									
								
								Transceiver52M/ms/threadpool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Transceiver52M/ms/threadpool.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| #pragma once | ||||
| /* | ||||
|  * (C) 2023 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 <functional> | ||||
| #include <thread> | ||||
| #include <atomic> | ||||
| #include <vector> | ||||
| #include <future> | ||||
| #include <mutex> | ||||
| #include <queue> | ||||
|  | ||||
| struct single_thread_pool { | ||||
| 	std::mutex m; | ||||
| 	std::condition_variable cv; | ||||
| 	std::atomic<bool> stop_flag; | ||||
| 	std::atomic<bool> is_ready; | ||||
| 	std::deque<std::function<void()>> wq; | ||||
| 	std::thread worker_thread; | ||||
|  | ||||
| 	template <class F> | ||||
| 	void add_task(F &&f) | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> l(m); | ||||
| 		wq.emplace_back(std::forward<F>(f)); | ||||
| 		cv.notify_one(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	single_thread_pool() : stop_flag(false), is_ready(false), worker_thread(std::thread([this] { thread_loop(); })) | ||||
| 	{ | ||||
| 	} | ||||
| 	~single_thread_pool() | ||||
| 	{ | ||||
| 		stop(); | ||||
| 	} | ||||
|  | ||||
| 	std::thread::native_handle_type get_handle() | ||||
| 	{ | ||||
| 		return worker_thread.native_handle(); | ||||
| 	} | ||||
|  | ||||
|     private: | ||||
| 	void stop() | ||||
| 	{ | ||||
| 		{ | ||||
| 			std::unique_lock<std::mutex> l(m); | ||||
| 			wq.clear(); | ||||
| 			stop_flag = true; | ||||
| 			cv.notify_one(); | ||||
| 		} | ||||
| 		worker_thread.join(); | ||||
| 	} | ||||
|  | ||||
| 	void thread_loop() | ||||
| 	{ | ||||
| 		while (true) { | ||||
| 			is_ready = true; | ||||
| 			std::function<void()> f; | ||||
| 			{ | ||||
| 				std::unique_lock<std::mutex> l(m); | ||||
| 				if (wq.empty()) { | ||||
| 					cv.wait(l, [&] { return !wq.empty() || stop_flag; }); | ||||
| 				} | ||||
| 				if (stop_flag) | ||||
| 					return; | ||||
| 				is_ready = false; | ||||
| 				f = std::move(wq.front()); | ||||
| 				wq.pop_front(); | ||||
| 			} | ||||
| 			f(); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| @@ -1,5 +1,4 @@ | ||||
| #pragma once | ||||
|  | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
| @@ -66,7 +65,8 @@ struct uhd_buf_wrap { | ||||
| using dev_buf_t = uhd_buf_wrap; | ||||
| using bh_fn_t = std::function<int(dev_buf_t *)>; | ||||
|  | ||||
| template <typename T> struct uhd_hw { | ||||
| template <typename T> | ||||
| struct uhd_hw { | ||||
| 	uhd::usrp::multi_usrp::sptr dev; | ||||
| 	uhd::rx_streamer::sptr rx_stream; | ||||
| 	uhd::tx_streamer::sptr tx_stream; | ||||
| @@ -77,32 +77,38 @@ template <typename T> struct uhd_hw { | ||||
| 	const unsigned int rxFullScale, txFullScale; | ||||
| 	const int rxtxdelay; | ||||
| 	float rxgain, txgain; | ||||
| 	volatile bool stop_me_flag; | ||||
| 	static std::atomic<bool> stop_lower_threads_flag; | ||||
| 	double rxfreq_cache, txfreq_cache; | ||||
|  | ||||
| 	virtual ~uhd_hw() | ||||
| 	{ | ||||
| 		delete[] one_pkt_buf; | ||||
| 	} | ||||
| 	uhd_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67), stop_me_flag(false) | ||||
| 	uhd_hw() : rxFullScale(32767), txFullScale(32767 * 0.3), rxtxdelay(-67), rxfreq_cache(0), txfreq_cache(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	void close_device() | ||||
| 	{ | ||||
| 		stop_me_flag = true; | ||||
| 	} | ||||
|  | ||||
| 	bool tuneTx(double freq, size_t chan = 0) | ||||
| 	{ | ||||
| 		if (txfreq_cache == freq) | ||||
| 			return true; | ||||
| 		msleep(25); | ||||
| 		dev->set_tx_freq(freq, chan); | ||||
| 		txfreq_cache = freq; | ||||
| 		msleep(25); | ||||
| 		return true; | ||||
| 	}; | ||||
| 	bool tuneRx(double freq, size_t chan = 0) | ||||
| 	{ | ||||
| 		if (rxfreq_cache == freq) | ||||
| 			return true; | ||||
| 		msleep(25); | ||||
| 		dev->set_rx_freq(freq, chan); | ||||
| 		rxfreq_cache = freq; | ||||
| 		msleep(25); | ||||
| 		return true; | ||||
| 	}; | ||||
| @@ -135,6 +141,7 @@ template <typename T> struct uhd_hw { | ||||
| 	int init_device(bh_fn_t rxh, bh_fn_t txh) | ||||
| 	{ | ||||
| 		auto const lock_delay_ms = 500; | ||||
| 		auto clock_lock_attempts = 15; // x lock_delay_ms | ||||
| 		auto const mcr = 26e6; | ||||
| 		auto const rate = (1625e3 / 6) * 4; | ||||
| 		auto const ref = "external"; | ||||
| @@ -142,7 +149,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; | ||||
| @@ -158,8 +166,18 @@ template <typename T> struct uhd_hw { | ||||
| 		dev->set_tx_bandwidth(bw, channel); | ||||
|  | ||||
| 		while (!(dev->get_rx_sensor("lo_locked", channel).to_bool() && | ||||
| 			 dev->get_mboard_sensor("ref_locked").to_bool())) | ||||
| 			 dev->get_mboard_sensor("ref_locked").to_bool()) && | ||||
| 		       clock_lock_attempts > 0) { | ||||
| 			std::cerr << "clock source lock attempts remaining: " << clock_lock_attempts << ".." | ||||
| 				  << std::endl; | ||||
| 			std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms)); | ||||
| 			clock_lock_attempts--; | ||||
| 		} | ||||
|  | ||||
| 		if (clock_lock_attempts <= 0) { | ||||
| 			std::cerr << "Error locking clock, gpsdo missing? quitting.." << std::endl; | ||||
| 			return -1; | ||||
| 		} | ||||
|  | ||||
| 		uhd::stream_args_t stream_args("sc16", "sc16"); | ||||
| 		rx_stream = dev->get_rx_stream(stream_args); | ||||
| @@ -174,9 +192,14 @@ template <typename T> struct uhd_hw { | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	void actually_enable_streams() | ||||
| 	{ | ||||
| 		// nop: stream cmd in handler | ||||
| 	} | ||||
|  | ||||
| 	void *rx_cb(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		void *ret; | ||||
| 		void *ret = nullptr; | ||||
| 		static int to_skip = 0; | ||||
|  | ||||
| 		uhd::rx_metadata_t md; | ||||
| @@ -216,7 +239,7 @@ template <typename T> struct uhd_hw { | ||||
| 			stream_cmd.time_spec = uhd::time_spec_t(); | ||||
| 			rx_stream->issue_stream_cmd(stream_cmd); | ||||
|  | ||||
| 			while (!stop_me_flag) { | ||||
| 			while (!stop_lower_threads_flag) { | ||||
| 				rx_cb(burst_handler); | ||||
| 			} | ||||
| 		}; | ||||
|   | ||||
| @@ -77,6 +77,24 @@ static struct ctrl_handle *g_ctrlh; | ||||
| static RadioDevice *usrp; | ||||
| static RadioInterface *radio; | ||||
|  | ||||
| /* adjusts read timestamp offset to make the viterbi equalizer happy by including the start tail bits */ | ||||
| template <typename B> | ||||
| class rif_va_wrapper : public B { | ||||
| 	bool use_va; | ||||
|  | ||||
|     public: | ||||
| 	template <typename... Args> | ||||
| 	rif_va_wrapper(bool use_va, Args &&...args) : B(std::forward<Args>(args)...), use_va(use_va) | ||||
| 	{ | ||||
| 	} | ||||
| 	bool start() override | ||||
| 	{ | ||||
| 		auto rv = B::start(); | ||||
| 		B::readTimestamp -= use_va ? 20 : 0; | ||||
| 		return rv; | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| /* Create radio interface | ||||
|  *     The interface consists of sample rate changes, frequency shifts, | ||||
|  *     channel multiplexing, and other conversions. The transceiver core | ||||
| @@ -91,17 +109,17 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx, | ||||
|  | ||||
| 	switch (type) { | ||||
| 	case RadioDevice::NORMAL: | ||||
| 		radio = new RadioInterface(usrp, trx->cfg.tx_sps, | ||||
| 					   trx->cfg.rx_sps, trx->cfg.num_chans); | ||||
| 		radio = new rif_va_wrapper<RadioInterface>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, trx->cfg.rx_sps, | ||||
| 							   trx->cfg.num_chans); | ||||
| 		break; | ||||
| 	case RadioDevice::RESAMP_64M: | ||||
| 	case RadioDevice::RESAMP_100M: | ||||
| 		radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps, | ||||
| 						 trx->cfg.rx_sps); | ||||
| 		radio = new rif_va_wrapper<RadioInterfaceResamp>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, | ||||
| 								 trx->cfg.rx_sps); | ||||
| 		break; | ||||
| 	case RadioDevice::MULTI_ARFCN: | ||||
| 		radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps, | ||||
| 						trx->cfg.rx_sps, trx->cfg.num_chans); | ||||
| 		radio = new rif_va_wrapper<RadioInterfaceMulti>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, trx->cfg.rx_sps, | ||||
| 								trx->cfg.num_chans); | ||||
| 		break; | ||||
| 	default: | ||||
| 		LOG(ALERT) << "Unsupported radio interface configuration"; | ||||
| @@ -465,6 +483,12 @@ int trx_validate_config(struct trx_ctx *trx) | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	if (trx->cfg.use_va && | ||||
| 	    (trx->cfg.egprs || trx->cfg.multi_arfcn || trx->cfg.tx_sps != 4 || trx->cfg.rx_sps != 4)) { | ||||
| 		LOG(ERROR) << "Viterbi equalizer only works for gmsk with 4 tx/rx samples per symbol!"; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| @@ -571,24 +595,14 @@ static void trx_stop() | ||||
| static int trx_start(struct trx_ctx *trx) | ||||
| { | ||||
| 	int type, chans; | ||||
| 	unsigned int i; | ||||
| 	std::vector<std::string> rx_paths, tx_paths; | ||||
| 	RadioDevice::InterfaceType iface = RadioDevice::NORMAL; | ||||
|  | ||||
| 	/* Create the low level device object */ | ||||
| 	if (trx->cfg.multi_arfcn) | ||||
| 		iface = RadioDevice::MULTI_ARFCN; | ||||
|  | ||||
| 	/* Generate vector of rx/tx_path: */ | ||||
| 	for (i = 0; i < trx->cfg.num_chans; i++) { | ||||
| 		rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path)); | ||||
| 		tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path)); | ||||
| 	} | ||||
|  | ||||
| 	usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface, | ||||
| 				 trx->cfg.num_chans, trx->cfg.offset, | ||||
| 				 tx_paths, rx_paths); | ||||
| 	type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels); | ||||
| 	usrp = RadioDevice::make(iface, &trx->cfg); | ||||
| 	type = usrp->open(); | ||||
| 	if (type < 0) { | ||||
| 		LOG(ALERT) << "Failed to create radio device" << std::endl; | ||||
| 		goto shutdown; | ||||
| @@ -651,11 +665,11 @@ int main(int argc, char *argv[]) | ||||
| 		exit(2); | ||||
| 	} | ||||
|  | ||||
| 	rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX); | ||||
| 	rc = telnet_init_default(tall_trx_ctx, NULL, OSMO_VTY_PORT_TRX); | ||||
| 	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); | ||||
|   | ||||
| @@ -48,9 +48,8 @@ struct trxd_hdr_common { | ||||
| 		reserved:1, | ||||
| 		version:4; | ||||
| #elif OSMO_IS_BIG_ENDIAN | ||||
| 	uint8_t version:4, | ||||
| 		reserved:1, | ||||
| 		tn:3; | ||||
| /* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ | ||||
| 	uint8_t version:4, reserved:1, tn:3; | ||||
| #endif | ||||
| 	uint32_t fn; /* big endian */ | ||||
| } __attribute__ ((packed)); | ||||
| @@ -86,9 +85,8 @@ struct trxd_hdr_v1_specific { | ||||
| 		modulation:4, | ||||
| 		idle:1; | ||||
| #elif OSMO_IS_BIG_ENDIAN | ||||
| 	uint8_t idle:1, | ||||
| 		modulation:4, | ||||
| 		tsc:3; | ||||
| /* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ | ||||
| 	uint8_t idle:1, modulation:4, tsc:3; | ||||
| #endif | ||||
| 	int16_t ci;  /* big endian, in centiBels */ | ||||
| } __attribute__ ((packed)); | ||||
|   | ||||
| @@ -28,7 +28,7 @@ | ||||
|  | ||||
| RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen, | ||||
| 			 size_t hLen, bool outDirection) | ||||
| 	: writeIndex(0), readIndex(0), availSamples(0) | ||||
| 	: writeIndex(0), readIndex(0), availSamples(0), segments(numSegments) | ||||
| { | ||||
| 	if (!outDirection) | ||||
| 		hLen = 0; | ||||
| @@ -36,7 +36,6 @@ RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen, | ||||
| 	buffer = new float[2 * (hLen + numSegments * segmentLen)]; | ||||
| 	bufferLen = numSegments * segmentLen; | ||||
|  | ||||
| 	segments.resize(numSegments); | ||||
|  | ||||
| 	for (size_t i = 0; i < numSegments; i++) | ||||
| 		segments[i] = &buffer[2 * (hLen + i * segmentLen)]; | ||||
|   | ||||
| @@ -39,9 +39,10 @@ extern "C" { | ||||
| RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps, | ||||
|                                size_t rx_sps, size_t chans, | ||||
|                                int wReceiveOffset, GSM::Time wStartTime) | ||||
|   : mDevice(wDevice), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), | ||||
|     underrun(false), overrun(false), writeTimestamp(0), readTimestamp(0), | ||||
|     receiveOffset(wReceiveOffset), mOn(false) | ||||
|   : mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mReceiveFIFO(mChans), mDevice(wDevice), | ||||
|     sendBuffer(mChans), recvBuffer(mChans), convertRecvBuffer(mChans), | ||||
|     convertSendBuffer(mChans), powerScaling(mChans), underrun(false), overrun(false), | ||||
|     writeTimestamp(0), readTimestamp(0), receiveOffset(wReceiveOffset), mOn(false) | ||||
| { | ||||
|   mClock.set(wStartTime); | ||||
| } | ||||
| @@ -58,15 +59,6 @@ bool RadioInterface::init(int type) | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   close(); | ||||
|  | ||||
|   sendBuffer.resize(mChans); | ||||
|   recvBuffer.resize(mChans); | ||||
|   convertSendBuffer.resize(mChans); | ||||
|   convertRecvBuffer.resize(mChans); | ||||
|   mReceiveFIFO.resize(mChans); | ||||
|   powerScaling.resize(mChans); | ||||
|  | ||||
|   for (size_t i = 0; i < mChans; i++) { | ||||
|     sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true); | ||||
|     recvBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSRx, 0, false); | ||||
|   | ||||
| @@ -31,6 +31,9 @@ static const unsigned gSlotLen = 148;      ///< number of symbols per slot, not | ||||
| class RadioInterface { | ||||
|  | ||||
| protected: | ||||
|   size_t mSPSTx; | ||||
|   size_t mSPSRx; | ||||
|   size_t mChans; | ||||
|  | ||||
|   Thread mAlignRadioServiceLoopThread;	      ///< thread that synchronizes transmit and receive sections | ||||
|  | ||||
| @@ -38,10 +41,6 @@ protected: | ||||
|  | ||||
|   RadioDevice *mDevice;			      ///< the USRP object | ||||
|  | ||||
|   size_t mSPSTx; | ||||
|   size_t mSPSRx; | ||||
|   size_t mChans; | ||||
|  | ||||
|   std::vector<RadioBuffer *> sendBuffer; | ||||
|   std::vector<RadioBuffer *> recvBuffer; | ||||
|  | ||||
| @@ -76,7 +75,7 @@ private: | ||||
| public: | ||||
|  | ||||
|   /** start the interface */ | ||||
|   bool start(); | ||||
|   virtual bool start(); | ||||
|   bool stop(); | ||||
|  | ||||
|   /** initialization */ | ||||
| @@ -152,7 +151,7 @@ private: | ||||
|  | ||||
| public: | ||||
|   RadioInterfaceResamp(RadioDevice* wDevice, size_t tx_sps, size_t rx_sps); | ||||
|   ~RadioInterfaceResamp(); | ||||
|   virtual ~RadioInterfaceResamp(); | ||||
|  | ||||
|   bool init(int type); | ||||
|   void close(); | ||||
| @@ -185,7 +184,7 @@ private: | ||||
| public: | ||||
|   RadioInterfaceMulti(RadioDevice* radio, size_t tx_sps, | ||||
|                       size_t rx_sps, size_t chans = 1); | ||||
|   ~RadioInterfaceMulti(); | ||||
|   virtual ~RadioInterfaceMulti(); | ||||
|  | ||||
|   bool init(int type); | ||||
|   void close(); | ||||
|   | ||||
| @@ -44,8 +44,9 @@ extern "C" { | ||||
| RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *radio, size_t tx_sps, | ||||
| 					 size_t rx_sps, size_t chans) | ||||
| 	: RadioInterface(radio, tx_sps, rx_sps, chans), | ||||
| 	  outerSendBuffer(NULL), outerRecvBuffer(NULL), | ||||
| 	  dnsampler(NULL), upsampler(NULL), channelizer(NULL), synthesis(NULL) | ||||
| 	  outerSendBuffer(NULL), outerRecvBuffer(NULL), history(mChans), active(MCHANS, false), | ||||
| 	  rx_freq_state(mChans), tx_freq_state(mChans), dnsampler(NULL), upsampler(NULL), channelizer(NULL), | ||||
| 	  synthesis(NULL) | ||||
| { | ||||
| } | ||||
|  | ||||
| @@ -74,12 +75,12 @@ void RadioInterfaceMulti::close() | ||||
| 	for (std::vector<signalVector*>::iterator it = history.begin(); it != history.end(); ++it) | ||||
| 		delete *it; | ||||
|  | ||||
| 	mReceiveFIFO.resize(0); | ||||
| 	powerScaling.resize(0); | ||||
| 	history.resize(0); | ||||
| 	active.resize(0); | ||||
| 	rx_freq_state.resize(0); | ||||
| 	tx_freq_state.resize(0); | ||||
| 	mReceiveFIFO.clear(); | ||||
| 	powerScaling.clear(); | ||||
| 	history.clear(); | ||||
| 	active.clear(); | ||||
| 	rx_freq_state.clear(); | ||||
| 	tx_freq_state.clear(); | ||||
|  | ||||
| 	RadioInterface::close(); | ||||
| } | ||||
| @@ -152,20 +153,9 @@ bool RadioInterfaceMulti::init(int type) | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	close(); | ||||
|  | ||||
| 	sendBuffer.resize(mChans); | ||||
| 	recvBuffer.resize(mChans); | ||||
| 	convertSendBuffer.resize(1); | ||||
| 	convertRecvBuffer.resize(1); | ||||
|  | ||||
| 	mReceiveFIFO.resize(mChans); | ||||
| 	powerScaling.resize(mChans); | ||||
| 	history.resize(mChans); | ||||
| 	rx_freq_state.resize(mChans); | ||||
| 	tx_freq_state.resize(mChans); | ||||
| 	active.resize(MCHANS, false); | ||||
|  | ||||
| 	/* 4 == sps */ | ||||
| 	inchunk = RESAMP_INRATE * 4; | ||||
| 	outchunk = RESAMP_OUTRATE * 4; | ||||
|   | ||||
| @@ -98,15 +98,6 @@ bool RadioInterfaceResamp::init(int type) | ||||
| { | ||||
| 	float cutoff = 1.0f; | ||||
|  | ||||
| 	close(); | ||||
|  | ||||
| 	sendBuffer.resize(1); | ||||
| 	recvBuffer.resize(1); | ||||
| 	convertSendBuffer.resize(1); | ||||
| 	convertRecvBuffer.resize(1); | ||||
| 	mReceiveFIFO.resize(1); | ||||
| 	powerScaling.resize(1); | ||||
|  | ||||
| 	switch (type) { | ||||
| 	case RadioDevice::RESAMP_64M: | ||||
| 		resamp_inrate = RESAMP_64M_INRATE; | ||||
|   | ||||
							
								
								
									
										41
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								configure.ac
									
									
									
									
									
								
							| @@ -82,10 +82,10 @@ AC_TYPE_SIZE_T | ||||
| AC_HEADER_TIME | ||||
| AC_C_BIGENDIAN | ||||
|  | ||||
| PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.6.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.6.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.6.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 1.6.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.9.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.9.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.9.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 1.9.0) | ||||
|  | ||||
| AC_ARG_ENABLE(sanitize, | ||||
| 	[AS_HELP_STRING( | ||||
| @@ -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]) | ||||
| @@ -204,6 +209,25 @@ 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]) | ||||
|     LIBTRXCON_DIR="osmocom-bb/src/host/trxcon" | ||||
|     if ! test -d "$srcdir/$LIBTRXCON_DIR"; then | ||||
|         AC_MSG_RESULT([no]) | ||||
|         AC_MSG_ERROR([$LIBTRXCON_DIR does not exist]) | ||||
|     fi | ||||
|     AC_SUBST(LIBTRXCON_DIR) | ||||
|     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) | ||||
| ]) | ||||
| @@ -260,6 +284,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) | ||||
| @@ -327,13 +352,6 @@ AC_MSG_RESULT([CFLAGS="$CFLAGS"]) | ||||
| AC_MSG_RESULT([CXXFLAGS="$CXXFLAGS"]) | ||||
| AC_MSG_RESULT([LDFLAGS="$LDFLAGS"]) | ||||
|  | ||||
| AM_CONDITIONAL(ENABLE_MS_TRX, [test -d osmocom-bb]) | ||||
|  | ||||
| if ENABLE_MS_TRX; then | ||||
| 	AC_MSG_NOTICE(["Enabling ms-trx..."]) | ||||
|     AC_CONFIG_SUBDIRS([osmocom-bb/src/host/trxcon]) | ||||
| fi | ||||
|  | ||||
|  | ||||
| dnl Output files | ||||
| AC_CONFIG_FILES([\ | ||||
| @@ -351,7 +369,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 \ | ||||
|   | ||||
| @@ -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,6 +101,7 @@ echo | ||||
| set -x | ||||
|  | ||||
| cd "$base" | ||||
| git submodule status | ||||
| autoreconf --install --force | ||||
| ./configure $CONFIG | ||||
| $MAKE $PARALLEL_MAKE | ||||
|   | ||||
| @@ -34,10 +34,10 @@ BuildRequires:  pkgconfig(LimeSuite) | ||||
| BuildRequires:  pkgconfig(usrp) >= 3.3 | ||||
| %endif | ||||
| BuildRequires:  pkgconfig(fftw3f) | ||||
| BuildRequires:  pkgconfig(libosmocoding) >= 1.6.0 | ||||
| BuildRequires:  pkgconfig(libosmocore) >= 1.6.0 | ||||
| BuildRequires:  pkgconfig(libosmoctrl) >= 1.6.0 | ||||
| BuildRequires:  pkgconfig(libosmovty) >= 1.6.0 | ||||
| BuildRequires:  pkgconfig(libosmocoding) >= 1.9.0 | ||||
| BuildRequires:  pkgconfig(libosmocore) >= 1.9.0 | ||||
| BuildRequires:  pkgconfig(libosmoctrl) >= 1.9.0 | ||||
| BuildRequires:  pkgconfig(libosmovty) >= 1.9.0 | ||||
| BuildRequires:  pkgconfig(libusb-1.0) | ||||
| BuildRequires:  pkgconfig(uhd) | ||||
| %{?systemd_requires} | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| [Unit] | ||||
| Description=Osmocom SDR BTS L1 Transceiver (IPC Backend) | ||||
| After=network-online.target | ||||
| Wants=network-online.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| [Unit] | ||||
| Description=Osmocom SDR BTS L1 Transceiver (LimeSuite backend) | ||||
| After=network-online.target | ||||
| Wants=network-online.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| [Unit] | ||||
| Description=Osmocom SDR BTS L1 Transceiver (UHD Backend) | ||||
| After=network-online.target | ||||
| Wants=network-online.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| [Unit] | ||||
| Description=Osmocom SDR BTS L1 Transceiver (libusrp backend) | ||||
| After=network-online.target | ||||
| Wants=network-online.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
|   | ||||
							
								
								
									
										109
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										109
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,112 @@ | ||||
| osmo-trx (1.6.0) unstable; urgency=medium | ||||
|  | ||||
|   [ Vadim Yanitskiy ] | ||||
|   * configure.ac: check if LIBTRXCON_DIR (submodule) exists | ||||
|   * tests: Makefile.am: move -I flags from AM_CFLAGS to AM_CPPFLAGS | ||||
|   * tests: there shall be no libraries in LDFLAGS | ||||
|   * tests: use -no-install libtool flag to avoid ./lt-* scripts | ||||
|   * tests: LMSDeviceTest: fix CPPFLAGS vs CXXFLAGS | ||||
|   * ipc-driver-test: clean up variables in Makefile.am | ||||
|   * CommonLibs: remove unused *trx in cfg[_no]_ctr_error_threshold_cmd | ||||
|   * CommonLibs: clean up and fix Makefile.am | ||||
|   * ms: logging: print category, level, and extended timestamp | ||||
|  | ||||
|   [ Oliver Smith ] | ||||
|   * Run struct_endianness.py | ||||
|   * debian: set compat level to 10 | ||||
|   * systemd: depend on networking-online.target | ||||
|   * USRPDevice:updateAlignment: remove byteswap code | ||||
|  | ||||
|   [ Eric ] | ||||
|   * .clang-format: adjust template formatting | ||||
|   * ms: update submodule to currently known working version | ||||
|   * ms: adjust tx scaling for tx samples | ||||
|   * ms : fix the template formatting | ||||
|   * ms: fix the gain init for blade | ||||
|   * ms: prettify scheduling + add odroid | ||||
|   * ms: fix startup & shutdown of blade | ||||
|   * ms: block burst q to upper layer | ||||
|   * ms: use single thread pool | ||||
|   * ms : rename var | ||||
|   * ms : rename var | ||||
|   * ms: cache frequency | ||||
|   * ms: pretty tx buf class | ||||
|   * ms: rearrange internal trxcon<->phy if | ||||
|   * ms: remove syncthing tool | ||||
|   * ms: prune common sch acq code | ||||
|   * ms: rearrange code to allow clean exits | ||||
|   * ms: flexible template for value_type buffer sum | ||||
|   * ms: make init call less confusing | ||||
|   * transceiver: pass cfg struct instead of args | ||||
|   * devices: unify band handling | ||||
|   * ms: fix blocking logging | ||||
|   * ms: drop the tx burst padding | ||||
|   * trx: fix dev-args issue | ||||
|   * ms: update osmocom-bb | ||||
|   * ms: restructure the va code to add rach support | ||||
|   * transceiver: add experimental viterbi equalizer support | ||||
|   * ms/va: make ancient gcc < 8 happy | ||||
|   * ms: fix thread prio startup issue | ||||
|   * ms: fix a few coverity complaints related to initialization | ||||
|   * ms: bump osmocom-bb submodule to current head | ||||
|  | ||||
|   [ Eric Wild ] | ||||
|   * ms: adjust float<->integral type conversion | ||||
|   * ms: sch: drop intermediate softvector | ||||
|   * devices: add freq/gain override for uhd | ||||
|  | ||||
|   [ arehbein ] | ||||
|   * Transition to use of 'telnet_init_default' | ||||
|  | ||||
|   [ Pau Espin Pedrol ] | ||||
|   * Call osmo_fd_unregister() before closing and changing bfd->fd | ||||
|   * ms: update osmocom-bb submodule | ||||
|  | ||||
|  -- Pau Espin Pedrol <pespin@sysmocom.de>  Tue, 12 Sep 2023 15:56:57 +0200 | ||||
|  | ||||
| osmo-trx (1.5.0) unstable; urgency=medium | ||||
|  | ||||
|   [ Oliver Smith ] | ||||
|   * configure.ac: add -lboost_thread for uhd < 4.2.0 | ||||
|   * gitignore: add uhddev_ipc.cpp | ||||
|   * contrib/jenkins: don't run "make distcheck" on arm | ||||
|  | ||||
|   [ Vadim Yanitskiy ] | ||||
|   * threshold_timer_update_intv(): call osmo_timer_del() unconditionally | ||||
|   * Transceiver::expectedCorrType(): RACH is always 8-bit on PTCCH/U | ||||
|   * contrib/jenkins.sh: dump submodule status before building | ||||
|   * configure.ac: fix: properly check whether to enable ms-trx | ||||
|   * configure.ac: allow building without cloning submodules | ||||
|   * configure.ac: cosmetic: rearrange MS TRX related logic | ||||
|   * configure.ac: make use of AC_MSG_CHECKING and AC_MSG_RESULT | ||||
|  | ||||
|   [ Max ] | ||||
|   * Set working directory in systemd service file | ||||
|   * Add realtime scheduling and set priority in service file | ||||
|   * ctrl: take both address and port from vty config | ||||
|  | ||||
|   [ Eric ] | ||||
|   * ignore vscode dirs | ||||
|   * rename noisevector class -> avgvector | ||||
|   * osmocom-bb for ms-trx side trxcon integration | ||||
|   * add checkpatch config | ||||
|   * bladerf xa4 support | ||||
|   * update osmocom-bb submodule to fix make distcheck | ||||
|   * vita demod by piotr krysik, modified | ||||
|   * properly update osmocom-bb submodule, for real this time.. | ||||
|   * ms-trx support | ||||
|   * clean up mutex, scopedlock, and signal classes | ||||
|   * ipc: add missing override | ||||
|   * clang-format: proper c++ standard | ||||
|   * ipc: remove old autotools workaround | ||||
|   * ms: init trash used to escape the usb callbacks | ||||
|   * radio interface: fix init | ||||
|  | ||||
|   [ Eric Wild ] | ||||
|   * mstrx: do not wait forever if clock locking fails | ||||
|  | ||||
|  -- Pau Espin Pedrol <pespin@sysmocom.de>  Tue, 07 Feb 2023 17:08:17 +0100 | ||||
|  | ||||
| osmo-trx (1.4.1) unstable; urgency=medium | ||||
|  | ||||
|   [ Oliver Smith ] | ||||
|   | ||||
							
								
								
									
										2
									
								
								debian/compat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								debian/compat
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1 @@ | ||||
| 9 | ||||
| 10 | ||||
|   | ||||
							
								
								
									
										6
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ Source: osmo-trx | ||||
| Section: net | ||||
| Priority: optional | ||||
| Maintainer: Osmocom team <openbsc@lists.osmocom.org> | ||||
| Build-Depends: debhelper (>= 9), | ||||
| Build-Depends: debhelper (>= 10), | ||||
|                autotools-dev, | ||||
|                autoconf-archive, | ||||
|                pkg-config, | ||||
| @@ -14,8 +14,8 @@ Build-Depends: debhelper (>= 9), | ||||
|                libtalloc-dev, | ||||
|                libusrp-dev, | ||||
|                liblimesuite-dev, | ||||
|                libosmocore-dev (>= 1.6.0), | ||||
|                osmo-gsm-manuals-dev | ||||
|                libosmocore-dev (>= 1.9.0), | ||||
|                osmo-gsm-manuals-dev (>= 1.5.0) | ||||
| Standards-Version: 3.9.6 | ||||
| Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx | ||||
| Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx | ||||
|   | ||||
 Submodule osmocom-bb updated: a4aac5c355...05ddc05233
									
								
							| @@ -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; | ||||
|   | ||||
| @@ -1,7 +1,25 @@ | ||||
| include $(top_srcdir)/Makefile.common | ||||
|  | ||||
| AM_CPPFLAGS = -Wall -I$(top_srcdir)/CommonLibs $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -g | ||||
| AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS) | ||||
| AM_CPPFLAGS = \ | ||||
| 	-I$(top_srcdir)/CommonLibs \ | ||||
| 	$(STD_DEFINES_AND_INCLUDES) \ | ||||
| 	$(NULL) | ||||
|  | ||||
| AM_CXXFLAGS = \ | ||||
| 	-Wall -g \ | ||||
| 	$(LIBOSMOCORE_CFLAGS) \ | ||||
| 	$(LIBOSMOCTRL_CFLAGS) \ | ||||
| 	$(LIBOSMOVTY_CFLAGS) \ | ||||
| 	$(NULL) | ||||
|  | ||||
| AM_LDFLAGS = -no-install | ||||
|  | ||||
| LDADD = \ | ||||
| 	$(COMMON_LA) \ | ||||
| 	$(LIBOSMOCORE_LIBS) \ | ||||
| 	$(LIBOSMOCTRL_LIBS) \ | ||||
| 	$(LIBOSMOVTY_LIBS) \ | ||||
| 	$(NULL) | ||||
|  | ||||
| EXTRA_DIST = BitVectorTest.ok \ | ||||
|              PRBSTest.ok \ | ||||
| @@ -20,21 +38,16 @@ check_PROGRAMS = \ | ||||
| 	LogTest | ||||
|  | ||||
| BitVectorTest_SOURCES = BitVectorTest.cpp | ||||
| BitVectorTest_LDADD = $(COMMON_LA) | ||||
|  | ||||
| PRBSTest_SOURCES = PRBSTest.cpp | ||||
|  | ||||
| InterthreadTest_SOURCES = InterthreadTest.cpp | ||||
| InterthreadTest_LDADD = $(COMMON_LA) | ||||
| InterthreadTest_LDFLAGS = -lpthread $(AM_LDFLAGS) | ||||
| InterthreadTest_LDADD = $(LDADD) -lpthread | ||||
|  | ||||
| TimevalTest_SOURCES = TimevalTest.cpp | ||||
| TimevalTest_LDADD = $(COMMON_LA) | ||||
|  | ||||
| VectorTest_SOURCES = VectorTest.cpp | ||||
| VectorTest_LDADD = $(COMMON_LA) | ||||
|  | ||||
| LogTest_SOURCES = LogTest.cpp | ||||
| LogTest_LDADD = $(COMMON_LA) | ||||
|  | ||||
| MOSTLYCLEANFILES += testSource testDestination | ||||
|   | ||||
| @@ -1,6 +1,13 @@ | ||||
| include $(top_srcdir)/Makefile.common | ||||
|  | ||||
| AM_CFLAGS = -Wall -I$(top_srcdir)/Transceiver52M -I$(top_srcdir)/Transceiver52M/arch/common $(STD_DEFINES_AND_INCLUDES) -g | ||||
| AM_CPPFLAGS = \ | ||||
| 	-I$(top_srcdir)/Transceiver52M \ | ||||
| 	-I$(top_srcdir)/Transceiver52M/arch/common \ | ||||
| 	$(STD_DEFINES_AND_INCLUDES) \ | ||||
| 	$(NULL) | ||||
|  | ||||
| AM_CFLAGS = -Wall -g | ||||
| AM_LDFLAGS = -no-install | ||||
|  | ||||
| EXTRA_DIST = convolve_test.ok convolve_test_golden.h | ||||
|  | ||||
| @@ -20,10 +27,11 @@ endif | ||||
| if DEVICE_LMS | ||||
| check_PROGRAMS += LMSDeviceTest | ||||
| LMSDeviceTest_SOURCES = LMSDeviceTest.cpp | ||||
| LMSDeviceTest_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LMS_LIBS) | ||||
| LMSDeviceTest_LDADD = \ | ||||
| 	$(top_builddir)/Transceiver52M/device/lms/libdevice.la \ | ||||
| 	$(LIBOSMOCORE_LIBS) \ | ||||
| 	$(COMMON_LA) \ | ||||
| 	$(LMS_LIBS) | ||||
| LMSDeviceTest_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) | ||||
| 	$(LMS_LIBS) \ | ||||
| 	$(NULL) | ||||
| LMSDeviceTest_CXXFLAGS = $(AM_CFLAGS) $(LMS_CFLAGS) | ||||
| endif | ||||
|   | ||||
		Reference in New Issue
	
	Block a user