mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
				synced 2025-10-31 12:13:34 +00:00 
			
		
		
		
	Compare commits
	
		
			47 Commits
		
	
	
		
			1.3.1
			...
			mstx_newtr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a0e1ed3215 | ||
|  | a5439daaf7 | ||
|  | 42cc715fac | ||
|  | 008418327b | ||
|  | 040497e0a4 | ||
|  | 8984d7f2ca | ||
|  | aa7a40ee84 | ||
|  | f2f35fc592 | ||
|  | 00ddcfaf50 | ||
|  | 2f20c564bf | ||
|  | 19faae85c6 | ||
|  | 1c6a3459cd | ||
|  | 32311d8635 | ||
|  | 71c46e91df | ||
|  | a39fa875a3 | ||
|  | 9a3e3fceb8 | ||
|  | 1a19caf002 | ||
|  | 424c74d006 | ||
|  | a7143d3cd0 | ||
|  | 019d698126 | ||
|  | a686277c72 | ||
|  | 683f140739 | ||
|  | 5e40d92400 | ||
|  | bb2cb9d54b | ||
|  | b9423b25b6 | ||
|  | 069f5cd857 | ||
|  | c90b207803 | ||
|  | 985694175d | ||
|  | ac8a4e7297 | ||
|  | e16d0e1330 | ||
|  | e6fdf8fcad | ||
|  | cdd77a447d | ||
|  | 7f696801ae | ||
|  | d16eb314ed | ||
|  | 27bd2f6dd1 | ||
|  | 8803f923f9 | ||
|  | ecea734b97 | ||
|  | 0c34c64a16 | ||
|  | 0a038223d0 | ||
|  | 5e6b10cd9e | ||
|  | b6f238c0f2 | ||
|  | c27fe60a25 | ||
|  | a1ea63f777 | ||
|  | c7930b0b22 | ||
|  | 17ce7740e5 | ||
|  | d06259f348 | ||
|  | 6c646c35b9 | 
							
								
								
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,6 +6,15 @@ Transceiver52M/osmo-trx-uhd | ||||
| Transceiver52M/osmo-trx-usrp1 | ||||
| Transceiver52M/osmo-trx-lms | ||||
| Transceiver52M/osmo-trx-ipc | ||||
| Transceiver52M/osmo-trx-blade | ||||
| Transceiver52M/osmo-trx-ipc2 | ||||
| Transceiver52M/osmo-trx-syncthing-blade | ||||
| Transceiver52M/osmo-trx-syncthing-uhd | ||||
| Transceiver52M/osmo-trx-syncthing-ipc | ||||
| Transceiver52M/osmo-trx-ms-blade | ||||
| Transceiver52M/osmo-trx-ms-uhd | ||||
| Transceiver52M/osmo-trx-ms-ipc | ||||
| Transceiver52M/device/ipc/uhddev_ipc.cpp | ||||
|  | ||||
| .clang-format | ||||
|  | ||||
| @@ -67,7 +76,6 @@ doc/manuals/generated/ | ||||
| doc/manuals/vty/osmotrx-*-vty-reference.xml | ||||
| doc/manuals/vty/osmotrx-*-vty-reference.xml.inc.gen | ||||
| doc/manuals/vty/osmotrx-*-vty-reference.xml.inc.merged | ||||
| doc/manuals/osmomsc-usermanual.xml | ||||
| doc/manuals/common | ||||
| doc/manuals/build | ||||
|  | ||||
| @@ -75,3 +83,6 @@ contrib/osmo-trx.spec | ||||
| !contrib/osmo-trx.spec.in | ||||
|  | ||||
| utils/osmo-prbs-tool | ||||
| /.qtc_clangd/* | ||||
| /.cache/* | ||||
| /.vscode/* | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| [submodule "osmocom-bb"] | ||||
| 	path = osmocom-bb | ||||
| 	url = https://gitea.osmocom.org/phone-side/osmocom-bb.git | ||||
| 	branch = a4aac5c3554559c2c994609f90b92a9daf6e8a89 | ||||
| @@ -50,19 +50,19 @@ extern "C" { | ||||
| #endif | ||||
|  | ||||
| #define LOG(level) \ | ||||
| 	Log(DMAIN, LOGL_##level, __BASE_FILE__, __LINE__).get() <<  "[tid=" << pthread_self() << "] " | ||||
| 	Log(DMAIN, LOGL_##level, __BASE_FILE__, __LINE__).get() | ||||
|  | ||||
| #define LOGC(category, level) \ | ||||
| 	Log(category, LOGL_##level, __BASE_FILE__, __LINE__).get() <<  "[tid=" << pthread_self() << "] " | ||||
| 	Log(category, LOGL_##level, __BASE_FILE__, __LINE__).get() | ||||
|  | ||||
| #define LOGLV(category, level) \ | ||||
| 	Log(category, level, __BASE_FILE__, __LINE__).get() <<  "[tid=" << pthread_self() << "] " | ||||
| 	Log(category, level, __BASE_FILE__, __LINE__).get() | ||||
|  | ||||
| #define LOGSRC(category, level, file, line) \ | ||||
| 	Log(category, level, file, line).get() <<  "[tid=" << pthread_self() << "] " | ||||
| 	Log(category, level, file, line).get() | ||||
|  | ||||
| #define LOGCHAN(chan, category, level) \ | ||||
| 	Log(category, LOGL_##level, __BASE_FILE__, __LINE__).get() <<  "[tid=" << pthread_self() << "][chan=" << chan << "] " | ||||
| 	Log(category, LOGL_##level, __BASE_FILE__, __LINE__).get() <<  "[chan=" << chan << "] " | ||||
|  | ||||
| /** | ||||
| 	A C++ stream-based thread-safe logger. | ||||
|   | ||||
| @@ -12,10 +12,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #ifndef PRBS_H | ||||
|   | ||||
| @@ -32,7 +32,9 @@ | ||||
| #include "Timeval.h" | ||||
| #include "Logger.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/core/thread.h> | ||||
| } | ||||
|  | ||||
| using namespace std; | ||||
|  | ||||
| @@ -47,7 +49,7 @@ void lockCout() | ||||
| { | ||||
| 	gStreamLock.lock(); | ||||
| 	Timeval entryTime; | ||||
| 	cout << entryTime << " " << pthread_self() << ": "; | ||||
| 	cout << entryTime << " " << osmo_gettid() << ": "; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -62,7 +64,7 @@ void lockCerr() | ||||
| { | ||||
| 	gStreamLock.lock(); | ||||
| 	Timeval entryTime; | ||||
| 	cerr << entryTime << " " << pthread_self() << ": "; | ||||
| 	cerr << entryTime << " " << osmo_gettid() << ": "; | ||||
| } | ||||
|  | ||||
| void unlockCerr() | ||||
|   | ||||
| @@ -12,10 +12,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <vector> | ||||
|   | ||||
| @@ -12,10 +12,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|   | ||||
| @@ -48,6 +48,7 @@ struct trx_cfg { | ||||
| 	double offset; | ||||
| 	double freq_offset_khz; | ||||
| 	double rssi_offset; | ||||
| 	int ul_fn_offset; | ||||
| 	bool force_rssi_offset; /* Force value set in VTY? */ | ||||
| 	bool swap_channels; | ||||
| 	bool ext_rach; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| #include <stdbool.h> | ||||
|  | ||||
| #include <osmocom/core/logging.h> | ||||
| #include <osmocom/core/thread.h> | ||||
|  | ||||
| extern const struct log_info log_info; | ||||
|  | ||||
| @@ -19,10 +18,6 @@ enum { | ||||
| 	DCTR, | ||||
| }; | ||||
|  | ||||
| #define CLOGC(category, level, fmt, args...) do { \ | ||||
| 	LOGP(category, level, "[tid=%ld] " fmt, (long int) osmo_gettid(), ##args);  \ | ||||
| } while(0) | ||||
|  | ||||
| #define CLOGCHAN(chan, category, level, fmt, args...) do { \ | ||||
| 	LOGP(category, level, "[tid=%ld][chan=%zu] " fmt, (long int) osmo_gettid(), chan, ##args);  \ | ||||
| 	LOGP(category, level, "[chan=%zu] " fmt, chan, ##args);  \ | ||||
| } while(0) | ||||
|   | ||||
| @@ -147,17 +147,17 @@ static int dev_rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) { | ||||
| 		if (dev_ctrs_pending[chan].chan == PENDING_CHAN_NONE) | ||||
| 			continue; | ||||
| 		LOGCHAN(chan, DCTR, DEBUG) << "rate_ctr update"; | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_RX_OVERRUNS]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_RX_OVERRUNS); | ||||
| 		rate_ctr_add(ctr, dev_ctrs_pending[chan].rx_overruns - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_TX_UNDERRUNS]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_TX_UNDERRUNS); | ||||
| 		rate_ctr_add(ctr, dev_ctrs_pending[chan].tx_underruns - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_RX_DROP_EV]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_RX_DROP_EV); | ||||
| 		rate_ctr_add(ctr, dev_ctrs_pending[chan].rx_dropped_events - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_RX_DROP_SMPL]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_RX_DROP_SMPL); | ||||
| 		rate_ctr_add(ctr, dev_ctrs_pending[chan].rx_dropped_samples - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_TX_DROP_EV]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_TX_DROP_EV); | ||||
| 		rate_ctr_add(ctr, dev_ctrs_pending[chan].tx_dropped_events - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_TX_DROP_SMPL]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_TX_DROP_SMPL); | ||||
| 		rate_ctr_add(ctr, dev_ctrs_pending[chan].tx_dropped_samples - ctr->current); | ||||
|  | ||||
| 		/* Mark as done */ | ||||
| @@ -178,21 +178,21 @@ static int trx_rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) { | ||||
| 		if (trx_ctrs_pending[chan].chan == PENDING_CHAN_NONE) | ||||
| 			continue; | ||||
| 		LOGCHAN(chan, DCTR, DEBUG) << "rate_ctr update"; | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TX_STALE_BURSTS]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TX_STALE_BURSTS); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_stale_bursts - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_unavailable_bursts - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TRXD_FN_REPEATED]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TRXD_FN_REPEATED); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_trxd_fn_repeated - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TRXD_FN_OUTOFORDER]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TRXD_FN_OUTOFORDER); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_trxd_fn_outoforder - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TRXD_FN_SKIPPED]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TRXD_FN_SKIPPED); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_trxd_fn_skipped - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_RX_EMPTY_BURST]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_RX_EMPTY_BURST); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].rx_empty_burst - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_RX_CLIPPING]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_RX_CLIPPING); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].rx_clipping - ctr->current); | ||||
| 		ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_RX_NO_BURST_DETECTED]; | ||||
| 		ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_RX_NO_BURST_DETECTED); | ||||
| 		rate_ctr_add(ctr, trx_ctrs_pending[chan].rx_no_burst_detected - ctr->current); | ||||
| 		/* Mark as done */ | ||||
| 		trx_ctrs_pending[chan].chan = PENDING_CHAN_NONE; | ||||
| @@ -214,6 +214,7 @@ static int device_sig_cb(unsigned int subsys, unsigned int signal, | ||||
| 	struct timespec next_sched = {.tv_sec = 0, .tv_nsec = 20*1000*1000}; | ||||
| 	/* no automatic re-trigger */ | ||||
| 	struct timespec intv_sched = {.tv_sec = 0, .tv_nsec = 0}; | ||||
| 	char err_buf[256]; | ||||
|  | ||||
| 	switch (signal) { | ||||
| 	case S_DEVICE_COUNTER_CHANGE: | ||||
| @@ -222,7 +223,8 @@ static int device_sig_cb(unsigned int subsys, unsigned int signal, | ||||
| 		dev_rate_ctr_mutex.lock(); | ||||
| 		dev_ctrs_pending[dev_ctr->chan] = *dev_ctr; | ||||
| 		if (osmo_timerfd_schedule(&dev_rate_ctr_timerfd, &next_sched, &intv_sched) < 0) { | ||||
| 			LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno); | ||||
| 			LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno | ||||
| 					  << " = "<< strerror_r(errno, err_buf, sizeof(err_buf)); | ||||
| 		} | ||||
| 		dev_rate_ctr_mutex.unlock(); | ||||
| 		break; | ||||
| @@ -232,7 +234,8 @@ static int device_sig_cb(unsigned int subsys, unsigned int signal, | ||||
| 		trx_rate_ctr_mutex.lock(); | ||||
| 		trx_ctrs_pending[trx_ctr->chan] = *trx_ctr; | ||||
| 		if (osmo_timerfd_schedule(&trx_rate_ctr_timerfd, &next_sched, &intv_sched) < 0) { | ||||
| 			LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno); | ||||
| 			LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno | ||||
| 					  << " = "<< strerror_r(errno, err_buf, sizeof(err_buf)); | ||||
| 		} | ||||
| 		trx_rate_ctr_mutex.unlock(); | ||||
| 		break; | ||||
| @@ -263,7 +266,7 @@ static void threshold_timer_cb(void *data) | ||||
|  | ||||
| 	llist_for_each_entry(ctr_thr, &threshold_list, list) { | ||||
| 		for (chan = 0; chan < chan_len; chan++) { | ||||
| 			rate_ctr = &rate_ctrs[chan]->ctr[ctr_thr->ctr_id]; | ||||
| 			rate_ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], ctr_thr->ctr_id); | ||||
| 			LOGCHAN(chan, DCTR, INFO) << "checking threshold: " << ctr_threshold_2_vty_str(ctr_thr) | ||||
| 						   << " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")"; | ||||
| 			if (rate_ctr->intv[ctr_thr->intv].rate >= ctr_thr->val) { | ||||
| @@ -309,8 +312,7 @@ static void threshold_timer_update_intv() { | ||||
| 		return; | ||||
|  | ||||
| 	if (llist_empty(&threshold_list)) { | ||||
| 		if (osmo_timer_pending(&threshold_timer)) | ||||
| 			osmo_timer_del(&threshold_timer); | ||||
| 		osmo_timer_del(&threshold_timer); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -271,6 +271,20 @@ DEFUN(cfg_rssi_offset, cfg_rssi_offset_cmd, | ||||
| 	return CMD_SUCCESS; | ||||
| } | ||||
|  | ||||
|  | ||||
| DEFUN_ATTR(cfg_ul_fn_offset, cfg_ul_fn_offset_cmd, | ||||
| 	"ul-fn-offset <-10-10>", | ||||
| 	"Adjusts the uplink frame FN by the specified amount\n" | ||||
| 	"Frame Number offset\n", | ||||
| 	CMD_ATTR_HIDDEN) | ||||
| { | ||||
| 	struct trx_ctx *trx = trx_from_vty(vty); | ||||
|  | ||||
| 	trx->cfg.ul_fn_offset = atoi(argv[0]); | ||||
|  | ||||
| 	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" | ||||
| @@ -624,6 +638,8 @@ static int config_write_trx(struct vty *vty) | ||||
| 		vty_out(vty, " filler access-burst-delay %u%s", trx->cfg.rach_delay, VTY_NEWLINE); | ||||
| 	if (trx->cfg.stack_size != 0) | ||||
| 		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); | ||||
| 	trx_rate_ctr_threshold_write_config(vty, " "); | ||||
|  | ||||
| 	for (i = 0; i < trx->cfg.num_chans; i++) { | ||||
| @@ -787,6 +803,7 @@ int trx_vty_init(struct trx_ctx* trx) | ||||
| 	install_element(TRX_NODE, &cfg_stack_size_cmd); | ||||
|  | ||||
| 	install_element(TRX_NODE, &cfg_chan_cmd); | ||||
| 	install_element(TRX_NODE, &cfg_ul_fn_offset_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); | ||||
|   | ||||
| @@ -26,8 +26,15 @@ AM_CXXFLAGS = -Wall -pthread | ||||
| #AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread | ||||
| #AM_CFLAGS = -Wall -O2 -NDEBUG -pthread | ||||
|  | ||||
| SUBDIRS = | ||||
|  | ||||
| if ENABLE_MS_TRX | ||||
| 	SUBDIRS += osmocom-bb/src/host/trxcon | ||||
| endif | ||||
|  | ||||
| # Order must be preserved | ||||
| SUBDIRS = \ | ||||
| SUBDIRS += \ | ||||
| 	osmocom-bb/src/host/trxcon \ | ||||
| 	CommonLibs \ | ||||
| 	GSM \ | ||||
| 	Transceiver52M \ | ||||
|   | ||||
| @@ -27,9 +27,9 @@ GIT Repository | ||||
|  | ||||
| You can clone from the official osmo-trx.git repository using | ||||
|  | ||||
|         git clone git://git.osmocom.org/osmo-trx.git | ||||
|         git clone https://gitea.osmocom.org/cellular-infrastructure/osmo-trx` | ||||
|  | ||||
| There is a cgit interface at <https://git.osmocom.org/osmo-trx/> | ||||
| There is a web interface at <https://gitea.osmocom.org/cellular-infrastructure/osmo-trx> | ||||
|  | ||||
| Documentation | ||||
| ------------- | ||||
|   | ||||
| @@ -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 | ||||
| AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
| AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) | ||||
| 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/ | ||||
|  | ||||
| noinst_LTLIBRARIES = libtransceiver_common.la | ||||
|  | ||||
| @@ -40,7 +40,9 @@ COMMON_SOURCES = \ | ||||
| 	ChannelizerBase.cpp \ | ||||
| 	Channelizer.cpp \ | ||||
| 	Synthesis.cpp \ | ||||
| 	proto_trxd.c | ||||
| 	proto_trxd.c \ | ||||
| 	grgsm_vitac/grgsm_vitac.cpp \ | ||||
| 	grgsm_vitac/viterbi_detector.cc | ||||
|  | ||||
| libtransceiver_common_la_SOURCES = \ | ||||
| 	$(COMMON_SOURCES) \ | ||||
| @@ -61,7 +63,9 @@ noinst_HEADERS = \ | ||||
| 	ChannelizerBase.h \ | ||||
| 	Channelizer.h \ | ||||
| 	Synthesis.h \ | ||||
| 	proto_trxd.h | ||||
| 	proto_trxd.h \ | ||||
| 	grgsm_vitac/viterbi_detector.h \ | ||||
| 	grgsm_vitac/constants.h | ||||
|  | ||||
| COMMON_LDADD = \ | ||||
| 	libtransceiver_common.la \ | ||||
| @@ -70,9 +74,26 @@ 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 | ||||
|  | ||||
| MS_SOURCES = \ | ||||
| 	ms/sch.c \ | ||||
| 	ms/ms.cpp \ | ||||
| 	ms/ms_rx_lower.cpp | ||||
|  | ||||
| noinst_HEADERS += \ | ||||
| 	ms/ms.h \ | ||||
| 	ms/bladerf_specific.h \ | ||||
| 	ms/uhd_specific.h \ | ||||
| 	ms/ms_upper.h \ | ||||
| 	ms/itrq.h | ||||
|  | ||||
| bin_PROGRAMS = | ||||
|  | ||||
| if DEVICE_UHD | ||||
| @@ -83,6 +104,26 @@ osmo_trx_uhd_LDADD = \ | ||||
| 	$(COMMON_LDADD) \ | ||||
| 	$(UHD_LIBS) | ||||
| osmo_trx_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) | ||||
|  | ||||
| if ENABLE_MS_TRX | ||||
| bin_PROGRAMS += osmo-trx-ms-uhd | ||||
| osmo_trx_ms_uhd_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.cpp ms/l1ctl_server_cb.cpp | ||||
| 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 | ||||
|  | ||||
| if DEVICE_USRP1 | ||||
| @@ -105,6 +146,36 @@ osmo_trx_lms_LDADD = \ | ||||
| osmo_trx_lms_CPPFLAGS  = $(AM_CPPFLAGS) $(LMS_CFLAGS) | ||||
| endif | ||||
|  | ||||
| if DEVICE_BLADE | ||||
| bin_PROGRAMS += osmo-trx-blade | ||||
| osmo_trx_blade_SOURCES = osmo-trx.cpp | ||||
| osmo_trx_blade_LDADD = \ | ||||
| 	$(builddir)/device/bladerf/libdevice.la \ | ||||
| 	$(COMMON_LDADD) \ | ||||
| 	$(BLADE_LIBS) | ||||
| osmo_trx_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(LMS_CFLAGS) | ||||
|  | ||||
| if ENABLE_MS_TRX | ||||
| bin_PROGRAMS += osmo-trx-ms-blade | ||||
| osmo_trx_ms_blade_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.cpp ms/l1ctl_server_cb.cpp | ||||
| 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 | ||||
|  | ||||
| if DEVICE_IPC | ||||
| bin_PROGRAMS += osmo-trx-ipc | ||||
| osmo_trx_ipc_SOURCES = osmo-trx.cpp | ||||
| @@ -112,5 +183,24 @@ osmo_trx_ipc_LDADD = \ | ||||
| 	$(builddir)/device/ipc/libdevice.la \ | ||||
| 	$(COMMON_LDADD) | ||||
| osmo_trx_ipc_CPPFLAGS  = $(AM_CPPFLAGS) | ||||
| endif | ||||
|  | ||||
| # 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 | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <stdlib.h> | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #ifndef _RESAMPLER_H_ | ||||
|   | ||||
| @@ -584,7 +584,7 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime, | ||||
|   case XIII: { | ||||
|     int mod52 = burstFN % 52; | ||||
|     if ((mod52 == 12) || (mod52 == 38)) | ||||
|       return cfg->ext_rach ? EXT_RACH : RACH; | ||||
|       return RACH; /* RACH is always 8-bit on PTCCH/U */ | ||||
|     else if ((mod52 == 25) || (mod52 == 51)) | ||||
|       return IDLE; | ||||
|     else /* Enable 8-PSK burst detection if EDGE is enabled */ | ||||
| @@ -649,7 +649,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) | ||||
|   } | ||||
|  | ||||
|   /* Set time and determine correlation type */ | ||||
|   burstTime = radio_burst->getTime(); | ||||
|   burstTime = radio_burst->getTime() + cfg->ul_fn_offset; | ||||
|   CorrType type = expectedCorrType(burstTime, chan); | ||||
|  | ||||
|   /* Initialize struct bi */ | ||||
| @@ -1003,7 +1003,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan) | ||||
|     LOGCHAN(chan, DTRXCTRL, INFO) << "BTS requests TRXD version switch: " << version_recv; | ||||
|     if (version_recv > TRX_DATA_FORMAT_VER) { | ||||
|       LOGCHAN(chan, DTRXCTRL, INFO) << "rejecting TRXD version " << version_recv | ||||
|                                     << "in favor of " <<  TRX_DATA_FORMAT_VER; | ||||
|                                     << " in favor of " <<  TRX_DATA_FORMAT_VER; | ||||
|       sprintf(response, "RSP SETFORMAT %u %u", TRX_DATA_FORMAT_VER, version_recv); | ||||
|     } else { | ||||
|       LOGCHAN(chan, DTRXCTRL, NOTICE) << "switching to TRXD version " << version_recv; | ||||
|   | ||||
| @@ -80,7 +80,7 @@ struct TransceiverState { | ||||
|  | ||||
|   /* Received noise energy levels */ | ||||
|   float mNoiseLev; | ||||
|   noiseVector mNoises; | ||||
|   avgVector mNoises; | ||||
|  | ||||
|   /* Shadowed downlink attenuation */ | ||||
|   int mPower; | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| 	.syntax unified | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #ifdef HAVE_CONFIG_H | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| 	.syntax unified | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| 	.syntax unified | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include "convert.h" | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include <malloc.h> | ||||
|   | ||||
| @@ -11,10 +11,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|   | ||||
| @@ -3,7 +3,7 @@ include $(top_srcdir)/Makefile.common | ||||
| SUBDIRS = common | ||||
|  | ||||
| if DEVICE_IPC | ||||
| SUBDIRS += ipc | ||||
| SUBDIRS += ipc ipc2 | ||||
| endif | ||||
|  | ||||
| if DEVICE_USRP1 | ||||
| @@ -17,3 +17,7 @@ endif | ||||
| if DEVICE_LMS | ||||
| SUBDIRS += lms | ||||
| endif | ||||
|  | ||||
| if DEVICE_BLADE | ||||
| SUBDIRS += bladerf | ||||
| endif | ||||
|   | ||||
							
								
								
									
										11
									
								
								Transceiver52M/device/bladerf/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Transceiver52M/device/bladerf/Makefile.am
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| include $(top_srcdir)/Makefile.common | ||||
|  | ||||
| AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common | ||||
| AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(BLADE_CFLAGS) | ||||
|  | ||||
| noinst_HEADERS = bladerf.h | ||||
|  | ||||
| noinst_LTLIBRARIES = libdevice.la | ||||
|  | ||||
| libdevice_la_SOURCES = bladerf.cpp | ||||
| libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la | ||||
							
								
								
									
										733
									
								
								Transceiver52M/device/bladerf/bladerf.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										733
									
								
								Transceiver52M/device/bladerf/bladerf.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,733 @@ | ||||
| /* | ||||
|  * Copyright 2022 sysmocom - s.f.m.c. GmbH | ||||
|  * | ||||
|  * 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 <map> | ||||
| #include <libbladeRF.h> | ||||
| #include "radioDevice.h" | ||||
| #include "bladerf.h" | ||||
| #include "Threads.h" | ||||
| #include "Logger.h" | ||||
|  | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/core/utils.h> | ||||
| #include <osmocom/gsm/gsm_utils.h> | ||||
| #include <osmocom/vty/cpu_sched_vty.h> | ||||
| } | ||||
|  | ||||
| #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));  \ | ||||
| 	} | ||||
|  | ||||
| /* | ||||
|  * 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{ | ||||
| 	{ std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } }, | ||||
| }; | ||||
|  | ||||
| typedef std::tuple<blade_dev_type, enum gsm_band> dev_band_key; | ||||
| typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it; | ||||
| static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map{ | ||||
| 	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, | ||||
| 	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, | ||||
| 	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, | ||||
| 	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, | ||||
| }; | ||||
|  | ||||
| /* So far measurements done for B210 show really close to linear relationship | ||||
|  * between gain and real output power, so we simply adjust the measured offset | ||||
|  */ | ||||
| static double TxGain2TxPower(const dev_band_desc &desc, double tx_gain_db) | ||||
| { | ||||
| 	return desc.nom_out_tx_power - (desc.nom_uhd_tx_gain - tx_gain_db); | ||||
| } | ||||
| 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() | ||||
| { | ||||
| 	if (dev) { | ||||
| 		bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), false); | ||||
| 		bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), false); | ||||
| 	} | ||||
|  | ||||
| 	stop(); | ||||
|  | ||||
| 	for (size_t i = 0; i < rx_buffers.size(); i++) | ||||
| 		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; | ||||
| 	int status; | ||||
|  | ||||
| 	const struct bladerf_range *r; | ||||
| 	bladerf_get_gain_range(dev, BLADERF_RX, &r); | ||||
|  | ||||
| 	rx_gain_min = r->min; | ||||
| 	rx_gain_max = r->max; | ||||
| 	LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]"; | ||||
|  | ||||
| 	for (size_t i = 0; i < rx_gains.size(); i++) { | ||||
| 		double gain = (rx_gain_min + rx_gain_max) / 2; | ||||
| 		status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC); | ||||
| 		CHKRET() | ||||
| 		bladerf_gain_mode m; | ||||
| 		bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m); | ||||
| 		LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO"); | ||||
|  | ||||
| 		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0); //gain); | ||||
| 		CHKRET() | ||||
| 		int actual_gain; | ||||
| 		status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain); | ||||
| 		CHKRET() | ||||
| 		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " | ||||
| 				 << r->scale << " actual " << actual_gain; | ||||
| 		rx_gains[i] = actual_gain; | ||||
|  | ||||
| 		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0); //gain); | ||||
| 		CHKRET() | ||||
| 		status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain); | ||||
| 		CHKRET() | ||||
| 		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " | ||||
| 				 << r->scale << " actual " << actual_gain; | ||||
| 		rx_gains[i] = actual_gain; | ||||
| 	} | ||||
|  | ||||
| 	status = bladerf_get_gain_range(dev, BLADERF_TX, &r); | ||||
| 	CHKRET() | ||||
| 	tx_gain_min = r->min; | ||||
| 	tx_gain_max = r->max; | ||||
| 	LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]"; | ||||
|  | ||||
| 	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); | ||||
| 		CHKRET() | ||||
| 		int actual_gain; | ||||
| 		status = bladerf_get_gain(dev, BLADERF_CHANNEL_TX(i), &actual_gain); | ||||
| 		CHKRET() | ||||
| 		LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale " | ||||
| 				 << r->scale << " actual " << actual_gain; | ||||
| 		tx_gains[i] = actual_gain; | ||||
| 	} | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| void blade_device::set_rates() | ||||
| { | ||||
| 	//dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps)); | ||||
|  | ||||
| 	struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)), 6 }, actual; | ||||
| 	auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual); | ||||
| 	CHKRET() | ||||
| 	status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), &rate, &actual); | ||||
| 	CHKRET() | ||||
|  | ||||
| 	tx_rate = rx_rate = (double)rate.num / (double)rate.den; | ||||
|  | ||||
| 	LOGC(DDEV, INFO) << "Rates set to" << tx_rate << " / " << rx_rate; | ||||
|  | ||||
| 	bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL); | ||||
| 	bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL); | ||||
|  | ||||
| 	ts_offset = 60; //static_cast<TIMESTAMP>(desc.offset * rx_rate); | ||||
| 	//LOGC(DDEV, INFO) << "Rates configured for " << desc.str; | ||||
| } | ||||
|  | ||||
| double blade_device::setRxGain(double db, size_t chan) | ||||
| { | ||||
| 	if (chan >= rx_gains.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30); //db); | ||||
| 	int actual_gain; | ||||
| 	bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain); | ||||
|  | ||||
| 	rx_gains[chan] = actual_gain; | ||||
|  | ||||
| 	LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)"; | ||||
|  | ||||
| 	return rx_gains[chan]; | ||||
| } | ||||
|  | ||||
| double blade_device::getRxGain(size_t chan) | ||||
| { | ||||
| 	if (chan >= rx_gains.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	return rx_gains[chan]; | ||||
| } | ||||
|  | ||||
| double blade_device::rssiOffset(size_t chan) | ||||
| { | ||||
| 	double rssiOffset; | ||||
| 	dev_band_desc desc; | ||||
|  | ||||
| 	if (chan >= rx_gains.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	get_dev_band_desc(desc); | ||||
| 	rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; | ||||
| 	return rssiOffset; | ||||
| } | ||||
|  | ||||
| double blade_device::setPowerAttenuation(int atten, size_t chan) | ||||
| { | ||||
| 	double tx_power, db; | ||||
| 	dev_band_desc desc; | ||||
|  | ||||
| 	if (chan >= tx_gains.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan; | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	get_dev_band_desc(desc); | ||||
| 	tx_power = desc.nom_out_tx_power - atten; | ||||
| 	db = TxPower2TxGain(desc, tx_power); | ||||
|  | ||||
| 	bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30); //db); | ||||
| 	int actual_gain; | ||||
| 	bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain); | ||||
|  | ||||
| 	tx_gains[chan] = actual_gain; | ||||
|  | ||||
| 	LOGC(DDEV, INFO) | ||||
| 		<< "Set TX gain to " << tx_gains[chan] << "dB, ~" << TxGain2TxPower(desc, tx_gains[chan]) << " dBm " | ||||
| 		<< "(asked for " << db << " dB, ~" << tx_power << " dBm)"; | ||||
|  | ||||
| 	return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); | ||||
| } | ||||
| double blade_device::getPowerAttenuation(size_t chan) | ||||
| { | ||||
| 	dev_band_desc desc; | ||||
| 	if (chan >= tx_gains.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return 0.0f; | ||||
| 	} | ||||
|  | ||||
| 	get_dev_band_desc(desc); | ||||
| 	return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); | ||||
| } | ||||
|  | ||||
| int blade_device::getNominalTxPower(size_t chan) | ||||
| { | ||||
| 	dev_band_desc desc; | ||||
| 	get_dev_band_desc(desc); | ||||
|  | ||||
| 	return desc.nom_out_tx_power; | ||||
| } | ||||
|  | ||||
| int blade_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| { | ||||
| 	bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE); | ||||
| 	bladerf_set_usb_reset_on_open(true); | ||||
| 	auto success = bladerf_open(&dev, args.c_str()); | ||||
| 	if (success != 0) { | ||||
| 		struct bladerf_devinfo *info; | ||||
| 		auto num_devs = bladerf_get_device_list(&info); | ||||
| 		LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << args << "'"; | ||||
| 		if (num_devs) { | ||||
| 			for (int i = 0; i < num_devs; i++) | ||||
| 				LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial; | ||||
| 		} | ||||
|  | ||||
| 		return -1; | ||||
| 	} | ||||
| 	if (strcmp("bladerf2", bladerf_get_board_name(dev))) { | ||||
| 		LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	dev_type = blade_dev_type::BLADE2; | ||||
| 	tx_window = TX_WINDOW_FIXED; | ||||
|  | ||||
| 	struct bladerf_devinfo info; | ||||
| 	bladerf_get_devinfo(dev, &info); | ||||
| 	// Use the first found device | ||||
| 	LOGC(DDEV, INFO) << "Using discovered bladerf device " << info.serial; | ||||
|  | ||||
| 	tx_freqs.resize(chans); | ||||
| 	rx_freqs.resize(chans); | ||||
| 	tx_gains.resize(chans); | ||||
| 	rx_gains.resize(chans); | ||||
| 	rx_buffers.resize(chans); | ||||
|  | ||||
| 	switch (ref) { | ||||
| 	case REF_INTERNAL: | ||||
| 	case REF_EXTERNAL: | ||||
| 		break; | ||||
| 	default: | ||||
| 		LOGC(DDEV, ALERT) << "Invalid reference type"; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	if (ref == REF_EXTERNAL) { | ||||
| 		bool is_locked; | ||||
| 		int status = bladerf_set_pll_enable(dev, true); | ||||
| 		CHKRET() | ||||
| 		status = bladerf_set_pll_refclk(dev, 10000000); | ||||
| 		CHKRET() | ||||
| 		for (int i = 0; i < 20; i++) { | ||||
| 			usleep(50 * 1000); | ||||
| 			status = bladerf_get_pll_lock_state(dev, &is_locked); | ||||
| 			CHKRET() | ||||
| 			if (is_locked) | ||||
| 				break; | ||||
| 		} | ||||
| 		if (!is_locked) { | ||||
| 			LOGC(DDEV, ALERT) << "unable to lock refclk!"; | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	LOGC(DDEV, INFO) << "Selected clock source is " << ((ref == REF_INTERNAL) ? "internal" : "external 10Mhz"); | ||||
|  | ||||
| 	set_rates(); | ||||
|  | ||||
| 	/* | ||||
| 	1ts = 3/5200s | ||||
| 	1024*2 = small gap(~180us) every 9.23ms = every 16 ts? -> every 2 frames | ||||
| 	1024*1 = large gap(~627us) every 9.23ms = every 16 ts? -> every 2 frames | ||||
|  | ||||
| 	rif convertbuffer = 625*4 = 2500 -> 4 ts | ||||
| 	rif rxtxbuf = 4 * segment(625*4) = 10000 -> 16 ts | ||||
| */ | ||||
| 	const unsigned int num_buffers = 256; | ||||
| 	const unsigned int buffer_size = 1024 * 4; /* Must be a multiple of 1024 */ | ||||
| 	const unsigned int num_transfers = 32; | ||||
| 	const unsigned int timeout_ms = 3500; | ||||
|  | ||||
| 	bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers, | ||||
| 			    timeout_ms); | ||||
|  | ||||
| 	bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers, | ||||
| 			    timeout_ms); | ||||
|  | ||||
| 	/* Number of samples per over-the-wire packet */ | ||||
| 	tx_spp = rx_spp = buffer_size; | ||||
|  | ||||
| 	// Create receive buffer | ||||
| 	size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t); | ||||
| 	for (size_t i = 0; i < rx_buffers.size(); i++) | ||||
| 		rx_buffers[i] = new smpl_buf(buf_len); | ||||
|  | ||||
| 	// Create vector buffer | ||||
| 	pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp)); | ||||
| 	for (size_t i = 0; i < pkt_bufs.size(); i++) | ||||
| 		pkt_ptrs.push_back(&pkt_bufs[i].front()); | ||||
|  | ||||
| 	// Initialize and shadow gain values | ||||
| 	init_gains(); | ||||
|  | ||||
| 	return NORMAL; | ||||
| } | ||||
|  | ||||
| bool blade_device::restart() | ||||
| { | ||||
| 	/* Allow 100 ms delay to align multi-channel streams */ | ||||
| 	double delay = 0.2; | ||||
| 	int status; | ||||
|  | ||||
| 	status = bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), true); | ||||
| 	CHKRET() | ||||
| 	status = bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), true); | ||||
| 	CHKRET() | ||||
|  | ||||
| 	bladerf_timestamp now; | ||||
| 	status = bladerf_get_timestamp(dev, BLADERF_RX, &now); | ||||
| 	ts_initial = now + rx_rate * delay; | ||||
| 	LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool blade_device::start() | ||||
| { | ||||
| 	LOGC(DDEV, INFO) << "Starting USRP..."; | ||||
|  | ||||
| 	if (started) { | ||||
| 		LOGC(DDEV, ERROR) << "Device already started"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	// Start streaming | ||||
| 	if (!restart()) | ||||
| 		return false; | ||||
|  | ||||
| 	started = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool blade_device::stop() | ||||
| { | ||||
| 	if (!started) | ||||
| 		return false; | ||||
|  | ||||
| 	/* reset internal buffer timestamps */ | ||||
| 	for (size_t i = 0; i < rx_buffers.size(); i++) | ||||
| 		rx_buffers[i]->reset(); | ||||
|  | ||||
| 	band_ass_curr_sess = false; | ||||
|  | ||||
| 	started = false; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun) | ||||
| { | ||||
| 	ssize_t rc; | ||||
| 	uint64_t ts; | ||||
|  | ||||
| 	if (bufs.size() != chans) { | ||||
| 		LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size(); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	*overrun = false; | ||||
| 	*underrun = false; | ||||
|  | ||||
| 	// Shift read time with respect to transmit clock | ||||
| 	timestamp += ts_offset; | ||||
|  | ||||
| 	ts = timestamp; | ||||
| 	LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts; | ||||
|  | ||||
| 	// 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; | ||||
| 	} | ||||
|  | ||||
| 	struct bladerf_metadata meta = {}; | ||||
| 	meta.timestamp = ts; | ||||
| 	//static bool first_rx = true; | ||||
| 	// meta.timestamp = (first_rx) ? ts : 0; | ||||
| 	// meta.flags = (!first_rx) ? 0:BLADERF_META_FLAG_RX_NOW; | ||||
| 	// if(first_rx) | ||||
| 	// 	first_rx = false; | ||||
|  | ||||
| 	// Receive samples from the usrp until we have enough | ||||
| 	while (rx_buffers[0]->avail_smpls(timestamp) < len) { | ||||
| 		thread_enable_cancel(false); | ||||
| 		int status = bladerf_sync_rx(dev, pkt_ptrs[0], len, &meta, 200U); | ||||
| 		thread_enable_cancel(true); | ||||
|  | ||||
| 		if (status != 0) | ||||
| 			std::cerr << "RX fucked: " << bladerf_strerror(status); | ||||
| 		if (meta.flags & BLADERF_META_STATUS_OVERRUN) | ||||
| 			std::cerr << "RX fucked OVER: " << bladerf_strerror(status); | ||||
|  | ||||
| 		size_t num_smpls = meta.actual_count; | ||||
| 		; | ||||
| 		ts = meta.timestamp; | ||||
|  | ||||
| 		for (size_t i = 0; i < rx_buffers.size(); i++) { | ||||
| 			rc = rx_buffers[i]->write((short *)&pkt_bufs[i].front(), num_smpls, ts); | ||||
|  | ||||
| 			// Continue on local overrun, exit on other errors | ||||
| 			if ((rc < 0)) { | ||||
| 				LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc); | ||||
| 				LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); | ||||
| 				if (rc != smpl_buf::ERROR_OVERFLOW) | ||||
| 					return 0; | ||||
| 			} | ||||
| 		} | ||||
| 		meta = {}; | ||||
| 		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)) { | ||||
| 			LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc); | ||||
| 			LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return len; | ||||
| } | ||||
|  | ||||
| int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp) | ||||
| { | ||||
| 	*underrun = false; | ||||
| 	static bool first_tx = true; | ||||
| 	struct bladerf_metadata meta = {}; | ||||
| 	if (first_tx) { | ||||
| 		meta.timestamp = timestamp; | ||||
| 		meta.flags = BLADERF_META_FLAG_TX_BURST_START; | ||||
| 		first_tx = false; | ||||
| 	} | ||||
|  | ||||
| 	thread_enable_cancel(false); | ||||
| 	int status = bladerf_sync_tx(dev, (const void *)bufs[0], len, &meta, 200U); | ||||
| 	//size_t num_smpls = tx_stream->send(bufs, len, metadata); | ||||
| 	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; | ||||
|  | ||||
| 	return len; | ||||
| } | ||||
|  | ||||
| bool blade_device::updateAlignment(TIMESTAMP timestamp) | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool blade_device::set_freq(double freq, size_t chan, bool tx) | ||||
| { | ||||
| 	if (tx) { | ||||
| 		bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(chan), freq); | ||||
| 		bladerf_frequency f; | ||||
| 		bladerf_get_frequency(dev, BLADERF_CHANNEL_TX(chan), &f); | ||||
| 		tx_freqs[chan] = f; | ||||
| 	} else { | ||||
| 		bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(chan), freq); | ||||
| 		bladerf_frequency f; | ||||
| 		bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(chan), &f); | ||||
| 		rx_freqs[chan] = f; | ||||
| 	} | ||||
| 	LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx ? "TX" : "RX") << "): " << std::endl; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!set_freq(wFreq, chan, true)) | ||||
| 		return false; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| 		return false; | ||||
|  | ||||
| 	return set_freq(wFreq, chan, false); | ||||
| } | ||||
|  | ||||
| double blade_device::getTxFreq(size_t chan) | ||||
| { | ||||
| 	if (chan >= tx_freqs.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return 0.0; | ||||
| 	} | ||||
|  | ||||
| 	return tx_freqs[chan]; | ||||
| } | ||||
|  | ||||
| double blade_device::getRxFreq(size_t chan) | ||||
| { | ||||
| 	if (chan >= rx_freqs.size()) { | ||||
| 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; | ||||
| 		return 0.0; | ||||
| 	} | ||||
|  | ||||
| 	return rx_freqs[chan]; | ||||
| } | ||||
|  | ||||
| bool blade_device::requiresRadioAlign() | ||||
| { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| GSM::Time blade_device::minLatency() | ||||
| { | ||||
| 	/* Empirical data from a handful of | ||||
| 	relatively recent machines shows that the B100 will underrun when | ||||
| 	the transmit threshold is reduced to a time of 6 and a half frames, | ||||
| 	so we set a minimum 7 frame threshold. */ | ||||
| 	return GSM::Time(6, 7); | ||||
| } | ||||
|  | ||||
| TIMESTAMP blade_device::initialWriteTimestamp() | ||||
| { | ||||
| 	return ts_initial; | ||||
| } | ||||
|  | ||||
| TIMESTAMP blade_device::initialReadTimestamp() | ||||
| { | ||||
| 	return ts_initial; | ||||
| } | ||||
|  | ||||
| double blade_device::fullScaleInputValue() | ||||
| { | ||||
| 	return (double)2047; | ||||
| } | ||||
|  | ||||
| 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) | ||||
| { | ||||
| 	return new blade_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										188
									
								
								Transceiver52M/device/bladerf/bladerf.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								Transceiver52M/device/bladerf/bladerf.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| /* | ||||
|  * Copyright 2022 sysmocom - s.f.m.c. GmbH | ||||
|  * | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
|  | ||||
| #include "radioDevice.h" | ||||
| #include "smpl_buf.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/gsm/gsm_utils.h> | ||||
| } | ||||
|  | ||||
| #include <bladerf.h> | ||||
|  | ||||
| enum class blade_dev_type { BLADE1, BLADE2 }; | ||||
|  | ||||
| struct dev_band_desc { | ||||
| 	/* Maximum UHD Tx Gain which can be set/used without distorting the | ||||
| 	   output signal, and the resulting real output power measured when that | ||||
| 	   gain is used. Correct measured values only provided for B210 so far. */ | ||||
| 	double nom_uhd_tx_gain; /* dB */ | ||||
| 	double nom_out_tx_power; /* dBm */ | ||||
| 	/* Factor used to infer base real RSSI offset on the Rx path based on current | ||||
| 	   configured RxGain. The resulting rssiOffset is added to the per burst | ||||
| 	   calculated energy in upper layers. These values were empirically | ||||
| 	   found and may change based on multiple factors, see OS#4468. | ||||
| 	   rssiOffset = rxGain + rxgain2rssioffset_rel; | ||||
| 	*/ | ||||
| 	double rxgain2rssioffset_rel; /* dB */ | ||||
| }; | ||||
|  | ||||
| class blade_device : public RadioDevice { | ||||
|     public: | ||||
| 	blade_device(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset, | ||||
| 		     const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths); | ||||
| 	~blade_device(); | ||||
|  | ||||
| 	int open(const std::string &args, int ref, bool swap_channels); | ||||
| 	bool start(); | ||||
| 	bool stop(); | ||||
| 	bool restart(); | ||||
| 	enum TxWindowType getWindowType() | ||||
| 	{ | ||||
| 		return tx_window; | ||||
| 	} | ||||
|  | ||||
| 	int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun); | ||||
|  | ||||
| 	int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, TIMESTAMP timestamp); | ||||
|  | ||||
| 	bool updateAlignment(TIMESTAMP timestamp); | ||||
|  | ||||
| 	bool setTxFreq(double wFreq, size_t chan); | ||||
| 	bool setRxFreq(double wFreq, size_t chan); | ||||
|  | ||||
| 	TIMESTAMP initialWriteTimestamp(); | ||||
| 	TIMESTAMP initialReadTimestamp(); | ||||
|  | ||||
| 	double fullScaleInputValue(); | ||||
| 	double fullScaleOutputValue(); | ||||
|  | ||||
| 	double setRxGain(double db, size_t chan); | ||||
| 	double getRxGain(size_t chan); | ||||
| 	double maxRxGain(void) | ||||
| 	{ | ||||
| 		return rx_gain_max; | ||||
| 	} | ||||
| 	double minRxGain(void) | ||||
| 	{ | ||||
| 		return rx_gain_min; | ||||
| 	} | ||||
| 	double rssiOffset(size_t chan); | ||||
|  | ||||
| 	double setPowerAttenuation(int atten, size_t chan); | ||||
| 	double getPowerAttenuation(size_t chan = 0); | ||||
|  | ||||
| 	int getNominalTxPower(size_t chan = 0); | ||||
|  | ||||
| 	double getTxFreq(size_t chan); | ||||
| 	double getRxFreq(size_t chan); | ||||
| 	double getRxFreq(); | ||||
|  | ||||
| 	bool setRxAntenna(const std::string &ant, size_t chan) | ||||
| 	{ | ||||
| 		return {}; | ||||
| 	}; | ||||
| 	std::string getRxAntenna(size_t chan) | ||||
| 	{ | ||||
| 		return {}; | ||||
| 	}; | ||||
| 	bool setTxAntenna(const std::string &ant, size_t chan) | ||||
| 	{ | ||||
| 		return {}; | ||||
| 	}; | ||||
| 	std::string getTxAntenna(size_t chan) | ||||
| 	{ | ||||
| 		return {}; | ||||
| 	}; | ||||
|  | ||||
| 	bool requiresRadioAlign(); | ||||
|  | ||||
| 	GSM::Time minLatency(); | ||||
|  | ||||
| 	inline double getSampleRate() | ||||
| 	{ | ||||
| 		return tx_rate; | ||||
| 	} | ||||
|  | ||||
| 	/** Receive and process asynchronous message | ||||
| 	    @return true if message received or false on timeout or error | ||||
| 	*/ | ||||
| 	bool recv_async_msg(); | ||||
|  | ||||
| 	enum err_code { | ||||
| 		ERROR_TIMING = -1, | ||||
| 		ERROR_TIMEOUT = -2, | ||||
| 		ERROR_UNRECOVERABLE = -3, | ||||
| 		ERROR_UNHANDLED = -4, | ||||
| 	}; | ||||
|  | ||||
|     protected: | ||||
| 	struct bladerf *dev; | ||||
| 	void *usrp_dev; | ||||
|  | ||||
| 	enum TxWindowType tx_window; | ||||
| 	enum blade_dev_type dev_type; | ||||
|  | ||||
| 	double tx_rate, rx_rate; | ||||
|  | ||||
| 	double rx_gain_min, rx_gain_max; | ||||
|  | ||||
| 	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; | ||||
| 	bool aligned; | ||||
|  | ||||
| 	size_t drop_cnt; | ||||
| 	uint64_t prev_ts; | ||||
|  | ||||
| 	TIMESTAMP ts_initial, ts_offset; | ||||
| 	std::vector<smpl_buf *> rx_buffers; | ||||
| 	/* Sample buffers used to receive samples: */ | ||||
| 	std::vector<std::vector<short> > pkt_bufs; | ||||
| 	/* Used to call UHD API: Buffer pointer of each elem in pkt_ptrs will | ||||
| 	   point to corresponding buffer of vector pkt_bufs. */ | ||||
| 	std::vector<short *> pkt_ptrs; | ||||
|  | ||||
| 	void init_gains(); | ||||
| 	void set_channels(bool swap); | ||||
| 	void set_rates(); | ||||
| 	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; | ||||
| }; | ||||
| @@ -100,12 +100,14 @@ IPCDevice::~IPCDevice() | ||||
| int IPCDevice::ipc_shm_connect(const char *shm_name) | ||||
| { | ||||
| 	int fd; | ||||
| 	char err_buf[256]; | ||||
| 	size_t shm_len; | ||||
| 	int rc; | ||||
|  | ||||
| 	LOGP(DDEV, LOGL_NOTICE, "Opening shm path %s\n", shm_name); | ||||
| 	if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) { | ||||
| 		LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno)); | ||||
| 		LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, | ||||
| 		     strerror_r(errno, err_buf, sizeof(err_buf))); | ||||
| 		rc = -errno; | ||||
| 		goto err_shm_open; | ||||
| 	} | ||||
| @@ -113,7 +115,8 @@ int IPCDevice::ipc_shm_connect(const char *shm_name) | ||||
| 	// Get size of the allocated memory | ||||
| 	struct stat shm_stat; | ||||
| 	if (fstat(fd, &shm_stat) < 0) { | ||||
| 		LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, strerror(errno)); | ||||
| 		LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, | ||||
| 		     strerror_r(errno, err_buf, sizeof(err_buf))); | ||||
| 		rc = -errno; | ||||
| 		goto err_mmap; | ||||
| 	} | ||||
| @@ -122,7 +125,8 @@ int IPCDevice::ipc_shm_connect(const char *shm_name) | ||||
|  | ||||
| 	LOGP(DDEV, LOGL_NOTICE, "mmaping shared memory fd %d (size=%zu)\n", fd, shm_len); | ||||
| 	if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { | ||||
| 		LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno)); | ||||
| 		LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, | ||||
| 		     strerror_r(errno, err_buf, sizeof(err_buf))); | ||||
| 		rc = -errno; | ||||
| 		goto err_mmap; | ||||
| 	} | ||||
| @@ -835,6 +839,7 @@ void IPCDevice::manually_poll_sock_fds() | ||||
| { | ||||
| 	struct timeval wait = { 0, 100000 }; | ||||
| 	fd_set crfds, cwfds; | ||||
| 	char err_buf[256]; | ||||
| 	int max_fd = 0; | ||||
|  | ||||
| 	FD_ZERO(&crfds); | ||||
| @@ -849,7 +854,11 @@ void IPCDevice::manually_poll_sock_fds() | ||||
| 			FD_SET(curr_fd->fd, &cwfds); | ||||
| 	} | ||||
|  | ||||
| 	select(max_fd + 1, &crfds, &cwfds, 0, &wait); | ||||
| 	if (select(max_fd + 1, &crfds, &cwfds, 0, &wait) < 0) { | ||||
| 		LOGP(DDEV, LOGL_ERROR, "select() failed: %s\n", | ||||
| 		     strerror_r(errno, err_buf, sizeof(err_buf))); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	for (unsigned int i = 0; i < chans; i++) { | ||||
| 		int flags = 0; | ||||
|   | ||||
| @@ -32,11 +32,12 @@ bin_PROGRAMS = ipc-driver-test | ||||
| 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_LDADD = \ | ||||
|         shm.lo \ | ||||
| 	$(top_builddir)/Transceiver52M/device/common/libdevice_common.la \ | ||||
| 	$(COMMON_LA) | ||||
| 	$(LIBOSMOCORE_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) | ||||
| ipc_driver_test_LDADD += $(top_builddir)/Transceiver52M/device/common/libdevice_common.la $(top_builddir)/CommonLibs/libcommon.la | ||||
| endif | ||||
|   | ||||
							
								
								
									
										318
									
								
								Transceiver52M/device/ipc2/IPCDevice.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										318
									
								
								Transceiver52M/device/ipc2/IPCDevice.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,318 @@ | ||||
| /* | ||||
| * 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); | ||||
| } | ||||
							
								
								
									
										186
									
								
								Transceiver52M/device/ipc2/IPCDevice.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								Transceiver52M/device/ipc2/IPCDevice.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| /* | ||||
| * 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_ | ||||
							
								
								
									
										14
									
								
								Transceiver52M/device/ipc2/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Transceiver52M/device/ipc2/Makefile.am
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| 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 | ||||
							
								
								
									
										387
									
								
								Transceiver52M/device/ipc2/ipcif.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								Transceiver52M/device/ipc2/ipcif.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,387 @@ | ||||
| /* | ||||
|  * (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 */ | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										375
									
								
								Transceiver52M/device/ipc2/shmif.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								Transceiver52M/device/ipc2/shmif.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,375 @@ | ||||
| /* | ||||
|  * (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 | ||||
| @@ -134,8 +134,9 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev) | ||||
| 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((enum gsm_band)0), m_dev_type(LMS_DEV_UNKNOWN) | ||||
| 		     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) | ||||
| { | ||||
| 	LOGC(DDEV, INFO) << "creating LMS device..."; | ||||
|  | ||||
| @@ -240,14 +241,17 @@ void LMSDevice::assign_band_desc(enum gsm_band req_band) | ||||
|  | ||||
| bool LMSDevice::set_band(enum gsm_band req_band) | ||||
| { | ||||
| 	if (band != 0 && req_band != 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; | ||||
| 	} | ||||
|  | ||||
| 	band = req_band; | ||||
| 	assign_band_desc(band); | ||||
| 	if (req_band != band) { | ||||
| 		band = req_band; | ||||
| 		assign_band_desc(band); | ||||
| 	} | ||||
| 	band_ass_curr_sess = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -273,11 +277,12 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) | ||||
|  | ||||
| 	LMS_RegisterLogHandler(&lms_log_callback); | ||||
|  | ||||
| 	if ((n = LMS_GetDeviceList(NULL)) < 0) | ||||
| 	if ((rc = LMS_GetDeviceList(NULL)) < 0) | ||||
| 		LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed"; | ||||
| 	LOGC(DDEV, INFO) << "Devices found: " << n; | ||||
| 	if (n < 1) | ||||
| 	LOGC(DDEV, INFO) << "Devices found: " << rc; | ||||
| 	if (rc < 1) | ||||
| 	    return -1; | ||||
| 	n = rc; | ||||
|  | ||||
| 	info_list = new lms_info_str_t[n]; | ||||
|  | ||||
| @@ -464,6 +469,8 @@ bool LMSDevice::stop() | ||||
| 		LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]); | ||||
| 	} | ||||
|  | ||||
| 	band_ass_curr_sess = false; | ||||
|  | ||||
| 	started = false; | ||||
| 	return true; | ||||
| } | ||||
| @@ -1013,25 +1020,38 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan) | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (band != 0 && req_band != band) { | ||||
| 		LOGCHAN(chan, DDEV, ALERT) << "Requesting Tx Frequency " << wFreq | ||||
| 					   << " Hz different from previous band " << gsm_band_name(band); | ||||
| 	if (!set_band(req_band)) | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) { | ||||
| 		LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	band = req_band; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) { | ||||
| 		LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz"; | ||||
| 		return false; | ||||
|   | ||||
| @@ -87,6 +87,7 @@ 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; | ||||
|  | ||||
|   | ||||
| @@ -225,7 +225,7 @@ uhd_device::uhd_device(size_t tx_sps, size_t rx_sps, | ||||
| 		       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), | ||||
| 	  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) | ||||
| @@ -258,14 +258,17 @@ void uhd_device::assign_band_desc(enum gsm_band req_band) | ||||
|  | ||||
| bool uhd_device::set_band(enum gsm_band req_band) | ||||
| { | ||||
| 	if (band != 0 && req_band != 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; | ||||
| 	} | ||||
|  | ||||
| 	band = req_band; | ||||
| 	assign_band_desc(band); | ||||
| 	if (req_band != band) { | ||||
| 		band = req_band; | ||||
| 		assign_band_desc(band); | ||||
| 	} | ||||
| 	band_ass_curr_sess = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -548,6 +551,7 @@ void uhd_device::set_channels(bool swap) | ||||
| int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
| { | ||||
| 	const char *refstr; | ||||
| 	int clock_lock_attempts = 15; | ||||
|  | ||||
| 	/* Register msg handler. Different APIs depending on UHD version */ | ||||
| #ifdef USE_UHD_3_11 | ||||
| @@ -620,6 +624,19 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) | ||||
|  | ||||
| 	usrp_dev->set_clock_source(refstr); | ||||
|  | ||||
| 	std::vector<std::string> sensor_names = usrp_dev->get_mboard_sensor_names(); | ||||
| 	if (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) { | ||||
| 		LOGC(DDEV, INFO) << "Waiting for clock reference lock (max " << clock_lock_attempts << "s)..." << std::flush; | ||||
| 		while (!usrp_dev->get_mboard_sensor("ref_locked", 0).to_bool() && clock_lock_attempts--) | ||||
| 			sleep(1); | ||||
|  | ||||
| 		if (!clock_lock_attempts) { | ||||
| 			LOGC(DDEV, ALERT) << "Locking to external 10Mhz failed!"; | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
| 	LOGC(DDEV, INFO) << "Selected clock source is " << usrp_dev->get_clock_source(0); | ||||
|  | ||||
| 	try { | ||||
| 		set_rates(); | ||||
|         } catch (const std::exception &e) { | ||||
| @@ -779,6 +796,8 @@ bool uhd_device::stop() | ||||
| 	for (size_t i = 0; i < rx_buffers.size(); i++) | ||||
| 		rx_buffers[i]->reset(); | ||||
|  | ||||
| 	band_ass_curr_sess = false; | ||||
|  | ||||
| 	started = false; | ||||
| 	return true; | ||||
| } | ||||
| @@ -1088,12 +1107,29 @@ bool uhd_device::setTxFreq(double wFreq, size_t chan) | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -160,6 +160,7 @@ 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; | ||||
|   | ||||
							
								
								
									
										142
									
								
								Transceiver52M/grgsm_vitac/constants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								Transceiver52M/grgsm_vitac/constants.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| #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 | ||||
| }; | ||||
							
								
								
									
										299
									
								
								Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,299 @@ | ||||
| /* -*- 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); | ||||
| } | ||||
							
								
								
									
										62
									
								
								Transceiver52M/grgsm_vitac/grgsm_vitac.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Transceiver52M/grgsm_vitac/grgsm_vitac.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| #pragma once | ||||
| /* -*- c++ -*- */ | ||||
| /* | ||||
|  * @file | ||||
|  * @author (C) 2009-2017  by Piotr Krysik <ptrkrysik@gmail.com> | ||||
|  * @section LICENSE | ||||
|  * | ||||
|  * Gr-gsm is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; either version 3, or (at your option) | ||||
|  * any later version. | ||||
|  * | ||||
|  * Gr-gsm is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with gr-gsm; see the file COPYING.  If not, write to | ||||
|  * the Free Software Foundation, Inc., 51 Franklin Street, | ||||
|  * Boston, MA 02110-1301, USA. | ||||
|  */ | ||||
|  | ||||
| #include <vector> | ||||
| #include "constants.h" | ||||
|  | ||||
| #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"))) | ||||
| #else | ||||
| #define MULTI_VER_TARGET_ATTR | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #define SYNC_SEARCH_RANGE 30 | ||||
| const int d_OSR(4); | ||||
|  | ||||
| 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 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); | ||||
| inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output); | ||||
| 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); | ||||
|  | ||||
| enum class btype { NB, SCH }; | ||||
| struct fdata { | ||||
| 	btype t; | ||||
| 	unsigned int fn; | ||||
| 	int tn; | ||||
| 	int bcc; | ||||
| 	std::string fpath; | ||||
| 	std::vector<gr_complex> data; | ||||
| 	unsigned int data_start_offset; | ||||
| }; | ||||
							
								
								
									
										392
									
								
								Transceiver52M/grgsm_vitac/viterbi_detector.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								Transceiver52M/grgsm_vitac/viterbi_detector.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +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; | ||||
