Compare commits

..

47 Commits

Author SHA1 Message Date
Eric
a0e1ed3215 ms-trx support
This is basically a trxcon that includes a transceiver, and can just
be used with existing and future apps supporting the trxcon interface,
i.e. mobile or ccch_scan.

Supports bladerf and uhd, binaries are automatically built if those libs
are detected + the osmocom-bb submodule exists.
Currently using hardcoded sched/prios aimed at a setup with working,
reliable usb and reserved cores, for example a raspi 4 (ONLY 4, not 3,
not 2, not any other version)

Additionally builds test tools used for development: osmo-trx-syncthing*

Change-Id: I36c65a8c725c4da76dc70006cd96b0a2b6878e84
2022-12-01 12:00:04 +01:00
Eric
a5439daaf7 vita demod by piotr krysik, modified
Had a few rounds of extensive cleanup. Uses gcc multiversioning for x86
targets.

Change-Id: I5466c522cf4de984a4810ec46df43a10b52ed78f
2022-12-01 12:00:04 +01:00
Eric
42cc715fac ill-fated ipcv2 for mstrx
The problem here is that the ipc if requires reducing the clock ind
inteval to a few fn/ts on the trx side to work at all, but scheduling
still does not work out, unless you start driving the IF using the tx
side, at which point the approach is useless, because it does not really
do more than a burst loopback that is easier to do with the higher
layers/interfaces.

The code is still useful should there be a reason to continue working on
this.

Change-Id: I8f582c7c06fed8d1dcc5ea52472a97dc313fdde5
2022-12-01 12:00:04 +01:00
Eric
008418327b bladerf xa4 support
This is not really finished, there are multiple reasons to not use this:
1) main clock is not a gsm multiple, so it will continously drift
2) small buffer sizes lead to tx gaps that are hard to detect and break
everything.

Change-Id: I455c34bb9520d5f09eeb1ac76fceb4bdea94d1ac
2022-12-01 12:00:04 +01:00
Eric
040497e0a4 osmocom-bb for ms-trx side trxcon integration
Change-Id: I784a3d66b68f6458056d92db2ec596755b704e61
2022-12-01 12:00:04 +01:00
Eric
8984d7f2ca rename noisevector class -> avgvector
The vectors feature is averaging, and not adding noise.

Change-Id: I05def8ab9ea7a2cece8db09c36c303e13ef40927
2022-11-30 16:40:42 +01:00
Eric
aa7a40ee84 ignore vscode dirs
Change-Id: Iad9fd20924b7cfc6dbbfb708aa9c692a3cab574c
2022-11-28 19:19:38 +01:00
Max
f2f35fc592 Add realtime scheduling and set priority in service file
This sets highest realtime priority among all Osmocom services.

Related: OS#5687
Change-Id: Id74acf201d4388493f0eeb38909641c662551164
2022-11-11 17:33:25 +03:00
Vadim Yanitskiy
00ddcfaf50 Transceiver::expectedCorrType(): RACH is always 8-bit on PTCCH/U
It does not make sense for the MS to use 11-bit Access Bursts on
PTCCH/U because the payload does not matter, only the ToA does.

Change-Id: I5fb9f1c6810cdcd26a885b183e414d01d422eb28
2022-10-26 04:13:43 +07:00
Max
2f20c564bf Set working directory in systemd service file
By default systemd will execute service with root directory
(or home directory for user instance) which might result in
attempts to create files in unexpected place. Let's set it
to 'osmocom' subdir of state directory
(/var/lib for system instance) instead.

Related: OS#4821
Change-Id: I041bb9e0b4250826d2d15d0ea2cdb93d7a0cd04b
2022-09-09 18:54:42 +07:00
Vadim Yanitskiy
19faae85c6 threshold_timer_update_intv(): call osmo_timer_del() unconditionally
osmo_timer_del() does check if a timer is active internally.

Change-Id: Icaf6f31a3235a3fb6993fa2bb35190150e35c2c2
2022-08-06 05:03:00 +07:00
Oliver Smith
1c6a3459cd contrib/jenkins: don't run "make distcheck" on arm
Avoid building osmo-trx a second time on arm during "make distcheck". If
the build of osmo-trx already passed on arm, running "make distcheck"
should give the exact same result as on x86_64.

