mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
				synced 2025-11-03 21:53:18 +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,7 +312,6 @@ static void threshold_timer_update_intv() {
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (llist_empty(&threshold_list)) {
 | 
			
		||||
		if (osmo_timer_pending(&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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -135,7 +135,8 @@ LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t c
 | 
			
		||||
		     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)
 | 
			
		||||
		     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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
  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;
 | 
			
		||||
  } else {
 | 
			
		||||
    corr_in = &burst;
 | 
			
		||||
     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
 | 
			
		||||
 | 
			
		||||
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