|    } | ||||
| } | ||||
							
								
								
									
										64
									
								
								Transceiver52M/grgsm_vitac/viterbi_detector.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								Transceiver52M/grgsm_vitac/viterbi_detector.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +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 */ | ||||
							
								
								
									
										445
									
								
								Transceiver52M/ms/bladerf_specific.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										445
									
								
								Transceiver52M/ms/bladerf_specific.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,445 @@ | ||||
| #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 "itrq.h" | ||||
| #include <atomic> | ||||
| #include <complex> | ||||
| #include <cstdint> | ||||
| #include <functional> | ||||
| #include <iostream> | ||||
| #include <cassert> | ||||
| #include <cstring> | ||||
|  | ||||
| #include <libbladeRF.h> | ||||
| #include <Timeval.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| const size_t BLADE_BUFFER_SIZE = 1024 * 1; | ||||
| 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) | ||||
| { | ||||
| 	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...); | ||||
|  | ||||
| // 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) | ||||
| { | ||||
| 	R rval = func(std::forward<Args>(args)...); | ||||
| 	if (rval != 0) { | ||||
| 		std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':' | ||||
| 			  << funcname << ':' << fname; | ||||
| 		expand_args(std::cerr, args...); | ||||
| 	} | ||||
| 	return rval; | ||||
| } | ||||
|  | ||||
| // only macros can pass a func name string | ||||
| #define blade_check(func, ...) exec_and_check(func, #func, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) | ||||
|  | ||||
| #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 { | ||||
| 	uint32_t reserved; | ||||
| 	uint64_t ts; | ||||
| 	uint32_t meta_flags; | ||||
| 	blade_sample_type d[(T == blade_speed_buffer_type::SS ? 512 : 256) - 4]; | ||||
| }; | ||||
|  | ||||
| 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 { | ||||
| 	static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!"); | ||||
| 	blade_usb_message<T> m[SZ]; | ||||
| 	int actual_samples_per_msg() | ||||
| 	{ | ||||
| 		return sizeof(blade_usb_message<T>::d) / sizeof(typeof(blade_usb_message<T>::d[0])); | ||||
| 	} | ||||
| 	int actual_samples_per_buffer() | ||||
| 	{ | ||||
| 		return SZ * actual_samples_per_msg(); | ||||
| 	} | ||||
| 	int samples_per_buffer() | ||||
| 	{ | ||||
| 		return SZ * sizeof(blade_usb_message<T>) / sizeof(typeof(blade_usb_message<T>::d[0])); | ||||
| 	} | ||||
| 	int num_msgs_per_buffer() | ||||
| 	{ | ||||
| 		return SZ; | ||||
| 	} | ||||
| 	auto get_first_ts() | ||||
| 	{ | ||||
| 		return m[0].ts; | ||||
| 	} | ||||
| 	constexpr auto *getsampleoffset(int ofs) | ||||
| 	{ | ||||
| 		auto full = ofs / actual_samples_per_msg(); | ||||
| 		auto rem = ofs % actual_samples_per_msg(); | ||||
| 		return &m[full].d[rem]; | ||||
| 	} | ||||
| 	int readall(blade_sample_type *outaddr) | ||||
| 	{ | ||||
| 		blade_sample_type *addr = outaddr; | ||||
| 		for (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(); | ||||
| 		} | ||||
| 		return actual_samples_per_buffer(); | ||||
| 	} | ||||
| 	int read_n(blade_sample_type *outaddr, int start, int num) | ||||
| 	{ | ||||
| 		assert((start + num) <= actual_samples_per_buffer()); | ||||
| 		assert(start >= 0); | ||||
|  | ||||
| 		if (!num) | ||||
| 			return 0; | ||||
|  | ||||
| 		// which buffer? | ||||
| 		int start_buf_idx = (start > 0) ? start / actual_samples_per_msg() : 0; | ||||
| 		// offset from actual buffer start | ||||
| 		auto start_offset_in_buf = (start - (start_buf_idx * actual_samples_per_msg())); | ||||
| 		auto samp_rem_in_first_buf = actual_samples_per_msg() - start_offset_in_buf; | ||||
| 		auto remaining_first_buf = num > samp_rem_in_first_buf ? samp_rem_in_first_buf : num; | ||||
|  | ||||
| 		memcpy(outaddr, &m[start_buf_idx].d[start_offset_in_buf], | ||||
| 		       remaining_first_buf * sizeof(blade_sample_type)); | ||||
| 		outaddr += remaining_first_buf; | ||||
|  | ||||
| 		auto remaining = num - remaining_first_buf; | ||||
|  | ||||
| 		if (!remaining) | ||||
| 			return num; | ||||
|  | ||||
| 		start_buf_idx++; | ||||
|  | ||||
| 		auto rem_full_bufs = remaining / actual_samples_per_msg(); | ||||
| 		remaining -= rem_full_bufs * actual_samples_per_msg(); | ||||
|  | ||||
| 		for (int i = 0; i < rem_full_bufs; i++) { | ||||
| 			memcpy(outaddr, &m[start_buf_idx++].d[0], actual_samples_per_msg() * sizeof(blade_sample_type)); | ||||
| 			outaddr += actual_samples_per_msg(); | ||||
| 		} | ||||
|  | ||||
| 		if (remaining) | ||||
| 			memcpy(outaddr, &m[start_buf_idx].d[0], remaining * sizeof(blade_sample_type)); | ||||
| 		return num; | ||||
| 	} | ||||
| 	int write_n_burst(blade_sample_type *in, int num, uint64_t first_ts) | ||||
| 	{ | ||||
| 		assert(num <= actual_samples_per_buffer()); | ||||
| 		int len_rem = num; | ||||
| 		for (int i = 0; i < SZ; i++) { | ||||
| 			m[i] = {}; | ||||
| 			m[i].ts = first_ts + i * actual_samples_per_msg(); | ||||
| 			if (len_rem) { | ||||
| 				int max_to_copy = | ||||
| 					len_rem > actual_samples_per_msg() ? actual_samples_per_msg() : len_rem; | ||||
| 				memcpy(&m[i].d[0], in, max_to_copy * sizeof(blade_sample_type)); | ||||
| 				len_rem -= max_to_copy; | ||||
| 				in += actual_samples_per_msg(); | ||||
| 			} | ||||
| 		} | ||||
| 		return num; | ||||
| 	} | ||||
| }; | ||||
| #pragma pack(pop) | ||||
|  | ||||
| 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; | ||||
| }; | ||||
|  | ||||
| using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_buffer_type::SS>::x); | ||||
| // 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 { | ||||
| 	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>; | ||||
| 	const unsigned int rxFullScale, txFullScale; | ||||
| 	const int rxtxdelay; | ||||
|  | ||||
| 	float rxgain, txgain; | ||||
|  | ||||
| 	struct ms_trx_config { | ||||
| 		int tx_freq; | ||||
| 		int rx_freq; | ||||
| 		int sample_rate; | ||||
| 		int bandwidth; | ||||
|  | ||||
| 	    public: | ||||
| 		ms_trx_config() : tx_freq(881e6), rx_freq(926e6), sample_rate(((1625e3 / 6) * 4)), bandwidth(1e6) | ||||
| 		{ | ||||
| 		} | ||||
| 	} cfg; | ||||
|  | ||||
| 	struct buf_mgmt { | ||||
| 		void **rx_samples; | ||||
| 		void **tx_samples; | ||||
| 		tx_buf_q_type bufptrqueue; | ||||
|  | ||||
| 	} buf_mgmt; | ||||
|  | ||||
| 	virtual ~blade_hw() | ||||
| 	{ | ||||
| 		close_device(); | ||||
| 	} | ||||
| 	blade_hw() : rxFullScale(2047), txFullScale(2047), rxtxdelay(-60) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	void close_device() | ||||
| 	{ | ||||
| 		if (dev) { | ||||
| 			if (rx_stream) { | ||||
| 				bladerf_deinit_stream(rx_stream); | ||||
| 			} | ||||
|  | ||||
| 			if (tx_stream) { | ||||
| 				bladerf_deinit_stream(tx_stream); | ||||
| 			} | ||||
|  | ||||
| 			bladerf_enable_module(dev, BLADERF_MODULE_RX, false); | ||||
| 			bladerf_enable_module(dev, BLADERF_MODULE_TX, false); | ||||
|  | ||||
| 			bladerf_close(dev); | ||||
| 			dev = NULL; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	int init_device(bh_fn_t rxh, bh_fn_t txh) | ||||
| 	{ | ||||
| 		struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)) * 64, 6 * 64 }, actual; | ||||
|  | ||||
| 		bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_DEBUG); | ||||
| 		bladerf_set_usb_reset_on_open(true); | ||||
| 		blade_check(bladerf_open, &dev, ""); | ||||
| 		if (!dev) { | ||||
| 			std::cerr << "open failed, device missing?" << std::endl; | ||||
| 			exit(0); | ||||
| 		} | ||||
| 		if (bladerf_device_speed(dev) != bladerf_dev_speed::BLADERF_DEVICE_SPEED_SUPER) { | ||||
| 			std::cerr << "open failed, only superspeed (usb3) supported!" << std::endl; | ||||
| 			return -1; | ||||
| 		} | ||||
|  | ||||
| 		blade_check(bladerf_set_tuning_mode, dev, bladerf_tuning_mode::BLADERF_TUNING_MODE_FPGA); | ||||
|  | ||||
| 		bool is_locked; | ||||
| 		blade_check(bladerf_set_pll_enable, dev, true); | ||||
| 		blade_check(bladerf_set_pll_refclk, dev, 10000000UL); | ||||
| 		for (int i = 0; i < 20; i++) { | ||||
| 			usleep(50 * 1000); | ||||
| 			bladerf_get_pll_lock_state(dev, &is_locked); | ||||
|  | ||||
| 			if (is_locked) | ||||
| 				break; | ||||
| 		} | ||||
| 		if (!is_locked) { | ||||
| 			std::cerr << "unable to lock refclk!" << std::endl; | ||||
| 			return -1; | ||||
| 		} | ||||
|  | ||||
| 		blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual); | ||||
| 		blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual); | ||||
|  | ||||
| 		blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)cfg.rx_freq); | ||||
| 		blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)cfg.tx_freq); | ||||
|  | ||||
| 		blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)cfg.bandwidth, | ||||
| 			    (bladerf_bandwidth *)NULL); | ||||
| 		blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)cfg.bandwidth, | ||||
| 			    (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); | ||||
| 		usleep(1000); | ||||
| 		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++) { | ||||
| 			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; | ||||
| 	} | ||||
|  | ||||
| 	bool tuneTx(double freq, size_t chan = 0) | ||||
| 	{ | ||||
| 		msleep(15); | ||||
| 		blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq); | ||||
| 		msleep(15); | ||||
| 		return true; | ||||
| 	}; | ||||
| 	bool tuneRx(double freq, size_t chan = 0) | ||||
| 	{ | ||||
| 		msleep(15); | ||||
| 		blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq); | ||||
| 		msleep(15); | ||||
| 		return true; | ||||
| 	}; | ||||
| 	bool tuneRxOffset(double offset, size_t chan = 0) | ||||
| 	{ | ||||
| 		return true; | ||||
| 	}; | ||||
|  | ||||
| 	double setRxGain(double dB, size_t chan = 0) | ||||
| 	{ | ||||
| 		rxgain = dB; | ||||
| 		msleep(15); | ||||
| 		blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)dB); | ||||
| 		msleep(15); | ||||
| 		return dB; | ||||
| 	}; | ||||
| 	double setTxGain(double dB, size_t chan = 0) | ||||
| 	{ | ||||
| 		txgain = dB; | ||||
| 		msleep(15); | ||||
| 		blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)dB); | ||||
| 		msleep(15); | ||||
| 		return dB; | ||||
| 	}; | ||||
| 	int setPowerAttenuation(int atten, size_t chan = 0) | ||||
| 	{ | ||||
| 		return atten; | ||||
| 	}; | ||||
|  | ||||
| 	static void check_timestamp(dev_buf_t *rcd) | ||||
| 	{ | ||||
| 		static bool first = true; | ||||
| 		static uint64_t last_ts; | ||||
| 		if (first) { | ||||
| 			first = false; | ||||
| 			last_ts = rcd->m[0].ts; | ||||
| 		} else if (last_ts + rcd->actual_samples_per_buffer() != rcd->m[0].ts) { | ||||
| 			std::cerr << "RX Overrun!" << last_ts << " " << rcd->actual_samples_per_buffer() << " " | ||||
| 				  << last_ts + rcd->actual_samples_per_buffer() << " " << rcd->m[0].ts << std::endl; | ||||
| 			last_ts = rcd->m[0].ts; | ||||
| 		} else { | ||||
| 			last_ts = rcd->m[0].ts; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	bladerf_stream_cb getrxcb(bh_fn_t rxbh) | ||||
| 	{ | ||||
| 		// C cb -> no capture! | ||||
| 		static auto rxbhfn = rxbh; | ||||
| 		return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, | ||||
| 			  void *samples, size_t num_samples, void *user_data) -> void * { | ||||
| 			// struct blade_hw *trx = (struct blade_hw *)user_data; | ||||
| 			static int to_skip = 0; | ||||
| 			dev_buf_t *rcd = (dev_buf_t *)samples; | ||||
|  | ||||
| 			if (to_skip < 120) // prevents weird overflows on startup | ||||
| 				to_skip++; | ||||
| 			else { | ||||
| 				check_timestamp(rcd); | ||||
| 				rxbhfn(rcd); | ||||
| 			} | ||||
|  | ||||
| 			return samples; | ||||
| 		}; | ||||
| 	} | ||||
| 	bladerf_stream_cb gettxcb(bh_fn_t txbh) | ||||
| 	{ | ||||
| 		// C cb -> no capture! | ||||
| 		static auto txbhfn = txbh; | ||||
| 		return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, | ||||
| 			  void *samples, size_t num_samples, void *user_data) -> void * { | ||||
| 			struct blade_hw *trx = (struct blade_hw *)user_data; | ||||
| 			auto ptr = reinterpret_cast<tx_buf_q_type::elem_t>(samples); | ||||
|  | ||||
| 			if (samples) // put buffer address back into queue, ready to be reused | ||||
| 				trx->buf_mgmt.bufptrqueue.spsc_push(&ptr); | ||||
|  | ||||
| 			return BLADERF_STREAM_NO_DATA; | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	auto get_rx_burst_handler_fn(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		auto fn = [this] { | ||||
| 			int status; | ||||
| 			status = bladerf_stream(rx_stream, BLADERF_RX_X1); | ||||
| 			if (status < 0) | ||||
| 				std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl; | ||||
|  | ||||
| 			return NULL; | ||||
| 		}; | ||||
| 		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); | ||||
| 			if (status < 0) | ||||
| 				std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl; | ||||
|  | ||||
| 			return NULL; | ||||
| 		}; | ||||
| 		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; | ||||
|  | ||||
| 		while (!buf_mgmt.bufptrqueue.spsc_pop(&rcd)) | ||||
| 			buf_mgmt.bufptrqueue.spsc_prep_pop(); | ||||
| 		assert(rcd != nullptr); | ||||
|  | ||||
| 		rcd->write_n_burst(buffer, len, ts + rxtxdelay); // blade xa4 specific delay! | ||||
| 		blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										197
									
								
								Transceiver52M/ms/ipc_specific.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								Transceiver52M/ms/ipc_specific.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | ||||
| #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(); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										213
									
								
								Transceiver52M/ms/itrq.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								Transceiver52M/ms/itrq.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| #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 <atomic> | ||||
|  | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include <sys/eventfd.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include <stdatomic.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
|  | ||||
| /* | ||||
| classic lamport circular lockfree spsc queue: | ||||
| every "side" only writes its own ptr, but may read the other sides ptr | ||||
|  | ||||
| notify reader using eventfd as soon as element is added, reader then reads until | ||||
| read fails | ||||
| -> reader pops in a loop until FALSE and might get spurious events because it | ||||
| read before it was notified, which is fine | ||||
| -> writing pushes *the same data* in a loop until TRUE, blocks | ||||
|  | ||||
| shutting this down requires | ||||
| 1) to stop reading and pushing | ||||
| 2) ONE side to take care of the eventfds | ||||
| */ | ||||
|  | ||||
| namespace spsc_detail | ||||
| { | ||||
| template <bool block_read, bool block_write> class spsc_cond_detail { | ||||
| 	std::condition_variable cond_r, cond_w; | ||||
| 	std::mutex lr, lw; | ||||
| 	std::atomic_int r_flag, w_flag; | ||||
|  | ||||
|     public: | ||||
| 	explicit spsc_cond_detail() : r_flag(0), w_flag(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	~spsc_cond_detail() | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	ssize_t spsc_check_r() | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> lk(lr); | ||||
| 		while (r_flag == 0) | ||||
| 			cond_r.wait(lk); | ||||
| 		r_flag--; | ||||
| 		return 1; | ||||
| 	} | ||||
| 	ssize_t spsc_check_w() | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> lk(lw); | ||||
| 		while (w_flag == 0) | ||||
| 			cond_w.wait(lk); | ||||
| 		w_flag--; | ||||
| 		return 1; | ||||
| 	} | ||||
| 	void spsc_notify_r() | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> lk(lr); | ||||
| 		r_flag++; | ||||
| 		cond_r.notify_one(); | ||||
| 	} | ||||
| 	void spsc_notify_w() | ||||
| 	{ | ||||
| 		std::unique_lock<std::mutex> lk(lw); | ||||
| 		w_flag++; | ||||
| 		cond_w.notify_one(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| // originally designed for select loop integration | ||||
| template <bool block_read, bool block_write> class spsc_efd_detail { | ||||
| 	int efd_r, efd_w; /* eventfds used to block/notify readers/writers */ | ||||
|  | ||||
|     public: | ||||
| 	explicit spsc_efd_detail() | ||||
| 		: efd_r(eventfd(0, block_read ? 0 : EFD_NONBLOCK)), efd_w(eventfd(1, block_write ? 0 : EFD_NONBLOCK)) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	~spsc_efd_detail() | ||||
| 	{ | ||||
| 		close(efd_r); | ||||
| 		close(efd_w); | ||||
| 	} | ||||
|  | ||||
| 	ssize_t spsc_check_r() | ||||
| 	{ | ||||
| 		uint64_t efdr; | ||||
| 		return read(efd_r, &efdr, sizeof(uint64_t)); | ||||
| 	} | ||||
| 	ssize_t spsc_check_w() | ||||
| 	{ | ||||
| 		uint64_t efdr; | ||||
| 		return read(efd_w, &efdr, sizeof(uint64_t)); | ||||
| 	} | ||||
| 	void spsc_notify_r() | ||||
| 	{ | ||||
| 		uint64_t efdu = 1; | ||||
| 		write(efd_r, &efdu, sizeof(uint64_t)); | ||||
| 	} | ||||
| 	void spsc_notify_w() | ||||
| 	{ | ||||
| 		uint64_t efdu = 1; | ||||
| 		write(efd_w, &efdu, sizeof(uint64_t)); | ||||
| 	} | ||||
| 	int get_r_efd() | ||||
| 	{ | ||||
| 		return efd_r; | ||||
| 	} | ||||
| 	int get_w_efd() | ||||
| 	{ | ||||
| 		return efd_w; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| template <unsigned int SZ, typename ELEM, bool block_read, bool block_write, template <bool, bool> class T> | ||||
| class spsc : public T<block_read, block_write> { | ||||
| 	static_assert(SZ > 0, "queues need a size..."); | ||||
| 	std::atomic<unsigned int> readptr; | ||||
| 	std::atomic<unsigned int> writeptr; | ||||
|  | ||||
| 	ELEM buf[SZ]; | ||||
|  | ||||
|     public: | ||||
| 	using base_t = T<block_read, block_write>; | ||||
| 	using elem_t = ELEM; | ||||
| 	explicit spsc() : readptr(0), writeptr(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	~spsc() | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	/*! Adds element to the queue by copying the data. | ||||
|  *  \param[in] elem input buffer, must match the originally configured queue buffer size!. | ||||
|  *  \returns true if queue was not full and element was successfully pushed */ | ||||
| 	bool spsc_push(const ELEM *elem) | ||||
| 	{ | ||||
| 		size_t cur_wp, cur_rp; | ||||
| 		cur_wp = writeptr.load(std::memory_order_relaxed); | ||||
| 		cur_rp = readptr.load(std::memory_order_acquire); | ||||
| 		if ((cur_wp + 1) % SZ == cur_rp) { | ||||
| 			if (block_write) | ||||
| 				base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */ | ||||
| 			return false; | ||||
| 		} | ||||
| 		buf[cur_wp] = *elem; | ||||
| 		writeptr.store((cur_wp + 1) % SZ, std::memory_order_release); | ||||
| 		if (block_read) | ||||
| 			base_t::spsc_notify_r(); /* fine after release */ | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	/*! Removes element from the queue by copying the data. | ||||
|  *  \param[in] elem output buffer, must match the originally configured queue buffer size!. | ||||
|  *  \returns true if queue was not empty and element was successfully removed */ | ||||
| 	bool spsc_pop(ELEM *elem) | ||||
| 	{ | ||||
| 		size_t cur_wp, cur_rp; | ||||
| 		cur_wp = writeptr.load(std::memory_order_acquire); | ||||
| 		cur_rp = readptr.load(std::memory_order_relaxed); | ||||
|  | ||||
| 		if (cur_wp == cur_rp) /* blocks via prep_pop */ | ||||
| 			return false; | ||||
|  | ||||
| 		*elem = buf[cur_rp]; | ||||
| 		readptr.store((cur_rp + 1) % SZ, std::memory_order_release); | ||||
| 		if (block_write) | ||||
| 			base_t::spsc_notify_w(); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	/*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks. | ||||
|     * This function can be used to deliberately wait for a non-empty queue on the read side. | ||||
|     *  \returns result of reading the fd. */ | ||||
| 	ssize_t spsc_prep_pop() | ||||
| 	{ | ||||
| 		return base_t::spsc_check_r(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } // namespace spsc_detail | ||||
|  | ||||
| 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> {}; | ||||
							
								
								
									
										275
									
								
								Transceiver52M/ms/l1ctl_server.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								Transceiver52M/ms/l1ctl_server.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,275 @@ | ||||
| /* | ||||
|  * OsmocomBB <-> SDR connection bridge | ||||
|  * UNIX socket server for L1CTL | ||||
|  * | ||||
|  * (C) 2013 by Sylvain Munaut <tnt@246tNt.com> | ||||
|  * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> | ||||
|  * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <errno.h> | ||||
| #include <unistd.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
|  | ||||
| #include <sys/un.h> | ||||
| #include <arpa/inet.h> | ||||
| #include <sys/socket.h> | ||||
|  | ||||
| #include <osmocom/core/talloc.h> | ||||
| #include <osmocom/core/select.h> | ||||
| #include <osmocom/core/socket.h> | ||||
| #include <osmocom/core/write_queue.h> | ||||
|  | ||||
| #include <osmocom/bb/trxcon/logging.h> | ||||
| #include <osmocom/bb/trxcon/l1ctl_server.h> | ||||
|  | ||||
| #define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args) | ||||
|  | ||||
| static int l1ctl_client_read_cb(struct osmo_fd *ofd) | ||||
| { | ||||
| 	struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; | ||||
| 	struct msgb *msg; | ||||
| 	uint16_t len; | ||||
| 	int rc; | ||||
|  | ||||
| 	/* Attempt to read from socket */ | ||||
| 	rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD); | ||||
| 	if (rc != L1CTL_MSG_LEN_FIELD) { | ||||
| 		if (rc <= 0) { | ||||
| 			LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc, | ||||
| 				 strerror(errno)); | ||||
| 		} else { | ||||
| 			LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n"); | ||||
| 			rc = -EIO; | ||||
| 		} | ||||
| 		l1ctl_client_conn_close(client); | ||||
| 		return rc; | ||||
| 	} | ||||
|  | ||||
| 	/* Check message length */ | ||||
| 	len = ntohs(len); | ||||
| 	if (len > L1CTL_LENGTH) { | ||||
| 		LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
|  | ||||
| 	/* Allocate a new msg */ | ||||
| 	msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg"); | ||||
| 	if (!msg) { | ||||
| 		LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
|  | ||||
| 	msg->l1h = msgb_put(msg, len); | ||||
| 	rc = read(ofd->fd, msg->l1h, msgb_l1len(msg)); | ||||
| 	if (rc != len) { | ||||
| 		LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno)); | ||||
| 		msgb_free(msg); | ||||
| 		return rc; | ||||
| 	} | ||||
|  | ||||
| 	/* Debug print */ | ||||
| 	LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len)); | ||||
|  | ||||
| 	/* Call L1CTL handler */ | ||||
| 	client->server->cfg->conn_read_cb(client, msg); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg) | ||||
| { | ||||
| 	struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; | ||||
| 	int len; | ||||
|  | ||||
| 	if (ofd->fd <= 0) | ||||
| 		return -EINVAL; | ||||
|  | ||||
| 	len = write(ofd->fd, msg->data, msg->len); | ||||
| 	if (len != msg->len) { | ||||
| 		LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len, | ||||
| 			 msg->len); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Connection handler */ | ||||
| static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags) | ||||
| { | ||||
| 	struct l1ctl_server *server = (struct l1ctl_server *)sfd->data; | ||||
| 	struct l1ctl_client *client; | ||||
| 	int rc, client_fd; | ||||
|  | ||||
| 	client_fd = accept(sfd->fd, NULL, NULL); | ||||
| 	if (client_fd < 0) { | ||||
| 		LOGP(DL1C, LOGL_ERROR, | ||||
| 		     "Failed to accept() a new connection: " | ||||
| 		     "%s\n", | ||||
| 		     strerror(errno)); | ||||
| 		return client_fd; | ||||
| 	} | ||||
|  | ||||
| 	if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ && | ||||
| 	    server->num_clients >= server->cfg->num_clients_max) { | ||||
| 		LOGP(DL1C, LOGL_NOTICE, | ||||
| 		     "L1CTL server cannot accept more " | ||||
| 		     "than %u connection(s)\n", | ||||
| 		     server->cfg->num_clients_max); | ||||
| 		close(client_fd); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
|  | ||||
| 	client = talloc_zero(server, struct l1ctl_client); | ||||
| 	if (client == NULL) { | ||||
| 		LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n"); | ||||
| 		close(client_fd); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
|  | ||||
| 	/* Init the client's write queue */ | ||||
| 	osmo_wqueue_init(&client->wq, 100); | ||||
| 	INIT_LLIST_HEAD(&client->wq.bfd.list); | ||||
|  | ||||
| 	client->wq.write_cb = &l1ctl_client_write_cb; | ||||
| 	client->wq.read_cb = &l1ctl_client_read_cb; | ||||
| 	osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0); | ||||
|  | ||||
| 	/* Register the client's write queue */ | ||||
| 	rc = osmo_fd_register(&client->wq.bfd); | ||||
| 	if (rc != 0) { | ||||
| 		LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n"); | ||||
| 		close(client->wq.bfd.fd); | ||||
| 		talloc_free(client); | ||||
| 		return rc; | ||||
| 	} | ||||
|  | ||||
| 	llist_add_tail(&client->list, &server->clients); | ||||
| 	client->id = server->next_client_id++; | ||||
| 	client->server = server; | ||||
| 	server->num_clients++; | ||||
|  | ||||
| 	LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id); | ||||
|  | ||||
| 	if (client->server->cfg->conn_accept_cb != NULL) | ||||
| 		client->server->cfg->conn_accept_cb(client); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg) | ||||
| { | ||||
| 	uint8_t *len; | ||||
|  | ||||
| 	/* Debug print */ | ||||
| 	LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len)); | ||||
|  | ||||
| 	if (msg->l1h != msg->data) | ||||
| 		LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n"); | ||||
|  | ||||
| 	/* Prepend 16-bit length before sending */ | ||||
| 	len = msgb_push(msg, L1CTL_MSG_LEN_FIELD); | ||||
| 	osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len); | ||||
|  | ||||
| 	if (osmo_wqueue_enqueue(&client->wq, msg) != 0) { | ||||
| 		LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n"); | ||||
| 		msgb_free(msg); | ||||
| 		return -EIO; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void l1ctl_client_conn_close(struct l1ctl_client *client) | ||||
| { | ||||
| 	struct l1ctl_server *server = client->server; | ||||
|  | ||||
| 	LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n"); | ||||
|  | ||||
| 	if (server->cfg->conn_close_cb != NULL) | ||||
| 		server->cfg->conn_close_cb(client); | ||||
|  | ||||
| 	/* Close connection socket */ | ||||
| 	osmo_fd_unregister(&client->wq.bfd); | ||||
| 	close(client->wq.bfd.fd); | ||||
| 	client->wq.bfd.fd = -1; | ||||
|  | ||||
| 	/* Clear pending messages */ | ||||
| 	osmo_wqueue_clear(&client->wq); | ||||
|  | ||||
| 	client->server->num_clients--; | ||||
| 	llist_del(&client->list); | ||||
| 	talloc_free(client); | ||||
|  | ||||
| 	/* If this was the last client, reset the client IDs generator to 0. | ||||
| 	 * This way avoid assigning huge unreadable client IDs like 26545. */ | ||||
| 	if (llist_empty(&server->clients)) | ||||
| 		server->next_client_id = 0; | ||||
| } | ||||
|  | ||||
| struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg) | ||||
| { | ||||
| 	struct l1ctl_server *server; | ||||
| 	int rc; | ||||
|  | ||||
| 	LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path); | ||||
|  | ||||
| 	server = talloc(ctx, struct l1ctl_server); | ||||
| 	OSMO_ASSERT(server != NULL); | ||||
|  | ||||
| 	*server = (struct l1ctl_server){ | ||||
| 		.clients = LLIST_HEAD_INIT(server->clients), | ||||
| 		.cfg = cfg, | ||||
| 	}; | ||||
|  | ||||
| 	/* conn_read_cb shall not be NULL */ | ||||
| 	OSMO_ASSERT(cfg->conn_read_cb != NULL); | ||||
|  | ||||
| 	/* Bind connection handler */ | ||||
| 	osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0); | ||||
|  | ||||
| 	rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND); | ||||
| 	if (rc < 0) { | ||||
| 		LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno)); | ||||
| 		talloc_free(server); | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	return server; | ||||
| } | ||||
|  | ||||
| void l1ctl_server_free(struct l1ctl_server *server) | ||||
| { | ||||
| 	LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n"); | ||||
|  | ||||
| 	/* Close all client connections */ | ||||
| 	while (!llist_empty(&server->clients)) { | ||||
| 		struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list); | ||||
| 		l1ctl_client_conn_close(client); | ||||
| 	} | ||||
|  | ||||
| 	/* Unbind listening socket */ | ||||
| 	if (server->ofd.fd != -1) { | ||||
| 		osmo_fd_unregister(&server->ofd); | ||||
| 		close(server->ofd.fd); | ||||
| 		server->ofd.fd = -1; | ||||
| 	} | ||||
|  | ||||
| 	talloc_free(server); | ||||
| } | ||||
							
								
								
									
										77
									
								
								Transceiver52M/ms/l1ctl_server_cb.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Transceiver52M/ms/l1ctl_server_cb.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * Author: Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation; either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/bb/trxcon/trxcon.h> | ||||