This should shorten the build time significantly for osmo-trx jobs
running on raspberry pis, currently they take half an hour.

Change-Id: Ib4fbf7d54d479bbdda3c1415493bfc57b37d3971
2022-07-11 13:57:49 +02:00
Oliver Smith
32311d8635 gitignore: add uhddev_ipc.cpp
This file gets generated during the build.

Change-Id: I70bfb456e309daa4436767b2e55bce0c038a4975
2022-07-08 14:35:49 +02:00
Oliver Smith
71c46e91df configure.ac: add -lboost_thread for uhd < 4.2.0
Fix for the following error we see since building master-osmo-trx on
debian 10 instead of 9:
  /usr/bin/ld: ipc_driver_test-uhdwrap.o: undefined reference to symbol '_ZTIN5boost6detail16thread_data_baseE'
  /usr/bin/ld: //usr/lib/x86_64-linux-gnu/libboost_thread.so.1.67.0: error adding symbols: DSO missing from command line

After spending a lot of time on researching this, my understanding is
now that uhd.pc should have had "-lboost_thread" in versions up to the
latest release 4.2.0 because before that it would include boost thread
headers in its logging code:
04a83b6e76

ld is able to figure out which library provides the missing symbol, and
apparently depending on the binutils version and linker flags, it may
just ignore this and not show an error. This is why apparently it worked
with debian 9 and still does work in OBS (different flags), and why it
was not fixed upstream in uhd.pc. By now fixing it is not needed in the
latest version anymore, and there are already versions of uhd.pc in
various linux distributions without -lboost_thread, so I think it's
appropriate to add the workaround here in configure.ac.

Fixes: OS#5608
Related: https://stackoverflow.com/q/19901934
Change-Id: I0367f1c2981bf56252e7514d5993cbbec960b21b
2022-07-08 14:35:36 +02:00
Pau Espin Pedrol
a39fa875a3 Bump version: 1.4.0.3-9a3e → 1.4.1
Change-Id: I9a930264b7c40b3e205c9818634597cc21364a50
2022-06-29 09:32:57 +02:00
Harald Welte
9a3e3fceb8 update git URLs (git -> https; gitea)
Change-Id: I1ac62853250a6717f502959bd96fdc3e9ead7a91
2022-06-18 13:52:06 +02:00
Vadim Yanitskiy
1a19caf002 tests: use 'check_PROGRAMS' instead of 'noinst_PROGRAMS'
When using 'check_PROGRAMS', autoconf/automake generates smarter
Makefiles, so that the test programs are not being compiled during
the normal 'make all', but only during 'make check'.

Change-Id: I816689e2aeac9decbc44ba210956a929cc7a3169
2022-04-13 19:55:36 +03:00
Oliver Smith
424c74d006 treewide: remove FSF address
Remove the paragraph about writing to the Free Software Foundation's
mailing address. The FSF has changed addresses in the past, and may do
so again. In 2021 this is not useful, let's rather have a bit less
boilerplate at the start of source files.

Change-Id: I8ba71ab9ccde4ba25151ecbeb2a323f706b57d43
2021-12-14 12:23:00 +01:00
Pau Espin Pedrol
a7143d3cd0 Bump version: 1.3.1.28-019d-dirty → 1.4.0
Change-Id: Ie675909593b0c383b59b7b4a4edd46cd93283622
2021-11-16 16:27:26 +01:00
Vadim Yanitskiy
019d698126 LMSDevice: LMS_GetDeviceList() may return a negative number
Change-Id: I855bd8ea6d9cb0f285f4dbbf3bcd09bff4e71044
Fixes: CID#240718
2021-10-25 13:12:51 +03:00
Vadim Yanitskiy
a686277c72 IPCDevice: check value returned from select()
Change-Id: I1c823317659547bb2391c57ac4d7931de1a383e3
Fxies: CID#240744
2021-10-25 13:10:18 +03:00
Vadim Yanitskiy
683f140739 IPCDevice: use thread safe strerror_r() instead of strerror()
Change-Id: Ia51ffa51ec7729572faca0282ae41c1e4968049f
2021-10-25 12:56:44 +03:00
Vadim Yanitskiy
5e40d92400 trx_rate_ctr: use thread safe strerror() in device_sig_cb()
Change-Id: Ibd52af22bbe99652f402ada87410de167a124b73
2021-10-25 12:56:44 +03:00
Pau Espin Pedrol
bb2cb9d54b lms,uhd: Allow changing band between poweroff & poweron
Before this patch, reconnecting to osmo-trx and attempting to configure it for
another band is not going to work without restarting the process.
The new variable is added in order to still allow POWEROFF followed by a
POWERON without need to reconfigure the device. In that case, previous
configuration is kept.

Change-Id: I43e5e1e4dcb36be605c6bd25dd6a5f3649e244e7
2021-09-21 17:24:18 +02:00
Pau Espin Pedrol
b9423b25b6 lms,uhd: Skip re-assigning same band
There's no need to spend time looking up again the same band
description.

Change-Id: I6f5631c9e64b9c261d52a856d757d08d2f336947
2021-09-21 17:24:18 +02:00
Pau Espin Pedrol
069f5cd857 lms,uhd: Validate band of RxFreq too
So far the validation is only done on TxFreq for all TRX. Let's also do
it for RxFreq.

Change-Id: I30eef2727ee96b1344aa1416edd66e2302b88964
2021-09-21 17:24:11 +02:00
Pau Espin Pedrol
c90b207803 lms: Drop duplicated check
Same check is already done by set_band().

Change-Id: I48d14f35e83fa17d1a8f4154479f0a5cee0f816d
2021-09-21 13:48:20 +02:00
Pau Espin Pedrol
985694175d computeCI: Document hardcoded multiplier
Logarithm change of base rule is used. Document it so it's clear where
it comes from.

Change-Id: Ia588e8dafda4e1abe0721f12491661949339a1ba
2021-09-03 13:52:02 +02:00
Oliver Smith
ac8a4e7297 d/patches/build-for-debian8.patch: remove
Remove this workaround, as we are not building for debian 8 anymore.

Related: OS#5223
Depends: osmo-ci Ibe7ba124557969df62798ba49c4489e9606c2341
Change-Id: I5519075a7f95fa61b0b5f1825e4e9324b9eede76
2021-09-03 08:26:59 +00:00
Pau Espin Pedrol
e16d0e1330 cosmetic: Fix typo in comment
Change-Id: I33f4253cecab8d92eec75af49e1671874b8cc111
2021-09-01 20:43:03 +02:00
Pau Espin Pedrol
e6fdf8fcad detectGeneralBurst(): Increase log level about clipping to INFO
There's another related logging line also at INFO level in the caller
path in  Transceiver.cpp, but it only prints if detectBurst() failed.
Let's print this one as INFO too, which proved to be a good logging
level. This way user also notices gain is too high despite osmo-trx is
still able to decode bursts.

Change-Id: Ieca4f19ae1099a430e9b838f8b6780b1c61a87a9
2021-09-01 20:36:33 +02:00
Pau Espin Pedrol
cdd77a447d computeCI(): Constify read-only variable
Change-Id: Ia157b970db92eef252c3657b35607307b7ebf988
2021-09-01 20:10:04 +02:00
Pau Espin Pedrol
7f696801ae computeCI(): Rename verbose repeated getter to constant
Change-Id: I9b426d01a282f572c0b915c5666642dce4c60475
2021-09-01 20:08:55 +02:00
Pau Espin Pedrol
d16eb314ed computeCI(): Constify param and pass it as reference
Change-Id: Icba5fce57c858bd16196ae3012c100c7e4134335
2021-09-01 19:51:28 +02:00
Pau Espin Pedrol
27bd2f6dd1 detectBurst(): constify parameter
Change-Id: I3d8738b492a175f2ef0c570579e335e7b7695694
2021-09-01 19:46:43 +02:00
Pau Espin Pedrol
8803f923f9 detectBurst(): Clear downsampling code path
downsampleBurst() and the Resampler below it clearly only support or are
confgiured for 1<->4 setup currently.

Change-Id: Iebaff7a34bd24e56627f148182859918accbfa82
2021-09-01 19:43:11 +02:00
Eric
ecea734b97 lms: init band
Gain setting without a band was apparently led to a very low output
level, thanks to defog for pointing this out.