| #include <osmocom/bb/trxcon/trxcon_fsm.h> | ||||
| #include <osmocom/bb/trxcon/l1ctl_server.h> | ||||
| } | ||||
|  | ||||
| static struct l1ctl_server_cfg server_cfg; | ||||
| static struct l1ctl_server *server = NULL; | ||||
| namespace trxcon | ||||
| { | ||||
| extern struct trxcon_inst *g_trxcon; | ||||
| } | ||||
|  | ||||
| static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg) | ||||
| { | ||||
| 	struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv; | ||||
|  | ||||
| 	return trxcon_l1ctl_receive(trxcon, msg); | ||||
| } | ||||
|  | ||||
| static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c) | ||||
| { | ||||
| 	l1c->log_prefix = talloc_strdup(l1c, trxcon::g_trxcon->log_prefix); | ||||
| 	l1c->priv = trxcon::g_trxcon; | ||||
| 	trxcon::g_trxcon->l2if = l1c; | ||||
| } | ||||
|  | ||||
| static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) | ||||
| { | ||||
| 	struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv; | ||||
|  | ||||
| 	if (trxcon == NULL || trxcon->fi == NULL) | ||||
| 		return; | ||||
|  | ||||
| 	osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); | ||||
| } | ||||
|  | ||||
| namespace trxcon | ||||
| { | ||||
| void 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, | ||||
| 		.conn_close_cb = &l1ctl_conn_close_cb, | ||||
| 	}; | ||||
|  | ||||
| 	server = l1ctl_server_alloc(tallctx, &server_cfg); | ||||
| 	if (server == NULL) { | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| } // namespace trxcon | ||||
							
								
								
									
										103
									
								
								Transceiver52M/ms/logging.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								Transceiver52M/ms/logging.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * Author: Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation; either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/core/application.h> | ||||