Change-Id: I8b59d38dd7b0781776c9e61226185879541fdc53
Related: OS#3342
2021-07-11 21:12:04 +02:00
Eric
0c34c64a16 vty: printing fn offset should be signed
...because it is usually negative

Change-Id: I8297dbb0fec25720e73d59fd8e38834029154405
2021-06-16 15:20:51 +02:00
Pau Espin Pedrol
0a038223d0 Use new stat item/ctr getter APIs
Change-Id: I1fdfdae2810c3c82ff62fe725ffa364df4ebeff5
2021-06-04 17:21:42 +02:00
Eric
5e6b10cd9e uhd: ensure configured clock source is actually used
We wouldn't want to get caught running with unlocked external clock
sources, right?!

Change-Id: Ie38d85617f46eb5ab7d9527ddf6aaab4d3edf6bf
2021-05-29 15:55:28 +00:00
Vadim Yanitskiy
b6f238c0f2 ctrl_sock_handle_rx(): fix missing space in LOGCHAN() statement
Change-Id: I3c69d64dfe79dcc815e1d412569ed4e9ed428c52
2021-05-27 21:40:13 +00:00
Eric
c27fe60a25 add hidden fn adjustment command
This is only useful if the rf path delays the signal by at least one
frame, and therefore a fairly experimental command that might be removed
or reworked in the future and should not be relied upon.

Change-Id: I29964acffad5bad4f5bcad7f631e435a72979c46
2021-05-05 17:34:28 +02:00
Vadim Yanitskiy
a1ea63f777 gitignore: remove non-existing 'doc/manuals/osmomsc-usermanual.xml'
Change-Id: If0fef3f12f15780ed3a3e33db25cd29082ff142a
2021-04-16 19:04:48 +02:00
Pau Espin Pedrol
c7930b0b22 ipc: Makefile.am: Clean LDADD variable
Change-Id: I26c942496ab12883a4a1e0d549cb462642570636
2021-03-01 16:35:42 +01:00
Pau Espin Pedrol
17ce7740e5 Threads: Avoid printing pthread_self()
The type used to represent a thread ID is implementation
specific, and may be an opaqe structure, making it unsuitable to be
printed by standard means. Let's use osmo_gettid() instead.

Change-Id: Iaa4d0eaf52b901fff06cc67f8dd8b61ac6084911
Related: OS#5032
2021-03-01 16:35:42 +01:00
Pau Espin Pedrol
d06259f348 Drop logging pthread ID
new libosmocore osmo-trx already depends on does support printing thread
ID as prefix to all messages (confgiurable through VTY), so there's no
use in printing it in osmo-trx unconditionally.

Moreover, The type used to represent a thread ID is implementation
specific, and may be an opaqe structure, making it unsuitable to be
printed by standard means, so in any case we should be better printing
system's TID instead.

Related: OS#5032
Change-Id: Ie98a21246230c946afc47f4f5b9c6618eefde494
2021-03-01 16:35:42 +01:00
Pau Espin Pedrol
6c646c35b9 Threads.cpp: Fix missing extern C around libosmocore include
Change-Id: I76975ed71382ff2afa8cfaff2950e23ff750201e
2021-03-01 16:35:35 +01:00
92 changed files with 7230 additions and 281 deletions

13
.gitignore vendored
View File

@@ -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
View File

@@ -0,0 +1,4 @@
[submodule "osmocom-bb"]
path = osmocom-bb
url = https://gitea.osmocom.org/phone-side/osmocom-bb.git
branch = a4aac5c3554559c2c994609f90b92a9daf6e8a89

View File

@@ -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.

View File

@@ -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

View File

@@ -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()

View File

@@ -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>

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -147,17 +147,17 @@ static int dev_rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
if (dev_ctrs_pending[chan].chan == PENDING_CHAN_NONE)
continue;
LOGCHAN(chan, DCTR, DEBUG) << "rate_ctr update";
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_RX_OVERRUNS];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_RX_OVERRUNS);
rate_ctr_add(ctr, dev_ctrs_pending[chan].rx_overruns - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_TX_UNDERRUNS];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_TX_UNDERRUNS);
rate_ctr_add(ctr, dev_ctrs_pending[chan].tx_underruns - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_RX_DROP_EV];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_RX_DROP_EV);
rate_ctr_add(ctr, dev_ctrs_pending[chan].rx_dropped_events - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_RX_DROP_SMPL];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_RX_DROP_SMPL);
rate_ctr_add(ctr, dev_ctrs_pending[chan].rx_dropped_samples - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_TX_DROP_EV];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_TX_DROP_EV);
rate_ctr_add(ctr, dev_ctrs_pending[chan].tx_dropped_events - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_DEV_TX_DROP_SMPL];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_DEV_TX_DROP_SMPL);
rate_ctr_add(ctr, dev_ctrs_pending[chan].tx_dropped_samples - ctr->current);
/* Mark as done */
@@ -178,21 +178,21 @@ static int trx_rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
if (trx_ctrs_pending[chan].chan == PENDING_CHAN_NONE)
continue;
LOGCHAN(chan, DCTR, DEBUG) << "rate_ctr update";
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TX_STALE_BURSTS];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TX_STALE_BURSTS);
rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_stale_bursts - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS);
rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_unavailable_bursts - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TRXD_FN_REPEATED];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TRXD_FN_REPEATED);
rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_trxd_fn_repeated - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TRXD_FN_OUTOFORDER];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TRXD_FN_OUTOFORDER);
rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_trxd_fn_outoforder - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_TRXD_FN_SKIPPED];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_TRXD_FN_SKIPPED);
rate_ctr_add(ctr, trx_ctrs_pending[chan].tx_trxd_fn_skipped - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_RX_EMPTY_BURST];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_RX_EMPTY_BURST);
rate_ctr_add(ctr, trx_ctrs_pending[chan].rx_empty_burst - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_RX_CLIPPING];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_RX_CLIPPING);
rate_ctr_add(ctr, trx_ctrs_pending[chan].rx_clipping - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TRX_RX_NO_BURST_DETECTED];
ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], TRX_CTR_TRX_RX_NO_BURST_DETECTED);
rate_ctr_add(ctr, trx_ctrs_pending[chan].rx_no_burst_detected - ctr->current);
/* Mark as done */
trx_ctrs_pending[chan].chan = PENDING_CHAN_NONE;
@@ -214,6 +214,7 @@ static int device_sig_cb(unsigned int subsys, unsigned int signal,
struct timespec next_sched = {.tv_sec = 0, .tv_nsec = 20*1000*1000};
/* no automatic re-trigger */
struct timespec intv_sched = {.tv_sec = 0, .tv_nsec = 0};
char err_buf[256];
switch (signal) {
case S_DEVICE_COUNTER_CHANGE:
@@ -222,7 +223,8 @@ static int device_sig_cb(unsigned int subsys, unsigned int signal,
dev_rate_ctr_mutex.lock();
dev_ctrs_pending[dev_ctr->chan] = *dev_ctr;
if (osmo_timerfd_schedule(&dev_rate_ctr_timerfd, &next_sched, &intv_sched) < 0) {
LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno);
LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno
<< " = "<< strerror_r(errno, err_buf, sizeof(err_buf));
}
dev_rate_ctr_mutex.unlock();
break;
@@ -232,7 +234,8 @@ static int device_sig_cb(unsigned int subsys, unsigned int signal,
trx_rate_ctr_mutex.lock();
trx_ctrs_pending[trx_ctr->chan] = *trx_ctr;
if (osmo_timerfd_schedule(&trx_rate_ctr_timerfd, &next_sched, &intv_sched) < 0) {
LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno);
LOGC(DCTR, ERROR) << "Failed to schedule timerfd: " << errno
<< " = "<< strerror_r(errno, err_buf, sizeof(err_buf));
}
trx_rate_ctr_mutex.unlock();
break;
@@ -263,7 +266,7 @@ static void threshold_timer_cb(void *data)
llist_for_each_entry(ctr_thr, &threshold_list, list) {
for (chan = 0; chan < chan_len; chan++) {
rate_ctr = &rate_ctrs[chan]->ctr[ctr_thr->ctr_id];
rate_ctr = rate_ctr_group_get_ctr(rate_ctrs[chan], ctr_thr->ctr_id);
LOGCHAN(chan, DCTR, INFO) << "checking threshold: " << ctr_threshold_2_vty_str(ctr_thr)
<< " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")";
if (rate_ctr->intv[ctr_thr->intv].rate >= ctr_thr->val) {
@@ -309,8 +312,7 @@ static void threshold_timer_update_intv() {
return;
if (llist_empty(&threshold_list)) {
if (osmo_timer_pending(&threshold_timer))
osmo_timer_del(&threshold_timer);
osmo_timer_del(&threshold_timer);
return;
}

View File

@@ -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);

View File

@@ -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 \

View File

@@ -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
-------------

View File

@@ -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

View File

@@ -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>

View File

@@ -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_

View File

@@ -584,7 +584,7 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
case XIII: {
int mod52 = burstFN % 52;
if ((mod52 == 12) || (mod52 == 38))
return cfg->ext_rach ? EXT_RACH : RACH;
return RACH; /* RACH is always 8-bit on PTCCH/U */
else if ((mod52 == 25) || (mod52 == 51))
return IDLE;
else /* Enable 8-PSK burst detection if EDGE is enabled */
@@ -649,7 +649,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
}
/* Set time and determine correlation type */
burstTime = radio_burst->getTime();
burstTime = radio_burst->getTime() + cfg->ul_fn_offset;
CorrType type = expectedCorrType(burstTime, chan);
/* Initialize struct bi */
@@ -1003,7 +1003,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan)
LOGCHAN(chan, DTRXCTRL, INFO) << "BTS requests TRXD version switch: " << version_recv;
if (version_recv > TRX_DATA_FORMAT_VER) {
LOGCHAN(chan, DTRXCTRL, INFO) << "rejecting TRXD version " << version_recv
<< "in favor of " << TRX_DATA_FORMAT_VER;
<< " in favor of " << TRX_DATA_FORMAT_VER;
sprintf(response, "RSP SETFORMAT %u %u", TRX_DATA_FORMAT_VER, version_recv);
} else {
LOGCHAN(chan, DTRXCTRL, NOTICE) << "switching to TRXD version " << version_recv;

View File

@@ -80,7 +80,7 @@ struct TransceiverState {
/* Received noise energy levels */
float mNoiseLev;
noiseVector mNoises;
avgVector mNoises;
/* Shadowed downlink attenuation */
int mPower;

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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;
};

View File

@@ -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;

View File

@@ -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

View 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);
}

View 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_

View 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

View 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 */
}
};