| #include <osmocom/bb/trxcon/logging.h> | ||||
| #include <osmocom/bb/trxcon/trxcon.h> | ||||
| #include <osmocom/bb/l1sched/l1sched.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, | ||||
| }; | ||||
|  | ||||
| static struct log_info_cat trxcon_log_info_cat[] = { | ||||
| 	[DAPP] = { | ||||
| 		.name = "DAPP", | ||||
| 		.color = "\033[1;35m", | ||||
| 		.description = "Application", | ||||
| 		.loglevel = LOGL_NOTICE, .enabled = 1, | ||||
| 	}, | ||||
| 	[DL1C] = { | ||||
| 		.name = "DL1C", | ||||
| 		.color = "\033[1;31m", | ||||
| 		.description = "Layer 1 control interface", | ||||
| 		.loglevel = LOGL_NOTICE, .enabled = 1, | ||||
| 	}, | ||||
| 	[DL1D] = { | ||||
| 		.name = "DL1D", | ||||
| 		.color = "\033[1;31m", | ||||
| 		.description = "Layer 1 data", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 1, | ||||
| 	}, | ||||
| 	[DTRXC] = { | ||||
| 		.name = "DTRXC", | ||||
| 		.color = "\033[1;33m", | ||||
| 		.description = "Transceiver control interface", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 1, | ||||
| 	}, | ||||
| 	[DTRXD] = { | ||||
| 		.name = "DTRXD", | ||||
| 		.color = "\033[1;33m", | ||||
| 		.description = "Transceiver data interface", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 1, | ||||
| 	}, | ||||
| 	[DSCH] = { | ||||
| 		.name = "DSCH", | ||||
| 		.color = "\033[1;36m", | ||||
| 		.description = "Scheduler management", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 0, | ||||
| 	}, | ||||
| 	[DSCHD] = { | ||||
| 		.name = "DSCHD", | ||||
| 		.color = "\033[1;36m", | ||||
| 		.description = "Scheduler data", | ||||
|         .loglevel = LOGL_NOTICE, | ||||
| 		.enabled = 0,  | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| static struct log_info trxcon_log_info = { | ||||
| 	.cat = trxcon_log_info_cat, | ||||
| 	.num_cat = ARRAY_SIZE(trxcon_log_info_cat), | ||||
| }; | ||||
|  | ||||
| namespace trxcon | ||||
| { | ||||
|  | ||||
| void trxc_log_init(void *tallctx) | ||||
| { | ||||
| 	osmo_init_logging2(tallctx, &trxcon_log_info); | ||||
| 	// log_parse_category_mask(osmo_stderr_target, ""); | ||||
|  | ||||
| 	trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg)); | ||||
| } | ||||
|  | ||||
| } // namespace trxcon | ||||
							
								
								
									
										339
									
								
								Transceiver52M/ms/ms.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								Transceiver52M/ms/ms.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,339 @@ | ||||