View 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

View File

@@ -134,8 +134,9 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev)
LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths):
RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
m_lms_dev(NULL), started(false), band((enum gsm_band)0), m_dev_type(LMS_DEV_UNKNOWN)
RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
m_lms_dev(NULL), started(false), band_ass_curr_sess(false), band((enum gsm_band)0),
m_dev_type(LMS_DEV_UNKNOWN)
{
LOGC(DDEV, INFO) << "creating LMS device...";
@@ -240,14 +241,17 @@ void LMSDevice::assign_band_desc(enum gsm_band req_band)
bool LMSDevice::set_band(enum gsm_band req_band)
{
if (band != 0 && req_band != band) {
if (band_ass_curr_sess && req_band != band) {
LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
<< " different from previous band " << gsm_band_name(band);
return false;
}
band = req_band;
assign_band_desc(band);
if (req_band != band) {
band = req_band;
assign_band_desc(band);
}
band_ass_curr_sess = true;
return true;
}
@@ -273,11 +277,12 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
LMS_RegisterLogHandler(&lms_log_callback);
if ((n = LMS_GetDeviceList(NULL)) < 0)
if ((rc = LMS_GetDeviceList(NULL)) < 0)
LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed";
LOGC(DDEV, INFO) << "Devices found: " << n;
if (n < 1)
LOGC(DDEV, INFO) << "Devices found: " << rc;
if (rc < 1)
return -1;
n = rc;
info_list = new lms_info_str_t[n];
@@ -464,6 +469,8 @@ bool LMSDevice::stop()
LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
}
band_ass_curr_sess = false;
started = false;
return true;
}
@@ -1013,25 +1020,38 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan)
return false;
}
if (band != 0 && req_band != band) {
LOGCHAN(chan, DDEV, ALERT) << "Requesting Tx Frequency " << wFreq
<< " Hz different from previous band " << gsm_band_name(band);
if (!set_band(req_band))
return false;
}
if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz";
return false;
}
band = req_band;
return true;
}
bool LMSDevice::setRxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
enum gsm_band req_band;
LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
if (req_arfcn == 0xffff) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
return false;
}
if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
<< " Hz (ARFCN " << req_arfcn << " )";
return false;
}
if (!set_band(req_band))
return false;
if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz";
return false;