|  | ||||
| /* | ||||
|  * (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 "GSMCommon.h" | ||||
| #include <atomic> | ||||
| #include <cassert> | ||||
| #include <complex> | ||||
| #include <iostream> | ||||
| #include <cstdlib> | ||||
| #include <cstdio> | ||||
| #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; | ||||
|  | ||||
| #ifdef DBGXX | ||||
| const int offsetrange = 200; | ||||
| 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 | ||||
|  | ||||
| int ms_trx::init_dev_and_streams() | ||||
| { | ||||
| 	int status = 0; | ||||
| 	status = base::init_device(rx_bh(), tx_bh()); | ||||
| 	if (status < 0) { | ||||
| 		std::cerr << "failed to init dev!" << std::endl; | ||||
| 		return -1; | ||||
| 	} | ||||
| 	return status; | ||||
| } | ||||
|  | ||||
| bh_fn_t ms_trx::rx_bh() | ||||
| { | ||||
| 	return [this](dev_buf_t *rcd) -> int { | ||||
| 		if (this->search_for_sch(rcd) == SCH_STATE::FOUND) | ||||
| 			this->grab_bursts(rcd); | ||||
| 		return 0; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| bh_fn_t ms_trx::tx_bh() | ||||
| { | ||||
| 	return [this](dev_buf_t *rcd) -> int { | ||||
| #pragma unused(rcd) | ||||
| 		auto y = this; | ||||
| #pragma unused(y) | ||||
| 		/* nothing to do here */ | ||||
| 		return 0; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| void ms_trx::start() | ||||
| { | ||||
| 	auto fn = get_rx_burst_handler_fn(rx_bh()); | ||||
| 	rx_task = std::thread(fn); | ||||
| 	set_name_aff_sched(rx_task.native_handle(), "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2); | ||||
|  | ||||
| 	usleep(1000); | ||||
| 	auto fn2 = get_tx_burst_handler_fn(tx_bh()); | ||||
| 	tx_task = std::thread(fn2); | ||||
| 	set_name_aff_sched(tx_task.native_handle(), "txrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1); | ||||
| } | ||||
|  | ||||
| void ms_trx::set_upper_ready(bool is_ready) | ||||
| { | ||||
| 	upper_is_ready = is_ready; | ||||
| } | ||||
|  | ||||
| void ms_trx::stop_threads() | ||||
| { | ||||
| 	std::cerr << "killing threads...\r\n" << std::endl; | ||||
| 	close_device(); | ||||
| 	rx_task.join(); | ||||
| 	tx_task.join(); | ||||
| } | ||||
|  | ||||
| void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target) | ||||
| { | ||||
| 	int64_t now_ts; | ||||
| 	GSM::Time now_time; | ||||
| 	target.incTN(3); // ul dl offset | ||||
| 	int target_fn = target.FN(); | ||||
| 	int target_tn = target.TN(); | ||||
| 	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 | ||||
| 		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/OTHER: " << now_time.TN() | ||||
| 		  << "/" << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN() | ||||
| 		  << " check: " << check.FN() << ":" << check.TN() << " target: " << target.FN() << ":" << target.TN() | ||||
| 		  << " ts now: " << now_ts << " target ts:" << send_ts << std::endl; | ||||
| #endif | ||||
| #if 1 | ||||
| 	unsigned int pad = 4 * 4; | ||||
| 	blade_sample_type buf2[len + pad]; | ||||
| 	memset(buf2, 0, pad * sizeof(blade_sample_type)); | ||||
| 	memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type)); | ||||
|  | ||||
| 	assert(target.FN() == check.FN()); | ||||
| 	assert(target.TN() == check.TN()); | ||||
| 	submit_burst_ts(buf2, len + pad, send_ts - pad); | ||||
| #else | ||||
| 	submit_burst_ts(buffer, len, send_ts); | ||||
| #endif | ||||
| } | ||||
							
								
								
									
										264
									
								
								Transceiver52M/ms/ms.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								Transceiver52M/ms/ms.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | ||||
| #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 <atomic> | ||||
| #include <cassert> | ||||
| #include <complex> | ||||
| #include <cstdint> | ||||
| #include <mutex> | ||||
| #include <iostream> | ||||
| #include <thread> | ||||
|  | ||||
| #if defined(BUILDBLADE) | ||||
| #include "bladerf_specific.h" | ||||
| #define BASET blade_hw<ms_trx> | ||||
| #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 "GSMCommon.h" | ||||
| #include "itrq.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) | ||||
| { | ||||
| 	for (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) | ||||
| { | ||||
| 	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; | ||||
| } | ||||
| template <typename DST_T, typename SRC_T> void convert_and_scale_default(void *dst, void *src, unsigned int src_len) | ||||
| { | ||||
| 	return convert_and_scale<DST_T, SRC_T>(dst, src, src_len, SAMPLE_SCALE_FACTOR); | ||||
| } | ||||
|  | ||||
| struct one_burst { | ||||
| 	one_burst() | ||||
| 	{ | ||||
| 	} | ||||
| 	GSM::Time gsmts; | ||||
| 	union { | ||||
| 		blade_sample_type burst[ONE_TS_BURST_LEN]; | ||||
| 		char sch_bits[148]; | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| using rx_queue_t = spsc_cond<8 * NUM_RXQ_FRAMES, one_burst, true, true>; | ||||
|  | ||||
| enum class SCH_STATE { SEARCHING, FOUND }; | ||||
|  | ||||
| class dummylog : private std::streambuf { | ||||
| 	std::ostream null_stream; | ||||
|  | ||||
|     public: | ||||
| 	dummylog() : null_stream(this){}; | ||||
| 	~dummylog() override{}; | ||||
| 	std::ostream &operator()() | ||||
| 	{ | ||||
| 		return null_stream; | ||||
| 	} | ||||
| 	int overflow(int c) override | ||||
| 	{ | ||||
| 		return c; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| // keeps relationship between gsm time and (continuously adjusted) ts | ||||
| class time_keeper { | ||||
| 	GSM::Time global_time_keeper; | ||||
| 	int64_t global_ts_keeper; | ||||
| 	std::mutex m; | ||||
|  | ||||
|     public: | ||||
| 	time_keeper() : global_time_keeper(0), global_ts_keeper(0) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	void set(GSM::Time t, int64_t ts) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		global_time_keeper = t; | ||||
| 		global_ts_keeper = ts; | ||||
| 	} | ||||
| 	void inc_both() | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		global_time_keeper.incTN(1); | ||||
| 		global_ts_keeper += ONE_TS_BURST_LEN; | ||||
| 	} | ||||
| 	void inc_and_update(int64_t new_ts) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		global_time_keeper.incTN(1); | ||||
| 		global_ts_keeper = new_ts; | ||||
| 		// std::cerr << "u " << new_ts << std::endl; | ||||
| 	} | ||||
| 	void inc_and_update_safe(int64_t new_ts) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		auto diff = new_ts - global_ts_keeper; | ||||
| 		assert(diff < 1.5 * ONE_TS_BURST_LEN); | ||||
| 		assert(diff > 0.5 * ONE_TS_BURST_LEN); | ||||
| 		global_time_keeper.incTN(1); | ||||
| 		global_ts_keeper = new_ts; | ||||
| 		// std::cerr << "s " << new_ts << std::endl; | ||||
| 	} | ||||
| 	void dec_by_one() | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		global_time_keeper.decTN(1); | ||||
| 		global_ts_keeper -= ONE_TS_BURST_LEN; | ||||
| 	} | ||||
| 	auto get_ts() | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		return global_ts_keeper; | ||||
| 	} | ||||
| 	auto gsmtime() | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		return global_time_keeper; | ||||
| 	} | ||||
| 	void get_both(GSM::Time *t, int64_t *ts) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> g(m); | ||||
| 		*t = global_time_keeper; | ||||
| 		*ts = global_ts_keeper; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| using ts_hitter_q_t = spsc_cond<64, GSM::Time, true, false>; | ||||
|  | ||||
| struct ms_trx : public BASET { | ||||
| 	using base = BASET; | ||||
| 	static dummylog dummy_log; | ||||
| 	unsigned int mTSC; | ||||
| 	unsigned int mBSIC; | ||||
| 	int timing_advance; | ||||
| 	bool do_auto_gain; | ||||
|  | ||||
| 	std::thread rx_task; | ||||
| 	std::thread tx_task; | ||||
| 	std::thread *calcrval_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; | ||||
|  | ||||
| 	uint64_t first_sch_buf_rcv_ts; | ||||
| 	std::atomic<bool> rcv_done; | ||||
| 	std::atomic<bool> sch_thread_done; | ||||
|  | ||||
| 	int64_t temp_ts_corr_offset = 0; | ||||
| 	int64_t first_sch_ts_start = -1; | ||||
|  | ||||
| 	time_keeper timekeeper; | ||||
|  | ||||
| 	void start(); | ||||
| 	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); | ||||
| 	SCH_STATE search_for_sch(dev_buf_t *rcd); | ||||
| 	void grab_bursts(dev_buf_t *rcd) __attribute__((optnone)); | ||||
|  | ||||
| 	int init_device(); | ||||
| 	int init_dev_and_streams(); | ||||
| 	void stop_threads(); | ||||
| 	void *rx_cb(ms_trx *t); | ||||
| 	void *tx_cb(); | ||||
| 	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 } | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	virtual ~ms_trx() | ||||
| 	{ | ||||
| 		delete[] burst_copy_buffer; | ||||
| 		delete[] first_sch_buf; | ||||
| 	} | ||||
| 	bh_fn_t rx_bh(); | ||||
| 	bh_fn_t tx_bh(); | ||||
|  | ||||
| 	void submit_burst(blade_sample_type *buffer, int len, GSM::Time); | ||||
| 	void set_ta(int val) | ||||
| 	{ | ||||
| 		assert(val > -127 && val < 128); | ||||
| 		timing_advance = val * 4; | ||||
| 	} | ||||
|  | ||||
| 	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio) | ||||
| 	{ | ||||
| 		set_name_aff_sched(pthread_self(), name, cpunum, schedtype, prio); | ||||
| 	} | ||||
|  | ||||
| 	void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype, | ||||
| 				int prio) | ||||
| 	{ | ||||
| 		pthread_setname_np(h, name); | ||||
|  | ||||
| 		cpu_set_t cpuset; | ||||
|  | ||||
| 		CPU_ZERO(&cpuset); | ||||
| 		CPU_SET(cpunum, &cpuset); | ||||
|  | ||||
| 		auto rv = pthread_setaffinity_np(h, sizeof(cpuset), &cpuset); | ||||
| 		if (rv < 0) { | ||||
| 			std::cerr << name << " affinity: errreur! " << std::strerror(errno); | ||||
| 			return exit(0); | ||||
| 		} | ||||
|  | ||||
| 		sched_param sch_params; | ||||
| 		sch_params.sched_priority = prio; | ||||
| 		rv = pthread_setschedparam(h, schedtype, &sch_params); | ||||
| 		if (rv < 0) { | ||||
| 			std::cerr << name << " sched: errreur! " << std::strerror(errno); | ||||
| 			return exit(0); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										25
									
								
								Transceiver52M/ms/ms_rx_burst.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Transceiver52M/ms/ms_rx_burst.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| #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 "ms.h" | ||||
|  | ||||
| void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale); | ||||
							
								
								
									
										207
									
								
								Transceiver52M/ms/ms_rx_burst_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								Transceiver52M/ms/ms_rx_burst_test.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| /* | ||||
|  * (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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										343
									
								
								Transceiver52M/ms/ms_rx_lower.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								Transceiver52M/ms/ms_rx_lower.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,343 @@ | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * Author: Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation; either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "sigProcLib.h" | ||||
| #include "signalVector.h" | ||||
| #include <atomic> | ||||
| #include <cassert> | ||||
| #include <complex> | ||||
| #include <iostream> | ||||
| #include <future> | ||||
|  | ||||
| #include "ms.h" | ||||
| #include "grgsm_vitac/grgsm_vitac.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include "sch.h" | ||||
| } | ||||
|  | ||||
| #ifdef LOG | ||||
| #undef LOG | ||||
| #endif | ||||
|  | ||||
| #if !defined(SYNCTHINGONLY) //|| !defined(NODAMNLOG) | ||||
| #define DBGLG(...) ms_trx::dummy_log() | ||||
| #else | ||||
| #define DBGLG(...) std::cerr | ||||
| #endif | ||||
|  | ||||
| #if !defined(SYNCTHINGONLY) || !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) | ||||
| { | ||||
| 	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); | ||||
|  | ||||
| 	if (!gsm_sch_decode(info, data)) { | ||||
| 		gsm_sch_parse(info, &sch); | ||||
|  | ||||
| 		if (update_global_clock) { | ||||
| 			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; | ||||
| 		} | ||||
|  | ||||
| 		fn = gsm_sch_to_fn(&sch); | ||||
| 		if (fn < 0) { // how? wh? | ||||
| 			DBGLG() << "SCH : Failed to convert FN " << std::endl; | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		if (update_global_clock) { | ||||
| 			mBSIC = sch.bsic; | ||||
| 			mTSC = sch.bsic & 0x7; | ||||
| 			timekeeper.set(fn, 0); | ||||
| 			// 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; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void ms_trx::maybe_update_gain(one_burst &brst) | ||||
| { | ||||
| 	static_assert((sizeof(brst.burst) / sizeof(brst.burst[0])) == ONE_TS_BURST_LEN, "wtf, buffer size mismatch?"); | ||||
| 	const int avgburst_num = 8 * 20; // ~ 50*4.5ms = 90ms? | ||||
| 	static_assert(avgburst_num * 577 > (50 * 1000), "can't update faster then blade wait time?"); | ||||
| 	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; | ||||
|  | ||||
| 	runmean = gain_check ? (runmean * (gain_check + 2) - 1 + sum) / (gain_check + 2) : sum; | ||||
|  | ||||
| 	if (gain_check == avgburst_num - 1) { | ||||
| 		DBGLG2() << "\x1B[32m #RXG \033[0m" << rxgain << " " << runmean << " " << sum << std::endl; | ||||
| 		auto gainoffset = runmean < (rxFullScale / 4 ? 4 : 2); | ||||
| 		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(); | ||||
| 		runmean = 0; | ||||
| 	} | ||||
| 	gain_check = (gain_check + 1) % avgburst_num; | ||||
| } | ||||
|  | ||||
| static char sch_demod_bits[148]; | ||||
|  | ||||
| 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; | ||||
|  | ||||
| 	if (!is_sch) { | ||||
| 		memcpy(brst.burst, burst_copy_buffer, sizeof(blade_sample_type) * ONE_TS_BURST_LEN); | ||||
| 	} else { | ||||
| 		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 | ||||
|  | ||||
| 	if (do_auto_gain) | ||||
| 		maybe_update_gain(brst); | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| static float sch_acq_buffer[SCH_LEN_SPS * 2]; | ||||
|  | ||||
| bool ms_trx::handle_sch(bool is_first_sch_acq) | ||||
| { | ||||
| 	auto current_gsm_time = timekeeper.gsmtime(); | ||||
| 	const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN; | ||||
| 	const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer; | ||||
| 	const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2]; | ||||
| 	const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer); | ||||
| 	std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; | ||||
|  | ||||
| 	int start; | ||||
| 	memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer)); | ||||
| 	if (is_first_sch_acq) { | ||||
| 		float max_corr = 0; | ||||
| 		convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2, | ||||
| 						  1.f / float(rxFullScale)); | ||||
| 		start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr); | ||||
| 		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); | ||||
| 	} | ||||
|  | ||||
| 	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); | ||||
|  | ||||
| 	if (sch_decode_success) { | ||||
| 		const auto ts_offset_symb = 0; | ||||
| 		if (is_first_sch_acq) { | ||||
| 			// update ts to first sample in sch buffer, to allow delay calc for current ts | ||||
| 			first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4) - 1; | ||||
| 		} else if (abs(start) > 1) { | ||||
| 			// continuous sch tracking, only update if off too much | ||||
| 			temp_ts_corr_offset += -start; | ||||
| 			std::cerr << "offs: " << start << " " << temp_ts_corr_offset << std::endl; | ||||
| 		} | ||||
|  | ||||
| 		return true; | ||||
| 	} else { | ||||
| 		DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN() | ||||
| 			 << ":" << current_gsm_time.TN() << std::endl; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| __attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd) | ||||
| { | ||||
| 	static unsigned int sch_pos = 0; | ||||
| 	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 | ||||
| 		first_sch_buf_rcv_ts = rcd->get_first_ts(); | ||||
|  | ||||
| 	if (!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); | ||||
| 			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; | ||||
|  | ||||
| 			//FIXME: arbitrary value, gain cutoff | ||||
| 			if (sum > target_val || rxgain >= 60) // enough ? | ||||
| 				sch_thread_done = this->handle_sch(true); | ||||
| 			else { | ||||
| 				std::cerr << "\x1B[32m #RXG \033[0m gain " << rxgain << " -> " << rxgain + 4 | ||||
| 					  << " sample avg:" << sum << " target: >=" << target_val << std::endl; | ||||
| 				setRxGain(rxgain + 4); | ||||
| 			} | ||||
|  | ||||
| 			if (!sch_thread_done) | ||||
| 				rcv_done = false; // retry! | ||||
| 			return (bool)sch_thread_done; | ||||
| 		}).detach(); | ||||
| 	} | ||||
|  | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| void ms_trx::grab_bursts(dev_buf_t *rcd) | ||||
| { | ||||
| 	// partial burst samples read from the last buffer | ||||
| 	static int partial_rdofs = 0; | ||||
| 	static bool first_call = true; | ||||
| 	int to_skip = 0; | ||||
|  | ||||
| 	// round up to next burst by calculating the time between sch detection and now | ||||
| 	if (first_call) { | ||||
| 		const auto next_burst_start = rcd->get_first_ts() - first_sch_ts_start; | ||||
| 		const auto fullts = next_burst_start / ONE_TS_BURST_LEN; | ||||
| 		const auto fracts = next_burst_start % ONE_TS_BURST_LEN; | ||||
| 		to_skip = ONE_TS_BURST_LEN - fracts; | ||||
|  | ||||
| 		for (int i = 0; i < fullts; i++) | ||||
| 			timekeeper.inc_and_update(first_sch_ts_start + i * ONE_TS_BURST_LEN); | ||||
|  | ||||
| 		if (fracts) | ||||
| 			timekeeper.inc_both(); | ||||
| 		// timekeeper.inc_and_update(first_sch_ts_start + 1 * ONE_TS_BURST_LEN); | ||||
|  | ||||
| 		timekeeper.dec_by_one(); // oops, off by one? | ||||
|  | ||||
| 		timekeeper.set(timekeeper.gsmtime(), rcd->get_first_ts() - ONE_TS_BURST_LEN + to_skip); | ||||
|  | ||||
| 		DBGLG() << "this ts: " << rcd->get_first_ts() << " diff full TN: " << fullts << " frac TN: " << fracts | ||||
| 			<< " GSM now: " << timekeeper.gsmtime().FN() << ":" << timekeeper.gsmtime().TN() << " is sch? " | ||||
| 			<< gsm_sch_check_fn(timekeeper.gsmtime().FN()) << std::endl; | ||||
| 		first_call = false; | ||||
| 	} | ||||
|  | ||||
| 	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) { | ||||
| 			partial_rdofs += rd; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		timekeeper.inc_and_update_safe(rcd->get_first_ts() - partial_rdofs); | ||||
| 		handle_sch_or_nb(); | ||||
| 		to_skip = first_remaining; | ||||
| 	} | ||||
|  | ||||
| 	// apply sample rate slippage compensation | ||||
| 	to_skip -= temp_ts_corr_offset; | ||||
|  | ||||
| 	// FIXME: happens rarely, read_n start -1 blows up | ||||
| 	// this is fine: will just be corrected one buffer later | ||||
| 	if (to_skip < 0) | ||||
| 		to_skip = 0; | ||||
| 	else | ||||
| 		temp_ts_corr_offset = 0; | ||||
|  | ||||
| 	const auto left_after_burst = rcd->actual_samples_per_buffer() - to_skip; | ||||
|  | ||||
| 	const int full = left_after_burst / ONE_TS_BURST_LEN; | ||||
| 	const int frac = left_after_burst % ONE_TS_BURST_LEN; | ||||
|  | ||||
| 	for (int i = 0; i < full; i++) { | ||||
| 		rcd->read_n(burst_copy_buffer, to_skip + i * ONE_TS_BURST_LEN, ONE_TS_BURST_LEN); | ||||
| 		timekeeper.inc_and_update_safe(rcd->get_first_ts() + to_skip + i * ONE_TS_BURST_LEN); | ||||
| 		handle_sch_or_nb(); | ||||
| 	} | ||||
|  | ||||
| 	if (frac) | ||||
| 		rcd->read_n(burst_copy_buffer, to_skip + full * ONE_TS_BURST_LEN, frac); | ||||
| 	partial_rdofs = frac; | ||||
| } | ||||
							
								
								
									
										451
									
								
								Transceiver52M/ms/ms_upper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										451
									
								
								Transceiver52M/ms/ms_upper.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,451 @@ | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * Author: Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation; either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "sigProcLib.h" | ||||
| #include "ms.h" | ||||
| #include <signalVector.h> | ||||
| #include <radioVector.h> | ||||
| #include <radioInterface.h> | ||||
| #include "grgsm_vitac/grgsm_vitac.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include <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> | ||||
|  | ||||
| #ifdef LSANDEBUG | ||||
| void __lsan_do_recoverable_leak_check(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #include "ms_upper.h" | ||||
|  | ||||
| namespace trxcon | ||||
| { | ||||
| extern "C" { | ||||
| #include <osmocom/core/fsm.h> | ||||
| #include <osmocom/core/msgb.h> | ||||
| #include <osmocom/core/talloc.h> | ||||
| #include <osmocom/core/signal.h> | ||||
| #include <osmocom/core/select.h> | ||||
| #include <osmocom/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> | ||||
| } | ||||
| 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 | ||||
|  | ||||
| #ifdef LOG | ||||
| #undef LOG | ||||
| #define LOG(...) upper_trx::dummy_log() | ||||
| #endif | ||||
|  | ||||
| #define DBGLG(...) upper_trx::dummy_log() | ||||
|  | ||||
| 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(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// atomic ensures data is not written to q until loop reads | ||||
| 	start_lower_ms(); | ||||
|  | ||||
| 	set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5); | ||||
| 	while (1) { | ||||
| 		// set_upper_ready(true); | ||||
| 		driveReceiveFIFO(); | ||||
| 		osmo_select_main(1); | ||||
|  | ||||
| 		trxcon::trxcon_phyif_rsp r; | ||||
| 		if (trxcon::cmdq_from_phy.spsc_pop(&r)) { | ||||
| 			DBGLG() << "HAVE RESP:" << r.type << std::endl; | ||||
| 			trxcon_phyif_handle_rsp(trxcon::g_trxcon, &r); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| #ifdef LSANDEBUG | ||||
| 	std::thread([this] { | ||||
| 		set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10); | ||||
|  | ||||
| 		while (1) { | ||||
| 			std::this_thread::sleep_for(std::chrono::seconds{ 5 }); | ||||
| 			__lsan_do_recoverable_leak_check(); | ||||
| 		} | ||||
| 	}).detach(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void upper_trx::start_lower_ms() | ||||
| { | ||||
| 	ms_trx::start(); | ||||
| } | ||||
|  | ||||
| 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); | ||||
| 	one_burst e; | ||||
| 	auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[40]); | ||||
| 	memset((void *)&workbuf[0], 0, sizeof(workbuf)); | ||||
| 	// assert(sv.begin() == &workbuf[40]); | ||||
|  | ||||
| 	while (!rxqueue.spsc_pop(&e)) { | ||||
| 		rxqueue.spsc_prep_pop(); | ||||
| 	} | ||||
|  | ||||
| 	wTime = e.gsmts; | ||||
|  | ||||
| 	const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN()); | ||||
| 	const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN()); | ||||
|  | ||||
| 	trxcon::trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) }; | ||||
| 	trxcon::trxcon_phyif_rtr_rsp r = {}; | ||||
| 	trxcon_phyif_handle_rtr_ind(trxcon::g_trxcon, &i, &r); | ||||
| 	if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (is_fcch) { | ||||
| 		// return trash | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	if (is_sch) { | ||||
| 		for (int i = 0; i < 148; i++) | ||||
| 			(demodded_softbits)[i] = (e.sch_bits[i]); | ||||
| 		RSSI = 10; | ||||
| 		timingOffset = 0; | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale)); | ||||
|  | ||||
| 	pow = energyDetect(sv, 20 * 4 /*sps*/); | ||||
| 	if (pow < -1) { | ||||
| 		LOG(ALERT) << "Received empty burst"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	avg = sqrt(pow); | ||||
| 	{ | ||||
| 		float ncmax; | ||||
| 		std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; | ||||
| 		auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC); | ||||
| #ifdef DBGXX | ||||
| 		float dcmax; | ||||
| 		std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR]; | ||||
| 		auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY); | ||||
| 		auto is_nb = ncmax > dcmax; | ||||
| 		// DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start | ||||
| 		// 	  << " o db: " << dummy_burst_start << std::endl; | ||||
| #endif | ||||
| 		normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39; | ||||
| 		normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39; | ||||
| #ifdef DBGXX | ||||
| 		// fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN()); | ||||
| 		// if (is_nb) | ||||
| #endif | ||||
| 		detect_burst(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits); | ||||
| #ifdef DBGXX | ||||
| 		// else | ||||
| 		// 	detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin); | ||||
| #endif | ||||
| 	} | ||||
| 	RSSI = (int)floor(20.0 * log10(rxFullScale / avg)); | ||||
| 	timingOffset = (int)round(0); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void upper_trx::driveReceiveFIFO() | ||||
| { | ||||
| 	int RSSI; | ||||
| 	int TOA; // in 1/256 of a symbol | ||||
| 	GSM::Time burstTime; | ||||
|  | ||||
| 	if (!mOn) | ||||
| 		return; | ||||
|  | ||||
| 	if (pullRadioVector(burstTime, RSSI, TOA)) { | ||||
| 		// trxcon::trx_data_rx_handler(trxcon::trxcon_instance, (uint8_t *)&response); | ||||
| 		trxcon::trxcon_phyif_burst_ind bi; | ||||
| 		bi.fn = burstTime.FN(); | ||||
| 		bi.tn = burstTime.TN(); | ||||
| 		bi.rssi = RSSI; | ||||
| 		bi.toa256 = TOA; | ||||
| 		bi.burst = (sbit_t *)demodded_softbits; | ||||
| 		bi.burst_len = sizeof(demodded_softbits); | ||||
| 		// trxcon_phyif_handle_clock_ind(trxcon::g_trxcon, bi.fn); | ||||
| 		trxcon_phyif_handle_burst_ind(trxcon::g_trxcon, &bi); | ||||
| 	} | ||||
|  | ||||
| 	struct trxcon::trxcon_phyif_rts_ind rts { | ||||
| 		static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN()) | ||||
| 	}; | ||||
| 	trxcon_phyif_handle_rts_ind(trxcon::g_trxcon, &rts); | ||||
| } | ||||
|  | ||||
| void upper_trx::driveTx() | ||||
| { | ||||
| 	trxcon::internal_q_tx_buf e; | ||||
| 	while (!trxcon::txq.spsc_pop(&e)) { | ||||
| 		trxcon::txq.spsc_prep_pop(); | ||||
| 	} | ||||
|  | ||||
| 	trxcon::internal_q_tx_buf *burst = &e; | ||||
|  | ||||
| #ifdef TXDEBUG | ||||
| 	DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN() | ||||
| 		<< " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl; | ||||
| #endif | ||||
|  | ||||
| 	auto currTime = GSM::Time(burst->r.fn, burst->r.tn); | ||||
| 	int RSSI = (int)burst->r.pwr; | ||||
|  | ||||
| 	static BitVector newBurst(gSlotLen); | ||||
| 	BitVector::iterator itr = newBurst.begin(); | ||||
| 	auto *bufferItr = burst->buf; | ||||
| 	while (itr < newBurst.end()) | ||||
| 		*itr++ = *bufferItr++; | ||||
|  | ||||
| 	auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4); | ||||
| 	scaleVector(*txburst, txFullScale * pow(10, -RSSI / 10)); | ||||
|  | ||||
| 	// float -> int16 | ||||
| 	blade_sample_type burst_buf[txburst->size()]; | ||||
| 	convert_and_scale<int16_t, float>(burst_buf, txburst->begin(), txburst->size() * 2, 1); | ||||
| #ifdef TXDEBUG | ||||
| 	auto check = signalVector(txburst->size(), 40); | ||||
| 	convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2); | ||||
| 	estim_burst_params ebp; | ||||
| 	auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp); | ||||
| 	if (d) | ||||
| 		DBGLG() << "RACH D! " << ebp.toa << std::endl; | ||||
| 	else | ||||
| 		DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl; | ||||
|  | ||||
| 		// memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force | ||||
| #endif | ||||
| 	submit_burst(burst_buf, txburst->size(), currTime); | ||||
| 	delete txburst; | ||||
| } | ||||
|  | ||||
| static const char *cmd2str(trxcon::trxcon_phyif_cmd_type c) | ||||
| { | ||||
| 	switch (c) { | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_RESET: | ||||
| 		return "TRXCON_PHYIF_CMDT_RESET"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_POWERON: | ||||
| 		return "TRXCON_PHYIF_CMDT_POWERON"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_POWEROFF: | ||||
| 		return "TRXCON_PHYIF_CMDT_POWEROFF"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_MEASURE: | ||||
| 		return "TRXCON_PHYIF_CMDT_MEASURE"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETFREQ_H0"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETFREQ_H1"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETSLOT: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETSLOT"; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETTA: | ||||
| 		return "TRXCON_PHYIF_CMDT_SETTA"; | ||||
| 	default: | ||||
| 		return "UNKNOWN COMMAND!"; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void print_cmd(trxcon::trxcon_phyif_cmd_type c) | ||||
| { | ||||
| 	DBGLG() << cmd2str(c) << std::endl; | ||||
| } | ||||
|  | ||||
| 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(); | ||||
| 	} | ||||
| 	print_cmd(cmd.type); | ||||
|  | ||||
| 	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); | ||||
| 		set_ta(0); | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_MEASURE: | ||||
| 		r.type = trxcon::trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE; | ||||
| 		r.param.measure.band_arfcn = cmd.param.measure.band_arfcn; | ||||
| 		r.param.measure.dbm = -80; | ||||
| 		tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100); | ||||
| 		tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100); | ||||
| 		trxcon::cmdq_from_phy.spsc_push(&r); | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0: | ||||
| 		// 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); | ||||
|  | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1: | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETSLOT: | ||||
| 		break; | ||||
| 	case trxcon::TRXCON_PHYIF_CMDT_SETTA: | ||||
| 		set_ta(cmd.param.setta.ta); | ||||
| 		break; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| // trxcon C call(back) if | ||||
| extern "C" { | ||||
| int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon::trxcon_phyif_burst_req *br) | ||||
| { | ||||
| 	if (br->burst_len == 0) // dummy/nope | ||||
| 		return 0; | ||||
| 	assert(br->burst != 0); | ||||
|  | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| 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); | ||||
| } | ||||
| } | ||||
|  | ||||
| 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); | ||||
|  | ||||
| 	trxcon::g_trxcon = trxcon::trxcon_inst_alloc(tall_trxcon_ctx, 0, 3); | ||||
| 	trxcon::g_trxcon->gsmtap = 0; | ||||
| 	trxcon::g_trxcon->phyif = (void *)0x1234; | ||||
|  | ||||
| 	pthread_setname_np(pthread_self(), "main_trxc"); | ||||
| 	convolve_init(); | ||||
| 	convert_init(); | ||||
| 	sigProcLibSetup(); | ||||
| 	initvita(); | ||||
|  | ||||
| 	int status = 0; | ||||
| 	auto trx = new upper_trx(); | ||||
| 	trx->do_auto_gain = true; | ||||
|  | ||||
| 	status = trx->init_dev_and_streams(); | ||||
| 	trx->set_name_aff_sched("main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5); | ||||
|  | ||||
| 	trxcon::trxc_l1ctl_init(tall_trxcon_ctx); | ||||
|  | ||||
| 	trx->start_threads(); | ||||
|  | ||||
| 	return status; | ||||
| } | ||||
							
								
								
									
										57
									
								
								Transceiver52M/ms/ms_upper.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Transceiver52M/ms/ms_upper.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| /* | ||||
|  * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * Author: Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation; either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
| #include <netdb.h> | ||||
| #include <sys/socket.h> | ||||
| #include <arpa/inet.h> | ||||
|  | ||||
| #include "GSMCommon.h" | ||||
| #include "radioClock.h" | ||||
| #include "ms.h" | ||||
|  | ||||
| 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; | ||||
| 	char demodded_softbits[444]; | ||||
|  | ||||
| 	// void driveControl(); | ||||
| 	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(){}; | ||||
| }; | ||||
							
								
								
									
										324
									
								
								Transceiver52M/ms/sch.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								Transceiver52M/ms/sch.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| /* | ||||
|  * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> | ||||
|  * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> | ||||
|  * (C) 2016 by Tom Tsou <tom.tsou@ettus.com> | ||||
|  * (C) 2017 by Harald Welte <laforge@gnumonks.org> | ||||
|  * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * SPDX-License-Identifier: GPL-2.0+ | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  */ | ||||
|  | ||||
| #include <complex.h> | ||||
| #include <stdio.h> | ||||
| #include <math.h> | ||||
| #include <string.h> | ||||
|  | ||||
| #include <osmocom/core/bits.h> | ||||
| #include <osmocom/core/conv.h> | ||||
| #include <osmocom/core/utils.h> | ||||
| #include <osmocom/core/crcgen.h> | ||||
| #include <osmocom/coding/gsm0503_coding.h> | ||||
| #include <osmocom/coding/gsm0503_parity.h> | ||||
|  | ||||
| #include "sch.h" | ||||
|  | ||||
| /* GSM 04.08, 9.1.30 Synchronization channel information */ | ||||
| struct sch_packed_info { | ||||
| 	ubit_t t1_hi[2]; | ||||
| 	ubit_t bsic[6]; | ||||
| 	ubit_t t1_md[8]; | ||||
| 	ubit_t t3p_hi[2]; | ||||
| 	ubit_t t2[5]; | ||||
| 	ubit_t t1_lo[1]; | ||||
| 	ubit_t t3p_lo[1]; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct sch_burst { | ||||
| 	sbit_t tail0[3]; | ||||
| 	sbit_t data0[39]; | ||||
| 	sbit_t etsc[64]; | ||||
| 	sbit_t data1[39]; | ||||
| 	sbit_t tail1[3]; | ||||
| 	sbit_t guard[8]; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| static const uint8_t sch_next_output[][2] = { | ||||
| 	{ 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, | ||||
| 	{ 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, | ||||
| 	{ 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, | ||||
| 	{ 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, | ||||
| }; | ||||
|  | ||||
| static const uint8_t sch_next_state[][2] = { | ||||
| 	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 }, | ||||
| 	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, | ||||
| 	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 }, | ||||
| 	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, | ||||
| }; | ||||
|  | ||||
| static const struct osmo_conv_code gsm_conv_sch = { | ||||
| 	.N = 2, | ||||
| 	.K = 5, | ||||
| 	.len = GSM_SCH_UNCODED_LEN, | ||||
| 	.next_output = sch_next_output, | ||||
| 	.next_state  = sch_next_state, | ||||
| }; | ||||
|  | ||||
| #define GSM_MAX_BURST_LEN	157 * 4 | ||||
| #define GSM_SYM_RATE		(1625e3 / 6) * 4 | ||||
|  | ||||
| /* Pre-generated FCCH measurement tone */ | ||||
| static complex float fcch_ref[GSM_MAX_BURST_LEN]; | ||||
|  | ||||
| int float_to_sbit(const float *in, sbit_t *out, float scale, int len) | ||||
| { | ||||
| 	int i; | ||||
|  | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		out[i] = (in[i] - 0.5f) * scale; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Check if FN contains a FCCH burst */ | ||||
| int gsm_fcch_check_fn(int fn) | ||||
| { | ||||
| 	int fn51 = fn % 51; | ||||
|  | ||||
| 	switch (fn51) { | ||||
| 	case 0: | ||||
| 	case 10: | ||||
| 	case 20: | ||||
| 	case 30: | ||||
| 	case 40: | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Check if FN contains a SCH burst */ | ||||
| int gsm_sch_check_fn(int fn) | ||||
| { | ||||
| 	int fn51 = fn % 51; | ||||
|  | ||||
| 	switch (fn51) { | ||||
| 	case 1: | ||||
| 	case 11: | ||||
| 	case 21: | ||||
| 	case 31: | ||||
| 	case 41: | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int gsm_fcch_check_ts(int ts, int fn) { | ||||
| 	return ts == 0 && gsm_fcch_check_fn(fn); | ||||
| } | ||||
|  | ||||
| int gsm_sch_check_ts(int ts, int fn) { | ||||
| 	return ts == 0 && gsm_sch_check_fn(fn); | ||||
| } | ||||
|  | ||||
| /* SCH (T1, T2, T3p) to full FN value */ | ||||
| int gsm_sch_to_fn(struct sch_info *sch) | ||||
| { | ||||
| 	int t1 = sch->t1; | ||||
| 	int t2 = sch->t2; | ||||
| 	int t3p = sch->t3p; | ||||
|  | ||||
| 	if ((t1 < 0) || (t2 < 0) || (t3p < 0)) | ||||
| 		return -1; | ||||
| 	int tt; | ||||
| 	int t3 = t3p * 10 + 1; | ||||
|  | ||||
| 	if (t3 < t2) | ||||
| 		tt = (t3 + 26) - t2; | ||||
| 	else | ||||
| 		tt = (t3 - t2) % 26; | ||||
|  | ||||
| 	return t1 * 51 * 26 + tt * 51 + t3; | ||||
| } | ||||
|  | ||||
| /* Parse encoded SCH message */ | ||||
| int gsm_sch_parse(const uint8_t *info, struct sch_info *desc) | ||||
| { | ||||
| 	struct sch_packed_info *p = (struct sch_packed_info *) info; | ||||
|  | ||||
| 	desc->bsic = (p->bsic[0] << 0) | (p->bsic[1] << 1) | | ||||
| 		     (p->bsic[2] << 2) | (p->bsic[3] << 3) | | ||||
| 		     (p->bsic[4] << 4) | (p->bsic[5] << 5); | ||||
|  | ||||
| 	desc->t1 = (p->t1_lo[0] << 0) | (p->t1_md[0] << 1) | | ||||
| 		   (p->t1_md[1] << 2) | (p->t1_md[2] << 3) | | ||||
| 		   (p->t1_md[3] << 4) | (p->t1_md[4] << 5) | | ||||
| 		   (p->t1_md[5] << 6) | (p->t1_md[6] << 7) | | ||||
| 		   (p->t1_md[7] << 8) | (p->t1_hi[0] << 9) | | ||||
| 		   (p->t1_hi[1] << 10); | ||||
|  | ||||
| 	desc->t2 = (p->t2[0] << 0) | (p->t2[1] << 1) | | ||||
| 		   (p->t2[2] << 2) | (p->t2[3] << 3) | | ||||
| 		   (p->t2[4] << 4); | ||||
|  | ||||
| 	desc->t3p = (p->t3p_lo[0] << 0) | (p->t3p_hi[0] << 1) | | ||||
| 		    (p->t3p_hi[1] << 2); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* From osmo-bts */ | ||||
| int gsm_sch_decode(uint8_t *info, sbit_t *data) | ||||
| { | ||||
| 	int rc; | ||||
| 	ubit_t uncoded[GSM_SCH_UNCODED_LEN]; | ||||
|  | ||||
| 	osmo_conv_decode(&gsm_conv_sch, data, uncoded); | ||||
|  | ||||
| 	rc = osmo_crc16gen_check_bits(&gsm0503_sch_crc10, | ||||
| 				      uncoded, GSM_SCH_INFO_LEN, | ||||
| 				      uncoded + GSM_SCH_INFO_LEN); | ||||
| 	if (rc) | ||||
| 		return -1; | ||||
|  | ||||
| 	memcpy(info, uncoded, GSM_SCH_INFO_LEN * sizeof(ubit_t)); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| #define FCCH_TAIL_BITS_LEN	3*4 | ||||
| #define FCCH_DATA_LEN	100*4//	142 | ||||
| #if 1 | ||||
| /* Compute FCCH frequency offset */ | ||||
| double org_gsm_fcch_offset(float *burst, int len) | ||||
| { | ||||
| 	int i, start, end; | ||||
| 	float a, b, c, d, ang, avg = 0.0f; | ||||
| 	double freq; | ||||
|  | ||||
| 	if (len > GSM_MAX_BURST_LEN) | ||||
| 		len = GSM_MAX_BURST_LEN; | ||||
|  | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		a = burst[2 * i + 0]; | ||||
| 		b = burst[2 * i + 1]; | ||||
| 		c = crealf(fcch_ref[i]); | ||||
| 		d = cimagf(fcch_ref[i]); | ||||
|  | ||||
| 		burst[2 * i + 0] = a * c - b * d; | ||||
| 		burst[2 * i + 1] = a * d + b * c; | ||||
| 	} | ||||
|  | ||||
| 	start = FCCH_TAIL_BITS_LEN; | ||||
| 	end = start + FCCH_DATA_LEN; | ||||
|  | ||||
| 	for (i = start; i < end; i++) { | ||||
| 		a = cargf(burst[2 * (i - 1) + 0] + | ||||
| 			  burst[2 * (i - 1) + 1] * I); | ||||
| 		b = cargf(burst[2 * i + 0] + | ||||
| 			  burst[2 * i + 1] * I); | ||||
|  | ||||
| 		ang = b - a; | ||||
|  | ||||
| 		if (ang > M_PI) | ||||
| 			ang -= 2 * M_PI; | ||||
| 		else if (ang < -M_PI) | ||||
| 			ang += 2 * M_PI; | ||||
|  | ||||
| 		avg += ang; | ||||
| 	} | ||||
|  | ||||
| 	avg /= (float) (end - start); | ||||
| 	freq = avg / (2 * M_PI) * GSM_SYM_RATE; | ||||
|  | ||||
| 	return freq; | ||||
| } | ||||
|  | ||||
|  | ||||
| static const int L1 = 3; | ||||
| static const int L2 = 32; | ||||
| static const int N1 = 92; | ||||
| static const int N2 = 92; | ||||
|  | ||||
| static struct { int8_t r; int8_t s; } P_inv_table[3+32]; | ||||
|  | ||||
| void pinv(int P, int8_t* r, int8_t* s, int L1, int L2) { | ||||
| 	for (int i = 0; i < L1; i++) | ||||
| 		for (int j = 0; j < L2; j++) | ||||
| 			if (P == L2 * i - L1 * j) { | ||||
| 				*r = i; | ||||
| 				*s = j; | ||||
| 				return; | ||||
| 			} | ||||
| } | ||||
|  | ||||
|  | ||||
| float ac_sum_with_lag( complex float* in, int lag, int offset, int N) { | ||||
| 	complex float v = 0 + 0*I; | ||||
| 	int total_offset = offset + lag; | ||||
| 	for (int s = 0; s < N; s++) | ||||
| 		v += in[s + total_offset] * conjf(in[s + total_offset - lag]); | ||||
| 	return cargf(v); | ||||
| } | ||||
|  | ||||
|  | ||||
| double gsm_fcch_offset(float *burst, int len) | ||||
| { | ||||
| 	int start; | ||||
|  | ||||
| 	const float fs = 13. / 48. * 1e6 * 4; | ||||
| 	const float expected_fcch_val = ((2 * M_PI) / (fs)) * 67700; | ||||
|  | ||||
| 	if (len > GSM_MAX_BURST_LEN) | ||||
| 		len = GSM_MAX_BURST_LEN; | ||||
|  | ||||
| 	start = FCCH_TAIL_BITS_LEN+10 * 4; | ||||
| 	float alpha_one = ac_sum_with_lag((complex float*)burst, L1, start, N1); | ||||
| 	float alpha_two = ac_sum_with_lag((complex float*)burst, L2, start, N2); | ||||
|  | ||||
| 	float P_unrounded = (L1 * alpha_two - L2 * alpha_one) / (2 * M_PI); | ||||
| 	int P = roundf(P_unrounded); | ||||
|  | ||||
| 	int8_t r = 0, s = 0; | ||||
| 	pinv(P, &r, &s, L1, L2); | ||||
|  | ||||
| 	float omegal1 = (alpha_one + 2 * M_PI * r) / L1; | ||||
| 	float omegal2 = (alpha_two + 2 * M_PI * s) / L2; | ||||
|  | ||||
| 	float rv = org_gsm_fcch_offset(burst, len); | ||||
| 	//return rv; | ||||
|  | ||||
| 	float reval = GSM_SYM_RATE / (2 * M_PI) * (expected_fcch_val - (omegal1+omegal2)/2); | ||||
| 	//fprintf(stderr, "XX rv %f %f %f %f\n", rv, reval, omegal1 / (2 * M_PI) * fs, omegal2 / (2 * M_PI) * fs); | ||||
|  | ||||
| 	//fprintf(stderr, "XX rv %f %f\n", rv, reval); | ||||
|  | ||||
| 	return -reval; | ||||
| } | ||||
| #endif | ||||
| /* Generate FCCH measurement tone */ | ||||
| static __attribute__((constructor)) void init() | ||||
| { | ||||
| 	int i; | ||||
| 	double freq = 0.25; | ||||
|  | ||||
| 	for (i = 0; i < GSM_MAX_BURST_LEN; i++) { | ||||
| 		fcch_ref[i] = sin(2 * M_PI * freq * (double) i) + | ||||
| 			      cos(2 * M_PI * freq * (double) i) * I; | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										48
									
								
								Transceiver52M/ms/sch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Transceiver52M/ms/sch.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| #pragma once | ||||
| /* | ||||
|  * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> | ||||
|  * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> | ||||
|  * (C) 2016 by Tom Tsou <tom.tsou@ettus.com> | ||||
|  * (C) 2017 by Harald Welte <laforge@gnumonks.org> | ||||
|  * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> | ||||
|  * | ||||
|  * All Rights Reserved | ||||
|  * | ||||
|  * SPDX-License-Identifier: GPL-2.0+ | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  */ | ||||
|  | ||||
| #include <osmocom/core/bits.h> | ||||
|  | ||||
| struct sch_info  { | ||||
| 	int bsic; | ||||
| 	int t1; | ||||
| 	int t2; | ||||
| 	int t3p; | ||||
| }; | ||||
|  | ||||
| #define GSM_SCH_INFO_LEN		25 | ||||
| #define GSM_SCH_UNCODED_LEN		35 | ||||
| #define GSM_SCH_CODED_LEN		78 | ||||
|  | ||||
| int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst); | ||||
| int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc); | ||||
| int gsm_sch_to_fn(struct sch_info *sch); | ||||
| int gsm_sch_check_fn(int fn); | ||||
| int gsm_fcch_check_fn(int fn); | ||||
| int gsm_fcch_check_ts(int ts, int fn); | ||||
| int gsm_sch_check_ts(int ts, int fn); | ||||
|  | ||||
| double gsm_fcch_offset(float *burst, int len); | ||||
|  | ||||
| int float_to_sbit(const float *in, sbit_t *out, float scale, int len); | ||||
|  | ||||
							
								
								
									
										251
									
								
								Transceiver52M/ms/uhd_specific.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								Transceiver52M/ms/uhd_specific.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | ||||
| #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 <uhd/version.hpp> | ||||
| #include <uhd/usrp/multi_usrp.hpp> | ||||
| #include <uhd/types/metadata.hpp> | ||||
| #include <complex> | ||||
| #include <cstring> | ||||
| #include <iostream> | ||||
| #include <thread> | ||||
|  | ||||
| #include <Timeval.h> | ||||
| #include <vector> | ||||
|  | ||||
| using blade_sample_type = std::complex<int16_t>; | ||||
| const int SAMPLE_SCALE_FACTOR = 1; | ||||
|  | ||||
| struct uhd_buf_wrap { | ||||
| 	double rxticks; | ||||
| 	size_t num_samps; | ||||
| 	uhd::rx_metadata_t *md; | ||||
| 	blade_sample_type *buf; | ||||
| 	auto actual_samples_per_buffer() | ||||
| 	{ | ||||
| 		return num_samps; | ||||
| 	} | ||||
| 	long get_first_ts() | ||||
| 	{ | ||||
| 		return 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 uhd_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; | ||||
| 	volatile bool stop_me_flag; | ||||
|  | ||||
| 	virtual ~uhd_hw() | ||||
| 	{ | ||||
| 		delete[] one_pkt_buf; | ||||
| 	} | ||||
| 	uhd_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67), stop_me_flag(false) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	void close_device() | ||||
| 	{ | ||||
| 		stop_me_flag = true; | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	{ | ||||
| 		auto const lock_delay_ms = 500; | ||||
| 		auto const mcr = 26e6; | ||||
| 		auto const rate = (1625e3 / 6) * 4; | ||||
| 		auto const ref = "external"; | ||||
| 		auto const gain = 35; | ||||
| 		auto const freq = 931.4e6; // 936.8e6 | ||||
| 		auto bw = 0.5e6; | ||||
| 		auto const channel = 0; | ||||
| 		std::string args = {}; | ||||
|  | ||||
| 		dev = uhd::usrp::multi_usrp::make(args); | ||||
| 		std::cout << "Using Device: " << dev->get_pp_string() << std::endl; | ||||
| 		dev->set_clock_source(ref); | ||||
| 		dev->set_master_clock_rate(mcr); | ||||
| 		dev->set_rx_rate(rate, channel); | ||||
| 		dev->set_tx_rate(rate, channel); | ||||
| 		uhd::tune_request_t tune_request(freq, 0); | ||||
| 		dev->set_rx_freq(tune_request, channel); | ||||
| 		dev->set_rx_gain(gain, channel); | ||||
| 		dev->set_tx_gain(60, channel); | ||||
| 		dev->set_rx_bandwidth(bw, channel); | ||||
| 		dev->set_tx_bandwidth(bw, channel); | ||||
|  | ||||
| 		while (!(dev->get_rx_sensor("lo_locked", channel).to_bool() && | ||||
| 			 dev->get_mboard_sensor("ref_locked").to_bool())) | ||||
| 			std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms)); | ||||
|  | ||||
| 		uhd::stream_args_t stream_args("sc16", "sc16"); | ||||
| 		rx_stream = dev->get_rx_stream(stream_args); | ||||
| 		uhd::stream_args_t stream_args2("sc16", "sc16"); | ||||
| 		tx_stream = dev->get_tx_stream(stream_args2); | ||||
|  | ||||
| 		rx_spp = rx_stream->get_max_num_samps(); | ||||
| 		rxticks = dev->get_rx_rate(); | ||||
| 		assert(rxticks == dev->get_tx_rate()); | ||||
| 		one_pkt_buf = new blade_sample_type[rx_spp]; | ||||
| 		pkt_ptrs = { 1, &one_pkt_buf[0] }; | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	void *rx_cb(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		void *ret; | ||||
| 		static int to_skip = 0; | ||||
|  | ||||
| 		uhd::rx_metadata_t md; | ||||
| 		auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 1.0, true); | ||||
|  | ||||
| 		if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) { | ||||
| 			std::cerr << boost::format("Timeout while streaming") << std::endl; | ||||
| 			exit(0); | ||||
| 		} | ||||
| 		if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) { | ||||
| 			std::cerr << boost::format("Got an overflow indication\n") << std::endl; | ||||
| 			exit(0); | ||||
| 		} | ||||
| 		if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) { | ||||
| 			std::cerr << str(boost::format("Receiver error: %s") % md.strerror()); | ||||
| 			exit(0); | ||||
| 		} | ||||
|  | ||||
| 		dev_buf_t rcd = { rxticks, num_rx_samps, &md, &one_pkt_buf[0] }; | ||||
|  | ||||
| 		if (to_skip < 120) // prevents weird overflows on startup | ||||
| 			to_skip++; | ||||
| 		else { | ||||
| 			burst_handler(&rcd); | ||||
| 		} | ||||
|  | ||||
| 		return ret; | ||||
| 	} | ||||
|  | ||||
| 	auto get_rx_burst_handler_fn(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		auto fn = [this, burst_handler] { | ||||
| 			pthread_setname_np(pthread_self(), "rxrun"); | ||||
|  | ||||
| 			uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); | ||||
| 			stream_cmd.stream_now = true; | ||||
| 			stream_cmd.time_spec = uhd::time_spec_t(); | ||||
| 			rx_stream->issue_stream_cmd(stream_cmd); | ||||
|  | ||||
| 			while (!stop_me_flag) { | ||||
| 				rx_cb(burst_handler); | ||||
| 			} | ||||
| 		}; | ||||
| 		return fn; | ||||
| 	} | ||||
| 	auto get_tx_burst_handler_fn(bh_fn_t burst_handler) | ||||
| 	{ | ||||
| 		auto fn = [] { | ||||
| 			// dummy | ||||
| 		}; | ||||
| 		return fn; | ||||
| 	} | ||||
| 	void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts) | ||||
| 	{ | ||||
| 		uhd::tx_metadata_t m = {}; | ||||
| 		m.end_of_burst = true; | ||||
| 		m.start_of_burst = true; | ||||
| 		m.has_time_spec = true; | ||||
| 		m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay! | ||||
| 		std::vector<void *> ptrs(1, buffer); | ||||
|  | ||||
| 		tx_stream->send(ptrs, len, m, 1.0); | ||||
| #ifdef DBGXX | ||||
| 		uhd::async_metadata_t async_md; | ||||
| 		bool tx_ack = false; | ||||
| 		while (!tx_ack && tx_stream->recv_async_msg(async_md)) { | ||||
| 			tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); | ||||
| 		} | ||||
| 		std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl; | ||||
| #endif | ||||
| 	} | ||||
| }; | ||||
| @@ -12,10 +12,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #ifdef HAVE_CONFIG_H | ||||
|   | ||||
| @@ -76,22 +76,25 @@ bool radioVector::setVector(signalVector *vector, size_t chan) | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| noiseVector::noiseVector(size_t size) | ||||
| avgVector::avgVector(size_t size) | ||||
| 	: std::vector<float>(size), itr(0) | ||||
| { | ||||
| } | ||||
|  | ||||
| float noiseVector::avg() const | ||||
| float avgVector::avg() const | ||||
| { | ||||
| 	float val = 0.0; | ||||
|  | ||||
| 	if (!size()) | ||||
| 		return 0.0f; | ||||
|  | ||||
| 	for (size_t i = 0; i < size(); i++) | ||||
| 		val += (*this)[i]; | ||||
|  | ||||
| 	return val / (float) size(); | ||||
| } | ||||
|  | ||||
| bool noiseVector::insert(float val) | ||||
| bool avgVector::insert(float val) | ||||
| { | ||||
| 	if (!size()) | ||||
| 		return false; | ||||
|   | ||||
| @@ -48,9 +48,9 @@ private: | ||||
| 	GSM::Time mTime; | ||||
| }; | ||||
|  | ||||
| class noiseVector : std::vector<float> { | ||||
| class avgVector : std::vector<float> { | ||||
| public: | ||||
| 	noiseVector(size_t size = 0); | ||||
| 	avgVector(size_t size = 0); | ||||
| 	bool insert(float val); | ||||
| 	float avg() const; | ||||
|  | ||||
|   | ||||
| @@ -34,6 +34,7 @@ | ||||
| #include "Resampler.h" | ||||
|  | ||||
| extern "C" { | ||||
| #include <osmocom/core/panic.h> | ||||
| #include "convolve.h" | ||||
| #include "scale.h" | ||||
| #include "mult.h" | ||||
| @@ -1460,25 +1461,30 @@ static signalVector *downsampleBurst(const signalVector &burst) | ||||
|  * It is computed from the training sequence of each received burst, | ||||
|  * by comparing the "ideal" training sequence with the actual one. | ||||
|  */ | ||||
| static float computeCI(const signalVector *burst, CorrelationSequence *sync, | ||||
|                        float toa, int start, complex xcorr) | ||||
| static float computeCI(const signalVector *burst, const CorrelationSequence *sync, | ||||
|                        float toa, int start, const complex &xcorr) | ||||
| { | ||||
|   const int N = sync->sequence->size(); | ||||
|   float S, C; | ||||
|   int ps; | ||||
|  | ||||
|   /* Integer position where the sequence starts */ | ||||
|   ps = start + 1 - sync->sequence->size() + (int)roundf(toa); | ||||
|   const int ps = start + 1 - N + (int)roundf(toa); | ||||
|  | ||||
|   /* Estimate Signal power */ | ||||
|   S = 0.0f; | ||||
|   for (int i=0, j=ps; i<(int)sync->sequence->size(); i++,j++) | ||||
|   for (int i=0, j=ps; i<(int)N; i++,j++) | ||||
|     S += (*burst)[j].norm2(); | ||||
|   S /= sync->sequence->size(); | ||||
|   S /= N; | ||||
|  | ||||
|   /* Esimate Carrier power */ | ||||
|   C = xcorr.norm2() / ((sync->sequence->size() - 1) * sync->gain.abs()); | ||||
|   C = xcorr.norm2() / ((N - 1) * sync->gain.abs()); | ||||
|  | ||||
|   /* Interference = Signal - Carrier, so C/I = C / (S - C) */ | ||||
|   /* Interference = Signal - Carrier, so C/I = C / (S - C). | ||||
|    * Calculated in dB: | ||||
|    * C/I_dB = 10 * log10(C/I) | ||||
|    * C/I_dB = 10 * (1/log2(10)) * log2(C/I) | ||||
|    * C/I_dB = 10 * 0.30103 * log2(C/I) | ||||
|    * C/I_dB = 3.0103 * log2(C/I) | ||||
|    */ | ||||
|   return 3.0103f * log2f(C / (S - C)); | ||||
| } | ||||
|  | ||||
| @@ -1491,7 +1497,7 @@ static float computeCI(const signalVector *burst, CorrelationSequence *sync, | ||||
|  * and we run full interpolating peak detection. | ||||
|  */ | ||||
| static int detectBurst(const signalVector &burst, | ||||
|                        signalVector &corr, CorrelationSequence *sync, | ||||
|                        signalVector &corr, const CorrelationSequence *sync, | ||||
|                        float thresh, int sps, int start, int len, | ||||
|                        struct estim_burst_params *ebp) | ||||
| { | ||||
| @@ -1500,12 +1506,18 @@ static int detectBurst(const signalVector &burst, | ||||
|   complex xcorr; | ||||
|   int rc = 1; | ||||
|  | ||||
|   if (sps == 4) { | ||||
|     dec = downsampleBurst(burst); | ||||
|     corr_in = dec; | ||||
|     sps = 1; | ||||
|   } else { | ||||
|   switch (sps) { | ||||
|   case 1: | ||||
|     corr_in = &burst; | ||||
|     break; | ||||
|   case 4: | ||||
|     dec = downsampleBurst(burst); | ||||
|      /* Running at the downsampled rate at this point: */ | ||||
|      corr_in = dec; | ||||
|      sps = 1; | ||||
|      break; | ||||
|   default: | ||||
|      osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps); | ||||
|   } | ||||
|  | ||||
|   /* Correlate */ | ||||
| @@ -1515,9 +1527,6 @@ static int detectBurst(const signalVector &burst, | ||||
|     goto del_ret; | ||||
|   } | ||||
|  | ||||
|   /* Running at the downsampled rate at this point */ | ||||
|   sps = 1; | ||||
|  | ||||
|   /* Peak detection - place restrictions at correlation edges */ | ||||
|   ebp->amp = fastPeakDetect(corr, &ebp->toa); | ||||
|  | ||||
| @@ -1586,7 +1595,7 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps | ||||
|   // and only report clipping if we can't demod. | ||||
|   float maxAmpl = maxAmplitude(rxBurst); | ||||
|   if (maxAmpl > CLIP_THRESH) { | ||||
|     LOG(DEBUG) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; | ||||
|     LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; | ||||
|     clipping = true; | ||||
|   } | ||||
|  | ||||
| @@ -1862,7 +1871,7 @@ static float computeEdgeCI(const signalVector *rot) | ||||
|  * delay filters. Symbol rotation and after always operates at 1 SPS. | ||||
|  * | ||||
|  * Allow 1 SPS demodulation here, but note that other parts of the | ||||
|  * transceiver restrict EDGE operatoin to 4 SPS - 8-PSK distortion | ||||
|  * transceiver restrict EDGE operation to 4 SPS - 8-PSK distortion | ||||
|  * through the fractional delay filters at 1 SPS renders signal | ||||
|  * nearly unrecoverable. | ||||
|  */ | ||||
|   | ||||
							
								
								
									
										43
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								configure.ac
									
									
									
									
									
								
							| @@ -82,10 +82,10 @@ AC_TYPE_SIZE_T | ||||
| AC_HEADER_TIME | ||||
| AC_C_BIGENDIAN | ||||
|  | ||||
| PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.5.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.5.0) | ||||
| PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 1.5.0) | ||||
| 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) | ||||
|  | ||||
| AC_ARG_ENABLE(sanitize, | ||||
| 	[AS_HELP_STRING( | ||||
| @@ -138,6 +138,11 @@ AC_ARG_WITH(ipc, [ | ||||
|         [enable IPC]) | ||||
| ]) | ||||
|  | ||||
| AC_ARG_WITH(bladerf, [ | ||||
|     AS_HELP_STRING([--with-bladerf], | ||||
|         [enable bladeRF]) | ||||
| ]) | ||||
|  | ||||
| AC_ARG_WITH(singledb, [ | ||||
|     AS_HELP_STRING([--with-singledb], | ||||
|         [enable single daughterboard use on USRP1]) | ||||
| @@ -185,6 +190,18 @@ AS_IF([test "x$with_uhd" = "xyes"],[ | ||||
|             [PKG_CHECK_MODULES(UHD, uhd >= 003.005)] | ||||
|         )] | ||||
|     ) | ||||
|     # OS#5608: libuhd < 4.2.0 includes boost/thread/thread.hpp in its logging | ||||
|     # code and therefore requires linking against boost_thread. It's missing in | ||||
|     # uhd.pc, so work around it here. | ||||
|     # https://github.com/EttusResearch/uhd/commit/04a83b6e76beef970854da69ba882d717669b49c | ||||
|     PKG_CHECK_MODULES(UHD, uhd < 004.002, | ||||
|         [LIBS="$LIBS -lboost_thread"], | ||||
|         [] | ||||
|     ) | ||||
| ]) | ||||
|  | ||||
| AS_IF([test "x$with_bladerf" = "xyes"], [ | ||||
|     PKG_CHECK_MODULES(BLADE, libbladeRF >= 2.0) | ||||
| ]) | ||||
|  | ||||
| AS_IF([test "x$with_singledb" = "xyes"], [ | ||||
| @@ -240,6 +257,7 @@ AM_CONDITIONAL(DEVICE_UHD, [test "x$with_uhd" = "xyes"]) | ||||
| AM_CONDITIONAL(DEVICE_USRP1, [test "x$with_usrp1" = "xyes"]) | ||||
| AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"]) | ||||
| 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"]) | ||||
|  | ||||
| @@ -309,6 +327,14 @@ 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([\ | ||||
|     Makefile \ | ||||
| @@ -325,6 +351,8 @@ 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 \ | ||||
|     tests/Transceiver52M/Makefile \ | ||||
| @@ -333,8 +361,7 @@ AC_CONFIG_FILES([\ | ||||
|     doc/examples/Makefile \ | ||||
|     contrib/Makefile \ | ||||
|     contrib/systemd/Makefile \ | ||||
|     doc/manuals/Makefile \ | ||||
|     contrib/osmo-trx.spec \ | ||||
| ]) | ||||
|  | ||||
| AC_OUTPUT( | ||||
| 	doc/manuals/Makefile | ||||
| 	contrib/osmo-trx.spec) | ||||
| AC_OUTPUT | ||||
|   | ||||
| @@ -106,8 +106,11 @@ autoreconf --install --force | ||||
| $MAKE $PARALLEL_MAKE | ||||
| $MAKE check \ | ||||
|   || cat-testlogs.sh | ||||
| DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck \ | ||||
|   || cat-testlogs.sh | ||||
|  | ||||
| if arch | grep -v -q arm; then | ||||
| 	DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck \ | ||||
| 	  || cat-testlogs.sh | ||||
| fi | ||||
|  | ||||
| if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then | ||||
| 	make -C "$base/doc/manuals" publish | ||||
| @@ -115,9 +118,4 @@ fi | ||||
|  | ||||
| $MAKE $PARALLEL_MAKE maintainer-clean | ||||
|  | ||||
| # Verify distro-specific package patches apply: | ||||
| for patch in debian/patches/*.patch; do | ||||
|         patch --dry-run -p1 < "$patch" | ||||
| done | ||||
|  | ||||
| osmo-clean-workspace.sh | ||||
|   | ||||
| @@ -34,10 +34,10 @@ BuildRequires:  pkgconfig(LimeSuite) | ||||
| BuildRequires:  pkgconfig(usrp) >= 3.3 | ||||
| %endif | ||||
| BuildRequires:  pkgconfig(fftw3f) | ||||
| BuildRequires:  pkgconfig(libosmocoding) >= 1.5.0 | ||||
| BuildRequires:  pkgconfig(libosmocore) >= 1.5.0 | ||||
| BuildRequires:  pkgconfig(libosmoctrl) >= 1.5.0 | ||||
| BuildRequires:  pkgconfig(libosmovty) >= 1.5.0 | ||||
| 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(libusb-1.0) | ||||
| BuildRequires:  pkgconfig(uhd) | ||||
| %{?systemd_requires} | ||||
|   | ||||
| @@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (IPC Backend) | ||||
| [Service] | ||||
| Type=simple | ||||
| Restart=always | ||||
| StateDirectory=osmocom | ||||
| WorkingDirectory=%S/osmocom | ||||
| ExecStart=/usr/bin/osmo-trx-ipc -C /etc/osmocom/osmo-trx-ipc.cfg | ||||
| RestartSec=2 | ||||
| # CPU scheduling policy: | ||||
| CPUSchedulingPolicy=rr | ||||
| # For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority): | ||||
| CPUSchedulingPriority=21 | ||||
| # See sched(7) for further details on real-time policies and priorities | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
|   | ||||
| @@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (LimeSuite backend) | ||||
| [Service] | ||||
| Type=simple | ||||
| Restart=always | ||||
| StateDirectory=osmocom | ||||
| WorkingDirectory=%S/osmocom | ||||
| ExecStart=/usr/bin/osmo-trx-lms -C /etc/osmocom/osmo-trx-lms.cfg | ||||
| RestartSec=2 | ||||
| # CPU scheduling policy: | ||||
| CPUSchedulingPolicy=rr | ||||
| # For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority): | ||||
| CPUSchedulingPriority=21 | ||||
| # See sched(7) for further details on real-time policies and priorities | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
|   | ||||
| @@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (UHD Backend) | ||||
| [Service] | ||||
| Type=simple | ||||
| Restart=always | ||||
| StateDirectory=osmocom | ||||
| WorkingDirectory=%S/osmocom | ||||
| ExecStart=/usr/bin/osmo-trx-uhd -C /etc/osmocom/osmo-trx-uhd.cfg | ||||
| RestartSec=2 | ||||
| # CPU scheduling policy: | ||||
| CPUSchedulingPolicy=rr | ||||
| # For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority): | ||||
| CPUSchedulingPriority=21 | ||||
| # See sched(7) for further details on real-time policies and priorities | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
|   | ||||
| @@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (libusrp backend) | ||||
| [Service] | ||||
| Type=simple | ||||
| Restart=always | ||||
| StateDirectory=osmocom | ||||
| WorkingDirectory=%S/osmocom | ||||
| ExecStart=/usr/bin/osmo-trx-usrp1 -C /etc/osmocom/osmo-trx-usrp1.cfg | ||||
| RestartSec=2 | ||||
| # CPU scheduling policy: | ||||
| CPUSchedulingPolicy=rr | ||||
| # For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority): | ||||
| CPUSchedulingPriority=21 | ||||
| # See sched(7) for further details on real-time policies and priorities | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
|   | ||||
							
								
								
									
										53
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										53
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,56 @@ | ||||
| osmo-trx (1.4.1) unstable; urgency=medium | ||||
|  | ||||
|   [ Oliver Smith ] | ||||
|   * treewide: remove FSF address | ||||
|  | ||||
|   [ Vadim Yanitskiy ] | ||||
|   * tests: use 'check_PROGRAMS' instead of 'noinst_PROGRAMS' | ||||
|  | ||||
|   [ Harald Welte ] | ||||
|   * update git URLs (git -> https; gitea) | ||||
|  | ||||
|  -- Pau Espin Pedrol <pespin@sysmocom.de>  Wed, 29 Jun 2022 09:32:56 +0200 | ||||
|  | ||||
| osmo-trx (1.4.0) unstable; urgency=medium | ||||
|  | ||||
|   [ Pau Espin Pedrol ] | ||||
|   * Threads.cpp: Fix missing extern C around libosmocore include | ||||
|   * Drop logging pthread ID | ||||
|   * Threads: Avoid printing pthread_self() | ||||
|   * ipc: Makefile.am: Clean LDADD variable | ||||
|   * Use new stat item/ctr getter APIs | ||||
|   * detectBurst(): Clear downsampling code path | ||||
|   * detectBurst(): constify parameter | ||||
|   * computeCI(): Constify param and pass it as reference | ||||
|   * computeCI(): Rename verbose repeated getter to constant | ||||
|   * computeCI(): Constify read-only variable | ||||
|   * detectGeneralBurst(): Increase log level about clipping to INFO | ||||
|   * cosmetic: Fix typo in comment | ||||
|   * computeCI: Document hardcoded multiplier | ||||
|   * lms: Drop duplicated check | ||||
|   * lms,uhd: Validate band of RxFreq too | ||||
|   * lms,uhd: Skip re-assigning same band | ||||
|   * lms,uhd: Allow changing band between poweroff & poweron | ||||
|  | ||||
|   [ Vadim Yanitskiy ] | ||||
|   * gitignore: remove non-existing 'doc/manuals/osmomsc-usermanual.xml' | ||||
|   * ctrl_sock_handle_rx(): fix missing space in LOGCHAN() statement | ||||
|   * trx_rate_ctr: use thread safe strerror() in device_sig_cb() | ||||
|   * IPCDevice: use thread safe strerror_r() instead of strerror() | ||||
|   * IPCDevice: check value returned from select() | ||||
|   * LMSDevice: LMS_GetDeviceList() may return a negative number | ||||
|  | ||||
|   [ Eric ] | ||||
|   * add hidden fn adjustment command | ||||
|   * uhd: ensure configured clock source is actually used | ||||
|   * vty: printing fn offset should be signed | ||||
|   * lms: init band | ||||
|  | ||||
|   [ Oliver Smith ] | ||||
|   * d/patches/build-for-debian8.patch: remove | ||||
|  | ||||
|  -- Pau Espin Pedrol <pespin@sysmocom.de>  Tue, 16 Nov 2021 16:27:26 +0100 | ||||
|  | ||||
| osmo-trx (1.3.1) unstable; urgency=medium | ||||
|  | ||||
|   * mark uhddev_ipc.cpp as BUILT_SOURCES | ||||
|   | ||||
							
								
								
									
										6
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							| @@ -14,11 +14,11 @@ Build-Depends: debhelper (>= 9), | ||||
|                libtalloc-dev, | ||||
|                libusrp-dev, | ||||
|                liblimesuite-dev, | ||||
|                libosmocore-dev (>= 1.5.0), | ||||
|                libosmocore-dev (>= 1.6.0), | ||||
|                osmo-gsm-manuals-dev | ||||
| Standards-Version: 3.9.6 | ||||
| Vcs-Browser: http://cgit.osmocom.org/osmo-trx | ||||
| Vcs-Git: git://git.osmocom.org/osmo-trx | ||||
| Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx | ||||
| Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx | ||||
| Homepage: https://projects.osmocom.org/projects/osmotrx | ||||
|  | ||||
| Package: osmo-trx | ||||
|   | ||||
							
								
								
									
										2
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | ||||
| Upstream-Name: OsmoTRX | ||||
| Source: http://cgit.osmocom.org/osmo-trx/ | ||||
| Source: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx | ||||
| Files-Excluded: Transceiver52M/device/usrp1/std_inband.rbf | ||||
|  | ||||
| Files: * | ||||
|   | ||||
							
								
								
									
										60
									
								
								debian/patches/build-for-debian8.patch
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								debian/patches/build-for-debian8.patch
									
									
									
									
										vendored
									
									
								
							| @@ -1,60 +0,0 @@ | ||||
| diff --git a/debian/control b/debian/control | ||||
| index 12d9af5..27b9d60 100644 | ||||
| --- a/debian/control | ||||
| +++ b/debian/control | ||||
| @@ -13,7 +13,6 @@ Build-Depends: debhelper (>= 9), | ||||
|                 libfftw3-dev, | ||||
|                 libtalloc-dev, | ||||
|                 libusrp-dev, | ||||
| -               liblimesuite-dev, | ||||
|                 libosmocore-dev (>= 1.5.0), | ||||
|                 osmo-gsm-manuals-dev | ||||
|  Standards-Version: 3.9.6 | ||||
| @@ -30,7 +29,7 @@ Package: osmo-trx-dbg | ||||
|  Architecture: any | ||||
|  Section: debug | ||||
|  Priority: extra | ||||
| -Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), osmo-trx-lms (= ${binary:Version}), osmo-trx-ipc (= ${binary:Version}), ${misc:Depends} | ||||
| +Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), osmo-trx-ipc (= ${binary:Version}), ${misc:Depends} | ||||
|  Description: Debug symbols for the osmo-trx-* | ||||
|   Make debugging possible | ||||
|   | ||||
| @@ -72,25 +71,6 @@ Description: SDR transceiver that implements Layer 1 of a GSM BTS (USRP1) | ||||
|   between different telecommunication associations for developing new | ||||
|   generations of mobile phone networks. (post-2G/GSM) | ||||
|   | ||||
| -Package: osmo-trx-lms | ||||
| -Architecture: any | ||||
| -Depends: ${shlibs:Depends}, ${misc:Depends} | ||||
| -Description: SDR transceiver that implements Layer 1 of a GSM BTS (LimeSuite) | ||||
| - OsmoTRX is a software-defined radio transceiver that implements the Layer 1 | ||||
| - physical layer of a BTS comprising the following 3GPP specifications: | ||||
| - . | ||||
| - TS 05.01 "Physical layer on the radio path" | ||||
| - TS 05.02 "Multiplexing and Multiple Access on the Radio Path" | ||||
| - TS 05.04 "Modulation" | ||||
| - TS 05.10 "Radio subsystem synchronization" | ||||
| - . | ||||
| - In this context, BTS is "Base transceiver station". It's the stations that | ||||
| - connect mobile phones to the mobile network. | ||||
| - . | ||||
| - 3GPP is the "3rd Generation Partnership Project" which is the collaboration | ||||
| - between different telecommunication associations for developing new | ||||
| - generations of mobile phone networks. (post-2G/GSM) | ||||
| - | ||||
|  Package: osmo-trx-ipc | ||||
|  Architecture: any | ||||
|  Depends: ${shlibs:Depends}, ${misc:Depends} | ||||
| diff --git a/debian/rules b/debian/rules | ||||
| index 5795643..5937c17 100755 | ||||
| --- a/debian/rules | ||||
| +++ b/debian/rules | ||||
| @@ -9,7 +9,7 @@ override_dh_shlibdeps: | ||||
|  	dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info | ||||
|   | ||||
|  override_dh_auto_configure: | ||||
| -	dh_auto_configure -- --with-uhd --with-usrp1 --with-lms --with-ipc --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals | ||||
| +	dh_auto_configure -- --with-uhd --with-usrp1 --with-ipc --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals | ||||
|   | ||||
|  override_dh_strip: | ||||
|  	dh_strip --dbg-package=osmo-trx-dbg | ||||
							
								
								
									
										1
									
								
								debian/patches/series
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								debian/patches/series
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| # build-for-debian8.patch | ||||
							
								
								
									
										1
									
								
								osmocom-bb
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								osmocom-bb
									
									
									
									
									
										Submodule
									
								
							 Submodule osmocom-bb added at a4aac5c355
									
								
							| @@ -11,7 +11,7 @@ EXTRA_DIST = BitVectorTest.ok \ | ||||
|              LogTest.ok \ | ||||
|              LogTest.err | ||||
|  | ||||
| noinst_PROGRAMS = \ | ||||
| check_PROGRAMS = \ | ||||
| 	BitVectorTest \ | ||||
| 	PRBSTest \ | ||||
| 	InterthreadTest \ | ||||
|   | ||||
| @@ -12,10 +12,6 @@ | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  */ | ||||
|  | ||||
| #include "PRBS.h" | ||||
|   | ||||
| @@ -4,7 +4,7 @@ AM_CFLAGS = -Wall -I$(top_srcdir)/Transceiver52M -I$(top_srcdir)/Transceiver52M/ | ||||
|  | ||||
| EXTRA_DIST = convolve_test.ok convolve_test_golden.h | ||||
|  | ||||
| noinst_PROGRAMS = \ | ||||
| check_PROGRAMS = \ | ||||
| 	convolve_test | ||||
|  | ||||
| convolve_test_SOURCES = convolve_test.c | ||||
| @@ -18,7 +18,7 @@ convolve_test_CFLAGS += $(SIMD_FLAGS) | ||||
| endif | ||||
|  | ||||
| if DEVICE_LMS | ||||
| noinst_PROGRAMS += LMSDeviceTest | ||||
| check_PROGRAMS += LMSDeviceTest | ||||
| LMSDeviceTest_SOURCES = LMSDeviceTest.cpp | ||||
| LMSDeviceTest_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LMS_LIBS) | ||||
| LMSDeviceTest_LDADD = \ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user