View File

@@ -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;

View File

@@ -225,7 +225,7 @@ uhd_device::uhd_device(size_t tx_sps, size_t rx_sps,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths)
: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
rx_gain_min(0.0), rx_gain_max(0.0),
rx_gain_min(0.0), rx_gain_max(0.0), band_ass_curr_sess(false),
band((enum gsm_band)0), tx_spp(0), rx_spp(0),
started(false), aligned(false), drop_cnt(0),
prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
@@ -258,14 +258,17 @@ void uhd_device::assign_band_desc(enum gsm_band req_band)
bool uhd_device::set_band(enum gsm_band req_band)
{
if (band != 0 && req_band != band) {
if (band_ass_curr_sess && req_band != band) {
LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
<< " different from previous band " << gsm_band_name(band);
return false;
}
band = req_band;
assign_band_desc(band);
if (req_band != band) {
band = req_band;
assign_band_desc(band);
}
band_ass_curr_sess = true;
return true;
}
@@ -548,6 +551,7 @@ void uhd_device::set_channels(bool swap)
int uhd_device::open(const std::string &args, int ref, bool swap_channels)
{
const char *refstr;
int clock_lock_attempts = 15;
/* Register msg handler. Different APIs depending on UHD version */
#ifdef USE_UHD_3_11
@@ -620,6 +624,19 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
usrp_dev->set_clock_source(refstr);
std::vector<std::string> sensor_names = usrp_dev->get_mboard_sensor_names();
if (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) {
LOGC(DDEV, INFO) << "Waiting for clock reference lock (max " << clock_lock_attempts << "s)..." << std::flush;
while (!usrp_dev->get_mboard_sensor("ref_locked", 0).to_bool() && clock_lock_attempts--)
sleep(1);
if (!clock_lock_attempts) {
LOGC(DDEV, ALERT) << "Locking to external 10Mhz failed!";
return -1;
}
}
LOGC(DDEV, INFO) << "Selected clock source is " << usrp_dev->get_clock_source(0);
try {
set_rates();
} catch (const std::exception &e) {
@@ -779,6 +796,8 @@ bool uhd_device::stop()
for (size_t i = 0; i < rx_buffers.size(); i++)
rx_buffers[i]->reset();
band_ass_curr_sess = false;
started = false;
return true;
}
@@ -1088,12 +1107,29 @@ bool uhd_device::setTxFreq(double wFreq, size_t chan)
bool uhd_device::setRxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
enum gsm_band req_band;
if (chan >= rx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
}
ScopedLock lock(tune_lock);
req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
if (req_arfcn == 0xffff) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
return false;
}
if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
<< " Hz (ARFCN " << req_arfcn << " )";
return false;
}
if (!set_band(req_band))
return false;
return set_freq(wFreq, chan, false);
}

View File

@@ -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;

View 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
};

View 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);
}

View 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;
};

View 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;
}
}

View 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 */

View 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);
}
};

View 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
View 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> {};

View 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);
}

View 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

View 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
View 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, &gtx);
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
View 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);
}
}
};

View 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);

View 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, &param);
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
}
}

View 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;
}

View 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;
}

View 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
View 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
View 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);

View 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
}
};

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -34,6 +34,7 @@
#include "Resampler.h"
extern "C" {
#include <osmocom/core/panic.h>
#include "convolve.h"
#include "scale.h"
#include "mult.h"
@@ -1460,25 +1461,30 @@ static signalVector *downsampleBurst(const signalVector &burst)
* It is computed from the training sequence of each received burst,
* by comparing the "ideal" training sequence with the actual one.
*/
static float computeCI(const signalVector *burst, CorrelationSequence *sync,
float toa, int start, complex xcorr)
static float computeCI(const signalVector *burst, const CorrelationSequence *sync,
float toa, int start, const complex &xcorr)
{
const int N = sync->sequence->size();
float S, C;
int ps;
/* Integer position where the sequence starts */
ps = start + 1 - sync->sequence->size() + (int)roundf(toa);
const int ps = start + 1 - N + (int)roundf(toa);
/* Estimate Signal power */
S = 0.0f;
for (int i=0, j=ps; i<(int)sync->sequence->size(); i++,j++)
for (int i=0, j=ps; i<(int)N; i++,j++)
S += (*burst)[j].norm2();
S /= sync->sequence->size();
S /= N;
/* Esimate Carrier power */
C = xcorr.norm2() / ((sync->sequence->size() - 1) * sync->gain.abs());
C = xcorr.norm2() / ((N - 1) * sync->gain.abs());
/* Interference = Signal - Carrier, so C/I = C / (S - C) */
/* Interference = Signal - Carrier, so C/I = C / (S - C).
* Calculated in dB:
* C/I_dB = 10 * log10(C/I)
* C/I_dB = 10 * (1/log2(10)) * log2(C/I)
* C/I_dB = 10 * 0.30103 * log2(C/I)
* C/I_dB = 3.0103 * log2(C/I)
*/
return 3.0103f * log2f(C / (S - C));
}
@@ -1491,7 +1497,7 @@ static float computeCI(const signalVector *burst, CorrelationSequence *sync,
* and we run full interpolating peak detection.
*/
static int detectBurst(const signalVector &burst,
signalVector &corr, CorrelationSequence *sync,
signalVector &corr, const CorrelationSequence *sync,
float thresh, int sps, int start, int len,
struct estim_burst_params *ebp)
{
@@ -1500,12 +1506,18 @@ static int detectBurst(const signalVector &burst,
complex xcorr;
int rc = 1;
if (sps == 4) {
dec = downsampleBurst(burst);
corr_in = dec;
sps = 1;
} else {
switch (sps) {
case 1:
corr_in = &burst;
break;
case 4:
dec = downsampleBurst(burst);
/* Running at the downsampled rate at this point: */
corr_in = dec;
sps = 1;
break;
default:
osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps);
}
/* Correlate */
@@ -1515,9 +1527,6 @@ static int detectBurst(const signalVector &burst,
goto del_ret;
}
/* Running at the downsampled rate at this point */
sps = 1;
/* Peak detection - place restrictions at correlation edges */
ebp->amp = fastPeakDetect(corr, &ebp->toa);
@@ -1586,7 +1595,7 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
// and only report clipping if we can't demod.
float maxAmpl = maxAmplitude(rxBurst);
if (maxAmpl > CLIP_THRESH) {
LOG(DEBUG) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
clipping = true;
}
@@ -1862,7 +1871,7 @@ static float computeEdgeCI(const signalVector *rot)
* delay filters. Symbol rotation and after always operates at 1 SPS.
*
* Allow 1 SPS demodulation here, but note that other parts of the
* transceiver restrict EDGE operatoin to 4 SPS - 8-PSK distortion
* transceiver restrict EDGE operation to 4 SPS - 8-PSK distortion
* through the fractional delay filters at 1 SPS renders signal
* nearly unrecoverable.
*/

View File

@@ -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

View File

@@ -106,8 +106,11 @@ autoreconf --install --force
$MAKE $PARALLEL_MAKE
$MAKE check \
|| cat-testlogs.sh
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck \
|| cat-testlogs.sh
if arch | grep -v -q arm; then
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck \
|| cat-testlogs.sh
fi
if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
make -C "$base/doc/manuals" publish
@@ -115,9 +118,4 @@ fi
$MAKE $PARALLEL_MAKE maintainer-clean
# Verify distro-specific package patches apply:
for patch in debian/patches/*.patch; do
patch --dry-run -p1 < "$patch"
done
osmo-clean-workspace.sh

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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: *

View File

@@ -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

View File

@@ -1 +0,0 @@
# build-for-debian8.patch

1
osmocom-bb Submodule

Submodule osmocom-bb added at a4aac5c355

View File

@@ -11,7 +11,7 @@ EXTRA_DIST = BitVectorTest.ok \
LogTest.ok \
LogTest.err
noinst_PROGRAMS = \
check_PROGRAMS = \
BitVectorTest \
PRBSTest \
InterthreadTest \

View File

@@ -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"

View File

@@ -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 = \