Compare commits

..

221 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
Harald Welte
90d841748e Bump version: 1.3.0.1-e2404 → 1.3.1
Change-Id: I559b8d8608b3e492ae1ba0d5a54e226ab424b23b
2021-02-28 11:32:12 +01:00
Harald Welte
e2404f4e41 mark uhddev_ipc.cpp as BUILT_SOURCES
fixes "make dist-bzip2" on a clean checkout

Closes: OS#5052
Change-Id: Ieb4cefb16c8f43e708a96353c13342fe40ffdb54
2021-02-28 11:02:25 +01:00
Pau Espin Pedrol
309ad4d901 Bump version: 1.2.0.132-3b8f-dirty → 1.3.0
Change-Id: I92b99ebab1d54e9cbdc8469d76105c55bcb03f36
2021-02-23 14:27:15 +01:00
Vadim Yanitskiy
3b8f7c4d97 Add a (hidden) VTY parameter for Rx/Tx freq. shifting
Change-Id: I360e8ba91471757210c7f096c04928a6fbb91c61
Related: SYS#4454
2021-02-21 12:01:16 +01:00
Pau Espin Pedrol
48cad832ea tests: Replace deprecated API log_set_print_filename
Change-Id: I3cc0a92da39ab2594b3a7cefb314e2f2ecb628e2
2021-02-19 14:09:01 +01:00
Pau Espin Pedrol
56237bce95 tests: Explicitly drop category from log
Let's disable category here since we don't care about its formatting here.

In any case, every test relying on logging output validation should
always explicitly state the config to avoid issues in the future if
default values change.

Change-Id: Iaa77f8a7d3f752173507afd988bd76a8aa632082
Related: OS#5034
2021-02-19 14:08:44 +01:00
Pau Espin Pedrol
5738940535 Replace my_gettid with libosmocore osmo_gettid API
The API was moved to libosmocore, let's use it instead of defining our
own here with all the complexity in build system involved.

Depends: libosmocore.git Change-Id Id7534beeb22fcd50813dab76dd68818e2ff87ec2
Related: OS#5027
Change-Id: I19e32fbc47bd88a668e0c912e89b001b0f8831dd
2021-02-17 18:27:41 +01:00
Pau Espin Pedrol
cec9eda227 Threads.cpp: Use already existing gettid wrapper function
A wrapper function with better support already exists in debug.c and
announced in debug.h. Let's use that one instead.

Related: OS#5027
Change-Id: I2ccf94f95a531d5873da2a4681cf89cbc5b31422
2021-02-17 17:28:06 +01:00
Sylvain Munaut
ad202d72e1 sigProcLib: fix C/I computation for 8-PSK modulated bursts
Change-Id: I860af60bc0fbd36dfb38316fad65ddd3a5827a8f
Related: Ib4ceec553f2e5f77bf3f6777724968456a180f5e
Related: OS#4006, OS#4373
2021-02-04 20:44:51 +01:00
Oliver Smith
0f4d5791df configure.ac: set -std=gnu11
Change-Id: Ie95876d1d2ebf31ff588999d85584f6981522fa8
2021-01-28 10:48:35 +00:00
Vadim Yanitskiy
2a637a5c9c Transceiver: use proper factor for amplitude scaling
In Transceiver::addRadioVector() we scale the I/Q samples by scaling
the output voltage of the DAC.   A relative factor/divisor/ration in
the voltage domain cannot be used 1:1 in the power domain.

There exist two similar formulas:

  a) X_dB = 10 * log10(X_lin / X_ref)
  b) Y_db = 20 * log10(Y_lin / Y_ref)

both of them are correct, and according to [1]:

  a) If you convert a quantity X that relates to power or energy,
     => the factor is 10.
  b) If you convert a quantity Y that relates to amplitude,
     => the factor is 20.

Therefore we should be using 20 instead of 10.  This change makes
osmo-trx apply per-lchan attenuation values correctly.  Otherwise
it would double the values indicated in TRXD messages.

[1] https://dspillustrations.com/pages/posts/misc/decibel-conversion-factor-10-or-factor-20.html

Change-Id: I98bc00bd25df4913d45e55eb008d715aca76fc7c
Related: SYS#4918
2021-01-27 01:01:50 +01:00
Vadim Yanitskiy
819cad1776 Transceiver: fix integer division in addRadioVector()
By default, C/C++ compiler does assume integer division.  The
lack of explicit cast to 'double' causes the transceiver to
ignore non-decimal attenuation values (x % 10 > 0):

  txFullScale * 10 ^ ( -3 / 10)
	== txFullScale * 10 ^ 0
	== txFullScale * 1.0

  txFullScale * 10 ^ ( -8 / 10)
	== txFullScale * 10 ^ 0
	== txFullScale * 1.0

  txFullScale * 10 ^ (-10 / 10)
	== txFullScale * 10 ^ -1
	== txFullScale * 0.1

  txFullScale * 10 ^ (-18 / 10)
	== txFullScale * 10 ^ -1
	== txFullScale * 0.1

Change-Id: I85b1063f57f630d90c6da32827bec4a05afc6514
Related: SYS#4918
2021-01-21 00:24:16 +01:00
Pau Espin Pedrol
54a98b5b52 ChannelizerBase: Fix memory leak
The memory leak was reported by ASan:

Direct leak of 32 byte(s) in 1 object(s) allocated from:
    #0 0x7f23b488e459 in __interceptor_malloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x558e83e39e3c in ChannelizerBase::initFilters() /osmo-trx/Transceiver52M/ChannelizerBase.cpp:84
    #2 0x558e83e3a8a0 in ChannelizerBase::init() /osmo-trx/Transceiver52M/ChannelizerBase.cpp:188
    #3 0x558e83e2d263 in RadioInterfaceMulti::init(int) /osmo-trx/Transceiver52M/radioInterfaceMulti.cpp:197
    #4 0x558e83de76d2 in makeRadioInterface(trx_ctx*, RadioDevice*, int) /osmo-trx/Transceiver52M/osmo-trx.cpp:115
    #5 0x558e83dea663 in trx_start /osmo-trx/Transceiver52M/osmo-trx.cpp:600
    #6 0x558e83dead6f in main /osmo-trx/Transceiver52M/osmo-trx.cpp:695
    #7 0x7f23b2576151 in __libc_start_main (/usr/lib/libc.so.6+0x28151)

Change-Id: Ibc4c7edeb9bba517db08fce152d863e6cc0c7bbb
2021-01-18 11:39:24 +00:00
Pau Espin Pedrol
fca503d0b4 radioInterfaceMulti: Fix memory leak upon close()
The leak was reported by ASan.

Direct leak of 48 byte(s) in 1 object(s) allocated from:
    #0 0x7fd9c9c29f41 in operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:99
    #1 0x55bd63ae2364 in RadioInterfaceMulti::init(int) /git/osmo-trx/Transceiver52M/radioInterfaceMulti.cpp:209
    #2 0x55bd63a9c6d2 in makeRadioInterface(trx_ctx*, RadioDevice*, int) /git/osmo-trx/Transceiver52M/osmo-trx.cpp:115
    #3 0x55bd63a9f663 in trx_start /git/osmo-trx/Transceiver52M/osmo-trx.cpp:600
    #4 0x55bd63a9fd6f in main /git/osmo-trx/Transceiver52M/osmo-trx.cpp:695
    #5 0x7fd9c7910151 in __libc_start_main (/usr/lib/libc.so.6+0x28151)

Change-Id: Ia4f9d4e47caa86ada98054763573e652d281992c
2021-01-17 17:18:49 +00:00
Oliver Smith
e8edd1fcae contrib/jenkins: don't build osmo-gsm-manuals
Related: OS#4912
Change-Id: Ibacb11da37acfd324cee37068099627136717781
2021-01-13 13:16:48 +01:00
Alexander Couzens
7e27deb8cb osmo-trx.spec: move ipc-driver-test into package ipc-test
Allow to drop the uhd runtime dependency of osmo-trx-ipc.
uhd is only required for the driver-test utility.

Related: SYS#5266
Change-Id: Iff91e09684167247c9c7de0141451a5b9344aa0d
2021-01-08 09:32:03 +01:00
Harald Welte
a9512d963a manual: Fix typo OsmTRX -> OsmoTRX
Change-Id: I4b3a76e41d4abbb08046a241ae9b7c079ce990ae
2021-01-07 14:07:14 +01:00
Harald Welte
c6220741b1 README update
* use https for hyperlinks
* explicitly mention the primary use case with OsmoBTS
* fix spelling in title

Change-Id: I4f20ad8666dcc6bbc23d78b40b7c73ddd7e6eacc
2021-01-06 13:27:56 +01:00
Pau Espin Pedrol
4a4e607a19 ipc-driver-test: Allow setting dir prefix for UD socket
Change-Id: I35282b38a1d560fb3440fe0aa9a27808d9d116cc
2020-12-10 15:26:32 +00:00
Vadim Yanitskiy
1587307a99 vty: fix swapped documentation for 'filler type' command
vty_cmd_string_from_valstr() expands the given 'struct value_string'
sequentionally, so the order of entries in both filler_{types,docs}
shall match (regardless of the value assigned).

Change-Id: Ieb3bbc4fb30f303c47555ca77d03a9e965bc72b5
2020-12-08 15:38:00 +01:00
Pau Espin Pedrol
7e83f18bba ipc: Fix wrong reference to BTS in log line
Change-Id: Idd272959e335c46ca88e348dd792e15ddb317d61
2020-12-07 19:28:44 +01:00
Pau Espin Pedrol
57db77f185 main: generate coredump and exit upon SIGABRT received
Previous code relied on abort() switching sigaction to SIG_FDL +
retriggering SIGABRT in case the signal handler returns, which would
then generate the coredump + terminate the process.
However, if a SIGABRT is received from somewhere else (kill -SIGABRT),
then the process would print the talloc report and continue running,
which is not desired.

Change-Id: I3a3ff56cb2d740a33731ecfdf76aa32606872883
Fixes: OS#4865
2020-11-25 17:50:21 +01:00
Harald Welte
94def47fdf Use osmo_fd_*_{disable,enable}
Change-Id: Ic8c8c418e123fbdff625556a900b19650deefe0b
Depends: libosmocore.git Idb89ba7bc7c129a6304a76900d17f47daf54d17d
2020-11-11 20:15:59 +00:00
Vadim Yanitskiy
3fb4d31ecb doc/manuals: generate XML VTY reference at build-time
Unfortunately, we cannot re-use the existing Makefile rules from:

  $(OSMO_GSM_MANUALS_DIR)/build/Makefile.vty-reference.inc

because they do not allow to generate the list of $(DOCBOOKS) from
a template, and require the project to store everything in separate
folders with specific names.  Also, those rules expect that the
target PDFs contain only a single word in their names (for example,
'osmoapp-vty-reference', not 'osmo-app-vty-reference'), while in a
project with multiple similarly named targets this would reduce
readability (imagine 'osmotrxuhd-vty-reference').

Change-Id: I798ea3b7417b8ca3e9c7d50911158c5413526237
Depends: I6aac73d998c5937894233631e654a160d5623198
Related: SYS#4937, SYS#4910
2020-11-04 19:33:05 +00:00
Vadim Yanitskiy
744e44eaa1 main: use logging API to print SIMD info instead of printf()
Otherwise these logging lines end up in the automatically generated
XML VTY reference (stdout), so this breaks further XML processing.

Change-Id: I8e0fd728d406e2452c9c0ddad5bce5f6b17fab42
Related: SYS#4937, SYS#4910
2020-11-04 19:33:05 +00:00
Vadim Yanitskiy
faacb1987e vty: fix documentation for 'ext-rach (disable|enable)'
Do not use 'extended' because it's not the same 11-bit Access Burst,
as it was assumed before.  Add missing docs for 'enable'/'disable'.

Change-Id: I80b5a584e554eb7cc2416017b10fee032202b372
2020-11-04 19:29:45 +00:00
Vadim Yanitskiy
c0e7ce922a vty: auto-generate cmd and doc strings for cfg_filler_type_cmd
Change-Id: I7fb228c63f3246f443ece67106abba0432b1659e
2020-11-02 09:28:19 +00:00
Vadim Yanitskiy
ab6e7f35ab vty: remove groundless statement about filler type 'dummy'
Dummy bursts have nothing to do with A5/x encryption, and I see
no reason why (and how?!?) would using that filler type break
encryption in osmo-bts-trx.  I asked the author of this code
back in August 2020 [1], and so far didn't get any response.

[1] https://lists.osmocom.org/pipermail/openbsc/2020-August/013208.html

Change-Id: Iae513d7acbb8ef682e1744ac8726cbd6ece8bd87
2020-11-02 09:27:59 +00:00
Vadim Yanitskiy
5d6504c45a vty: fix documentation for 'multi-arfcn (disable|enable)'
Change-Id: Id653ee058b208ddc105947319adad8da667be11f
2020-11-02 09:27:14 +00:00
Vadim Yanitskiy
c620ced36d vty: cosmetic: use VTY_IPV4_CMD in 'bind-ip' / 'remote-ip'
Change-Id: I8e44f771cbd03629c066bc24c3186997761f93f1
2020-11-02 09:27:08 +00:00
Vadim Yanitskiy
ef79fd9b95 vty: fix documentation for 'rx-sps (1|4)' and 'tx-sps (1|4)'
Change-Id: I70d9b16fd2b1c2cbaafc978724369cd2c9679cbd
2020-11-02 09:27:02 +00:00
Vadim Yanitskiy
e0bdb6b47b vty: fix documentation for 'egprs (disable|enable)'
Change-Id: I7a28bede8fd7d68b3afe9deec381fc93e46d65a9
2020-10-30 04:17:39 +07:00
Vadim Yanitskiy
948b4e4096 vty: fix documentation for 'swap-channels (disable|enable)'
Change-Id: Idca1a2beab07ef4df9ae5c55658cab80f7cc7565
2020-10-30 04:17:39 +07:00
Vadim Yanitskiy
c7a750d428 Transceiver: explicitly init m{Rx,Tx}LowerLoopThread
Coverity warns us that a non-static class members:

  - mRxLowerLoopThread, and
  - mTxLowerLoopThread,

are not initialized in this constructor nor in any functions that
it calls.  I don't think it's critical, because we do initialize
them in Transceiver::start(), but let's make them nullptr.

Change-Id: If9e06aa7965f17383ab6599c15945e8ce2703bbf
Fixes: CID#214952
2020-10-25 15:24:21 +07:00
Vadim Yanitskiy
0ff9c9fca3 Transceiver: use size_t and ARRAY_SIZE() in constructor
Change-Id: I164d66aad04d77957300b07e83b085f43a3ee8c1
2020-10-24 23:41:57 +00:00
Vadim Yanitskiy
24cb0c9948 device: drop unreasonable LIBOSMO{CTRL,VTY}_{CFLAGS,LIBS}
Neither VTY nor CTRL API is used in device specific code, excluding
the 'uhd' where osmo_cpu_sched_vty_apply_localthread() is called.

Change-Id: I568b443da4b96c005734d749faa22b9c7440f951
2020-10-24 23:41:57 +00:00
Vadim Yanitskiy
6be2d15541 main: add --vty-ref-mode, use vty_dump_xml_ref_mode()
Change-Id: Ie54c45fdcc8660f37f8db2367b53404b189b3ffc
Depends: Ie2022a7f9e167e5ceacf15350c037dd43768ff40
Related: SYS#4910
2020-10-24 21:16:17 +00:00
Vadim Yanitskiy
bc5263cee1 device/common/Makefile.am: remove $(LMS_CFLAGS) from AM_CXXFLAGS
This is device-independent code, so it should not be here.

Change-Id: I1ffc3431a9a1a46c74c354b3f8a256684bfcbe73
2020-10-24 19:44:21 +00:00
Vadim Yanitskiy
6b4acc12f7 device/lms: get rid of 'using namespace std'
Change-Id: I4329801c502db73efa946f15c103b2c081cee5a7
2020-10-24 19:25:32 +07:00
Vadim Yanitskiy
d1ca287d83 device/lms: fix missing semicolon in LMSDevice::assign_band_desc()
Change-Id: I6aedb72306461ebb944fc13a795b0bf3121ea275
2020-10-24 19:25:32 +07:00
Vadim Yanitskiy
a0d862ba1d device/lms: fix: 'trx_vty.h' header requires C linkage
Otherwise, the linker fails to produce osmo-trx-lms binary:

  LMSDevice.cpp:493: undefined reference to
    `get_value_string(value_string const*, unsigned int)'

  LMSDevice.cpp:237: undefined reference to
    `osmo_panic(char const*, ...)'

Change-Id: I2fef166c13136af7b7aaa744d39427d76ad11769
Fixes: OS#4828
2020-10-24 19:25:32 +07:00
Harald Welte
c5989fe180 Use osmo_fd_setup() wherever applicable
Change-Id: Ie093dea96ec8990368695c0c5824e0fe44fb8540
2020-10-19 12:27:36 +02:00
Harald Welte
08970c562f ipc: Use OSMO_FD_* instead of deprecated BSC_FD_*
Change-Id: I98b3f9525954d6882f7488d650038a8e28f7b769
2020-10-18 22:41:40 +02:00
Pau Espin Pedrol
e91544d740 Calculate RSSI offset based on RxGain configuration
Prior to this patch, osmo-trx relied totally on proper VTY configuration
being set in "rssi-offset" together with the RxGain set through TRXC in
order to provide correct Uplink RSSI measurements to bts-trx.

With this patch, RSSI is now by default calculated (in LMS and UHD
backends) based on the currently set RxGain, by providing empirically
discovered values. Still, for backward compatibility, the old
"rssi-offset" command will overwrite completely the per-default
calculated rssi offset.
A new optional parameter "relative" is added at the end of the
"rssi-offset" VTY command to flag the value as relative to the newly
per-default calculated value. This way specific setups (like adding a
LNA / RF fronted) can still be expressed while still keeping the
automatic per-default offset.

Related: OS#4468
Change-Id: I8ef78fd20c22c60d61bfb18d80a4a36df4fd6c20
2020-10-14 12:53:04 +02:00
Pau Espin Pedrol
93fee1f163 Transceiver: Pass config struct instead of large list of params
Change-Id: Ifb43cb11f3e7a69b0a88f632f0a0c90ada7f939e
2020-10-14 12:52:04 +02:00
Pau Espin Pedrol
e69a56cec5 contrib/jenkins: Enable parallel make in make distcheck
Change-Id: I32925b35126bcd4ef7b5e1315dde28869c2b4b86
Related: OS#4421
2020-10-12 19:48:47 +02:00
Philipp Maier
76795401fb osmo-trx: add commandline option --vty-ref-xml
The commandline option --vty-ref-xml is needed to enable automatic
generation of the VTY reference manual.

Change-Id: I34dd36183e013ab005f39b235c4ab561590befb7
Related: SYS#4937, OS#1601
2020-10-09 20:48:00 +02:00
Philipp Maier
30863e8720 vty: add attributes to VTY commands indicating when they apply
Change-Id: I6dfdedc081eb8c3d53913f6fa38591920c8b3b43
Related: SYS#4937, OS#1601
2020-10-08 19:36:35 +02:00
Pau Espin Pedrol
0fbdfefebc arch: x86: Fix convolve optimizations breaking signal
This patch fixes MS failing to even see the network, and only RACHs of 1
zeroed byte being seen in GSMTAP.

The issue seems to only appear on some specific machines; others have
been running fine for weeks without this memset being an issue.

Fixes: 7a52e42ee0
Change-Id: I98ad885a5d71e7775973a4d881c0f1cd665ea711
2020-09-18 20:19:56 +02:00
Vadim Yanitskiy
b7c6f1e83f radioDevice: fix set_antennas(): consider MULTI_ARFCN mode
In the multi-ARFCN mode, if the Tx/Rx antenna names are explicitly
set in 'chan N' sections of the configuration file:

  trx
   ...
   multi-arfcn disable
   chan 0
    tx-path TX/RX
    rx-path RX2
   chan 1
    tx-path TX/RX
    rx-path RX2
   chan 2
    tx-path TX/RX
    rx-path RX2

osmo-trx would crash, because radioDevice::set_antennas() would
attempt to configure antenna names for all N physical channels,
while USRP devices usually have 2 or even 1 available.

The easiest approach is to remove both 'tx-path'/'rx-path' from
all 'chan N' sections excluding 'chan 0', so it would work fine.
This makes sense, because in the multi-ARFCN mode we actually
use only one physical channel.

However, let's still make sure that explicit configuration of the
Tx/Rx antenna names would not crash osmo-trx and skip N > 0 in
radioDevice::set_antennas().

Change-Id: I09f316f181cbbc2214e8913b73f7c1fcea4e8c05
Related: OS#4636
2020-09-12 14:47:21 +07:00
Vadim Yanitskiy
4d43684194 vty: add multi-ARFCN specific warning for chan N > 0
Change-Id: I12d7c466a9a428a384233c4377627e262f165401
Related: OS#4636
2020-09-12 14:39:20 +07:00
Vadim Yanitskiy
54bde5a8ba proto_trxd: cosmetic: 'if' is not a function, add space
Change-Id: I99cf10662232b1f6845d4019dd3b9be45a82d85b
2020-09-11 21:03:08 +07:00
Harald Welte
fd88564acb [cosmetic] radioIntefaceMulti: Fix whitespace / indent
Change-Id: I3addb844a79a8f4e8af03323fc50e57b8a3590a8
2020-09-11 12:43:05 +02:00
Harald Welte
82c72218fd [cosmetic] radioInterfaceMulti: More comments
Change-Id: If608627a77c39b5faabc72c7dd72d00fae8697a9
2020-09-11 12:43:02 +02:00
Eric
1f37e4dd74 transceiver: initialize reorder flag so we don't miscount
Change-Id: Ia7740b45611dbf09a406b3fd9f0a810d46c96bde
2020-09-04 20:36:34 +02:00
Pau Espin Pedrol
7d8676a144 Add support for TRXC MUTE command
Related: SYS#4920
Change-Id: I39983d026ad54c479aa224968e9491d01f30dc35
2020-09-01 13:27:39 +02:00
Harald Welte
8808fa86f0 Fix build on Debian8
We cannot use C99 or higher features in C code!

Last lines of build log:
[  265s]    for (unsigned int i = 0; i < decoded_region->num_chans; i++)
[  265s]    ^
[  265s] ipc-driver-test.c:473:3: note: use option -std=c99, -std=gnu99, -std=c11 or -std=gnu11 to compile
your code
[  265s] Makefile:580: recipe for target 'ipc_driver_test-ipc-driver-test.o' failed
[  265s] make[5]: *** [ipc_driver_test-ipc-driver-test.o] Error 1

Change-Id: I80c9cbd77f1cdf323ad2b492de7e9a177840c383
2020-08-27 10:08:53 +02:00
Pau Espin Pedrol
2a0fb962c7 ipc: fix var declaration in for loop
"""
error: 'for' loop initial declarations are only allowed in C99 or C11 mode
"""

Change-Id: I97cb9a0a3ecf64e3e5fcfca75431f8fe2a07bd10
2020-08-26 09:30:41 +00:00
Pau Espin Pedrol
03334967c9 jenkins.sh: Verify distro-specific patches apply
Change-Id: I75792c5defff63b7deaeb533b6818deaac3e0fd3
2020-08-25 15:41:30 +02:00
Pau Espin Pedrol
e30e0ad9be debian: Update debian8 osmo-trx specific patch
Recent commit adding osmo-trx-ipc didn't update the patch, which fails
to apply now due to files having changed its contents.

Change-Id: I6fa50e82320330f83c9753352418755e8b414edf
2020-08-25 15:41:30 +02:00
Eric
7a52e42ee0 transceiver: optimize code if optimizations are enabled
There is no point in checking basic stuff ten thousand times per second
since the sizes never change, so it's enough to enable the
checks/assertions for unoptimized (debug) builds.

This significantly decreases branch mispredictions.

Change-Id: Iebd9e91b3c7f37f2dc646d3017c45139977e4d15
2020-08-25 01:00:19 +02:00
Eric
4080eb76f8 devices: reset internal smart sample buffers upon stop
They are too smart, they keep the timestamps.

Change-Id: Idb4b8f03eb5ffdfd6d3fdbc137b20e3ddc4cfa65
2020-08-25 01:00:19 +02:00
Eric Wild
1e17c4fb0a osmo-trx-ipc
This adds a IPC backend that uses shared memory interface
to communicate with (proprietary) devices.

Requires config file option
dev-args ipc_msock=/path/to/socket
to specify the master socket the ipc backend should connect to.

If UHD is avaialble the ipc-driver-test tool can be used to test the
backend with a uhd device, this was so far only tested with a b2xx.

Change-Id: Ice63d3499026293ade8aad675ff7a883bcdd5756
2020-08-25 01:00:03 +02:00
Eric
f9a2f44272 add kernel style .clang-format with 120 chars per line limit
Change-Id: I1dc6610d7adfa1a52f3d41ad04544806c2be2c39
2020-08-14 03:47:43 +02:00
Vadim Yanitskiy
3bf2c5de8d debian/control: change maintainer to the Osmocom team / mailing list
Change-Id: Ida1a3583749a004e2a6c92cfed0b6806a5b83c78
2020-08-13 16:09:03 +07:00
Pau Espin Pedrol
553a25033e Use new libosmovty cpu sched config features
Using the new libosmovty features allow for:
* Setting different cpu-affinity masks for each thread in the process,
  both at startup through .cfg file as well as changing it at runtime.
* Unified VTY interface to change the scheduling policy of the process
  inherited by all osmocom processes enabling the feature.

Depends: libosmocore.git Change-Id If76a4bd2cc7b3c7adf5d84790a944d78be70e10a
Depends: osmo-gsm-masnuals.git Change-Id Icd75769ef630c3fa985fc5e2154d5521689cdd3c

Related: SYS#4986
Change-Id: I3798603779b88ea37da03033cf7737a6e4751d6e
2020-07-31 13:54:35 +02:00
Pau Espin Pedrol
1d165a043e Transceiver: Add several rate_ctr for rx error conditions
Since there's now a rate counter, we can drop log level for those events
which can be bursty and hence print lots of output in short periods of
time, which may affect performance. This way setting them to INFO it's
enough to avoid getting them in stderr unless explicitly configured by
the user (for instance to debug stuff), while still allowing a good
enough level to be enabled for other targets such as gsmtap.

Related: OS#4679
Change-Id: I000f7112e35ac68d3d922444f78468b1ea74cbba
2020-07-27 11:52:46 +02:00
Pau Espin Pedrol
199a306d27 Transceiver: Check log level before generating burst str representation
Avoid entering the logRxBurst() function and running a long loop even if
not used.

Change-Id: I67408bc8643d5d97355f277c4a2007064a83ae90
2020-07-27 10:58:54 +02:00
Pau Espin Pedrol
c249ce2a58 Transceiver: Lower some log levels which have an associated counter
They are left as INFO instead of DEBUG since they show possible
timing/performance issues in the setup.

Change-Id: I8aab10054ac89c29b871259fdbe59636723ddfb6
2020-07-17 18:33:13 +02:00
Pau Espin Pedrol
761da1a08a Introduce CTR log category
This way i most usual rate_ctr related internal logging is disabled by default
(notice), and it can b eeasily enabled by switching the category to info
or debug.

Change-Id: Id6c36432da7e7ce673c585bcae6772a695028ec5
2020-07-17 18:29:26 +02:00
Pau Espin Pedrol
9032c87d80 trx_rate_ctr: Lower some log levels
Change-Id: I1b5a63e6cc09ac02305c31ea7e94aff53ac0e5c2
2020-07-17 18:22:18 +02:00
Pau Espin Pedrol
df675784a7 Transceiver: Restrict conditions where FN gaps are detected
In pushRadioVector, like we did for driveTxPriorityQueue already, gaps
are possible in any channel for TRX!=0, since TRX0 is the only one
expected to be always transmitting.

Since we always need to transmit there, it makes no sense to check for
channel being not NONE.

Change-Id: I3b4b85b9100f69dfa113c54a4357120bd11ec86f
2020-07-17 14:12:37 +02:00
Pau Espin Pedrol
99330740dd Transceiver: Use already obtained value from Rx msg structure
Change-Id: I3854f284e6d6a561b3816b467985f59e690a282b
2020-07-15 14:06:03 +02:00
Pau Espin Pedrol
b70686c13d Transceiver: Provide initial value for TransceiverState::mFiller in constructor
Fixes: Coverity CID#211258
Change-Id: Ic00cc5939ca46407cb5bf8b6fcbcf3dc677041a2
2020-07-15 13:57:06 +02:00
Pau Espin Pedrol
1d0c6fe752 Add rate counter for missing Txbursts when scheduled towards the radioInterface
Related: OS#4487
Change-Id: Ibb2c492b3c67cbab11fbb936ae3a090fb5756aa8
2020-07-10 17:32:03 +02:00
Pau Espin Pedrol
8b0c5368f5 Transceiver: Fix race condition obtaining Dl burst from Upper layer
The queue was being accessed sequentially obtaining and releasing the
mutual exclusion zone twice. First in getStaleBurst() dropping all
FN<currTime, then in getCurrentBurst() trying to obtain FN=currTime.

However, since in between the mutex is released, it could happen that
for instance upper layer would introduce currTime-1 in the queue, which
would make then getCurrentBurst() detect that one instead of potential
currTime in the queue and return NULL.

By holding the mutex during the call to both functions we make sure the
state is kept during the whole transaction.

Related: OS#4487 (comment #7)
Change-Id: If1fd8d7fc5f21ee2894192ef1ac2a3cdda6bbb98
2020-07-10 17:32:03 +02:00
Pau Espin Pedrol
c0d6fd27ff Introduce rate counters to detect issues in received Dl bursts from TRXD
This ones together with rate counters already available in lower layers
allows to understand better the source of the problem with stalled tx
bursts.

Change-Id: Ia34f7e7d780ad1e12f24638a07f05fe91f2afea5
2020-07-10 17:32:03 +02:00
Pau Espin Pedrol
032c1d8da9 trx_rate_ctr: Fix locking wrong mutex
It was notcied due to sometimes causing deadlock at shutdown time.

Fixes: 92ba59dacf
Change-Id: I49bea4b0ae469794b5c80ee8fa4f275914a5194c
2020-07-10 17:31:10 +02:00
Pau Espin Pedrol
c62a05140c doc/manuals: Update thread documentation after dropping CTRL sock threads
Per-ARFCN CTRL threads managing CTRL socket loops were dropped a while
ago, but it was forgotten to udpate the documentation.

Change-Id: I34d117325e60b04b075c205d21bb0b827a5e8c52
2020-07-01 12:24:56 +02:00
Pau Espin Pedrol
a71c5d073f TransceiverState: Initialize ctrs field in constructor
Coverity dixit:
"Non-static class member field "ctrs.tx_stale_bursts" is not initialized
in this constructor nor in any functions that it calls."

Fixes: CID#211258
Change-Id: I4643a0500e9cad09938c05fab2f358167f72ffa9
2020-07-01 11:50:03 +02:00
Pau Espin Pedrol
92ba59dacf Introduce rate counter tx_stale_bursts
This allows checking if there's timing issues on the downlink side
between osmo-bts-trx and osmo-trx. This counter is useful to find
information about osmo-bts-trx 'fn-advance' setting, since this counter
basically counts if burstrs from it arrived too late to osmo-trx.

Change-Id: Id6df00da81f6d6884f4dddc5a2c4b354dca3af97
2020-06-29 17:08:37 +02:00
Pau Espin Pedrol
6a3a2b8647 Rename device specific rate counter multi-thread helpers
RadioInterface ones will be added in next commit, so let's differentiate
the structs required for each one.

Change-Id: Ib0e142a1dd4bedefdb4c5f15c34132da872c0975
2020-06-29 16:51:08 +02:00
Pau Espin Pedrol
fb96767ac5 trx_rate_ctr: Fix immediate rescheduling on per-sec thresholds
For instance, use in VTY:
"ctr-error-threshold tx_underruns 5 per-second"

If the condition becomes true (eg 5 underruns happened in one sec), the
statement inside OSMO_MAX would become -1, but it was being handled as
an unsigned when doing the OSMO_MAX internal comparison. As a result,
OSMO_MAX((unsigned)-1, 1) was returning -1 (unsigned) stored in
threshold_timer_sched_secs which then became and int -1, which was
handled by osmo_timer_schedule as a 0, hence having an immediate
trigerring all the time.

While at it, make threshold_timer_sched_secs unsigned since it doesn't
make sense to have it as signed anyway.

Change-Id: I6ea3d64dff189a5bc924e72d846e02d50536a8ea
2020-06-29 16:51:08 +02:00
Pau Espin Pedrol
394053e599 cosmetic: trx_rate_ctr: Fix whitespace
Change-Id: I4dc8220a6813d6ff30f1b241cc46b801adec4057
2020-06-29 16:51:08 +02:00
Vadim Yanitskiy
68d8db4d8c UHDDevice: catch LookupError/IndexError in set{Rx,Tx}Antenna()
Currently configuring 3 channels in multi-ARFCN mode makes the
process crash during the Rx/Tx antenna configuration due to
uncaught UHD specific LookupError/IndexError exceptions:

  terminate called after throwing an instance of 'uhd::index_error'
    what():  LookupError: IndexError: multi_usrp:
      TX channel 2 out of range for configured TX frontends

Let's catch them and terminate gracefully.

Change-Id: If66305f2787c6292375e4bfbd60c1d3d764cffd4
Related: OS#4636
2020-06-29 12:43:06 +00:00
Pau Espin Pedrol
58d80a014e {UHD,LMS}Dervice: Log expected resulting TxPower when setting device specific TxGain
Change-Id: I3c54c61cd6dd7e40bb2831fd4962ff72130b390d
2020-06-25 13:07:22 +02:00
Pau Espin Pedrol
5bd3d4263b Drop old TxGain APIs from parent radioDevice abstract class
All radioDevice subclasses except USRPDevice have already been reworked
to use the new SetPowerAttenuation() methods, hence we can drop the
compatibility layer that was added to transition from the old API to the
new one, and move those functions to USRPDevice.

This way we simplify the parent abstract class with methods not needed
by most devices and not used anymore by external users of those classes.

Change-Id: Ice005cd0a07c49b6e212c06f1228ef93c24db727
2020-06-25 13:07:22 +02:00
Pau Espin Pedrol
f68f19b110 LMSDevice: Compute TxGain on LimeSuite API based on expected Tx output power
Right now, according to a few measurements taken on LimeMicro devices, we
expect the Tx Gain at UHD level to relate 1:1 with the slope in Tx output
power given a specific band.

If more fine-grained results are wanted or some device doesn't follow a
1:1 slope relationship, functions TxGain2TxPower and TxPower2TxGain need
to be adapted/improved.

This patch is basically doing the same thing as was done previously for
UHDDevice in 992c9bd1ce.

Related: OS#4583
Change-Id: If154fe4d4cd118aa30ea43c22ee7119117b77da6
2020-06-25 11:05:36 +00:00
Harald Welte
8ac169f7ed osmo-trx.spec.in: Use %config(noreplace) to retain current config file
Change-Id: Ia6a279e4e19eee8368219e3bc1b011802b1fcadc
2020-06-24 05:25:34 +00:00
Pau Espin Pedrol
405f17a98c Transceiver: Allow sending negative nominal tx power in RSP NOMTXPOWER
Some SDR devices under some bands may provide only under 0 dBm Tx Power.

Change-Id: I8cecb7a37eb80db341a624eb7b826180eac4a1d4
2020-06-22 10:11:58 +00:00
Pau Espin Pedrol
b536ab9bdf proto_trxd: Fix UndefinedBehaviorSanitizer from ubsan
From UBSan:
proto_trxd.c:65:18: runtime error: 128 is outside the range of representable values of type 'char'.

Fixes: OS#4507
Change-Id: I71f815fe794a00934ee0e876848af56f30a21bfe
2020-06-19 18:31:05 +02:00
Harald Welte
174fb03b8e RPM spec file: Require uhd-firmware for osmo-trx-uhd
The automatic dependency will only depend on the UHD library
package.  The user will then fail to start osmo-trx-uhd due to
missing firmware.  So let's include an explicit 'Requires' to the
uhd-firmware package to solve this.

Change-Id: I0ebfe669c21d3957a48d57bd7248244e3e924236
2020-06-17 15:28:18 +02:00
Pau Espin Pedrol
b899c19f1f UHDDevice: Compute TxGain on UHD API based on expected Tx output power
Right now, according to a few measurements taken on B210, we expect the
Tx Gain at UHD level to relate 1:1 with the slope in Tx output power
given a specific band.

If more fine-grained results are wanted or some device doesn't follow a
1:1 slope relationship, functions TxGain2TxPower and TxPower2TxGain need
to be adapted/improved.

Change-Id: I6f432465dce5c6ec1f1bc4653f6149efb18c3f43
2020-06-15 10:41:16 +02:00
Pau Espin Pedrol
992c9bd1ce radioInterface: Operate on real Tx power attenuation rather than on device specific gains
All the Tx gain related APIs are left out of reach from radioInterface,
and in there we simply interact with radioDevice passing the attenuation
received from TRXC.

Prior gain logic is moved in base radiodevice class, with the idea that
the setTxGain() and related functions will be dropped over time in each
sublcass in favour of an specific implementation of the
SetPowerAttenuation API.

Change-Id: I4f8a1bcbed74aa9310306b97b0b1bfb02f7855e6
2020-06-09 11:43:32 +02:00
Pau Espin Pedrol
056ce136e6 UHDDevice: Implement getNominalTxPower() based on TxFrequency
The table with nominal UHD Tx Gains and real transmit power is filled
with values measured experimentally. More information can be found in
OS#4583.

Related: OS#4583

Change-Id: If7ef5bf95ffe4afe5864c0f051853aa38b9639eb
2020-06-09 11:43:24 +02:00
Pau Espin Pedrol
0e09e7c98a Transceiver: Implement TRXC cmd NOMTXPOWER
It allows the BTS to retrieve the nominal transmit output power value of
each TRX in order to compute attenuation later on and apply it through
SETPOWER or ADJPOWER TRXC commands.

Change-Id: I1d7efe56e008d8d60e23f9a85aa40809f7f84d9c
2020-06-08 15:49:36 +02:00
Pau Espin Pedrol
1b3a8881eb Transceiver: Fix extra space in RSP NOISELEV error
Change-Id: I35c2f3b3b9358ddb64a53f36969621d45bb243f8
2020-05-29 16:15:18 +02:00
Philipp Maier
fdefbfac39 Transceiver: Log when sending of CLK indications begins
When the logging category TRXCLK is set to info osmo-trx prints a
logline that informs about the sending of clock indications. In practice
this those log lines are often used to identify that osmo-trx and
osmo-bts are running properly, so it would be helpful, even in
productive use, if there would be an information in the log that the
sending of clock indications has begun. However, the regular printing of
the clock indication log line would soon flood the log. So, lets have an
addional log line that logs only once when the transceiver starts and
quickly informs at loglevel NOTICE that clock indications are now sent.

Change-Id: I6aa88943b76c9a2bf7aed60d6a3d325c1f27820a
Related: OS#2577
2020-05-26 22:49:02 +02:00
Philipp Maier
5e16f79f0f doc: switch log levels to notice
Some of the example configs have loglevels set to info. This is too
verbose, lets make sure all loglevels are set to notice

Change-Id: Ief82b85d9ff0e0e94eaabd255ebea961396fff32
2020-05-23 11:50:47 +00:00
Oliver Smith
f3155e33b9 Makefile.am: EXTRA_DIST: debian, contrib/*.spec.in
Change-Id: Ie192c9b516ff98f2b1ab8e7927da55a0c1e9eb56
2020-05-22 13:48:11 +02:00
Oliver Smith
7bbe19ee9d contrib: integrate RPM spec
Remove OpenSUSE bug report link, set version to @VERSION@, make it build
with CentOS 8 etc.

Disable lms, usrp1 for CentOS 8.

Related: OS#4550
Change-Id: Ie27fcc4f9033f0049507d9dcc295541ac0744c73
2020-05-19 17:05:30 +02:00
Oliver Smith
f669bf43e7 contrib: import RPM spec
Copy the RPM spec file from:
https://build.opensuse.org/project/show/home:mnhauke:osmocom:nightly

Related: OS#4550
Change-Id: I694fcd888778ab68d13165f4d0bf65e5d6870fb4
2020-05-19 17:04:19 +02:00
Pau Espin Pedrol
4a575b02b5 Use OSMO_FD_READ instead of deprecated BSC_FD_READ
New define is available since libosmocore 1.1.0, and we already require
1.3.0, so no need to update dependenices.
Let's change it to avoid people re-using old BSC_FD_READ symbol when
copy-pasting somewhere else.

Change-Id: I868baf575b77c448b3115701e70d439de89b0fb3
2020-05-09 18:54:20 +02:00
Harald Welte
cf35c37c94 prbs-tool: Don't require C99
from a Debian8 build:

[  256s] prbs-tool.c: In function 'apply_errors_prbs':
[  256s] prbs-tool.c:190:2: error: 'for' loop initial declarations are only allowed in C99 or C11 mode
[  256s]   for (int i = 0; i < sizeof(pchan->prbs_u)-4; i++) {
[  256s]   ^

Change-Id: I8541fce50405525efffb12210a269e0ab8a687ab
2020-05-07 23:55:56 +02:00
Harald Welte
6e369348b0 utils: Ensure content of this directory is included in 'make dist'
The python + matlab should be part of every source release

Change-Id: I9814a9a352dbee03177ef9e8dfd19bc2baf0ca07
2020-05-02 22:34:27 +02:00
Harald Welte
546516d79c prbs-tool: Add error simulation capabilities
The tool can now simulate:
* lost bursts on the TRX->BTS interface
* zeroed/overwritten bursts on the TRX->BTS interface
* errors in the TCH codec frames before passing them to the encoder

Change-Id: I0b52c2af6d973669ac233bf9868400e497496460
TODO: Ability to introduce errors in certain classes of the bits only.
2020-05-02 22:32:51 +02:00
Harald Welte
6879bb0db9 PRBS tool sending PRBS sequence to TRX
Change-Id: I2300f909bbfda10a7053320edfd1deaea763759a
2020-05-02 22:32:34 +02:00
Eric
a7143bf7a1 transceiver: get rid of the ctrl threads
There is no need to have n threads handle n ctrl sockets, since they all
will immediately respond to commands, so handle them from the existing
main osmo select loop.

Care must be taken to ensure that calls from within the command handler
do not block, or at least don't block too long, which currently is the
case.

Change-Id: I642a34451e1825eafecf71a902df916ccee7944c
2020-04-14 19:10:51 +00:00
Philipp Maier
4ffdca10d4 doc: apply an rssi-offset of 28 by default.
Set an rssi offset of 28 in the example configs to make sure that the
power control loop gets RSSI values that match at least half way the
reality when the 1800 Mhz band is used. For other bands the value will
be different (See also related osmocom ticket)

Change-Id: I62725fe454f54e2c7cb7550dadb1e6fc94337d78
Related: OS#4468
2020-04-14 17:16:23 +00:00
Eric
1a26b4f085 transceiver: check the right vector
Change-Id: I03800ae094c35c306fa4ca29f84e71d958ffdbdc
2020-04-14 13:17:25 +00:00
Eric
c5d5586fbd configure.ac: fix libtool issue with clang and sanitizer
As pointed out at https://github.com/libexpat/libexpat/issues/312
libtool does not play nice with clang sanitizer builds at all.
For those builds LD shoud be set to clang too (and LDFLAGS needs the
sanitizer flags as well), because the clang compiler driver knows how
linking to the sanitizer libs works, but then at a later stage libtool
fails to actually produce the shared libraries and the build fails. This
is fixed by this patch.

Addtionally LD_LIBRARY_PATH has no effect on conftest runs during
configure time, so the rpath needs to be set to the asan library path to
ensure the configure run does not fail due to a missing asan library,
i.e.:

SANS='-fsanitize=memory -fsanitize-recover=all -shared-libsan'
export CC=clang-10
ASANPATH=$(dirname `$CC -print-file-name=libclang_rt.asan-x86_64.so`)
export LDFLAGS="-Wl,-rpath,$ASANPATH $SANS $LDFLAGS"

Change-Id: If3d9465068b2c654b935fc3d9ab41d799d5e02e8
2020-04-11 18:33:42 +00:00
Philipp Maier
cc971aa1a6 doc: do not set the base-port of the trx
In current config files a base port for osmo-trx is set. Lets remove
this setting so that compiled-in default (which is the same value)
is used.

Change-Id: I105d1c51424836daa6893e83a81c83cc7ac6afd4
2020-04-01 10:21:50 +00:00
Philipp Maier
748d8edbf8 debug: use LOGL_NOTICE for log category DDEV
The log category DDEV ueses LOGL_INFO as debug level. This is too
verbose, lets use LOGL_NOTICE instead

Change-Id: I56d45ce5c3f55574491ffa6e4d902d6ba7499d46
Related: OS#2577
2020-03-25 12:36:15 +01:00
Pau Espin Pedrol
dfc6e5ffc7 radioDevice: Drop unused isControl param from WriteSamples API
The out "isControl" parameter is only used by internal callers of
USRPDevice, and not used at all by any user of the generic API
(radioInterface*.cpp). Hence, we can get rid of it and keep it as a flag
for an internal API of USRPDevice.

Change-Id: I843384e24b76cdd28a95f9ee4e95e6157098e4a3
2020-03-12 19:35:47 +01:00
Pau Espin Pedrol
f8c0c464b8 radioDevice: Drop unused RSSI param from readSamples API
The out "RSSI" parameter is only filled by USRPDevice, and not used at
all by any user of the API (radioInterface*.cpp).

RSSI seems to be computed nowadays in the common path in
Transceiver::pullRadioVector().

Change-Id: I06c2ea5a9891d170bc468f952bbf2a7e64d95784
2020-03-12 19:34:28 +01:00
Pau Espin Pedrol
93707d0227 cosmetic: fix several typos found by codespell
Change-Id: Id1f6766572fd313463201e6d03964965f227db25
2020-02-25 17:03:00 +01:00
Pau Espin Pedrol
5291e8a654 debug.h: Fix print format of chan in CLOGCHAN
Under armv7l arch, size_t is actually an unsigned int and not a long
unsigned int, and compiler errors:

CommonLibs/debug.h:28:24: error: format ‘%lu’ expects argument of type
‘long unsigned int’, but argument 8 has type ‘size_t {aka unsigned int}’ [-Werror=format=]

Change-Id: I7f6ded5a984570b5267916d6c84eb7d019db73a8
2020-02-19 18:08:20 +01:00
Pau Espin Pedrol
7a07de1efd debug.h: Avoid printing pthread_t type
Using %lu for pthread_t was wrong on armv7l arch. Even worse, according
to pthread_self() man page, pthread_t cannot be assumed to be of a simple
type and hence printable:
"""
POSIX.1 allows an implementation wide freedom in choosing the type used
to represent a thread ID; for example, representation using either an
arithmetic  type  or a structure is permitted.
"""

Let's use gettid() instead. According to glibc documentation:
"""
The pid_t data type is a signed integer type which is capable of
representing a process ID. In the GNU C Library, this is an int.
"""
It may not be the same on other libc's though, so let's better cast to a
long int just in case.

Accordign to gettid() man, the libc function was only added recently
during glibc 2.30, however the system call has been around for quite
some time (linux 2.4.11). Let's accomodate use udner non-glibc or older
versions of it by having a direct syscall fallback.

Change-Id: I40265fd4c62e550014ba3ff3335ca053c5bc01f2
2020-02-19 18:08:20 +01:00
Pau Espin Pedrol
fd67262df8 contrib/jenkins.sh: Reorder sanity checks
Change-Id: Idfe12148aa7a8030bdaf56d11c1547ebc3f56d14
2020-02-19 11:53:30 +01:00
Pau Espin Pedrol
0569845a08 lms: Initial multi-arfcn support
With current state multi-arfcn can be used (eg. I can place a call
between 2 phones using TRX1 and sustain for as long as wanted), but from
time to time (around every 20seconds), a burst of Tx packed dropped
events from LimeSuite appears.

LimeNet-micro coefficients have yet not been tested.

Related: OS#4362
Change-Id: I7e67d90a8126546eeeeba376f816ec5d158d4712
2020-01-15 15:46:14 +01:00
Pau Espin Pedrol
c69b87f9bd lms: Make ts_offset and smpl rate coefs device-specific
Right now the values are the same for all devices, but they will differ
in forthcoming commits once multi-arfcn support is added.

Change-Id: I262d3a71848fc3070473e29e42820848e7591d02
2020-01-15 15:46:14 +01:00
Pau Espin Pedrol
a7bf6cd8a4 lms: Store device type specific parameters in one place
Add an enum containing each supported device type (LimeSDR-USB,
LimeSDR-Mini and LimeNet-Micro) plus "unknown", to leave some room for
yet-to-come devices to run with some generic parameters without
rebuilding osmo-trx.

Each device type is assigned a dev_desc structure, and all of them are
put in HashMap, similar to what's already done in UHDDevice.cpp.

Device type is infered from string provided by LMS_GetDeviceInfo(), as
it was already done before in several places. From now on, we only need
to parse the string once since we store the device type after first
during open time.

Later on, more fields will be moved to device-type specific structure,
such as Tx timing offset, clock rate, etc.

Change-Id: I7658615787c5bc41c365bab9c11733b701ac2ae5
2020-01-15 15:45:29 +01:00
Pau Espin Pedrol
e7f6a27ab6 lms: Move rx_buffers allocation to constructor
Release is done in destructor, so let's move allocation to constructor
since there's really no need to have them in open() which is already
quite complex and large.

Change-Id: I8a4fd973590c4c165abd8f2837b2da8fc14a2066
2020-01-15 15:35:01 +01:00
Pau Espin Pedrol
a979f5f32b lms: Make reference to std::vector unambiguous
Change-Id: Ieebdbd3d5082a02aea2441e6737783370511cbc1
2020-01-15 15:34:49 +01:00
Pau Espin Pedrol
b0e54265ad lms: Change radioDevice constructor arg name to avoid masking instance attr
channel number mangling based on multi-arfcn feature being enabled was
moved to generic radioDevice() to reuse code. Hence, the generic parent
constructor sets this->chans to 1 if multi-arfcn feature is requested.
However, LMSDevice constructor argument had same name as the class
instance attribute, taking preference. As a result, if multi-arfcn is
enabled in LMSDevice, the generic constructor first sets this->chans=1
but afterwards LMSDEvice constructor keeps calling .resize() with the
argument value "chans" instead of using this->chans.

Let's rename the argument in all radioDevice child class constructors to
avoid potential future bugs in all of them.

Change-Id: Id6c837e9133f22783dd92a81dfcc493e51bf2d21
2020-01-13 16:13:28 +01:00
Pau Espin Pedrol
62c9280590 lms: Improve smpl_buf error logging
Change-Id: I511abe2c333443b978a3767bd7b7e320e07c4930
2020-01-13 14:35:06 +01:00
Pau Espin Pedrol
1421adbc61 smpl_buf: Fix str_code() param and print unknown error val
Change-Id: I95fadac15b9ad337ebc7cfb44a20dcf803ff8a47
2020-01-13 14:34:19 +01:00
Pau Espin Pedrol
dccc82491c lms: Drop unused define
Change-Id: Iaf3361ed29dd552e5e52b62bc738fa20c6b583fe
2020-01-07 16:21:12 +01:00
Pau Espin Pedrol
bf58370675 lms: Move initialization of field started to constructor
Change-Id: I135a2ff4a419775169452be1128c7b30f7d638ad
2020-01-07 16:10:34 +01:00
Pau Espin Pedrol
7c84925ea4 doc: Update vty reference xml file
Change-Id: Ib2707204cbba6df813ffc08d7098093cf4393da0
2020-01-07 16:04:04 +01:00
Pau Espin Pedrol
fd99c6ce05 radioInterfaceMulti: Fail to tune on freq not following multi-arfcn restrictions
multi-arfcn feature uses a hardcoded disposition of logical channels on
a physical channel. Logical channels in the phisical channel are
separated by MCBTS_SPACING Hz, that is 4 GSM ARFCNs.

As a result, multi-arfcn restricts the TRX ARFCN setup to the following:
ARFCN(TRX0)=N, ARFCN(TRX1)=N+1*4, ARFCN(TRX2)=N+2*4, ...

Let's make sure radioInterfaceMulti verifies the requested Rx/Tx
frequencies for each logical channel over TRXC match the restriction
explained above. It will check freq going to be set is indeed separated
by MCBTS_SPACING from already set channels, making sure the ARFCN series
is consistent.

Otherwise, before this patch, one could set in osmo-bsc:
ARFCN(TRX0)=N, ARFCN(TRX1)=N+2

and osmo-trx would silently ack the related Rx/TxTUNE TRXC commands, but
actually still transmit on ARFCN N+4 instead. As a result, in this
scenario TRX!=0 were unusable with multi-arfcn.

Related: OS#4207
Change-Id: I2f3d66a611d3a489b3e4d9431994f4ec77b4460f
2020-01-07 16:04:04 +01:00
Pau Espin Pedrol
e947db8d98 doc: clarify number of channels on B210 with multi-arfcn enabled
Change-Id: I082d4d8c346f1be1569fe63baa856029e439cb2c
2020-01-07 16:04:04 +01:00
Pau Espin Pedrol
9279e0e123 uhd: Improve some logging lines printing UHD pretty-print output
Change-Id: If5aba28aaf8a3312d89b3e963184f9f20966d199
2020-01-07 16:04:04 +01:00
Pau Espin Pedrol
84231bd8b7 uhd: Use DEVDRV log category and support UHD >=3.11 logging framework
Change-Id: I36f1ff7d425a2144fb512ff393af02741eb4a3d4
2020-01-07 16:04:04 +01:00
Pau Espin Pedrol
aebbfe0ee7 Make logging category DLMS generic and reusable for other backends
Make sure old configs using "logging level lms <level>" are still accepted.
Initialization order of VTY componenets need to be resorted since newly
introduced command requires logging VTY node to be already setup
beforehand.

Change-Id: Ia195a74a62a8a3dd6267fb1359acaa5628208d8e
2020-01-07 16:04:04 +01:00
Pau Espin Pedrol
da7fee8ea8 Bump version: 1.1.1.38-9f2b-dirty → 1.2.0
Change-Id: I9009eb44e7d8100294da139300480fc3a2f6b616
2020-01-03 19:54:00 +01:00
Pau Espin Pedrol
9f2baf3e04 Transceiver.cpp: Introduce and use new logging categories
Take the chance to clean up logging lines in this file:
* Use LOGCHAN in more places where chan is useful
* Replace inherited (old osmo-trx) categories such as WARNING with
osmocom ones.

Change-Id: Ic8c218f050f35d48046ccf1561fb0bfc505d4f63
2019-12-20 14:19:35 +01:00
Pau Espin Pedrol
0a2a40f43c uhd: use value already cached in tmp variable
Change-Id: I4568eaed6db3da12f83f2f503a50032f7bfb482c
2019-12-19 13:52:57 +01:00
Harald Welte
501d053b70 trx: exit() on unsupported positional arguments on command line
Change-Id: Ifeea65a359e9ca307efb2c721fd0fb6f0959e976
2019-12-03 21:41:38 +01:00
Pau Espin Pedrol
0fafe03199 Transceiver: Fix wrong response upon CMD HANDOVER failure
Change-Id: I9d3f120b1696a9ce92c81097d04e81dbb717287d
2019-11-27 13:17:59 +01:00
Alexander Chemeris
9a87d90c1e vty: Simplify filler burst settings and improve help and readability.
In the command line options time, filler table/filer burts settings
were a bit difficult to undertand because the number of one-letter
settings was limited. Now, with VTY configuration, there is no reason
to keep it so difficult.

Also, after the previous commit it was no longer posible to enable
random 8-PSK filler bursts. With this patch you can configure all
supported filler bursts in a simple and logical way.

Change-Id: I752eb2c1162d084e8769181f2fcd6c0877663448
2019-10-21 08:41:07 +00:00
Martin Hauke
066fd04f47 Fix common misspellings and typos
Change-Id: I4ec7accb1912c052b446be7c399bed32a8c62253
2019-10-17 08:06:19 +00:00
Alexander Chemeris
aeaba02e02 vty: Don't enable random filler bursts automatically with EDGE.
The EGPRS switch in the VTY config enables 8-PSK burst detection on
uplink. Enabling it shouldn't turn on filler bursts.

Change-Id: I2786c768e038b769a80c8b78fe58cfa09eb322a9
2019-10-15 12:00:55 +00:00
Pau Espin Pedrol
b7e992703c Use new libosmocore logging lock API
Since libosmocore Id7711893b34263baacac6caf4d489467053131bb, a new API
log_enable_multithread() is available which takes care of protecting
logging infrastructure from us (and actually does it correctly since we
cannot protect internal libosmocore structures from osmo-trx).

Let's drop all mutex related code from osmo-trx logging infra and simply
rely on libosmocore's.

Related: OS#4088
Change-Id: I519d0f30bce871005ca26b90177ea4aa4839360a
2019-10-09 19:15:49 +02:00
Pau Espin Pedrol
928141b7d6 radioInterfaceMulti: write frequency offset direction (rx/tx) in log line
Change-Id: I7b89426e5d7d5e7570d4ef800a30c7b74bd09b82
2019-09-30 12:18:31 +02:00
Pau Espin Pedrol
e503c988d8 radioInterface: Atomically fetch and change underrun variable
Otherwise, it could happen that underrun events are lost:
TxLower (isUnderrun):	RxLower (pullBuffer):
read(underrun)
			read(underrun)
			write(underrun, |val) [maybe underrun becomes TRUE]
write(underrun, false)

Similary, it could happen the other direction if atomic was only applied
to isUnderrun:
TxLower (isUnderrun):	RxLower (pullBuffer):
			read(underrun) -> true
read(underrun)-> true
write(underrun, false)
			write(underrun, true|val) where val=false

So in here isUnderrun would return true twice while it should only
return one.

Change-Id: I684e0a5d2a9583a161d5a6593559b3a9e7cd57e3
2019-09-20 19:17:22 +02:00
Pau Espin Pedrol
ee2ba19cec Move multi-ARFCN chan amount modification from UHDDevice to parent class
This way switch is applied correctly to parent structures and features
can be used later by other children classes (other devices).

Change-Id: I24d6c66bb3195ba2513b4a67daa14cdfbacdce6d
2019-09-20 19:17:22 +02:00
Pau Espin Pedrol
80d053acb9 radioInterface: Mark setRxGain as virtual
Otherwise the parent function is always called even if the iface is
radioInterfaceMult.

Change-Id: Ie41efab1e60b88677bbd1ec333ea656794503a5a
2019-09-20 19:17:22 +02:00
Timo Jacobus
18a615176e Transceiver: Fixed copying of history into and from channelizer buffer.
In multi arfcn mode, osmo-trx would drop some bursts because it couldn't detect it
and would emit idle burst instead. Specificaly detection of peak in correlation
vector failed. Correcting copying of history in pullBuffer method fixes this issue.

[Re-worked by Pau Espin Pedrol <pespin@sysmocom.de>]

Fixes: 57df2362f0eca0a330aad3e18906046dfadb9c8b
Change-Id: I93e43f6868cd67e69fc59d2980a03550d2505bf8
2019-09-20 19:17:22 +02:00
Pau Espin Pedrol
25383a3610 radioInterface{Multi,Resamp}: Fix successful writeSamples() masking underrun from readSamples()
The only who should be setting class instance value "underrun" to false
is isUnderrun().
Similar fixes were already applied lately to radioInterface.cpp.

Change-Id: I3239b1df4536c080365106b3e4daa523b57f5dff
2019-09-13 18:53:29 +02:00
Pau Espin Pedrol
e2ac20a96e UHDDevice: Drop unneeded MULTI_ARFCN checks
After previous changes, radioInterfaceMulti is expected to handle channel
conversion correctly, so it will always use chan 0 for all these
functions. This simplifies code in radioDevice avoiding need to add
checks to all devices supporting multi-arfcn in the future.

Change-Id: Ib2cd50a6ceaeedc6aaf3e1bb51d33b52911b6eba
2019-09-13 17:54:27 +02:00
Pau Espin Pedrol
62845241a2 radioInterfaceMulti: Override setTxGain() to avoid chan!=0 calls
Change-Id: I7e67f660c3b0b009db59b405de603f6058021802
2019-09-13 17:50:10 +02:00
Pau Espin Pedrol
2ab921812e radioDevice: Introduce getTxGain() API
It will be used in later commits by radioInterfaceMulti.

Change-Id: Ie3caca8971ed1e5370dfed6fb60716a24e7d82a5
2019-09-13 17:34:46 +02:00
Pau Espin Pedrol
752055c7c1 radioInterface: Remove unusued getRxGain()
Only the radioDevice->getRxGain() is called from inside
radioInterfaceMulti, so the API in radioInterface is not used at all.

Change-Id: Icc4e9a7ebfdafe7c72c535752a5e379d12592c9a
2019-09-13 17:34:46 +02:00
Pau Espin Pedrol
705a348326 LMSDevice: Return previous txGain/rxGain if setting value failed
Change-Id: I11e853e11bec99fc88e81642f9b2cd87d5815398
2019-09-13 17:33:14 +02:00
Pau Espin Pedrol
331c88ae82 USRPDevice: Return previous txGain if setting value failed
Change-Id: I0d8fd51586ef01141d4e5896f0fc3029a22743f8
2019-09-13 17:33:14 +02:00
Pau Espin Pedrol
ca0892d822 USRPDevice: Fix setRxGain return on error and getRxGain() returning always 0
field rxGain is set to 0 during constructor and never set after that
point.

Change-Id: I7fae7a315e5ab98a15c27628a88a92226ef89469
2019-09-13 17:32:03 +02:00
Pau Espin Pedrol
17e6cd0394 radioInterfaceMulti: Check equals zero explicitly
It's not a pointer check or a boolean expression, in here we really
check chan index is 0, so it's more clear doing so this way like it's
done in all other places.

Change-Id: I83b14487d14ba8272f58796f640f58a88891e532
2019-09-13 16:20:46 +02:00
Pau Espin Pedrol
a801ae5d94 radioInterface: Rename mRadio to mDevice
Previous naming is ready confusing, because "Radio" is actually the
common term between radioInterface and radioDevice, and it looks like
it's referring to radioInterface rather than radioDevice. On the other
hand, mDevice cleary states it refers to the radioDevice item.

Change-Id: I708bb1992a156fb63334f5590f2c6648ca27495e
2019-09-13 15:59:31 +02:00
Pau Espin Pedrol
923b4bc9a2 Transceiver: Don't stop TRX if pulling from OFF timeslot
BTS may have any timeslot disabled, or may have not yet sent initial
SETSLOT cmd to properly configure the timeslot.

Change-Id: Icf62e5d1200c7a440f255bb46023cdbf61532b7f
2019-09-09 10:27:04 +02:00
Pau Espin Pedrol
0d56d75dbb Transceiver: pullRadioVector(): Fix use of uninitialized value bi->tn
Change-Id: Ia0f2b5a51040663d7e8219e6ed51e0513b876548
2019-09-09 10:20:58 +02:00
Pau Espin Pedrol
94c5241403 Transceiver: Log error condition no burst in pullRadioVector()
Change-Id: I4f180cc852582b131ba28a8139215335f7ba567d
2019-09-06 14:37:36 +02:00
Pau Espin Pedrol
e3a2516280 Transceiver: Use LOGCHAN in logRxBurst to unify log format
Change-Id: Iec33ab73a9bf90fd0bff9ba453c32ea11bf0670e
2019-09-06 14:37:36 +02:00
Pau Espin Pedrol
1fba10409b Transceiver: Fix logging TN and version
Since tn is declared as uint8_t, it's actually a char, and by default
c++'s ostream& operator<<(ostream&, unsigned char) tries to print chars
with its ASCII visible character instead of numeric value.

Change-Id: I534158e8e1719ad19a9cde7c747a8f8ad5a01a2b
2019-09-06 14:33:59 +02:00
Pau Espin Pedrol
77f3396d04 device: Use LOGCHAN in set_antennas()
Change-Id: I3099498e3a3f26b53d55a96a36cc056f7b25b27a
2019-08-26 17:13:23 +02:00
Pau Espin Pedrol
e0010fa425 lms: Log available antennas if requested antenna fails
Related: OS#3343
Change-Id: Icd328e85b0a75ef67f371a7ed72638053b1854f9
2019-08-26 17:13:17 +02:00
Pau Espin Pedrol
bfc1d0bed7 Transceiver: Enable EDGE detection only on PDCH timeslots
Related: OS#3664
Change-Id: Id16248a1c03f9bc9f323c707857cdf3bf34c4e3e
2019-08-26 16:34:49 +02:00
Pau Espin Pedrol
76ff96e210 Transceiver: exit process when BTS drops connection
We don't want to keep osmo-trx running in a started state once the BTS
controlling it becomes unavailable. If a socket towards the BTS fails,
it means the BTS is gone and the best thing to do is to stop the process
(alternatively we could go back to stopped state instead, and wait for
BTS to re-connect, fur so far this action is good enough).

Related: OS#4170
Change-Id: I2ccbe3c17b39fb792ea7810f840235c348054d66
2019-08-26 15:55:49 +02:00
Pau Espin Pedrol
4e6ec4554d Transceiver: Fix idle ul burst indications being dropped
pullRadioVector() should return true on idle frames because those
indications may be handled by upper layers (TRXDv1). Clarify return code
in function documentation.

Change-Id: If592ed1c04cf7e995f656b313f60edd4d40d1bfd
2019-08-26 15:45:56 +02:00
Pau Espin Pedrol
e4166be76f Transceiver: Clean up receival of downlink bursts
Use a packed structure to clearly indicate what is contained in the
received buffer.

Change-Id: I4d8c0e3c0c717699889f79e50c778d14b6058f2d
2019-08-26 14:34:09 +02:00
Pau Espin Pedrol
e7195ac7d0 Transceiver: Check return value when binding IP addr for clock socket
No need to continue further since anyway we'll end up exiting.

Change-Id: Id8b38b40df5744beb705d560defb06532cf0fd44
2019-08-26 10:55:08 +02:00
Pau Espin Pedrol
ca6a78e193 Transceiver: fix segfault during init if IP addr binding fails
If osmo-trx is started and IP addr binding fails (ie because the IP
address is not assigned in the system), it will try to access a
heap-allocated Thread instance which was not initialized (because init()
function returned earlier).

Fixes: OS#4147
Change-Id: I19f9745cd026c0ff6581895a66bf91b40113b07d
2019-08-26 10:50:20 +02:00
Pau Espin Pedrol
67aa91b2c0 Drop old setPriority related code
This code is not needed anymore since we are setting SCHED_RR scheduler
with a real time priority in main thread during startup, so all threads
will inherit same rt priority, which should be enough to keep the
process working reliably even on high system loads (from non rt
processes).

osmo-trx was tested to be reliable during test with stress-ng as
explained in related ticket below.

Related: OS#2344
Change-Id: I3a88946dd71e9aeeaac9d19d396e2236c302b608
2019-08-21 13:00:41 +02:00
Pau Espin Pedrol
3eed8ebb0d osmo-trx: log to stderr on signal received
Since osmo-trx it's a big multithreaded process and shutdown sequence
can be complex, better use stderr to log signal received events to make
sure log is outputted straigh away and not buffered. In general stdout
is usually line-buffered, but buffering strategy can be more
conservative if output is for instance redirected to a file.

Change-Id: I70ba86919d1f7df41ef3db4916317d27697a025c
2019-08-14 16:26:47 +02:00
163 changed files with 14463 additions and 2629 deletions

521
.clang-format Normal file
View File

@@ -0,0 +1,521 @@
# SPDX-License-Identifier: GPL-2.0
#
# clang-format configuration file. Intended for clang-format >= 4.
#
# For more information, see:
#
# Documentation/process/clang-format.rst
# https://clang.llvm.org/docs/ClangFormat.html
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
#AfterExternBlock: false # Unknown to clang-format-5.0
BeforeCatch: false
BeforeElse: false
IndentBraces: false
#SplitEmptyFunction: true # Unknown to clang-format-4.0
#SplitEmptyRecord: true # Unknown to clang-format-4.0
#SplitEmptyNamespace: true # Unknown to clang-format-4.0
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
#CompactNamespaces: false # Unknown to clang-format-4.0
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
#FixNamespaceComments: false # Unknown to clang-format-4.0
# Taken from:
# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \
# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
# | sort | uniq
ForEachMacros:
- 'apei_estatus_for_each_section'
- 'ata_for_each_dev'
- 'ata_for_each_link'
- '__ata_qc_for_each'
- 'ata_qc_for_each'
- 'ata_qc_for_each_raw'
- 'ata_qc_for_each_with_internal'
- 'ax25_for_each'
- 'ax25_uid_for_each'
- '__bio_for_each_bvec'
- 'bio_for_each_bvec'
- 'bio_for_each_integrity_vec'
- '__bio_for_each_segment'
- 'bio_for_each_segment'
- 'bio_for_each_segment_all'
- 'bio_list_for_each'
- 'bip_for_each_vec'
- 'bitmap_for_each_clear_region'
- 'bitmap_for_each_set_region'
- 'blkg_for_each_descendant_post'
- 'blkg_for_each_descendant_pre'
- 'blk_queue_for_each_rl'
- 'bond_for_each_slave'
- 'bond_for_each_slave_rcu'
- 'bpf_for_each_spilled_reg'
- 'btree_for_each_safe128'
- 'btree_for_each_safe32'
- 'btree_for_each_safe64'
- 'btree_for_each_safel'
- 'card_for_each_dev'
- 'cgroup_taskset_for_each'
- 'cgroup_taskset_for_each_leader'
- 'cpufreq_for_each_entry'
- 'cpufreq_for_each_entry_idx'
- 'cpufreq_for_each_valid_entry'
- 'cpufreq_for_each_valid_entry_idx'
- 'css_for_each_child'
- 'css_for_each_descendant_post'
- 'css_for_each_descendant_pre'
- 'device_for_each_child_node'
- 'dma_fence_chain_for_each'
- 'drm_atomic_crtc_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane_state'
- 'drm_atomic_for_each_plane_damage'
- 'drm_client_for_each_connector_iter'
- 'drm_client_for_each_modeset'
- 'drm_connector_for_each_possible_encoder'
- 'drm_for_each_bridge_in_chain'
- 'drm_for_each_connector_iter'
- 'drm_for_each_crtc'
- 'drm_for_each_encoder'
- 'drm_for_each_encoder_mask'
- 'drm_for_each_fb'
- 'drm_for_each_legacy_plane'
- 'drm_for_each_plane'
- 'drm_for_each_plane_mask'
- 'drm_for_each_privobj'
- 'drm_mm_for_each_hole'
- 'drm_mm_for_each_node'
- 'drm_mm_for_each_node_in_range'
- 'drm_mm_for_each_node_safe'
- 'flow_action_for_each'
- 'for_each_active_dev_scope'
- 'for_each_active_drhd_unit'
- 'for_each_active_iommu'
- 'for_each_available_child_of_node'
- 'for_each_bio'
- 'for_each_board_func_rsrc'
- 'for_each_bvec'
- 'for_each_card_auxs'
- 'for_each_card_auxs_safe'
- 'for_each_card_components'
- 'for_each_card_pre_auxs'
- 'for_each_card_prelinks'
- 'for_each_card_rtds'
- 'for_each_card_rtds_safe'
- 'for_each_cgroup_storage_type'
- 'for_each_child_of_node'
- 'for_each_clear_bit'
- 'for_each_clear_bit_from'
- 'for_each_cmsghdr'
- 'for_each_compatible_node'
- 'for_each_component_dais'
- 'for_each_component_dais_safe'
- 'for_each_comp_order'
- 'for_each_console'
- 'for_each_cpu'
- 'for_each_cpu_and'
- 'for_each_cpu_not'
- 'for_each_cpu_wrap'
- 'for_each_dev_addr'
- 'for_each_dev_scope'
- 'for_each_displayid_db'
- 'for_each_dma_cap_mask'
- 'for_each_dpcm_be'
- 'for_each_dpcm_be_rollback'
- 'for_each_dpcm_be_safe'
- 'for_each_dpcm_fe'
- 'for_each_drhd_unit'
- 'for_each_dss_dev'
- 'for_each_efi_handle'
- 'for_each_efi_memory_desc'
- 'for_each_efi_memory_desc_in_map'
- 'for_each_element'
- 'for_each_element_extid'
- 'for_each_element_id'
- 'for_each_endpoint_of_node'
- 'for_each_evictable_lru'
- 'for_each_fib6_node_rt_rcu'
- 'for_each_fib6_walker_rt'
- 'for_each_free_mem_pfn_range_in_zone'
- 'for_each_free_mem_pfn_range_in_zone_from'
- 'for_each_free_mem_range'
- 'for_each_free_mem_range_reverse'
- 'for_each_func_rsrc'
- 'for_each_hstate'
- 'for_each_if'
- 'for_each_iommu'
- 'for_each_ip_tunnel_rcu'
- 'for_each_irq_nr'
- 'for_each_link_codecs'
- 'for_each_link_platforms'
- 'for_each_lru'
- 'for_each_matching_node'
- 'for_each_matching_node_and_match'
- 'for_each_member'
- 'for_each_memblock'
- 'for_each_memblock_type'
- 'for_each_memcg_cache_index'
- 'for_each_mem_pfn_range'
- 'for_each_mem_range'
- 'for_each_mem_range_rev'
- 'for_each_migratetype_order'
- 'for_each_msi_entry'
- 'for_each_msi_entry_safe'
- 'for_each_net'
- 'for_each_net_continue_reverse'
- 'for_each_netdev'
- 'for_each_netdev_continue'
- 'for_each_netdev_continue_rcu'
- 'for_each_netdev_continue_reverse'
- 'for_each_netdev_feature'
- 'for_each_netdev_in_bond_rcu'
- 'for_each_netdev_rcu'
- 'for_each_netdev_reverse'
- 'for_each_netdev_safe'
- 'for_each_net_rcu'
- 'for_each_new_connector_in_state'
- 'for_each_new_crtc_in_state'
- 'for_each_new_mst_mgr_in_state'
- 'for_each_new_plane_in_state'
- 'for_each_new_private_obj_in_state'
- 'for_each_node'
- 'for_each_node_by_name'
- 'for_each_node_by_type'
- 'for_each_node_mask'
- 'for_each_node_state'
- 'for_each_node_with_cpus'
- 'for_each_node_with_property'
- 'for_each_of_allnodes'
- 'for_each_of_allnodes_from'
- 'for_each_of_cpu_node'
- 'for_each_of_pci_range'
- 'for_each_old_connector_in_state'
- 'for_each_old_crtc_in_state'
- 'for_each_old_mst_mgr_in_state'
- 'for_each_oldnew_connector_in_state'
- 'for_each_oldnew_crtc_in_state'
- 'for_each_oldnew_mst_mgr_in_state'
- 'for_each_oldnew_plane_in_state'
- 'for_each_oldnew_plane_in_state_reverse'
- 'for_each_oldnew_private_obj_in_state'
- 'for_each_old_plane_in_state'
- 'for_each_old_private_obj_in_state'
- 'for_each_online_cpu'
- 'for_each_online_node'
- 'for_each_online_pgdat'
- 'for_each_pci_bridge'
- 'for_each_pci_dev'
- 'for_each_pci_msi_entry'
- 'for_each_populated_zone'
- 'for_each_possible_cpu'
- 'for_each_present_cpu'
- 'for_each_prime_number'
- 'for_each_prime_number_from'
- 'for_each_process'
- 'for_each_process_thread'
- 'for_each_property_of_node'
- 'for_each_registered_fb'
- 'for_each_reserved_mem_region'
- 'for_each_rtd_codec_dai'
- 'for_each_rtd_codec_dai_rollback'
- 'for_each_rtd_components'
- 'for_each_set_bit'
- 'for_each_set_bit_from'
- 'for_each_set_clump8'
- 'for_each_sg'
- 'for_each_sg_dma_page'
- 'for_each_sg_page'
- 'for_each_sibling_event'
- 'for_each_subelement'
- 'for_each_subelement_extid'
- 'for_each_subelement_id'
- '__for_each_thread'
- 'for_each_thread'
- 'for_each_wakeup_source'
- 'for_each_zone'
- 'for_each_zone_zonelist'
- 'for_each_zone_zonelist_nodemask'
- 'fwnode_for_each_available_child_node'
- 'fwnode_for_each_child_node'
- 'fwnode_graph_for_each_endpoint'
- 'gadget_for_each_ep'
- 'genradix_for_each'
- 'genradix_for_each_from'
- 'hash_for_each'
- 'hash_for_each_possible'
- 'hash_for_each_possible_rcu'
- 'hash_for_each_possible_rcu_notrace'
- 'hash_for_each_possible_safe'
- 'hash_for_each_rcu'
- 'hash_for_each_safe'
- 'hctx_for_each_ctx'
- 'hlist_bl_for_each_entry'
- 'hlist_bl_for_each_entry_rcu'
- 'hlist_bl_for_each_entry_safe'
- 'hlist_for_each'
- 'hlist_for_each_entry'
- 'hlist_for_each_entry_continue'
- 'hlist_for_each_entry_continue_rcu'
- 'hlist_for_each_entry_continue_rcu_bh'
- 'hlist_for_each_entry_from'
- 'hlist_for_each_entry_from_rcu'
- 'hlist_for_each_entry_rcu'
- 'hlist_for_each_entry_rcu_bh'
- 'hlist_for_each_entry_rcu_notrace'
- 'hlist_for_each_entry_safe'
- '__hlist_for_each_rcu'
- 'hlist_for_each_safe'
- 'hlist_nulls_for_each_entry'
- 'hlist_nulls_for_each_entry_from'
- 'hlist_nulls_for_each_entry_rcu'
- 'hlist_nulls_for_each_entry_safe'
- 'i3c_bus_for_each_i2cdev'
- 'i3c_bus_for_each_i3cdev'
- 'ide_host_for_each_port'
- 'ide_port_for_each_dev'
- 'ide_port_for_each_present_dev'
- 'idr_for_each_entry'
- 'idr_for_each_entry_continue'
- 'idr_for_each_entry_continue_ul'
- 'idr_for_each_entry_ul'
- 'in_dev_for_each_ifa_rcu'
- 'in_dev_for_each_ifa_rtnl'
- 'inet_bind_bucket_for_each'
- 'inet_lhash2_for_each_icsk_rcu'
- 'key_for_each'
- 'key_for_each_safe'
- 'klp_for_each_func'
- 'klp_for_each_func_safe'
- 'klp_for_each_func_static'
- 'klp_for_each_object'
- 'klp_for_each_object_safe'
- 'klp_for_each_object_static'
- 'kvm_for_each_memslot'
- 'kvm_for_each_vcpu'
- 'list_for_each'
- 'list_for_each_codec'
- 'list_for_each_codec_safe'
- 'list_for_each_continue'
- 'list_for_each_entry'
- 'list_for_each_entry_continue'
- 'list_for_each_entry_continue_rcu'
- 'list_for_each_entry_continue_reverse'
- 'list_for_each_entry_from'
- 'list_for_each_entry_from_rcu'
- 'list_for_each_entry_from_reverse'
- 'list_for_each_entry_lockless'
- 'list_for_each_entry_rcu'
- 'list_for_each_entry_reverse'
- 'list_for_each_entry_safe'
- 'list_for_each_entry_safe_continue'
- 'list_for_each_entry_safe_from'
- 'list_for_each_entry_safe_reverse'
- 'list_for_each_prev'
- 'list_for_each_prev_safe'
- 'list_for_each_safe'
- 'llist_for_each'
- 'llist_for_each_entry'
- 'llist_for_each_entry_safe'
- 'llist_for_each_safe'
- 'mci_for_each_dimm'
- 'media_device_for_each_entity'
- 'media_device_for_each_intf'
- 'media_device_for_each_link'
- 'media_device_for_each_pad'
- 'nanddev_io_for_each_page'
- 'netdev_for_each_lower_dev'
- 'netdev_for_each_lower_private'
- 'netdev_for_each_lower_private_rcu'
- 'netdev_for_each_mc_addr'
- 'netdev_for_each_uc_addr'
- 'netdev_for_each_upper_dev_rcu'
- 'netdev_hw_addr_list_for_each'
- 'nft_rule_for_each_expr'
- 'nla_for_each_attr'
- 'nla_for_each_nested'
- 'nlmsg_for_each_attr'
- 'nlmsg_for_each_msg'
- 'nr_neigh_for_each'
- 'nr_neigh_for_each_safe'
- 'nr_node_for_each'
- 'nr_node_for_each_safe'
- 'of_for_each_phandle'
- 'of_property_for_each_string'
- 'of_property_for_each_u32'
- 'pci_bus_for_each_resource'
- 'ping_portaddr_for_each_entry'
- 'plist_for_each'
- 'plist_for_each_continue'
- 'plist_for_each_entry'
- 'plist_for_each_entry_continue'
- 'plist_for_each_entry_safe'
- 'plist_for_each_safe'
- 'pnp_for_each_card'
- 'pnp_for_each_dev'
- 'protocol_for_each_card'
- 'protocol_for_each_dev'
- 'queue_for_each_hw_ctx'
- 'radix_tree_for_each_slot'
- 'radix_tree_for_each_tagged'
- 'rbtree_postorder_for_each_entry_safe'
- 'rdma_for_each_block'
- 'rdma_for_each_port'
- 'resource_list_for_each_entry'
- 'resource_list_for_each_entry_safe'
- 'rhl_for_each_entry_rcu'
- 'rhl_for_each_rcu'
- 'rht_for_each'
- 'rht_for_each_entry'
- 'rht_for_each_entry_from'
- 'rht_for_each_entry_rcu'
- 'rht_for_each_entry_rcu_from'
- 'rht_for_each_entry_safe'
- 'rht_for_each_from'
- 'rht_for_each_rcu'
- 'rht_for_each_rcu_from'
- '__rq_for_each_bio'
- 'rq_for_each_bvec'
- 'rq_for_each_segment'
- 'scsi_for_each_prot_sg'
- 'scsi_for_each_sg'
- 'sctp_for_each_hentry'
- 'sctp_skb_for_each'
- 'shdma_for_each_chan'
- '__shost_for_each_device'
- 'shost_for_each_device'
- 'sk_for_each'
- 'sk_for_each_bound'
- 'sk_for_each_entry_offset_rcu'
- 'sk_for_each_from'
- 'sk_for_each_rcu'
- 'sk_for_each_safe'
- 'sk_nulls_for_each'
- 'sk_nulls_for_each_from'
- 'sk_nulls_for_each_rcu'
- 'snd_array_for_each'
- 'snd_pcm_group_for_each_entry'
- 'snd_soc_dapm_widget_for_each_path'
- 'snd_soc_dapm_widget_for_each_path_safe'
- 'snd_soc_dapm_widget_for_each_sink_path'
- 'snd_soc_dapm_widget_for_each_source_path'
- 'tb_property_for_each'
- 'tcf_exts_for_each_action'
- 'udp_portaddr_for_each_entry'
- 'udp_portaddr_for_each_entry_rcu'
- 'usb_hub_for_each_child'
- 'v4l2_device_for_each_subdev'
- 'v4l2_m2m_for_each_dst_buf'
- 'v4l2_m2m_for_each_dst_buf_safe'
- 'v4l2_m2m_for_each_src_buf'
- 'v4l2_m2m_for_each_src_buf_safe'
- 'virtio_device_for_each_vq'
- 'xa_for_each'
- 'xa_for_each_marked'
- 'xa_for_each_range'
- 'xa_for_each_start'
- 'xas_for_each'
- 'xas_for_each_conflict'
- 'xas_for_each_marked'
- 'xbc_array_for_each_value'
- 'xbc_for_each_key_value'
- 'xbc_node_for_each_array_value'
- 'xbc_node_for_each_child'
- 'xbc_node_for_each_key_value'
- 'zorro_for_each_dev'
#IncludeBlocks: Preserve # Unknown to clang-format-5.0
IncludeCategories:
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
#IndentPPDirectives: None # Unknown to clang-format-5.0
IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
# Taken from git's rules
#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
#SortUsingDeclarations: false # Unknown to clang-format-4.0
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
SpaceBeforeParens: ControlStatements
#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: Always
...

25
.gitignore vendored
View File

@@ -5,6 +5,18 @@
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
# tests
tests/CommonLibs/BitVectorTest
@@ -19,6 +31,7 @@ tests/CommonLibs/VectorTest
tests/CommonLibs/PRBSTest
tests/Transceiver52M/convolve_test
tests/Transceiver52M/LMSDeviceTest
Transceiver52M/device/ipc/ipc-driver-test
# automake/autoconf
*.in
@@ -60,6 +73,16 @@ doc/manuals/*.pdf
doc/manuals/*__*.png
doc/manuals/*.check
doc/manuals/generated/
doc/manuals/osmomsc-usermanual.xml
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/common
doc/manuals/build
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

@@ -666,7 +666,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
=========================================================================
This marks the end of the AGPLv3 text. The following text is appended to the
same file for convience but constituting a distinct document, not part of the
same file for convenience but constituting a distinct document, not part of the
actual AGPL text and not part of an attempt to create a deriviative work based
on the AGPLv3 text.

View File

@@ -37,7 +37,7 @@ using namespace std;
/**
Apply a Galois polymonial to a binary seqeunce.
Apply a Galois polymonial to a binary sequence.
@param val The input sequence.
@param poly The polynomial.
@param order The order of the polynomial.

View File

@@ -47,7 +47,7 @@
// (pat) The elements in the queue are type T*, and
// the Fifo class implements the underlying queue.
// The default is class PointerFIFO, which does not place any restrictions on the type of T,
// and is implemented by allocating auxilliary structures for the queue,
// and is implemented by allocating auxiliary structures for the queue,
// or SingleLinkedList, which implements the queue using an internal pointer in type T,
// which must implement the functional interface of class SingleLinkListNode,
// namely: functions T*next() and void setNext(T*).

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under multiple licenses; see the COPYING file in
* the main directory for licensing information for this specific distribuion.
* the main directory for licensing information for this specific distribution.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.

View File

@@ -48,9 +48,11 @@ Log::~Log()
int neednl = (mlen==0 || mStream.str()[mlen-1] != '\n');
const char *fmt = neednl ? "%s\n" : "%s";
log_mutex_lock_canceldisable(&old_state);
/* print related function called inside a C++ destructor, use pthread_setcancelstate() APIs.
See osmo-trx commit 86be40b4eb762d5c12e8e3f7388ca9f254e77b36 for more information */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
LOGPSRC(mCategory, mPriority, filename, line, fmt, mStream.str().c_str());
log_mutex_unlock_canceldisable(old_state);
pthread_setcancelstate(old_state, NULL);
}
ostringstream& Log::get()

View File

@@ -50,16 +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()
#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,15 +32,15 @@
#include "Timeval.h"
#include "Logger.h"
#ifndef gettid
#include <sys/syscall.h>
#define gettid() syscall(SYS_gettid)
#endif
extern "C" {
#include <osmocom/core/thread.h>
}
using namespace std;
#ifndef HAVE_ATOMIC_OPS
pthread_mutex_t atomic_ops_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
Mutex gStreamLock; ///< Global lock to control access to cout and cerr.
@@ -49,7 +49,7 @@ void lockCout()
{
gStreamLock.lock();
Timeval entryTime;
cout << entryTime << " " << pthread_self() << ": ";
cout << entryTime << " " << osmo_gettid() << ": ";
}
@@ -64,7 +64,7 @@ void lockCerr()
{
gStreamLock.lock();
Timeval entryTime;
cerr << entryTime << " " << pthread_self() << ": ";
cerr << entryTime << " " << osmo_gettid() << ": ";
}
void unlockCerr()
@@ -112,7 +112,7 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const
void set_selfthread_name(const char *name)
{
pthread_t selfid = pthread_self();
pid_t tid = gettid();
pid_t tid = osmo_gettid();
if (pthread_setname_np(selfid, name) == 0) {
LOG(INFO) << "Thread "<< selfid << " (task " << tid << ") set name: " << name;
} else {

View File

@@ -28,6 +28,8 @@
#ifndef THREADS_H
#define THREADS_H
#include "config.h"
#include <pthread.h>
#include <iostream>
#include <assert.h>
@@ -184,12 +186,34 @@ class Thread {
}
}
/** Send cancelation to thread */
/** Send cancellation to thread */
void cancel() { pthread_cancel(mThread); }
};
#ifdef HAVE_ATOMIC_OPS
#define osmo_trx_sync_fetch_and_and(ptr, value) __sync_fetch_and_and((ptr), (value))
#define osmo_trx_sync_or_and_fetch(ptr, value) __sync_or_and_fetch((ptr), (value))
#else
extern pthread_mutex_t atomic_ops_mutex;
static inline int osmo_trx_sync_fetch_and_and(int *ptr, int value)
{
pthread_mutex_lock(&atomic_ops_mutex);
int tmp = *ptr;
*ptr &= value;
pthread_mutex_unlock(&atomic_ops_mutex);
return tmp;
}
static inline int osmo_trx_sync_or_and_fetch(int *ptr, int value)
{
int tmp;
pthread_mutex_lock(&atomic_ops_mutex);
*ptr |= value;
tmp = *ptr;
pthread_mutex_unlock(&atomic_ops_mutex);
return tmp;
}
#endif
#endif
// vim: ts=4 sw=4

View File

@@ -84,7 +84,7 @@ class Timeval {
uint32_t usec() const { return mTimespec.tv_nsec / 1000; }
uint32_t nsec() const { return mTimespec.tv_nsec; }
/** Return differnce from other (other-self), in ms. */
/** Return difference from other (other-self), in ms. */
long delta(const Timeval& other) const;
/** Elapsed time in ms. */

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

@@ -36,7 +36,12 @@
#include <assert.h>
#include <stdlib.h>
// We cant use Logger.h in this file...
#ifndef __OPTIMIZE__
#define assert_no_opt(x) assert(x)
#else
#define assert_no_opt(x)
#endif
// We can't use Logger.h in this file...
extern int gVectorDebug;
#define BVDEBUG(msg) if (gVectorDebug) {std::cout << msg;}
@@ -81,8 +86,8 @@ template <class T> class Vector {
/** Return the size of the Vector. */
size_t size() const
{
assert(mStart>=mData);
assert(mEnd>=mStart);
assert_no_opt(mStart>=mData);
assert_no_opt(mEnd>=mStart);
return mEnd - mStart;
}
@@ -112,7 +117,7 @@ template <class T> class Vector {
/** Reduce addressable size of the Vector, keeping content. */
void shrink(size_t newSize)
{
assert(newSize <= mEnd - mStart);
assert_no_opt(newSize <= mEnd - mStart);
mEnd = mStart + newSize;
}
@@ -199,7 +204,7 @@ template <class T> class Vector {
{
T* wStart = mStart + start;
T* wEnd = wStart + span;
assert(wEnd<=mEnd);
assert_no_opt(wEnd<=mEnd);
return Vector<T>(NULL,wStart,wEnd);
}
@@ -208,7 +213,7 @@ template <class T> class Vector {
{
T* wStart = mStart + start;
T* wEnd = wStart + span;
assert(wEnd<=mEnd);
assert_no_opt(wEnd<=mEnd);
return Vector<T>(NULL,wStart,wEnd);
}
@@ -228,11 +233,11 @@ template <class T> class Vector {
unsigned int i;
T* dst = other.mStart + start;
T* src = mStart;
assert(dst+span<=other.mEnd);
assert(mStart+span<=mEnd);
assert_no_opt(dst+span<=other.mEnd);
assert_no_opt(mStart+span<=mEnd);
for (i = 0; i < span; i++, src++, dst++)
*dst = *src;
/*TODO if not non-trivially copyable type class, optimize:
/*TODO if not non-trivially copiable type class, optimize:
memcpy(dst,mStart,span*sizeof(T)); */
}
@@ -250,8 +255,8 @@ template <class T> class Vector {
void segmentCopyTo(Vector<T>& other, size_t start, size_t span) const
{
const T* base = mStart + start;
assert(base+span<=mEnd);
assert(other.mStart+span<=other.mEnd);
assert_no_opt(base+span<=mEnd);
assert_no_opt(other.mStart+span<=other.mEnd);
memcpy(other.mStart,base,span*sizeof(T));
}
@@ -265,8 +270,8 @@ template <class T> class Vector {
{
const T* baseFrom = mStart + from;
T* baseTo = mStart + to;
assert(baseFrom+span<=mEnd);
assert(baseTo+span<=mEnd);
assert_no_opt(baseFrom+span<=mEnd);
assert_no_opt(baseTo+span<=mEnd);
memmove(baseTo,baseFrom,span*sizeof(T));
}
@@ -280,7 +285,7 @@ template <class T> class Vector {
{
T* dp=mStart+start;
T* end=dp+length;
assert(end<=mEnd);
assert_no_opt(end<=mEnd);
while (dp<end) *dp++=val;
}
@@ -292,13 +297,13 @@ template <class T> class Vector {
T& operator[](size_t index)
{
assert(mStart+index<mEnd);
assert_no_opt(mStart+index<mEnd);
return mStart[index];
}
const T& operator[](size_t index) const
{
assert(mStart+index<mEnd);
assert_no_opt(mStart+index<mEnd);
return mStart[index];
}

View File

@@ -5,6 +5,8 @@
* osmo-trx (CXX, dir Transceiver52)
*/
#include <stdbool.h>
enum FillerType {
FILLER_DUMMY,
FILLER_ZERO,
@@ -18,3 +20,41 @@ enum ReferenceType {
REF_EXTERNAL,
REF_GPS,
};
/* Maximum number of physical RF channels */
#define TRX_CHAN_MAX 8
struct trx_ctx;
struct trx_chan {
struct trx_ctx *trx; /* backpointer */
unsigned int idx; /* channel index */
char *rx_path;
char *tx_path;
};
struct trx_cfg {
char *bind_addr;
char *remote_addr;
char *dev_args;
unsigned int base_port;
unsigned int tx_sps;
unsigned int rx_sps;
unsigned int rtsc;
unsigned int rach_delay;
enum ReferenceType clock_ref;
enum FillerType filler;
bool multi_arfcn;
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;
bool egprs;
unsigned int sched_rr;
unsigned int stack_size;
unsigned int num_chans;
struct trx_chan chans[TRX_CHAN_MAX];
};

View File

@@ -21,8 +21,6 @@
* See the COPYING file in the main directory for details.
*/
#include <pthread.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include "debug.h"
@@ -35,21 +33,45 @@ static const struct log_info_cat default_categories[] = {
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXCLK] = {
.name = "DTRXCLK",
.description = "TRX Master Clock",
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXCTRL] = {
.name = "DTRXCTRL",
.description = "TRX CTRL interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXDDL] = {
.name = "DTRXDDL",
.description = "TRX Data interface Downlink",
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXDUL] = {
.name = "DTRXDUL",
.description = "TRX CTRL interface Uplink",
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DDEV] = {
.name = "DDEV",
.description = "Device/Driver specific code",
.color = NULL,
.enabled = 1, .loglevel = LOGL_INFO,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DLMS] = {
.name = "DLMS",
.description = "Logging from within LimeSuite itself",
[DDEVDRV] = {
.name = "DDEVDRV",
.description = "Logging from external device driver library implementing lower level specifics",
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DCTR] = {
.name = "DCTR",
.description = "Rate counter related logging",
.color = NULL,
.enabled = 1, .loglevel = LOGL_NOTICE,
},
@@ -59,49 +81,3 @@ const struct log_info log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
pthread_mutex_t log_mutex;
bool log_mutex_init() {
int rc;
pthread_mutexattr_t attr;
if ((rc = pthread_mutexattr_init(&attr))) {
fprintf(stderr, "pthread_mutexattr_init() failed: %d\n", rc);
return false;
}
if ((rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE))) {
fprintf(stderr, "pthread_mutexattr_settype() failed: %d\n", rc);
return false;
}
if ((rc = pthread_mutex_init(&log_mutex, &attr))) {
fprintf(stderr, "pthread_mutex_init() failed: %d\n", rc);
return false;
}
if ((rc = pthread_mutexattr_destroy(&attr))) {
fprintf(stderr, "pthread_mutexattr_destroy() failed: %d\n", rc);
return false;
}
return true;
/* FIXME: do we need to call pthread_mutex_destroy() during process exit? */
}
/* If called inside a C++ destructor, use log_mutex_(un)lock_canceldisable() APIs instead.
See osmo-trx commit 86be40b4eb762d5c12e8e3f7388ca9f254e77b36 for more information */
void log_mutex_lock() {
OSMO_ASSERT(!pthread_mutex_lock(&log_mutex));
}
void log_mutex_unlock() {
OSMO_ASSERT(!pthread_mutex_unlock(&log_mutex));
}
void log_mutex_lock_canceldisable(int *st) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, st);
log_mutex_lock();
}
void log_mutex_unlock_canceldisable(int st) {
log_mutex_unlock();
pthread_setcancelstate(st, NULL);
}

View File

@@ -1,7 +1,6 @@
#pragma once
#include <stdbool.h>
#include <pthread.h>
#include <osmocom/core/logging.h>
@@ -10,26 +9,15 @@ extern const struct log_info log_info;
/* Debug Areas of the code */
enum {
DMAIN,
DTRXCLK,
DTRXCTRL,
DTRXDDL,
DTRXDUL,
DDEV,
DLMS,
DDEVDRV,
DCTR,
};
bool log_mutex_init();
void log_mutex_lock();
void log_mutex_unlock();
void log_mutex_lock_canceldisable(int *st);
void log_mutex_unlock_canceldisable(int st);
#define CLOGC(category, level, fmt, args...) do { \
log_mutex_lock(); \
LOGP(category, level, "[tid=%lu] " fmt, pthread_self(), ##args); \
log_mutex_unlock(); \
} while(0)
#define CLOGCHAN(chan, category, level, fmt, args...) do { \
log_mutex_lock(); \
LOGP(category, level, "[tid=%lu][chan=%lu] " fmt, pthread_self(), chan, ##args); \
log_mutex_unlock(); \
LOGP(category, level, "[chan=%zu] " fmt, chan, ##args); \
} while(0)

View File

@@ -43,6 +43,7 @@ enum SS_DEVICE {
(struct device_counters). Must be sent with PTHREAD_CANCEL_DISABLE
to avoid deadlocks in case osmo-trx process is asked to exit. */
S_DEVICE_COUNTER_CHANGE,
S_TRX_COUNTER_CHANGE, /* same, but for Transceiver class */
};
/* signal cb for signal <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> */
@@ -55,3 +56,16 @@ struct device_counters {
unsigned int tx_dropped_events;
unsigned int tx_dropped_samples;
};
/* signal cb for signal <SS_DEVICE,S_TRX_COUNTER_CHANGE> */
struct trx_counters {
size_t chan;
unsigned int tx_stale_bursts;
unsigned int tx_unavailable_bursts;
unsigned int tx_trxd_fn_repeated;
unsigned int tx_trxd_fn_outoforder;
unsigned int tx_trxd_fn_skipped;
unsigned int rx_empty_burst;
unsigned int rx_clipping;
unsigned int rx_no_burst_detected;
};

View File

@@ -38,7 +38,7 @@
* That signal is processed here in device_sig_cb, where a copy of the "struct
* device_counters" structure is held and the main thread is instructed through
* a timerfd to update rate_ctr APIs against this copy. All this is done inside
* a mutex to avoid different race conditons (between Rx andTx threads, and
* a mutex to avoid different race conditions (between Rx andTx threads, and
* between Rx/Tx and main thread). For the same reason, callers of signal
* <SS_DEVICE,S_DEVICE_COUNTER_CHANGE> (device_sig_cb), that is Rx/Tx threads,
* must do so with PTHREAD_CANCEL_DISABLE, in order to avoid possible deadlocks
@@ -67,21 +67,24 @@ extern "C" {
#include "Threads.h"
#include "Logger.h"
/* Used in ctrs_pending, when set it means that channel slot contains unused
/* Used in dev_ctrs_pending, when set it means that channel slot contains unused
(non-pending) counter data */
#define PENDING_CHAN_NONE SIZE_MAX
static void *trx_rate_ctr_ctx;
static struct rate_ctr_group** rate_ctrs;
static struct device_counters* ctrs_pending;
static struct device_counters* dev_ctrs_pending;
static struct trx_counters* trx_ctrs_pending;
static size_t chan_len;
static struct osmo_fd rate_ctr_timerfd;
static Mutex rate_ctr_mutex;
static struct osmo_fd dev_rate_ctr_timerfd;
static struct osmo_fd trx_rate_ctr_timerfd;
static Mutex dev_rate_ctr_mutex;
static Mutex trx_rate_ctr_mutex;
struct osmo_timer_list threshold_timer;
static LLIST_HEAD(threshold_list);
static int threshold_timer_sched_secs;
static unsigned int threshold_timer_sched_secs;
static bool threshold_initied;
const struct value_string rate_ctr_intv[] = {
@@ -93,22 +96,38 @@ const struct value_string rate_ctr_intv[] = {
};
const struct value_string trx_chan_ctr_names[] = {
{ TRX_CTR_RX_OVERRUNS, "rx_overruns" },
{ TRX_CTR_TX_UNDERRUNS, "tx_underruns" },
{ TRX_CTR_RX_DROP_EV, "rx_drop_events" },
{ TRX_CTR_RX_DROP_SMPL, "rx_drop_samples" },
{ TRX_CTR_TX_DROP_EV, "tx_drop_events" },
{ TRX_CTR_TX_DROP_SMPL, "tx_drop_samples" },
{ TRX_CTR_DEV_RX_OVERRUNS, "rx_overruns" },
{ TRX_CTR_DEV_TX_UNDERRUNS, "tx_underruns" },
{ TRX_CTR_DEV_RX_DROP_EV, "rx_drop_events" },
{ TRX_CTR_DEV_RX_DROP_SMPL, "rx_drop_samples" },
{ TRX_CTR_DEV_TX_DROP_EV, "tx_drop_events" },
{ TRX_CTR_DEV_TX_DROP_SMPL, "tx_drop_samples" },
{ TRX_CTR_TRX_TX_STALE_BURSTS, "tx_stale_bursts" },
{ TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS, "tx_unavailable_bursts" },
{ TRX_CTR_TRX_TRXD_FN_REPEATED, "tx_trxd_fn_repeated" },
{ TRX_CTR_TRX_TRXD_FN_OUTOFORDER, "tx_trxd_fn_outoforder" },
{ TRX_CTR_TRX_TRXD_FN_SKIPPED, "tx_trxd_fn_skipped" },
{ TRX_CTR_TRX_RX_EMPTY_BURST, "rx_empty_burst" },
{ TRX_CTR_TRX_RX_CLIPPING, "rx_clipping" },
{ TRX_CTR_TRX_RX_NO_BURST_DETECTED, "rx_no_burst_detected" },
{ 0, NULL }
};
static const struct rate_ctr_desc trx_chan_ctr_desc[] = {
[TRX_CTR_RX_OVERRUNS] = { "device:rx_overruns", "Number of Rx overruns in FIFO queue" },
[TRX_CTR_TX_UNDERRUNS] = { "device:tx_underruns", "Number of Tx underruns in FIFO queue" },
[TRX_CTR_RX_DROP_EV] = { "device:rx_drop_events", "Number of times Rx samples were dropped by HW" },
[TRX_CTR_RX_DROP_SMPL] = { "device:rx_drop_samples", "Number of Rx samples dropped by HW" },
[TRX_CTR_TX_DROP_EV] = { "device:tx_drop_events", "Number of times Tx samples were dropped by HW" },
[TRX_CTR_TX_DROP_SMPL] = { "device:tx_drop_samples", "Number of Tx samples dropped by HW" }
[TRX_CTR_DEV_RX_OVERRUNS] = { "device:rx_overruns", "Number of Rx overruns in FIFO queue" },
[TRX_CTR_DEV_TX_UNDERRUNS] = { "device:tx_underruns", "Number of Tx underruns in FIFO queue" },
[TRX_CTR_DEV_RX_DROP_EV] = { "device:rx_drop_events", "Number of times Rx samples were dropped by HW" },
[TRX_CTR_DEV_RX_DROP_SMPL] = { "device:rx_drop_samples", "Number of Rx samples dropped by HW" },
[TRX_CTR_DEV_TX_DROP_EV] = { "device:tx_drop_events", "Number of times Tx samples were dropped by HW" },
[TRX_CTR_DEV_TX_DROP_SMPL] = { "device:tx_drop_samples", "Number of Tx samples dropped by HW" },
[TRX_CTR_TRX_TX_STALE_BURSTS] = { "trx:tx_stale_bursts", "Number of Tx burts dropped by TRX due to arriving too late" },
[TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS] = { "trx:tx_unavailable_bursts","Number of Tx burts unavailable (not enqueued) at the time they should be transmitted" },
[TRX_CTR_TRX_TRXD_FN_REPEATED] = { "trx:tx_trxd_fn_repeated", "Number of Tx burts received from TRXD with repeated FN" },
[TRX_CTR_TRX_TRXD_FN_OUTOFORDER] = { "trx:tx_trxd_fn_outoforder","Number of Tx burts received from TRXD with a past FN" },
[TRX_CTR_TRX_TRXD_FN_SKIPPED] = { "trx:tx_trxd_fn_skipped", "Number of Tx burts potentially skipped due to FN jumps" },
[TRX_CTR_TRX_RX_EMPTY_BURST] = { "trx:rx_empty_burst", "Number of Rx bursts empty" },
[TRX_CTR_TRX_RX_CLIPPING] = { "trx:rx_clipping", "Number of Rx bursts discarded due to clipping" },
[TRX_CTR_TRX_RX_NO_BURST_DETECTED] = { "trx:rx_no_burst_detected", "Number of Rx burts discarded due to burst detection error" },
};
static const struct rate_ctr_group_desc trx_chan_ctr_group_desc = {
@@ -119,34 +138,68 @@ static const struct rate_ctr_group_desc trx_chan_ctr_group_desc = {
.ctr_desc = trx_chan_ctr_desc,
};
static int rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
static int dev_rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
size_t chan;
struct rate_ctr *ctr;
LOGC(DMAIN, NOTICE) << "Main thread is updating counters";
rate_ctr_mutex.lock();
LOGC(DCTR, INFO) << "Main thread is updating Device counters";
dev_rate_ctr_mutex.lock();
for (chan = 0; chan < chan_len; chan++) {
if (ctrs_pending[chan].chan == PENDING_CHAN_NONE)
if (dev_ctrs_pending[chan].chan == PENDING_CHAN_NONE)
continue;
LOGCHAN(chan, DMAIN, INFO) << "rate_ctr update";
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_OVERRUNS];
rate_ctr_add(ctr, ctrs_pending[chan].rx_overruns - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_UNDERRUNS];
rate_ctr_add(ctr, ctrs_pending[chan].tx_underruns - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_DROP_EV];
rate_ctr_add(ctr, ctrs_pending[chan].rx_dropped_events - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_RX_DROP_SMPL];
rate_ctr_add(ctr, ctrs_pending[chan].rx_dropped_samples - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_DROP_EV];
rate_ctr_add(ctr, ctrs_pending[chan].tx_dropped_events - ctr->current);
ctr = &rate_ctrs[chan]->ctr[TRX_CTR_TX_DROP_SMPL];
rate_ctr_add(ctr, ctrs_pending[chan].tx_dropped_samples - ctr->current);
LOGCHAN(chan, DCTR, DEBUG) << "rate_ctr update";
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_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_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_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_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_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 */
ctrs_pending[chan].chan = PENDING_CHAN_NONE;
dev_ctrs_pending[chan].chan = PENDING_CHAN_NONE;
}
if (osmo_timerfd_disable(&rate_ctr_timerfd) < 0)
LOGC(DMAIN, ERROR) << "Failed to disable timerfd";
rate_ctr_mutex.unlock();
if (osmo_timerfd_disable(&dev_rate_ctr_timerfd) < 0)
LOGC(DCTR, ERROR) << "Failed to disable timerfd";
dev_rate_ctr_mutex.unlock();
return 0;
}
static int trx_rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
size_t chan;
struct rate_ctr *ctr;
LOGC(DCTR, INFO) << "Main thread is updating Transceiver counters";
trx_rate_ctr_mutex.lock();
for (chan = 0; chan < chan_len; chan++) {
if (trx_ctrs_pending[chan].chan == PENDING_CHAN_NONE)
continue;
LOGCHAN(chan, DCTR, DEBUG) << "rate_ctr update";
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_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_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_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_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_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_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_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;
}
if (osmo_timerfd_disable(&trx_rate_ctr_timerfd) < 0)
LOGC(DCTR, ERROR) << "Failed to disable timerfd";
trx_rate_ctr_mutex.unlock();
return 0;
}
@@ -154,23 +207,37 @@ static int rate_ctr_timerfd_cb(struct osmo_fd *ofd, unsigned int what) {
static int device_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct device_counters *ctr;
struct device_counters *dev_ctr;
struct trx_counters *trx_ctr;
/* Delay sched around 20 ms, in case we receive several calls from several
* channels batched */
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:
ctr = (struct device_counters *)signal_data;
LOGCHAN(ctr->chan, DMAIN, NOTICE) << "Received counter change from radioDevice";
rate_ctr_mutex.lock();
ctrs_pending[ctr->chan] = *ctr;
if (osmo_timerfd_schedule(&rate_ctr_timerfd, &next_sched, &intv_sched) < 0) {
LOGC(DMAIN, ERROR) << "Failed to schedule timerfd: " << errno << " = "<< strerror(errno);
dev_ctr = (struct device_counters *)signal_data;
LOGCHAN(dev_ctr->chan, DCTR, INFO) << "Received counter change from radioDevice";
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_r(errno, err_buf, sizeof(err_buf));
}
rate_ctr_mutex.unlock();
dev_rate_ctr_mutex.unlock();
break;
case S_TRX_COUNTER_CHANGE:
trx_ctr = (struct trx_counters *)signal_data;
LOGCHAN(trx_ctr->chan, DCTR, INFO) << "Received counter change from Transceiver";
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_r(errno, err_buf, sizeof(err_buf));
}
trx_rate_ctr_mutex.unlock();
break;
default:
break;
@@ -195,15 +262,15 @@ static void threshold_timer_cb(void *data)
struct ctr_threshold *ctr_thr;
struct rate_ctr *rate_ctr;
size_t chan;
LOGC(DMAIN, DEBUG) << "threshold_timer_cb fired!";
LOGC(DCTR, DEBUG) << "threshold_timer_cb fired!";
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];
LOGCHAN(chan, DMAIN, INFO) << "checking threshold: " << ctr_threshold_2_vty_str(ctr_thr)
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) {
LOGCHAN(chan, DMAIN, FATAL) << "threshold reached, stopping! " << ctr_threshold_2_vty_str(ctr_thr)
LOGCHAN(chan, DCTR, FATAL) << "threshold reached, stopping! " << ctr_threshold_2_vty_str(ctr_thr)
<< " ("<< rate_ctr->intv[ctr_thr->intv].rate << " vs " << ctr_thr->val << ")";
osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL);
return;
@@ -245,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;
}
@@ -255,13 +321,13 @@ static void threshold_timer_update_intv() {
llist_for_each_entry(ctr, &threshold_list, list) {
secs = ctr_threshold_2_seconds(ctr);
if( min_secs > secs)
if (min_secs > secs)
min_secs = secs;
}
threshold_timer_sched_secs = OSMO_MAX(min_secs / 2 - 1, 1);
LOGC(DMAIN, INFO) << "New ctr-error-threshold check interval: "
threshold_timer_sched_secs = OSMO_MAX((int)(min_secs / 2 - 1), 1);
LOGC(DCTR, INFO) << "New ctr-error-threshold check interval: "
<< threshold_timer_sched_secs << " seconds";
osmo_timer_schedule(&threshold_timer, threshold_timer_sched_secs, 0);
}
@@ -272,20 +338,27 @@ void trx_rate_ctr_init(void *ctx, struct trx_ctx* trx_ctx)
size_t i;
trx_rate_ctr_ctx = ctx;
chan_len = trx_ctx->cfg.num_chans;
ctrs_pending = (struct device_counters*) talloc_zero_size(ctx, chan_len * sizeof(struct device_counters));
dev_ctrs_pending = (struct device_counters*) talloc_zero_size(ctx, chan_len * sizeof(struct device_counters));
trx_ctrs_pending = (struct trx_counters*) talloc_zero_size(ctx, chan_len * sizeof(struct trx_counters));
rate_ctrs = (struct rate_ctr_group**) talloc_zero_size(ctx, chan_len * sizeof(struct rate_ctr_group*));
for (i = 0; i < chan_len; i++) {
ctrs_pending[i].chan = PENDING_CHAN_NONE;
dev_ctrs_pending[i].chan = PENDING_CHAN_NONE;
trx_ctrs_pending[i].chan = PENDING_CHAN_NONE;
rate_ctrs[i] = rate_ctr_group_alloc(ctx, &trx_chan_ctr_group_desc, i);
if (!rate_ctrs[i]) {
LOGCHAN(i, DMAIN, ERROR) << "Failed to allocate rate ctr";
LOGCHAN(i, DCTR, ERROR) << "Failed to allocate rate ctr";
exit(1);
}
}
rate_ctr_timerfd.fd = -1;
if (osmo_timerfd_setup(&rate_ctr_timerfd, rate_ctr_timerfd_cb, NULL) < 0) {
LOGC(DMAIN, ERROR) << "Failed to setup timerfd";
dev_rate_ctr_timerfd.fd = -1;
if (osmo_timerfd_setup(&dev_rate_ctr_timerfd, dev_rate_ctr_timerfd_cb, NULL) < 0) {
LOGC(DCTR, ERROR) << "Failed to setup timerfd";
exit(1);
}
trx_rate_ctr_timerfd.fd = -1;
if (osmo_timerfd_setup(&trx_rate_ctr_timerfd, trx_rate_ctr_timerfd_cb, NULL) < 0) {
LOGC(DCTR, ERROR) << "Failed to setup timerfd";
exit(1);
}
osmo_signal_register_handler(SS_DEVICE, device_sig_cb, NULL);
@@ -302,7 +375,7 @@ void trx_rate_ctr_threshold_add(struct ctr_threshold *ctr)
new_ctr = talloc_zero(trx_rate_ctr_ctx, struct ctr_threshold);
*new_ctr = *ctr;
LOGC(DMAIN, NOTICE) << "Adding new threshold check: " << ctr_threshold_2_vty_str(new_ctr);
LOGC(DCTR, NOTICE) << "Adding new threshold check: " << ctr_threshold_2_vty_str(new_ctr);
llist_add(&new_ctr->list, &threshold_list);
threshold_timer_update_intv();
}
@@ -317,7 +390,7 @@ int trx_rate_ctr_threshold_del(struct ctr_threshold *del_ctr)
ctr->val != del_ctr->val)
continue;
LOGC(DMAIN, NOTICE) << "Deleting threshold check: " << ctr_threshold_2_vty_str(del_ctr);
LOGC(DCTR, NOTICE) << "Deleting threshold check: " << ctr_threshold_2_vty_str(del_ctr);
llist_del(&ctr->list);
talloc_free(ctr);
threshold_timer_update_intv();

View File

@@ -4,12 +4,20 @@
#include <osmocom/vty/command.h>
enum TrxCtr {
TRX_CTR_RX_OVERRUNS,
TRX_CTR_TX_UNDERRUNS,
TRX_CTR_RX_DROP_EV,
TRX_CTR_RX_DROP_SMPL,
TRX_CTR_TX_DROP_EV,
TRX_CTR_TX_DROP_SMPL,
TRX_CTR_DEV_RX_OVERRUNS,
TRX_CTR_DEV_TX_UNDERRUNS,
TRX_CTR_DEV_RX_DROP_EV,
TRX_CTR_DEV_RX_DROP_SMPL,
TRX_CTR_DEV_TX_DROP_EV,
TRX_CTR_DEV_TX_DROP_SMPL,
TRX_CTR_TRX_TX_STALE_BURSTS,
TRX_CTR_TRX_TX_UNAVAILABLE_BURSTS,
TRX_CTR_TRX_TRXD_FN_REPEATED,
TRX_CTR_TRX_TRXD_FN_OUTOFORDER,
TRX_CTR_TRX_TRXD_FN_SKIPPED,
TRX_CTR_TRX_RX_EMPTY_BURST,
TRX_CTR_TRX_RX_CLIPPING,
TRX_CTR_TRX_RX_NO_BURST_DETECTED,
};
struct ctr_threshold {

View File

@@ -32,6 +32,7 @@
#include <osmocom/core/rate_ctr.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
@@ -41,22 +42,41 @@
static struct trx_ctx* g_trx_ctx;
static const struct value_string clock_ref_names[] = {
const struct value_string clock_ref_names[] = {
{ REF_INTERNAL, "internal" },
{ REF_EXTERNAL, "external" },
{ REF_GPS, "gpsdo" },
{ 0, NULL }
};
static const struct value_string filler_names[] = {
{ FILLER_DUMMY, "Dummy bursts" },
{ FILLER_ZERO, "Disabled" },
{ FILLER_NORM_RAND, "Normal bursts with random payload" },
{ FILLER_EDGE_RAND, "EDGE bursts with random payload" },
{ FILLER_ACCESS_RAND, "Access bursts with random payload" },
const struct value_string filler_names[] = {
{ FILLER_DUMMY, "Dummy bursts (C0 only)" },
{ FILLER_ZERO, "Empty bursts" },
{ FILLER_NORM_RAND, "GMSK Normal Bursts with random payload" },
{ FILLER_EDGE_RAND, "8-PSK Normal Bursts with random payload" },
{ FILLER_ACCESS_RAND, "Access Bursts with random payload" },
{ 0, NULL }
};
static const struct value_string filler_types[] = {
{ FILLER_DUMMY, "dummy" },
{ FILLER_ZERO, "zero" },
{ FILLER_NORM_RAND, "random-nb-gmsk" },
{ FILLER_EDGE_RAND, "random-nb-8psk" },
{ FILLER_ACCESS_RAND, "random-ab" },
{ 0, NULL }
};
static const struct value_string filler_docs[] = {
{ FILLER_DUMMY, "Send a Dummy Burst on C0 (TRX0) and empty burst on other channels" },
{ FILLER_ZERO, "Send an empty burst (default)" },
{ FILLER_NORM_RAND, "Send a GMSK modulated Normal Burst with random bits (spectrum mask testing)" },
{ FILLER_EDGE_RAND, "Send an 8-PSK modulated Normal Burst with random bits (spectrum mask testing)" },
{ FILLER_ACCESS_RAND, "Send an Access Burst with random bits (Rx/Tx alignment testing)" },
{ 0, NULL }
};
struct trx_ctx *trx_from_vty(struct vty *v)
{
/* It can't hurt to force callers to continue to pass the vty instance
@@ -101,7 +121,7 @@ DEFUN(cfg_trx, cfg_trx_cmd,
}
DEFUN(cfg_bind_ip, cfg_bind_ip_cmd,
"bind-ip A.B.C.D",
"bind-ip " VTY_IPV4_CMD,
"Set the IP address for the local bind\n"
"IPv4 Address\n")
{
@@ -113,7 +133,7 @@ DEFUN(cfg_bind_ip, cfg_bind_ip_cmd,
}
DEFUN(cfg_remote_ip, cfg_remote_ip_cmd,
"remote-ip A.B.C.D",
"remote-ip " VTY_IPV4_CMD,
"Set the IP address for the remote BTS\n"
"IPv4 Address\n")
{
@@ -151,7 +171,9 @@ DEFUN(cfg_dev_args, cfg_dev_args_cmd,
DEFUN(cfg_tx_sps, cfg_tx_sps_cmd,
"tx-sps (1|4)",
"Set the Tx Samples-per-Symbol\n"
"Tx Samples-per-Symbol\n")
"Tx Samples-per-Symbol\n"
"1 Sample-per-Symbol\n"
"4 Samples-per-Symbol\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
@@ -163,7 +185,9 @@ DEFUN(cfg_tx_sps, cfg_tx_sps_cmd,
DEFUN(cfg_rx_sps, cfg_rx_sps_cmd,
"rx-sps (1|4)",
"Set the Rx Samples-per-Symbol\n"
"Rx Samples-per-Symbol\n")
"Rx Samples-per-Symbol\n"
"1 Sample-per-Symbol\n"
"4 Samples-per-Symbol\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
@@ -172,57 +196,10 @@ DEFUN(cfg_rx_sps, cfg_rx_sps_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_test_rtsc, cfg_test_rtsc_cmd,
"test rtsc <0-7>",
"Set the Random Normal Burst test mode with TSC\n"
"TSC\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
if (trx->cfg.rach_delay_set) {
vty_out(vty, "rach-delay and rtsc options are mutual-exclusive%s",
VTY_NEWLINE);
return CMD_WARNING;
}
trx->cfg.rtsc_set = true;
trx->cfg.rtsc = atoi(argv[0]);
if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */
trx->cfg.filler = FILLER_NORM_RAND;
return CMD_SUCCESS;
}
DEFUN(cfg_test_rach_delay, cfg_test_rach_delay_cmd,
"test rach-delay <0-68>",
"Set the Random Access Burst test mode with delay\n"
"RACH delay\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
if (trx->cfg.rtsc_set) {
vty_out(vty, "rach-delay and rtsc options are mutual-exclusive%s",
VTY_NEWLINE);
return CMD_WARNING;
}
if (trx->cfg.egprs) {
vty_out(vty, "rach-delay and egprs options are mutual-exclusive%s",
VTY_NEWLINE);
return CMD_WARNING;
}
trx->cfg.rach_delay_set = true;
trx->cfg.rach_delay = atoi(argv[0]);
trx->cfg.filler = FILLER_ACCESS_RAND;
return CMD_SUCCESS;
}
DEFUN(cfg_clock_ref, cfg_clock_ref_cmd,
"clock-ref (internal|external|gpsdo)",
"Set the Reference Clock\n"
"Enable internal referece (default)\n"
"Enable internal reference (default)\n"
"Enable external 10 MHz reference\n"
"Enable GPSDO reference\n")
{
@@ -235,7 +212,8 @@ DEFUN(cfg_clock_ref, cfg_clock_ref_cmd,
DEFUN(cfg_multi_arfcn, cfg_multi_arfcn_cmd,
"multi-arfcn (disable|enable)",
"Enable multi-ARFCN transceiver (default=disable)\n")
"Multi-ARFCN transceiver mode (default=disable)\n"
"Enable multi-ARFCN mode\n" "Disable multi-ARFCN mode\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
@@ -266,21 +244,52 @@ DEFUN(cfg_offset, cfg_offset_cmd,
return CMD_SUCCESS;
}
DEFUN_ATTR(cfg_freq_offset, cfg_freq_offset_cmd,
"freq-offset FLOAT",
"Apply an artificial offset to Rx/Tx carrier frequency\n"
"Frequency offset in kHz (e.g. -145300)\n",
CMD_ATTR_HIDDEN)
{
struct trx_ctx *trx = trx_from_vty(vty);
trx->cfg.freq_offset_khz = atof(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_rssi_offset, cfg_rssi_offset_cmd,
"rssi-offset FLOAT",
"rssi-offset FLOAT [relative]",
"Set the RSSI to dBm offset in dB (default=0)\n"
"RSSI to dBm offset in dB\n")
"RSSI to dBm offset in dB\n"
"Add to the default rssi-offset value instead of completely replacing it\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
trx->cfg.rssi_offset = atof(argv[0]);
trx->cfg.force_rssi_offset = (argc == 1);
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 channels (default=disable)\n")
"Swap primary and secondary channels of the PHY (if any)\n"
"Do not swap primary and secondary channels (default)\n"
"Swap primary and secondary channels\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
@@ -297,7 +306,9 @@ DEFUN(cfg_swap_channels, cfg_swap_channels_cmd,
DEFUN(cfg_egprs, cfg_egprs_cmd,
"egprs (disable|enable)",
"Enable EDGE receiver (default=disable)\n")
"EGPRS (8-PSK demodulation) support (default=disable)\n"
"Disable EGPRS (8-PSK demodulation) support\n"
"Enable EGPRS (8-PSK demodulation) support\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
@@ -305,7 +316,6 @@ DEFUN(cfg_egprs, cfg_egprs_cmd,
trx->cfg.egprs = false;
} else if (strcmp("enable", argv[0]) == 0) {
trx->cfg.egprs = true;
trx->cfg.filler = FILLER_EDGE_RAND;
} else {
return CMD_WARNING;
}
@@ -315,7 +325,9 @@ DEFUN(cfg_egprs, cfg_egprs_cmd,
DEFUN(cfg_ext_rach, cfg_ext_rach_cmd,
"ext-rach (disable|enable)",
"Enable extended (11-bit) RACH (default=disable)\n")
"11-bit Access Burst correlation support (default=disable)\n"
"Disable 11-bit Access Burst (TS1 & TS2) correlation\n"
"Enable 11-bit Access Burst (TS1 & TS2) correlation\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
@@ -328,7 +340,7 @@ DEFUN(cfg_ext_rach, cfg_ext_rach_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_rt_prio, cfg_rt_prio_cmd,
DEFUN_DEPRECATED(cfg_rt_prio, cfg_rt_prio_cmd,
"rt-prio <1-32>",
"Set the SCHED_RR real-time priority\n"
"Real time priority\n")
@@ -336,6 +348,8 @@ DEFUN(cfg_rt_prio, cfg_rt_prio_cmd,
struct trx_ctx *trx = trx_from_vty(vty);
trx->cfg.sched_rr = atoi(argv[0]);
vty_out (vty, "%% 'rt-prio %u' is deprecated, use 'policy rr %u' under 'sched' node instead%s",
trx->cfg.sched_rr, trx->cfg.sched_rr, VTY_NEWLINE);
return CMD_SUCCESS;
}
@@ -352,14 +366,48 @@ DEFUN(cfg_stack_size, cfg_stack_size_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_filler, cfg_filler_cmd,
"filler dummy",
"Enable C0 filler table\n"
"Dummy method\n")
#define CFG_FILLER_DOC_STR \
"Filler burst settings\n"
DEFUN(cfg_filler, cfg_filler_type_cmd,
"AUTO-GENERATED", "AUTO-GENERATED")
{
struct trx_ctx *trx = trx_from_vty(vty);
// trx->cfg.filler is unsigned, so we need an interim int var to detect errors
int type = get_string_value(filler_types, argv[0]);
if (type < 0) {
trx->cfg.filler = FILLER_ZERO;
return CMD_WARNING;
}
trx->cfg.filler = type;
return CMD_SUCCESS;
}
DEFUN(cfg_test_rtsc, cfg_filler_tsc_cmd,
"filler tsc <0-7>",
CFG_FILLER_DOC_STR
"Set the TSC for GMSK/8-PSK Normal Burst random fillers. Used only with 'random-nb-gmsk' and"
" 'random-nb-8psk' filler types. (default=0)\n"
"TSC\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
trx->cfg.filler = FILLER_DUMMY;
trx->cfg.rtsc = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_test_rach_delay, cfg_filler_rach_delay_cmd,
"filler access-burst-delay <0-68>",
CFG_FILLER_DOC_STR
"Set the delay for Access Burst random fillers. Used only with 'random-ab' filler type. (default=0)\n"
"RACH delay in symbols\n")
{
struct trx_ctx *trx = trx_from_vty(vty);
trx->cfg.rach_delay = atoi(argv[0]);
return CMD_SUCCESS;
}
@@ -384,7 +432,7 @@ static int vty_intv_name_2_id(const char* str) {
return -1;
}
#define THRESHOLD_ARGS "(rx_overruns|tx_underruns|rx_drop_events|rx_drop_samples|tx_drop_events|tx_drop_samples)"
#define THRESHOLD_ARGS "(rx_overruns|tx_underruns|rx_drop_events|rx_drop_samples|tx_drop_events|tx_drop_samples|tx_stale_bursts|tx_unavailable_bursts|tx_trxd_fn_repeated|tx_trxd_fn_outoforder|tx_trxd_fn_skipped)"
#define THRESHOLD_STR_VAL(s) "Set threshold value for rate_ctr device:" OSMO_STRINGIFY_VAL(s) "\n"
#define THRESHOLD_STRS \
THRESHOLD_STR_VAL(rx_overruns) \
@@ -392,7 +440,13 @@ static int vty_intv_name_2_id(const char* str) {
THRESHOLD_STR_VAL(rx_drop_events) \
THRESHOLD_STR_VAL(rx_drop_samples) \
THRESHOLD_STR_VAL(tx_drop_events) \
THRESHOLD_STR_VAL(tx_drop_samples)
THRESHOLD_STR_VAL(tx_drop_samples) \
THRESHOLD_STR_VAL(tx_stale_bursts) \
THRESHOLD_STR_VAL(tx_unavailable_bursts) \
THRESHOLD_STR_VAL(tx_trxd_fn_repeated) \
THRESHOLD_STR_VAL(tx_trxd_fn_outoforder) \
THRESHOLD_STR_VAL(tx_trxd_fn_skipped) \
""
#define INTV_ARGS "(per-second|per-minute|per-hour|per-day)"
#define INTV_STR_VAL(s) "Threshold value sampled " OSMO_STRINGIFY_VAL(s) "\n"
#define INTV_STRS \
@@ -401,12 +455,13 @@ static int vty_intv_name_2_id(const char* str) {
INTV_STR_VAL(per-hour) \
INTV_STR_VAL(per-day)
DEFUN(cfg_ctr_error_threshold, cfg_ctr_error_threshold_cmd,
"ctr-error-threshold " THRESHOLD_ARGS " <0-65535> " INTV_ARGS,
"Threshold rate for error counter\n"
THRESHOLD_STRS
"Value to set for threshold\n"
INTV_STRS)
DEFUN_ATTR(cfg_ctr_error_threshold, cfg_ctr_error_threshold_cmd,
"ctr-error-threshold " THRESHOLD_ARGS " <0-65535> " INTV_ARGS,
"Threshold rate for error counter\n"
THRESHOLD_STRS
"Value to set for threshold\n"
INTV_STRS,
CMD_ATTR_IMMEDIATE)
{
int rc;
struct ctr_threshold ctr;
@@ -432,12 +487,13 @@ DEFUN(cfg_ctr_error_threshold, cfg_ctr_error_threshold_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_no_ctr_error_threshold, cfg_no_ctr_error_threshold_cmd,
"no ctr-error-threshold " THRESHOLD_ARGS " <0-65535> " INTV_ARGS,
NO_STR "Threshold rate for error counter\n"
THRESHOLD_STRS
"Value to set for threshold\n"
INTV_STRS)
DEFUN_ATTR(cfg_no_ctr_error_threshold, cfg_no_ctr_error_threshold_cmd,
"no ctr-error-threshold " THRESHOLD_ARGS " <0-65535> " INTV_ARGS,
NO_STR "Threshold rate for error counter\n"
THRESHOLD_STRS
"Value to set for threshold\n"
INTV_STRS,
CMD_ATTR_IMMEDIATE)
{
int rc;
struct ctr_threshold ctr;
@@ -506,6 +562,12 @@ DEFUN(cfg_chan_rx_path, cfg_chan_rx_path_cmd,
{
struct trx_chan *chan = vty->index;
if (chan->trx->cfg.multi_arfcn && chan->idx > 0) {
vty_out(vty, "%% Setting 'rx-path' for chan %u in multi-ARFCN mode "
"does not make sense, because only chan 0 is used%s",
chan->idx, VTY_NEWLINE);
}
osmo_talloc_replace_string(chan->trx, &chan->rx_path, argv[0]);
return CMD_SUCCESS;
@@ -518,6 +580,12 @@ DEFUN(cfg_chan_tx_path, cfg_chan_tx_path_cmd,
{
struct trx_chan *chan = vty->index;
if (chan->trx->cfg.multi_arfcn && chan->idx > 0) {
vty_out(vty, "%% Setting 'tx-path' for chan %u in multi-ARFCN mode "
"does not make sense, because only chan 0 is used%s",
chan->idx, VTY_NEWLINE);
}
osmo_talloc_replace_string(chan->trx, &chan->tx_path, argv[0]);
return CMD_SUCCESS;
@@ -547,24 +615,31 @@ static int config_write_trx(struct vty *vty)
vty_out(vty, " tx-sps %u%s", trx->cfg.tx_sps, VTY_NEWLINE);
if (trx->cfg.rx_sps != DEFAULT_RX_SPS)
vty_out(vty, " rx-sps %u%s", trx->cfg.rx_sps, VTY_NEWLINE);
if (trx->cfg.rtsc_set)
vty_out(vty, " test rtsc %u%s", trx->cfg.rtsc, VTY_NEWLINE);
if (trx->cfg.rach_delay_set)
vty_out(vty, " test rach-delay %u%s", trx->cfg.rach_delay, VTY_NEWLINE);
if (trx->cfg.clock_ref != REF_INTERNAL)
vty_out(vty, " clock-ref %s%s", get_value_string(clock_ref_names, trx->cfg.clock_ref), VTY_NEWLINE);
vty_out(vty, " multi-arfcn %s%s", trx->cfg.multi_arfcn ? "enable" : "disable", VTY_NEWLINE);
if (trx->cfg.offset != 0)
vty_out(vty, " offset %f%s", trx->cfg.offset, VTY_NEWLINE);
if (trx->cfg.rssi_offset != 0)
vty_out(vty, " rssi-offset %f%s", trx->cfg.rssi_offset, VTY_NEWLINE);
if (trx->cfg.freq_offset_khz != 0)
vty_out(vty, " freq-offset %f%s", trx->cfg.freq_offset_khz, VTY_NEWLINE);
if (!(trx->cfg.rssi_offset == 0 && !trx->cfg.force_rssi_offset))
vty_out(vty, " rssi-offset %f%s%s", trx->cfg.rssi_offset,
trx->cfg.force_rssi_offset ? " relative": "", VTY_NEWLINE);
vty_out(vty, " swap-channels %s%s", trx->cfg.swap_channels ? "enable" : "disable", VTY_NEWLINE);
vty_out(vty, " egprs %s%s", trx->cfg.egprs ? "enable" : "disable", VTY_NEWLINE);
vty_out(vty, " ext-rach %s%s", trx->cfg.ext_rach ? "enable" : "disable", VTY_NEWLINE);
if (trx->cfg.sched_rr != 0)
vty_out(vty, " rt-prio %u%s", trx->cfg.sched_rr, VTY_NEWLINE);
if (trx->cfg.filler != FILLER_ZERO)
vty_out(vty, " filler type %s%s", get_value_string(filler_types, trx->cfg.filler), VTY_NEWLINE);
if (trx->cfg.rtsc > 0)
vty_out(vty, " filler tsc %u%s", trx->cfg.rtsc, VTY_NEWLINE);
if (trx->cfg.rach_delay > 0)
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++) {
@@ -590,11 +665,9 @@ static void trx_dump_vty(struct vty *vty, struct trx_ctx *trx)
vty_out(vty, " Device args: %s%s", trx->cfg.dev_args, VTY_NEWLINE);
vty_out(vty, " Tx Samples-per-Symbol: %u%s", trx->cfg.tx_sps, VTY_NEWLINE);
vty_out(vty, " Rx Samples-per-Symbol: %u%s", trx->cfg.rx_sps, VTY_NEWLINE);
vty_out(vty, " Test Mode: TSC: %u (%s)%s", trx->cfg.rtsc,
trx->cfg.rtsc_set ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " Test Mode: RACH Delay: %u (%s)%s", trx->cfg.rach_delay,
trx->cfg.rach_delay_set ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " C0 Filler Table: %s%s", get_value_string(filler_names, trx->cfg.filler), VTY_NEWLINE);
vty_out(vty, " Filler Burst Type: %s%s", get_value_string(filler_names, trx->cfg.filler), VTY_NEWLINE);
vty_out(vty, " Filler Burst TSC: %u%s", trx->cfg.rtsc, VTY_NEWLINE);
vty_out(vty, " Filler Burst RACH Delay: %u%s", trx->cfg.rach_delay, VTY_NEWLINE);
vty_out(vty, " Clock Reference: %s%s", get_value_string(clock_ref_names, trx->cfg.clock_ref), VTY_NEWLINE);
vty_out(vty, " Multi-Carrier: %s%s", trx->cfg.multi_arfcn ? "Enabled" : "Disabled", VTY_NEWLINE);
vty_out(vty, " Tuning offset: %f%s", trx->cfg.offset, VTY_NEWLINE);
@@ -663,6 +736,7 @@ static int trx_vty_go_parent(struct vty *vty)
static const char trx_copyright[] =
"Copyright (C) 2007-2014 Free Software Foundation, Inc.\r\n"
"Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>\r\n"
"Copyright (C) 2013-2019 Fairwaves, Inc.\r\n"
"Copyright (C) 2015 Ettus Research LLC\r\n"
"Copyright (C) 2017-2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>\r\n"
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
@@ -687,12 +761,19 @@ struct trx_ctx *vty_trx_ctx_alloc(void *talloc_ctx)
trx->cfg.tx_sps = DEFAULT_TX_SPS;
trx->cfg.rx_sps = DEFAULT_RX_SPS;
trx->cfg.filler = FILLER_ZERO;
trx->cfg.rssi_offset = 0.0f;
return trx;
}
int trx_vty_init(struct trx_ctx* trx)
{
cfg_filler_type_cmd.string = vty_cmd_string_from_valstr(trx, filler_types,
"filler type (", "|", ")", 0);
cfg_filler_type_cmd.doc = vty_cmd_string_from_valstr(trx, filler_docs,
CFG_FILLER_DOC_STR "What to do when there is nothing to send "
"(filler type, default=zero)\n", "\n", "", 0);
g_trx_ctx = trx;
install_element_ve(&show_trx_cmd);
@@ -705,25 +786,29 @@ int trx_vty_init(struct trx_ctx* trx)
install_element(TRX_NODE, &cfg_dev_args_cmd);
install_element(TRX_NODE, &cfg_tx_sps_cmd);
install_element(TRX_NODE, &cfg_rx_sps_cmd);
install_element(TRX_NODE, &cfg_test_rtsc_cmd);
install_element(TRX_NODE, &cfg_test_rach_delay_cmd);
install_element(TRX_NODE, &cfg_clock_ref_cmd);
install_element(TRX_NODE, &cfg_multi_arfcn_cmd);
install_element(TRX_NODE, &cfg_offset_cmd);
install_element(TRX_NODE, &cfg_freq_offset_cmd);
install_element(TRX_NODE, &cfg_rssi_offset_cmd);
install_element(TRX_NODE, &cfg_swap_channels_cmd);
install_element(TRX_NODE, &cfg_egprs_cmd);
install_element(TRX_NODE, &cfg_ext_rach_cmd);
install_element(TRX_NODE, &cfg_rt_prio_cmd);
install_element(TRX_NODE, &cfg_filler_cmd);
install_element(TRX_NODE, &cfg_filler_type_cmd);
install_element(TRX_NODE, &cfg_filler_tsc_cmd);
install_element(TRX_NODE, &cfg_filler_rach_delay_cmd);
install_element(TRX_NODE, &cfg_ctr_error_threshold_cmd);
install_element(TRX_NODE, &cfg_no_ctr_error_threshold_cmd);
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);
logging_vty_add_deprecated_subsys(g_trx_ctx, "lms");
return 0;
}

View File

@@ -5,9 +5,9 @@
#include "config_defs.h"
extern struct vty_app_info g_vty_info;
extern const struct value_string clock_ref_names[];
extern const struct value_string filler_names[];
/* Maximum number of physical RF channels */
#define TRX_CHAN_MAX 8
/* Maximum number of carriers in multi-ARFCN mode */
#define TRX_MCHAN_MAX 3
@@ -33,40 +33,8 @@ extern struct vty_app_info g_vty_info;
#define DEFAULT_TRX_IP "127.0.0.1"
#define DEFAULT_CHANS 1
struct trx_ctx;
struct trx_chan {
struct trx_ctx *trx; /* backpointer */
unsigned int idx; /* channel index */
char *rx_path;
char *tx_path;
};
struct trx_ctx {
struct {
char *bind_addr;
char *remote_addr;
char *dev_args;
unsigned int base_port;
unsigned int tx_sps;
unsigned int rx_sps;
unsigned int rtsc;
bool rtsc_set;
unsigned int rach_delay;
bool rach_delay_set;
enum ReferenceType clock_ref;
enum FillerType filler;
bool multi_arfcn;
double offset;
double rssi_offset;
bool swap_channels;
bool ext_rach;
bool egprs;
unsigned int sched_rr;
unsigned int stack_size;
unsigned int num_chans;
struct trx_chan chans[TRX_CHAN_MAX];
} cfg;
struct trx_cfg cfg;
};
int trx_vty_init(struct trx_ctx* trx);

View File

@@ -26,19 +26,31 @@ 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 = \
doc \
SUBDIRS += \
osmocom-bb/src/host/trxcon \
CommonLibs \
GSM \
Transceiver52M \
contrib \
tests
tests \
utils \
doc \
$(NULL)
EXTRA_DIST = \
LEGAL \
COPYING \
README.md
README.md \
contrib/osmo-trx.spec.in \
debian \
$(NULL)
AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)

View File

@@ -1,5 +1,5 @@
About OsmTRX
============
About OsmoTRX
=============
OsmoTRX is a software-defined radio transceiver that implements the Layer 1
physical layer of a BTS comprising the following 3GPP specifications:
@@ -9,14 +9,12 @@ physical layer of a BTS comprising the following 3GPP specifications:
* TS 05.04 "Modulation"
* TS 05.10 "Radio subsystem synchronization"
OsmoTRX is based on the transceiver code from the
OsmoTRX is originally based on the transceiver code from the
[OpenBTS](https://osmocom.org/projects/osmobts/wiki/OpenBTS) project, but setup
to operate independently with the purpose of using with non-OpenBTS software and
projects, while still maintaining backwards compatibility with OpenBTS when
possible. Currently there are numerous features contained in OsmoTRX that extend
the functionality of the OpenBTS transceiver. These features include enhanced
support for various embedded platforms - notably ARM - and dual channel
diversity support for the Fairwaves umtrx.
projects, specifically within the Osmocom stack. Used together with
[OsmoBTS](https://osmocom.org/projects/osmobts/wiki) you can get a pretty
standard GSM BTS with Abis interface as per the relevant 3GPP specifications.
Homepage
--------
@@ -29,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 <http://git.osmocom.org/osmo-trx/>
There is a web interface at <https://gitea.osmocom.org/cellular-infrastructure/osmo-trx>
Documentation
-------------
@@ -39,7 +37,7 @@ Documentation
Doxygen-generated API documentation is generated during the build process, but
also available online for each of the sub-libraries at User Manual for OsmoTRX
can be generated during the build process, and is also available online at
<http://ftp.osmocom.org/docs/latest/osmotrx-usermanual.pdf>.
<https://ftp.osmocom.org/docs/latest/osmotrx-usermanual.pdf>.
Mailing List
------------

0
TODO-RELEASE Normal file
View File

View File

@@ -98,7 +98,7 @@ bool Channelizer::rotate(const float *in, size_t len)
return true;
}
/* Setup channelizer paramaters */
/* Setup channelizer parameters */
Channelizer::Channelizer(size_t m, size_t blockLen, size_t hLen)
: ChannelizerBase(m, blockLen, hLen)
{

View File

@@ -225,7 +225,7 @@ bool ChannelizerBase::checkLen(size_t innerLen, size_t outerLen)
}
/*
* Setup channelizer paramaters
* Setup channelizer parameters
*/
ChannelizerBase::ChannelizerBase(size_t m, size_t blockLen, size_t hLen)
: subFilters(NULL), hInputs(NULL), hOutputs(NULL), hist(NULL),
@@ -244,6 +244,7 @@ ChannelizerBase::~ChannelizerBase()
free(subFilters[i]);
delete[] hist[i];
}
free(subFilters);
fft_free(fftInput);
fft_free(fftOutput);

View File

@@ -32,7 +32,7 @@ protected:
/* Buffer length validity checking */
bool checkLen(size_t innerLen, size_t outerLen);
public:
/* Initilize channelizer/synthesis filter internals */
/* Initialize channelizer/synthesis filter internals */
bool init();
};

View File

@@ -5,7 +5,7 @@ unlike the built-in complex<> templates, these inline most operations for speed
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.

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
@@ -104,3 +145,62 @@ osmo_trx_lms_LDADD = \
$(LMS_LIBS)
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
osmo_trx_ipc_LDADD = \
$(builddir)/device/ipc/libdevice.la \
$(COMMON_LDADD)
osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
# bin_PROGRAMS += osmo-trx-ipc2
# osmo_trx_ipc2_SOURCES = osmo-trx.cpp
# osmo_trx_ipc2_LDADD = \
# $(builddir)/device/ipc2/libdevice.la \
# $(COMMON_LDADD)
# osmo_trx_ipc2_CPPFLAGS = $(AM_CPPFLAGS)
# if ENABLE_MS_TRX
# bin_PROGRAMS += osmo-trx-ms-ipc
# osmo_trx_ms_ipc_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp
# osmo_trx_ms_ipc_LDADD = \
# $(COMMON_LDADD) \
# $(TRXCON_LDADD)
# osmo_trx_ms_ipc_CPPFLAGS = $(AM_CPPFLAGS) -DBUILDIPC -I./device/ipc2 -I../device/ipc2
# bin_PROGRAMS += osmo-trx-syncthing-ipc
# osmo_trx_syncthing_ipc_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp
# osmo_trx_syncthing_ipc_LDADD = $(COMMON_LDADD)
# osmo_trx_syncthing_ipc_CPPFLAGS = $(AM_CPPFLAGS) -DSYNCTHINGONLY -DBUILDIPC -I./device/ipc2 -I../device/ipc2
# endif
endif

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>
@@ -99,6 +95,7 @@ void Resampler::initFilters(float bw)
reverse(&part[0], &part[filt_len]);
}
#ifndef __OPTIMIZE__
static bool check_vec_len(int in_len, int out_len, int p, int q)
{
if (in_len % q) {
@@ -129,14 +126,15 @@ static bool check_vec_len(int in_len, int out_len, int p, int q)
return true;
}
#endif
int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len)
{
int n, path;
#ifndef __OPTIMIZE__
if (!check_vec_len(in_len, out_len, p, q))
return -1;
#endif
/* Generate output from precomputed input/output paths */
for (size_t i = 0; i < out_len; i++) {
n = in_index[i];

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_
@@ -35,12 +31,12 @@ public:
Resampler(size_t p, size_t q, size_t filt_len = 16);
~Resampler();
/* Initilize resampler filterbank.
/* Initialize resampler filterbank.
* @param bw bandwidth factor on filter generation (pre-window)
* @return false on error, zero otherwise
*
* Automatic setting is to compute the filter to prevent aliasing with
* a Blackman-Harris window. Adjustment is made through a bandwith
* a Blackman-Harris window. Adjustment is made through a bandwidth
* factor to shift the cutoff and/or the constituent filter lengths.
* Calculation of specific rolloff factors or 3-dB cutoff points is
* left as an excersize for the reader.

File diff suppressed because it is too large Load Diff

View File

@@ -33,11 +33,14 @@
extern "C" {
#include <osmocom/core/signal.h>
#include <osmocom/core/select.h>
#include "config_defs.h"
}
class Transceiver;
extern Transceiver *transceiver;
/** Channel descriptor for transceiver object and channel number pair */
struct TrxChanThParams {
Transceiver *trx;
@@ -60,6 +63,7 @@ struct TransceiverState {
/* The filler table */
signalVector *fillerTable[102][8];
int fillerModulus[8];
FillerType mFiller;
bool mRetrans;
/* Most recent channel estimate of all timeslots */
@@ -76,37 +80,39 @@ struct TransceiverState {
/* Received noise energy levels */
float mNoiseLev;
noiseVector mNoises;
avgVector mNoises;
/* Shadowed downlink attenuation */
int mPower;
/* RF emission and reception disabled, as per NM Administrative State Locked */
bool mMuted;
/* counters */
struct trx_counters ctrs;
/* Used to keep track of lost and out of order frames */
bool first_dl_fn_rcv[8];
GSM::Time last_dl_time_rcv[8];
};
/** The Transceiver class, responsible for physical layer of basestation */
class Transceiver {
public:
/** Transceiver constructor
@param wBasePort base port number of UDP sockets
@param TRXAddress IP address of the TRX, as a string
@param GSMcoreAddress IP address of the GSM core, as a string
@param wSPS number of samples per GSM symbol
@param cfg VTY populated config
@param wTransmitLatency initial setting of transmit latency
@param radioInterface associated radioInterface object
*/
Transceiver(int wBasePort,
const char *TRXAddress,
const char *GSMcoreAddress,
size_t tx_sps, size_t rx_sps, size_t chans,
Transceiver(const struct trx_cfg *cfg,
GSM::Time wTransmitLatency,
RadioInterface *wRadioInterface,
double wRssiOffset, int stackSize);
RadioInterface *wRadioInterface);
/** Destructor */
~Transceiver();
/** Start the control loop */
bool init(FillerType filler, size_t rtsc, unsigned rach_delay,
bool edge, bool ext_rach);
bool init(void);
/** attach the radioInterface receive FIFO */
bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
@@ -119,7 +125,7 @@ public:
}
/** accessor for number of channels */
size_t numChans() const { return mChans; };
size_t numChans() const { return cfg->num_chans; };
/** Codes for channel combinations */
typedef enum {
@@ -142,12 +148,29 @@ public:
} ChannelCombination;
private:
int mBasePort;
std::string mLocalAddr;
std::string mRemoteAddr;
struct ctrl_msg {
char data[101];
ctrl_msg() {};
};
struct ctrl_sock_state {
osmo_fd conn_bfd;
std::deque<ctrl_msg> txmsgqueue;
ctrl_sock_state() {
conn_bfd.fd = -1;
}
~ctrl_sock_state() {
if(conn_bfd.fd >= 0) {
close(conn_bfd.fd);
conn_bfd.fd = -1;
osmo_fd_unregister(&conn_bfd);
}
}
};
const struct trx_cfg *cfg; ///< VTY populated config
std::vector<int> mDataSockets; ///< socket for writing to/reading from GSM core
std::vector<int> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
std::vector<ctrl_sock_state> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
int mClockSocket; ///< socket for writing clock updates to GSM core
std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core
@@ -156,7 +179,6 @@ private:
std::vector<Thread *> mRxServiceLoopThreads; ///< thread to pull bursts into receive FIFO
Thread *mRxLowerLoopThread; ///< thread to pull bursts into receive FIFO
Thread *mTxLowerLoopThread; ///< thread to push bursts into transmit FIFO
std::vector<Thread *> mControlServiceLoopThreads; ///< thread to process control messages from GSM core
std::vector<Thread *> mTxPriorityQueueServiceLoopThreads; ///< thread to process transmit bursts from GSM core
GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock
@@ -168,9 +190,6 @@ private:
double txFullScale; ///< full scale input to radio
double rxFullScale; ///< full scale output to radio
double rssiOffset; ///< RSSI to dBm conversion offset
int stackSize; ///< stack size for threads, 0 = OS default
/** modulate and add a burst to the transmit queue */
void addRadioVector(size_t chan, BitVector &bits,
int RSSI, GSM::Time &wTime);
@@ -182,7 +201,7 @@ private:
void pushRadioVector(GSM::Time &nowTime);
/** Pull and demodulate a burst from the receive FIFO */
bool pullRadioVector(size_t chan, struct trx_ul_burst_ind *ind);
int pullRadioVector(size_t chan, struct trx_ul_burst_ind *ind);
/** Set modulus for specific timeslot */
void setModulus(size_t timeslot, size_t chan);
@@ -191,14 +210,15 @@ private:
CorrType expectedCorrType(GSM::Time currTime, size_t chan);
/** send messages over the clock socket */
void writeClockInterface(void);
bool writeClockInterface(void);
static int ctrl_sock_cb(struct osmo_fd *bfd, unsigned int flags);
int ctrl_sock_write(int chan);
void ctrl_sock_send(ctrl_msg& m, int chan);
/** drive handling of control messages from GSM core */
int ctrl_sock_handle_rx(int chan);
int mSPSTx; ///< number of samples per Tx symbol
int mSPSRx; ///< number of samples per Rx symbol
size_t mChans;
bool mExtRACH;
bool mEdge;
bool mOn; ///< flag to indicate that transceiver is powered on
bool mForceClockInterface; ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started
bool mHandover[8][8]; ///< expect handover to the timeslot/subslot
@@ -216,22 +236,19 @@ private:
bool start();
void stop();
/** Protect destructor accessable stop call */
/** Protect destructor accessible stop call */
Mutex mLock;
protected:
/** drive lower receive I/O and burst generation */
void driveReceiveRadio();
bool driveReceiveRadio();
/** drive demodulation of GSM bursts */
void driveReceiveFIFO(size_t chan);
bool driveReceiveFIFO(size_t chan);
/** drive transmission of GSM bursts */
void driveTxFIFO();
/** drive handling of control messages from GSM core */
void driveControl(size_t chan);
/**
drive modulation and sorting of GSM bursts from GSM core
@return true if a burst was transferred successfully
@@ -242,14 +259,10 @@ protected:
friend void *TxUpperLoopAdapter(TrxChanThParams *params);
friend void *RxLowerLoopAdapter(Transceiver *transceiver);
friend void *TxLowerLoopAdapter(Transceiver *transceiver);
friend void *ControlServiceLoopAdapter(TrxChanThParams *params);
double rssiOffset(size_t chan);
void reset();
/** set priority on current thread */
void setPriority(float prio = 0.5) { mRadioInterface->setPriority(prio); }
void logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi);
};
@@ -259,8 +272,5 @@ void *RxUpperLoopAdapter(TrxChanThParams *params);
void *RxLowerLoopAdapter(Transceiver *transceiver);
void *TxLowerLoopAdapter(Transceiver *transceiver);
/** control message handler thread loop */
void *ControlServiceLoopAdapter(TrxChanThParams *params);
/** transmit queueing thread loop */
void *TxUpperLoopAdapter(TrxChanThParams *params);

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>
@@ -58,7 +54,7 @@ static void neon_conv_cmplx_4n(float *x, float *h, float *y, int h_len, int len)
}
#endif
/* API: Initalize convolve module */
/* API: Initialize convolve module */
void convolve_init(void)
{
/* Stub */

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

@@ -103,7 +103,7 @@ void free_fft(struct fft_hdl *hdl)
}
/*! \brief Run multiple DFT operations with the initialized plan
* \param[in] hdl handle to an intitialized fft struct
* \param[in] hdl handle to an initialized fft struct
*
* Input and output buffers are configured with init_fft().
*/

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>
@@ -27,7 +23,7 @@
#include "config.h"
#endif
/* Architecture dependant function pointers */
/* Architecture dependent function pointers */
struct convert_cpu_context {
void (*convert_si16_ps_16n) (float *, const short *, int);
void (*convert_si16_ps) (float *, const short *, int);

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>
@@ -27,7 +23,7 @@
#include "config.h"
#endif
/* Architecture dependant function pointers */
/* Architecture dependent function pointers */
struct convolve_cpu_context {
void (*conv_cmplx_4n) (const float *, int, const float *, int, float *,
int, int, int);
@@ -66,7 +62,7 @@ int _base_convolve_complex(const float *x, int x_len,
int bounds_check(int x_len, int h_len, int y_len,
int start, int len);
/* API: Initalize convolve module */
/* API: Initialize convolve module */
void convolve_init(void)
{
c.conv_cmplx_4n = (void *)_base_convolve_complex;
@@ -99,9 +95,10 @@ int convolve_real(const float *x, int x_len,
const float *h, int h_len,
float *y, int y_len, int start, int len)
{
#ifndef __OPTIMIZE__
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
return -1;
#endif
memset(y, 0, len * 2 * sizeof(float));
switch (h_len) {
@@ -138,9 +135,10 @@ int convolve_complex(const float *x, int x_len,
float *y, int y_len,
int start, int len)
{
#ifndef __OPTIMIZE__
if (bounds_check(x_len, h_len, y_len, start, len) < 0)
return -1;
#endif
memset(y, 0, len * 2 * sizeof(float));
if (!(h_len % 8))

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

@@ -2,6 +2,10 @@ include $(top_srcdir)/Makefile.common
SUBDIRS = common
if DEVICE_IPC
SUBDIRS += ipc ipc2
endif
if DEVICE_USRP1
SUBDIRS += usrp1
endif
@@ -13,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

@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
noinst_HEADERS = radioDevice.h smpl_buf.h

View File

@@ -1,7 +1,7 @@
/*
* Copyright 2008 Free Software Foundation, Inc.
*
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -70,9 +70,6 @@ class RadioDevice {
/** Get the Tx window type */
virtual enum TxWindowType getWindowType()=0;
/** Enable thread priority */
virtual void setPriority(float prio = 0.5) = 0;
/**
Read samples from the radio.
@param buf preallocated buf to contain read result
@@ -80,23 +77,20 @@ class RadioDevice {
@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 radio does not have data to transmit, e.g. data not being sent fast enough
@param RSSI The received signal strength of the read result
@return The number of samples actually read
*/
virtual int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp = 0xffffffff, bool *underrun = 0,
unsigned *RSSI = 0) = 0;
TIMESTAMP timestamp = 0xffffffff, bool *underrun = 0) = 0;
/**
Write samples to the radio.
@param buf Contains the data to be written.
@param len number of samples to write.
@param underrun Set if radio 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.
@param isControl Set if data is a control packet, e.g. a ping command
@return The number of samples actually written
*/
virtual int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp, bool isControl = false) = 0;
TIMESTAMP timestamp) = 0;
/** Update the alignment between the read and write timestamps */
virtual bool updateAlignment(TIMESTAMP timestamp)=0;
@@ -131,14 +125,11 @@ class RadioDevice {
/** return minimum Rx Gain **/
virtual double minRxGain(void) = 0;
/** sets the transmit chan gain, returns the gain setting **/
virtual double setTxGain(double dB, size_t chan = 0) = 0;
/** return base RSSI offset to apply for received samples **/
virtual double rssiOffset(size_t chan) = 0;
/** return maximum Tx Gain **/
virtual double maxTxGain(void) = 0;
/** return minimum Tx Gain **/
virtual double minTxGain(void) = 0;
/** returns the Nominal transmit output power of the transceiver in dBm, negative on error **/
virtual int getNominalTxPower(size_t chan = 0) = 0;
/** sets the RX path to use, returns true if successful and false otherwise */
virtual bool setRxAntenna(const std::string &ant, size_t chan = 0) = 0;
@@ -163,6 +154,9 @@ class RadioDevice {
virtual double getRxFreq(size_t chan = 0) = 0;
virtual double getSampleRate()=0;
virtual double setPowerAttenuation(int atten, size_t chan) = 0;
virtual double getPowerAttenuation(size_t chan=0) = 0;
protected:
size_t tx_sps, rx_sps;
InterfaceType iface;
@@ -171,12 +165,17 @@ class RadioDevice {
std::vector<std::string> tx_paths, rx_paths;
std::vector<struct device_counters> m_ctr;
RadioDevice(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chans, double offset,
RadioDevice(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths):
tx_sps(tx_sps), rx_sps(rx_sps), iface(type), chans(chans), lo_offset(offset),
tx_sps(tx_sps), rx_sps(rx_sps), iface(type), chans(chan_num), lo_offset(offset),
tx_paths(tx_paths), rx_paths(rx_paths)
{
if (iface == MULTI_ARFCN) {
LOGC(DDEV, INFO) << "Multi-ARFCN: "<< chan_num << " logical chans -> 1 physical chans";
chans = 1;
}
m_ctr.resize(chans);
for (size_t i = 0; i < chans; i++) {
memset(&m_ctr[i], 0, sizeof(m_ctr[i]));
@@ -190,9 +189,16 @@ class RadioDevice {
for (i = 0; i < tx_paths.size(); i++) {
if (tx_paths[i] == "")
continue;
LOG(DEBUG) << "Configuring channel " << i << " with antenna " << tx_paths[i];
if (iface == MULTI_ARFCN && i > 0) {
LOGCHAN(i, DDEV, NOTICE) << "Not setting Tx antenna "
<< tx_paths[i]
<< " for a logical channel";
continue;
}
LOGCHAN(i, DDEV, DEBUG) << "Configuring Tx antenna " << tx_paths[i];
if (!setTxAntenna(tx_paths[i], i)) {
LOG(ALERT) << "Failed configuring channel " << i << " with antenna " << tx_paths[i];
LOGCHAN(i, DDEV, ALERT) << "Failed configuring Tx antenna " << tx_paths[i];
return false;
}
}
@@ -200,9 +206,16 @@ class RadioDevice {
for (i = 0; i < rx_paths.size(); i++) {
if (rx_paths[i] == "")
continue;
LOG(DEBUG) << "Configuring channel " << i << " with antenna " << rx_paths[i];
if (iface == MULTI_ARFCN && i > 0) {
LOGCHAN(i, DDEV, NOTICE) << "Not setting Rx antenna "
<< rx_paths[i]
<< " for a logical channel";
continue;
}
LOGCHAN(i, DDEV, DEBUG) << "Configuring Rx antenna " << rx_paths[i];
if (!setRxAntenna(rx_paths[i], i)) {
LOG(ALERT) << "Failed configuring channel " << i << " with antenna " << rx_paths[i];
LOGCHAN(i, DDEV, ALERT) << "Failed configuring Rx antenna " << rx_paths[i];
return false;
}
}

View File

@@ -28,9 +28,9 @@
#include <inttypes.h>
smpl_buf::smpl_buf(size_t len)
: buf_len(len), time_start(0), time_end(0),
data_start(0), data_end(0)
: buf_len(len)
{
reset();
data = new uint32_t[len];
}
@@ -39,6 +39,14 @@ smpl_buf::~smpl_buf()
delete[] data;
}
void smpl_buf::reset()
{
time_start = 0;
time_end = 0;
data_start = 0;
data_end = 0;
}
ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const
{
if (timestamp < time_start)
@@ -154,7 +162,7 @@ std::string smpl_buf::str_status(TIMESTAMP timestamp) const
return ost.str();
}
std::string smpl_buf::str_code(ssize_t code)
std::string smpl_buf::str_code(int code)
{
switch (code) {
case ERROR_TIMESTAMP:
@@ -166,6 +174,8 @@ std::string smpl_buf::str_code(ssize_t code)
case ERROR_OVERFLOW:
return "Sample buffer: Overrun";
default:
return "Sample buffer: Unknown error";
std::stringstream ss;
ss << "Sample buffer: Unknown error " << code;
return ss.str();
}
}

View File

@@ -33,7 +33,7 @@
/*
Sample Buffer - Allows reading and writing of timed samples using osmo-trx
timestamps. Time conversions are handled
internally or accessable through the static convert calls.
internally or accessible through the static convert calls.
*/
class smpl_buf {
public:
@@ -44,6 +44,10 @@ public:
smpl_buf(size_t len);
~smpl_buf();
/** Reset this buffer, keeps the size
*/
void reset();
/** Query number of samples available for reading
@param timestamp time of first sample
@return number of available samples or error
@@ -68,7 +72,7 @@ public:
@param code an error code
@return a formatted error string
*/
static std::string str_code(ssize_t code);
static std::string str_code(int code);
enum err_code {
ERROR_TIMESTAMP = -1,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
#ifndef _IPC_DEVICE_H_
#define _IPC_DEVICE_H_
#include <cstdint>
#include <cstddef>
#include <climits>
#include <string>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
extern "C" {
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "shm.h"
}
#include "radioDevice.h"
class smpl_buf;
#define IPC_MAX_NUM_TRX 8
struct ipc_per_trx_sock_state {
struct osmo_fd conn_bfd; /* fd for connection to the BTS */
struct osmo_timer_list timer; /* socket connect retry timer */
struct llist_head upqueue; /* queue for sending messages */
uint32_t messages_processed_mask; // (=| IPC_IF_MSG_xxx-IPC_IF_CHAN_MSG_OFFSET) bitmask
ipc_per_trx_sock_state() : conn_bfd(), timer(), upqueue(), messages_processed_mask()
{
conn_bfd.fd = -1;
}
};
class IPCDevice : public RadioDevice {
protected:
struct ipc_per_trx_sock_state master_sk_state;
std::vector<struct ipc_per_trx_sock_state> sk_chan_state;
uint32_t tx_attenuation[IPC_MAX_NUM_TRX];
uint8_t tmp_state;
char shm_name[SHM_NAME_MAX];
int ipc_shm_connect(const char *shm_name);
void *shm;
struct ipc_shm_region *shm_dec;
std::vector<smpl_buf *> rx_buffers;
double actualSampleRate;
bool started;
TIMESTAMP ts_initial, ts_offset;
std::vector<double> tx_gains, rx_gains;
struct ipc_sk_if_info_req current_info_req;
struct ipc_sk_if_info_cnf current_info_cnf;
struct ipc_sk_if_open_cnf current_open_cnf;
std::vector<struct ipc_shm_io *> shm_io_rx_streams;
std::vector<struct ipc_shm_io *> shm_io_tx_streams;
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);
void manually_poll_sock_fds();
void ipc_sock_close(ipc_per_trx_sock_state *state);
int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim);
int ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf);
int ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf);
int ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf);
int ipc_tx_open_req(struct ipc_per_trx_sock_state *state, uint32_t num_chans, uint32_t ref);
int ipc_chan_rx(uint8_t msg_type, ipc_sk_chan_if *ipc_prim, uint8_t chan_nr);
int ipc_rx_chan_start_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr);
int ipc_rx_chan_stop_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr);
int ipc_rx_chan_setgain_cnf(ipc_sk_chan_if_gain *ret, uint8_t chan_nr);
int ipc_rx_chan_setfreq_cnf(ipc_sk_chan_if_freq_cnf *ret, uint8_t chan_nr);
int ipc_rx_chan_notify_underflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr);
int ipc_rx_chan_notify_overflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr);
int ipc_rx_chan_settxattn_cnf(ipc_sk_chan_if_tx_attenuation *ret, uint8_t chan_nr);
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:
int ipc_sock_read(struct osmo_fd *bfd);
int ipc_sock_write(struct osmo_fd *bfd);
int ipc_chan_sock_read(osmo_fd *bfd);
int ipc_chan_sock_write(osmo_fd *bfd);
/** Object constructor */
IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths);
virtual ~IPCDevice() 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_LMS1;
}
/**
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 * current_info_cnf.iq_scaling_val_rx;
}
/** returns the full-scale receive amplitude **/
virtual double fullScaleOutputValue() override
{
return (double)SHRT_MAX * current_info_cnf.iq_scaling_val_tx;
}
/** 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) { 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,43 @@
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 shm.h ipc_shm.h ipc_chan.h ipc_sock.h
if DEVICE_UHD
noinst_HEADERS += ../uhd/UHDDevice.h uhdwrap.h ipc-driver-test.h
endif
noinst_LTLIBRARIES = libdevice.la
libdevice_la_SOURCES = IPCDevice.cpp shm.c ipc_shm.c ipc_chan.c ipc_sock.c
libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC
#work around distclean issue on older autotools vers:
#a direct build of ../uhd/UHDDevice.cpp tries to clean
#../uhd/.dep/UHDDevice.Plo twice and fails
uhddev_ipc.cpp:
echo "#include \"../uhd/UHDDevice.cpp\"" >$@
CLEANFILES= uhddev_ipc.cpp
BUILT_SOURCES = uhddev_ipc.cpp
if DEVICE_UHD
bin_PROGRAMS = ipc-driver-test
#ipc_driver_test_SHORTNAME = drvt
ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ipc_shm.c ipc_chan.c ipc_sock.c uhddev_ipc.cpp
ipc_driver_test_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)
endif

View File

@@ -0,0 +1,492 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <debug.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include <getopt.h>
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "shm.h"
#include "ipc_shm.h"
#include "ipc_chan.h"
#include "ipc_sock.h"
#define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2"
#define IPC_SOCK_PATH_PREFIX "/tmp"
static void *tall_ctx;
struct ipc_sock_state *global_ipc_sock_state;
/* 8 channels are plenty */
struct ipc_sock_state *global_ctrl_socks[8];
struct ipc_shm_io *ios_tx_to_device[8];
struct ipc_shm_io *ios_rx_from_device[8];
void *shm;
void *global_dev;
static struct ipc_shm_region *decoded_region;
static struct {
int msocknum;
char *ud_prefix_dir;
} cmdline_cfg;
static const struct log_info_cat default_categories[] = {
[DMAIN] = {
.name = "DMAIN",
.color = NULL,
.description = "Main generic category",
.loglevel = LOGL_DEBUG,.enabled = 1,
},
[DDEV] = {
.name = "DDEV",
.description = "Device/Driver specific code",
.color = NULL,
.enabled = 1, .loglevel = LOGL_DEBUG,
},
};
const struct log_info log_infox = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
#include "uhdwrap.h"
volatile int ipc_exit_requested = 0;
static int ipc_shm_setup(const char *shm_name, size_t shm_len)
{
int fd;
int rc;
LOGP(DMAIN, LOGL_NOTICE, "Opening shm path %s\n", shm_name);
if ((fd = shm_open(shm_name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
LOGP(DMAIN, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno));
rc = -errno;
goto err_shm_open;
}
LOGP(DMAIN, LOGL_NOTICE, "Truncating %d to size %zu\n", fd, shm_len);
if (ftruncate(fd, shm_len) < 0) {
LOGP(DMAIN, LOGL_ERROR, "ftruncate %d: %s\n", errno, strerror(errno));
rc = -errno;
goto err_mmap;
}
LOGP(DMAIN, LOGL_NOTICE, "mmaping shared memory fd %d\n", fd);
if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
LOGP(DMAIN, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno));
rc = -errno;
goto err_mmap;
}
LOGP(DMAIN, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm);
/* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */
close(fd);
return 0;
err_mmap:
shm_unlink(shm_name);
close(fd);
err_shm_open:
return rc;
}
struct msgb *ipc_msgb_alloc(uint8_t msg_type)
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx");
if (!msg)
return NULL;
msgb_put(msg, sizeof(struct ipc_sk_if) + 1000);
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->msg_type = msg_type;
return msg;
}
static int ipc_tx_greeting_cnf(uint8_t req_version)
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->u.greeting_cnf.req_version = req_version;
return ipc_sock_send(msg);
}
static int ipc_tx_info_cnf()
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->data;
uhdwrap_fill_info_cnf(ipc_prim);
return ipc_sock_send(msg);
}
static int ipc_tx_open_cnf(int rc, uint32_t num_chans, int32_t timingoffset)
{
struct msgb *msg;
struct ipc_sk_if *ipc_prim;
struct ipc_sk_if_open_cnf_chan *chan_info;
unsigned int i;
msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->data;
ipc_prim->u.open_cnf.return_code = rc;
ipc_prim->u.open_cnf.path_delay = timingoffset; // 6.18462e-5 * 1625e3 / 6;
OSMO_STRLCPY_ARRAY(ipc_prim->u.open_cnf.shm_name, DEFAULT_SHM_NAME);
chan_info = ipc_prim->u.open_cnf.chan_info;
for (i = 0; i < num_chans; i++) {
snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path),"%s/ipc_sock%d_%d",
cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum, i);
/* FIXME: dynamc chan limit, currently 8 */
if (i < 8)
ipc_sock_init(chan_info->chan_ipc_sk_path, &global_ctrl_socks[i], ipc_chan_sock_accept, i);
chan_info++;
}
return ipc_sock_send(msg);
}
int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req)
{
if (greeting_req->req_version == IPC_SOCK_API_VERSION)
ipc_tx_greeting_cnf(IPC_SOCK_API_VERSION);
else
ipc_tx_greeting_cnf(0);
return 0;
}
int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req)
{
ipc_tx_info_cnf();
return 0;
}
int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req)
{
/* calculate size needed */
unsigned int len;
unsigned int i;
global_dev = uhdwrap_open(open_req);
/* b210 packet size is 2040, but our tx size is 2500, so just do *2 */
int shmbuflen = uhdwrap_get_bufsizerx(global_dev) * 2;
len = ipc_shm_encode_region(NULL, open_req->num_chans, 4, shmbuflen);
/* Here we verify num_chans, rx_path, tx_path, clockref, etc. */
int rc = ipc_shm_setup(DEFAULT_SHM_NAME, len);
len = ipc_shm_encode_region((struct ipc_shm_raw_region *)shm, open_req->num_chans, 4, shmbuflen);
// LOGP(DMAIN, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *)shm, 80));
/* set up our own copy of the decoded area, we have to do it here,
* since the uhd wrapper does not allow starting single channels
* additionally go for the producer init for both, so only we are responsible for the init, instead
* of splitting it with the client and causing potential races if one side uses it too early */
decoded_region = ipc_shm_decode_region(0, (struct ipc_shm_raw_region *)shm);
for (i = 0; i < open_req->num_chans; i++) {
// ios_tx_to_device[i] = ipc_shm_init_consumer(decoded_region->channels[i]->dl_stream);
ios_tx_to_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->dl_stream);
ios_rx_from_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->ul_stream);
}
ipc_tx_open_cnf(-rc, open_req->num_chans, uhdwrap_get_timingoffset(global_dev));
return 0;
}
volatile bool ul_running = false;
volatile bool dl_running = false;
void *uplink_thread(void *x_void_ptr)
{
uint32_t chann = decoded_region->num_chans;
ul_running = true;
pthread_setname_np(pthread_self(), "uplink_rx");
while (!ipc_exit_requested) {
int32_t read = uhdwrap_read(global_dev, chann);
if (read < 0)
return 0;
}
return 0;
}
void *downlink_thread(void *x_void_ptr)
{
int chann = decoded_region->num_chans;
dl_running = true;
pthread_setname_np(pthread_self(), "downlink_tx");
while (!ipc_exit_requested) {
bool underrun;
uhdwrap_write(global_dev, chann, &underrun);
}
return 0;
}
int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
int rc = 0;
rc = uhdwrap_start(global_dev, chan_nr);
/* no per-chan start/stop */
if (!dl_running || !ul_running) {
/* chan != first chan start will "fail", which is fine, usrp can't start/stop chans independently */
if (rc) {
LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads.. req for chan:%d\n", chan_nr);
pthread_t rx, tx;
pthread_create(&rx, NULL, uplink_thread, 0);
pthread_create(&tx, NULL, downlink_thread, 0);
}
} else
LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads request ignored.. req for chan:%d\n", chan_nr);
msg = ipc_msgb_alloc(IPC_IF_MSG_START_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.start_cnf.return_code = rc ? 0 : -1;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
int rc;
/* no per-chan start/stop */
rc = uhdwrap_stop(global_dev, chan_nr);
msg = ipc_msgb_alloc(IPC_IF_MSG_STOP_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.stop_cnf.return_code = rc ? 0 : -1;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
double rv;
rv = uhdwrap_set_gain(global_dev, req->gain, chan_nr, req->is_tx);
msg = ipc_msgb_alloc(IPC_IF_MSG_SETGAIN_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.set_gain_cnf.is_tx = req->is_tx;
ipc_prim->u.set_gain_cnf.gain = rv;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
bool rv;
rv = uhdwrap_set_freq(global_dev, req->freq, chan_nr, req->is_tx);
msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.set_freq_cnf.return_code = rv ? 0 : 1;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr)
{
struct msgb *msg;
struct ipc_sk_chan_if *ipc_prim;
double rv;
rv = uhdwrap_set_txatt(global_dev, req->attenuation, chan_nr);
msg = ipc_msgb_alloc(IPC_IF_MSG_SETTXATTN_CNF);
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
ipc_prim->u.txatten_cnf.attenuation = rv;
return ipc_chan_sock_send(msg, chan_nr);
}
int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var,
int (*sock_callback_fn)(struct osmo_fd *fd, unsigned int what), int n)
{
struct ipc_sock_state *state;
struct osmo_fd *bfd;
int rc;
state = talloc_zero(NULL, struct ipc_sock_state);
if (!state)
return -ENOMEM;
*global_state_var = state;
INIT_LLIST_HEAD(&state->upqueue);
state->conn_bfd.fd = -1;
bfd = &state->listen_bfd;
bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND);
if (bfd->fd < 0) {
LOGP(DMAIN, LOGL_ERROR, "Could not create %s unix socket: %s\n", path, strerror(errno));
talloc_free(state);
return -1;
}
osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, sock_callback_fn, state, n);
rc = osmo_fd_register(bfd);
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
close(bfd->fd);
talloc_free(state);
return rc;
}
LOGP(DMAIN, LOGL_INFO, "Started listening on IPC socket: %s\n", path);
return 0;
}
static void print_help(void)
{
printf("ipc-driver-test Usage:\n"
" -h --help This message\n"
" -u --unix-sk-dir DIR Existing directory where to create the Master socket\n"
" -n --sock-num NR Master socket suffix number NR\n");
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
const struct option long_options[] = { { "help", 0, 0, 'h' },
{ "unix-sk-dir", 1, 0, 'u' },
{ "sock-num", 1, 0, 'n' },
{ 0, 0, 0, 0 } };
c = getopt_long(argc, argv, "hu:n:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
break;
case 'u':
cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, optarg);
break;
case 'n':
cmdline_cfg.msocknum = atoi(optarg);
break;
default:
exit(2);
break;
}
}
if (argc > optind) {
fprintf(stderr, "Unsupported positional arguments on command line\n");
exit(2);
}
}
int main(int argc, char **argv)
{
char ipc_msock_path[128];
tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
msgb_talloc_ctx_init(tall_ctx, 0);
osmo_init_logging2(tall_ctx, &log_infox);
log_enable_multithread();
handle_options(argc, argv);
if (!cmdline_cfg.ud_prefix_dir)
cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, IPC_SOCK_PATH_PREFIX);
snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s/ipc_sock%d", cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum);
LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]);
ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0);
while (!ipc_exit_requested)
osmo_select_main(0);
if (global_dev) {
unsigned int i;
for (i = 0; i < decoded_region->num_chans; i++)
uhdwrap_stop(global_dev, i);
}
ipc_sock_close(global_ipc_sock_state);
return 0;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <osmocom/core/select.h>
#include "shm.h"
extern struct ipc_sock_state *global_ipc_sock_state;
/* 8 channels are plenty */
extern struct ipc_sock_state *global_ctrl_socks[8];
extern struct ipc_shm_io *ios_tx_to_device[8];
extern struct ipc_shm_io *ios_rx_from_device[8];
struct ipc_sock_state {
struct osmo_fd listen_bfd; /* fd for listen socket */
struct osmo_fd conn_bfd; /* fd for connection */
struct llist_head upqueue; /* queue for sending messages */
};
int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var,
int (*sock_callback_fn)(struct osmo_fd *fd, unsigned int what), int n);
int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req);
int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req);
int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req);
int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr);
int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr);
int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr);
int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr);
int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr);

View File

@@ -0,0 +1,254 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include <debug.h>
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "shm.h"
#include "ipc-driver-test.h"
#include "ipc_chan.h"
#include "ipc_sock.h"
static int ipc_chan_rx(uint8_t msg_type, struct ipc_sk_chan_if *ipc_prim, uint8_t chan_nr)
{
int rc = 0;
switch (msg_type) {
case IPC_IF_MSG_START_REQ:
rc = ipc_rx_chan_start_req(&ipc_prim->u.start_req, chan_nr);
break;
case IPC_IF_MSG_STOP_REQ:
rc = ipc_rx_chan_stop_req(&ipc_prim->u.stop_req, chan_nr);
break;
case IPC_IF_MSG_SETGAIN_REQ:
rc = ipc_rx_chan_setgain_req(&ipc_prim->u.set_gain_req, chan_nr);
break;
case IPC_IF_MSG_SETFREQ_REQ:
rc = ipc_rx_chan_setfreq_req(&ipc_prim->u.set_freq_req, chan_nr);
break;
case IPC_IF_MSG_SETTXATTN_REQ:
rc = ipc_rx_chan_settxatten_req(&ipc_prim->u.txatten_req, chan_nr);
break;
default:
LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type 0x%02x on chan %d\n", msg_type, chan_nr);
rc = -EINVAL;
}
return rc;
}
static int ipc_chan_sock_read(struct osmo_fd *bfd)
{
struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data;
struct ipc_sk_chan_if *ipc_prim;
struct msgb *msg;
int rc;
msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_chan_sock_rx");
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_chan_if *)msg->tail;
rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
if (rc == 0)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
msgb_free(msg);
return 0;
}
goto close;
}
if (rc < (int)sizeof(*ipc_prim)) {
LOGP(DDEV, LOGL_ERROR,
"Received %d bytes on Unix Socket, but primitive size "
"is %zu, discarding\n",
rc, sizeof(*ipc_prim));
msgb_free(msg);
return 0;
}
rc = ipc_chan_rx(ipc_prim->msg_type, ipc_prim, bfd->priv_nr);
/* as we always synchronously process the message in IPC_rx() and
* its callbacks, we can free the message here. */
msgb_free(msg);
return rc;
close:
msgb_free(msg);
ipc_sock_close(state);
return -1;
}
int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr)
{
struct ipc_sock_state *state = global_ctrl_socks[chan_nr];
struct osmo_fd *conn_bfd;
if (!state)
return -EINVAL;
if (!state) {
LOGP(DDEV, LOGL_INFO,
"IPC socket not created, "
"dropping message\n");
msgb_free(msg);
return -EINVAL;
}
conn_bfd = &state->conn_bfd;
if (conn_bfd->fd <= 0) {
LOGP(DDEV, LOGL_NOTICE,
"IPC socket not connected, "
"dropping message\n");
msgb_free(msg);
return -EIO;
}
msgb_enqueue(&state->upqueue, msg);
osmo_fd_write_enable(conn_bfd);
return 0;
}
static int ipc_chan_sock_write(struct osmo_fd *bfd)
{
struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data;
int rc;
while (!llist_empty(&state->upqueue)) {
struct msgb *msg, *msg2;
struct ipc_sk_chan_if *ipc_prim;
/* peek at the beginning of the queue */
msg = llist_entry(state->upqueue.next, struct msgb, list);
ipc_prim = (struct ipc_sk_chan_if *)msg->data;
osmo_fd_write_disable(bfd);
/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
if (!msgb_length(msg)) {
LOGP(DDEV, LOGL_ERROR,
"message type (%d) with ZERO "
"bytes!\n",
ipc_prim->msg_type);
goto dontsend;
}
/* try to send it over the socket */
rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
if (rc == 0)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
osmo_fd_write_enable(bfd);
break;
}
goto close;
}
dontsend:
/* _after_ we send it, we can dequeue */
msg2 = msgb_dequeue(&state->upqueue);
assert(msg == msg2);
msgb_free(msg);
}
return 0;
close:
ipc_sock_close(state);
return -1;
}
static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags)
{
int rc = 0;
if (flags & OSMO_FD_READ)
rc = ipc_chan_sock_read(bfd);
if (rc < 0)
return rc;
if (flags & OSMO_FD_WRITE)
rc = ipc_chan_sock_write(bfd);
return rc;
}
int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags)
{
struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data;
struct osmo_fd *conn_bfd = &state->conn_bfd;
struct sockaddr_un un_addr;
socklen_t len;
int rc;
len = sizeof(un_addr);
rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len);
if (rc < 0) {
LOGP(DDEV, LOGL_ERROR, "Failed to accept a new connection\n");
return -1;
}
if (conn_bfd->fd >= 0) {
LOGP(DDEV, LOGL_NOTICE,
"osmo-trx connects but we already have "
"another active connection ?!?\n");
/* We already have one IPC connected, this is all we support */
osmo_fd_read_disable(&state->listen_bfd);
close(rc);
return 0;
}
/* copy chan nr, required for proper bfd<->chan # mapping */
osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_chan_sock_cb, state, bfd->priv_nr);
if (osmo_fd_register(conn_bfd) != 0) {
LOGP(DDEV, LOGL_ERROR,
"Failed to register new connection "
"fd\n");
close(conn_bfd->fd);
conn_bfd->fd = -1;
return -1;
}
LOGP(DDEV, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n");
return 0;
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef IPC_CHAN_H
#define IPC_CHAN_H
#include "shm.h"
#include "ipc-driver-test.h"
int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr);
int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags);
#endif // IPC_CHAN_H

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include <shm.h>
#include "ipc_shm.h"
#include <pthread.h>
#include <semaphore.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/panic.h>
#include <debug.h>
#ifdef __cplusplus
}
#endif
#define SAMPLE_SIZE_BYTE (sizeof(uint16_t) * 2)
struct ipc_shm_io *ipc_shm_init_consumer(struct ipc_shm_stream *s)
{
unsigned int i;
struct ipc_shm_io *r = (struct ipc_shm_io *)malloc(sizeof(struct ipc_shm_io));
r->this_stream = s->raw;
r->buf_ptrs =
(volatile struct ipc_shm_raw_smpl_buf **)malloc(sizeof(struct ipc_shm_raw_smpl_buf *) * s->num_buffers);
/* save actual ptrs */
for (i = 0; i < s->num_buffers; i++)
r->buf_ptrs[i] = s->buffers[i];
r->partial_read_begin_ptr = 0;
return r;
}
struct ipc_shm_io *ipc_shm_init_producer(struct ipc_shm_stream *s)
{
int rv;
pthread_mutexattr_t att;
pthread_condattr_t t1, t2;
struct ipc_shm_io *r = ipc_shm_init_consumer(s);
rv = pthread_mutexattr_init(&att);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
rv = pthread_mutexattr_setrobust(&att, PTHREAD_MUTEX_ROBUST);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
rv = pthread_mutexattr_setpshared(&att, PTHREAD_PROCESS_SHARED);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
rv = pthread_mutex_init((pthread_mutex_t *)&r->this_stream->lock, &att);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
pthread_mutexattr_destroy(&att);
rv = pthread_condattr_setpshared(&t1, PTHREAD_PROCESS_SHARED);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
rv = pthread_condattr_setpshared(&t2, PTHREAD_PROCESS_SHARED);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->cf, &t1);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->ce, &t2);
if (rv != 0) {
osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv);
}
pthread_condattr_destroy(&t1);
pthread_condattr_destroy(&t2);
r->this_stream->read_next = 0;
r->this_stream->write_next = 0;
return r;
}
void ipc_shm_close(struct ipc_shm_io *r)
{
if (r) {
free(r->buf_ptrs);
free(r);
}
}
int32_t ipc_shm_enqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data)
{
volatile struct ipc_shm_raw_smpl_buf *buf;
int32_t rv;
struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
tv.tv_sec += 1;
rv = pthread_mutex_timedlock((pthread_mutex_t *)&r->this_stream->lock, &tv);
if (rv != 0)
return -rv;
while (((r->this_stream->write_next + 1) & (r->this_stream->num_buffers - 1)) == r->this_stream->read_next &&
rv == 0)
rv = pthread_cond_timedwait((pthread_cond_t *)&r->this_stream->ce,
(pthread_mutex_t *)&r->this_stream->lock, &tv);
if (rv != 0)
return -rv;
buf = r->buf_ptrs[r->this_stream->write_next];
buf->timestamp = timestamp;
rv = len_in_sps <= r->this_stream->buffer_size ? len_in_sps : r->this_stream->buffer_size;
memcpy((void *)buf->samples, data, SAMPLE_SIZE_BYTE * rv);
buf->data_len = rv;
r->this_stream->write_next = (r->this_stream->write_next + 1) & (r->this_stream->num_buffers - 1);
pthread_cond_signal((pthread_cond_t *)&r->this_stream->cf);
pthread_mutex_unlock((pthread_mutex_t *)&r->this_stream->lock);
return rv;
}
int32_t ipc_shm_read(struct ipc_shm_io *r, uint16_t *out_buf, uint32_t num_samples, uint64_t *timestamp,
uint32_t timeout_seconds)
{
volatile struct ipc_shm_raw_smpl_buf *buf;
int32_t rv;
uint8_t freeflag = 0;
struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
tv.tv_sec += timeout_seconds;
rv = pthread_mutex_timedlock((pthread_mutex_t *)&r->this_stream->lock, &tv);
if (rv != 0)
return -rv;
while (r->this_stream->write_next == r->this_stream->read_next && rv == 0)
rv = pthread_cond_timedwait((pthread_cond_t *)&r->this_stream->cf,
(pthread_mutex_t *)&r->this_stream->lock, &tv);
if (rv != 0)
return -rv;
buf = r->buf_ptrs[r->this_stream->read_next];
if (buf->data_len <= num_samples) {
memcpy(out_buf, (void *)&buf->samples[r->partial_read_begin_ptr * 2], SAMPLE_SIZE_BYTE * buf->data_len);
r->partial_read_begin_ptr = 0;
rv = buf->data_len;
buf->data_len = 0;
r->this_stream->read_next = (r->this_stream->read_next + 1) & (r->this_stream->num_buffers - 1);
freeflag = 1;
} else /*if (buf->data_len > num_samples)*/ {
memcpy(out_buf, (void *)&buf->samples[r->partial_read_begin_ptr * 2], SAMPLE_SIZE_BYTE * num_samples);
r->partial_read_begin_ptr += num_samples;
buf->data_len -= num_samples;
rv = num_samples;
}
*timestamp = buf->timestamp;
buf->timestamp += rv;
if (freeflag)
pthread_cond_signal((pthread_cond_t *)&r->this_stream->ce);
pthread_mutex_unlock((pthread_mutex_t *)&r->this_stream->lock);
return rv;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef IPC_SHM_H
#define IPC_SHM_H
#ifdef __cplusplus
extern "C" {
#endif
#include <shm.h>
#include <stdint.h>
#ifdef __cplusplus
}
#endif
struct ipc_shm_io {
volatile struct ipc_shm_raw_stream *this_stream;
volatile struct ipc_shm_raw_smpl_buf **volatile buf_ptrs;
uint32_t partial_read_begin_ptr;
};
struct ipc_shm_io *ipc_shm_init_consumer(struct ipc_shm_stream *s);
struct ipc_shm_io *ipc_shm_init_producer(struct ipc_shm_stream *s);
void ipc_shm_close(struct ipc_shm_io *r);
int32_t ipc_shm_enqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data);
int32_t ipc_shm_tryenqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data);
volatile struct ipc_shm_raw_smpl_buf *ipc_shm_dequeue(struct ipc_shm_io *r);
int32_t ipc_shm_read(struct ipc_shm_io *r, uint16_t *out_buf, uint32_t num_samples, uint64_t *timestamp,
uint32_t timeout_seconds);
#endif // IPC_SHM_H

View File

@@ -0,0 +1,266 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include <debug.h>
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "shm.h"
#include "ipc-driver-test.h"
extern volatile int ipc_exit_requested;
static int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim)
{
int rc = 0;
switch (msg_type) {
case IPC_IF_MSG_GREETING_REQ:
rc = ipc_rx_greeting_req(&ipc_prim->u.greeting_req);
break;
case IPC_IF_MSG_INFO_REQ:
rc = ipc_rx_info_req(&ipc_prim->u.info_req);
break;
case IPC_IF_MSG_OPEN_REQ:
rc = ipc_rx_open_req(&ipc_prim->u.open_req);
break;
default:
LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type 0x%02x\n", msg_type);
rc = -EINVAL;
}
return rc;
}
int ipc_sock_send(struct msgb *msg)
{
struct ipc_sock_state *state = global_ipc_sock_state;
struct osmo_fd *conn_bfd;
if (!state) {
LOGP(DDEV, LOGL_INFO,
"IPC socket not created, "
"dropping message\n");
msgb_free(msg);
return -EINVAL;
}
conn_bfd = &state->conn_bfd;
if (conn_bfd->fd <= 0) {
LOGP(DDEV, LOGL_NOTICE,
"IPC socket not connected, "
"dropping message\n");
msgb_free(msg);
return -EIO;
}
msgb_enqueue(&state->upqueue, msg);
osmo_fd_write_enable(conn_bfd);
return 0;
}
void ipc_sock_close(struct ipc_sock_state *state)
{
struct osmo_fd *bfd = &state->conn_bfd;
LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n");
ipc_exit_requested = 1;
close(bfd->fd);
bfd->fd = -1;
osmo_fd_unregister(bfd);
/* re-enable the generation of ACCEPT for new connections */
osmo_fd_read_enable(&state->listen_bfd);
/* flush the queue */
while (!llist_empty(&state->upqueue)) {
struct msgb *msg = msgb_dequeue(&state->upqueue);
msgb_free(msg);
}
}
int ipc_sock_read(struct osmo_fd *bfd)
{
struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data;
struct ipc_sk_if *ipc_prim;
struct msgb *msg;
int rc;
msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx");
if (!msg)
return -ENOMEM;
ipc_prim = (struct ipc_sk_if *)msg->tail;
rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
if (rc == 0)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
msgb_free(msg);
return 0;
}
goto close;
}
if (rc < (int)sizeof(*ipc_prim)) {
LOGP(DDEV, LOGL_ERROR,
"Received %d bytes on Unix Socket, but primitive size "
"is %zu, discarding\n",
rc, sizeof(*ipc_prim));
msgb_free(msg);
return 0;
}
rc = ipc_rx(ipc_prim->msg_type, ipc_prim);
/* as we always synchronously process the message in IPC_rx() and
* its callbacks, we can free the message here. */
msgb_free(msg);
return rc;
close:
msgb_free(msg);
ipc_sock_close(state);
return -1;
}
static int ipc_sock_write(struct osmo_fd *bfd)
{
struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data;
int rc;
while (!llist_empty(&state->upqueue)) {
struct msgb *msg, *msg2;
struct ipc_sk_if *ipc_prim;
/* peek at the beginning of the queue */
msg = llist_entry(state->upqueue.next, struct msgb, list);
ipc_prim = (struct ipc_sk_if *)msg->data;
osmo_fd_write_disable(bfd);
/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
if (!msgb_length(msg)) {
LOGP(DDEV, LOGL_ERROR,
"message type (%d) with ZERO "
"bytes!\n",
ipc_prim->msg_type);
goto dontsend;
}
/* try to send it over the socket */
rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
if (rc == 0)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
osmo_fd_write_enable(bfd);
break;
}
goto close;
}
dontsend:
/* _after_ we send it, we can deueue */
msg2 = msgb_dequeue(&state->upqueue);
assert(msg == msg2);
msgb_free(msg);
}
return 0;
close:
ipc_sock_close(state);
return -1;
}
static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags)
{
int rc = 0;
if (flags & OSMO_FD_READ)
rc = ipc_sock_read(bfd);
if (rc < 0)
return rc;
if (flags & OSMO_FD_WRITE)
rc = ipc_sock_write(bfd);
return rc;
}
/* accept connection coming from IPC */
int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags)
{
struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data;
struct osmo_fd *conn_bfd = &state->conn_bfd;
struct sockaddr_un un_addr;
socklen_t len;
int rc;
len = sizeof(un_addr);
rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len);
if (rc < 0) {
LOGP(DDEV, LOGL_ERROR, "Failed to accept a new connection\n");
return -1;
}
if (conn_bfd->fd >= 0) {
LOGP(DDEV, LOGL_NOTICE,
"ip clent connects but we already have "
"another active connection ?!?\n");
/* We already have one IPC connected, this is all we support */
osmo_fd_read_disable(&state->listen_bfd);
close(rc);
return 0;
}
osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_sock_cb, state, 0);
if (osmo_fd_register(conn_bfd) != 0) {
LOGP(DDEV, LOGL_ERROR,
"Failed to register new connection "
"fd\n");
close(conn_bfd->fd);
conn_bfd->fd = -1;
return -1;
}
LOGP(DDEV, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n");
return 0;
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef IPC_SOCK_H
#define IPC_SOCK_H
#include "shm.h"
#include "ipc-driver-test.h"
int ipc_sock_send(struct msgb *msg);
int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags);
void ipc_sock_close(struct ipc_sock_state *state);
#endif // IPC_SOCK_H

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdint.h>
#include <stddef.h>
#include <osmocom/core/talloc.h>
#include "shm.h"
#define ENCDECDEBUG(...) //fprintf(stderr, __VA_ARGS__)
/* Convert offsets to pointers */
struct ipc_shm_stream *ipc_shm_decode_stream(void *tall_ctx, struct ipc_shm_raw_region *root_raw,
struct ipc_shm_raw_stream *stream_raw)
{
unsigned int i;
struct ipc_shm_stream *stream;
stream = talloc_zero(tall_ctx, struct ipc_shm_stream);
stream = talloc_zero_size(tall_ctx, sizeof(struct ipc_shm_stream) +
sizeof(struct ipc_shm_raw_smpl_buf *) * stream_raw->num_buffers);
if (!stream)
return NULL;
stream->num_buffers = stream_raw->num_buffers;
stream->buffer_size = stream_raw->buffer_size;
stream->raw = stream_raw;
for (i = 0; i < stream->num_buffers; i++) {
ENCDECDEBUG("decode: smpl_buf %d at offset %u\n", i, stream_raw->buffer_offset[i]);
stream->buffers[i] =
(struct ipc_shm_raw_smpl_buf *)(((uint8_t *)root_raw) + stream_raw->buffer_offset[i]);
}
return stream;
}
struct ipc_shm_channel *ipc_shm_decode_channel(void *tall_ctx, struct ipc_shm_raw_region *root_raw,
struct ipc_shm_raw_channel *chan_raw)
{
struct ipc_shm_channel *chan;
chan = talloc_zero(tall_ctx, struct ipc_shm_channel);
if (!chan)
return NULL;
ENCDECDEBUG("decode: streams at offset %u and %u\n", chan_raw->dl_buf_offset, chan_raw->ul_buf_offset);
chan->dl_stream = ipc_shm_decode_stream(
chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t *)root_raw) + chan_raw->dl_buf_offset));
chan->ul_stream = ipc_shm_decode_stream(
chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t *)root_raw) + chan_raw->ul_buf_offset));
return chan;
}
struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw)
{
unsigned int i;
struct ipc_shm_region *root;
root = talloc_zero_size(tall_ctx,
sizeof(struct ipc_shm_region) + sizeof(struct ipc_shm_channel *) * root_raw->num_chans);
if (!root)
return NULL;
root->num_chans = root_raw->num_chans;
for (i = 0; i < root->num_chans; i++) {
ENCDECDEBUG("decode: channel %d at offset %u\n", i, root_raw->chan_offset[i]);
root->channels[i] = ipc_shm_decode_channel(
root, root_raw,
(struct ipc_shm_raw_channel *)(((uint8_t *)root_raw) + root_raw->chan_offset[i]));
}
return root;
}
unsigned int ipc_shm_encode_smpl_buf(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_smpl_buf *smpl_buf_raw,
uint32_t buffer_size)
{
unsigned int offset = sizeof(struct ipc_shm_raw_smpl_buf);
offset = (((uintptr_t)offset + 7) & ~0x07ULL);
ENCDECDEBUG("encode: smpl_buf at offset %u\n", offset);
offset += buffer_size * sizeof(uint16_t) * 2; /* samples */
return offset;
}
unsigned int ipc_shm_encode_stream(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_stream *stream_raw,
uint32_t num_buffers, uint32_t buffer_size)
{
unsigned int i;
ptrdiff_t start = (ptrdiff_t)stream_raw;
unsigned int offset = sizeof(struct ipc_shm_raw_stream) + sizeof(uint32_t) * num_buffers;
offset = (((uintptr_t)offset + 7) & ~0x07ULL);
ENCDECDEBUG("encode: stream at offset %lu\n", (start - (ptrdiff_t)root_raw));
if (root_raw) {
stream_raw->num_buffers = num_buffers;
stream_raw->buffer_size = buffer_size;
stream_raw->read_next = 0;
stream_raw->write_next = 0;
}
for (i = 0; i < num_buffers; i++) {
if (root_raw)
stream_raw->buffer_offset[i] = (start + offset - (ptrdiff_t)root_raw);
offset +=
ipc_shm_encode_smpl_buf(root_raw, (struct ipc_shm_raw_smpl_buf *)(start + offset), buffer_size);
}
return offset;
}
unsigned int ipc_shm_encode_channel(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_channel *chan_raw,
uint32_t num_buffers, uint32_t buffer_size)
{
uint8_t *start = (uint8_t *)chan_raw;
unsigned int offset = sizeof(struct ipc_shm_raw_channel);
offset = (((uintptr_t)offset + 7) & ~0x07ULL);
ENCDECDEBUG("encode: channel at offset %lu\n", (start - (uint8_t *)root_raw));
if (root_raw)
chan_raw->dl_buf_offset = (start + offset - (uint8_t *)root_raw);
offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers,
buffer_size);
if (root_raw)
chan_raw->ul_buf_offset = (start + offset - (uint8_t *)root_raw);
offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers,
buffer_size);
return offset;
}
/* if root_raw is NULL, then do a dry run, aka only calculate final offset */
unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers,
uint32_t buffer_size)
{
unsigned i;
uintptr_t start = (uintptr_t)root_raw;
unsigned int offset = sizeof(struct ipc_shm_raw_region) + sizeof(uint32_t) * num_chans;
offset = (((uintptr_t)offset + 7) & ~0x07ULL);
if (root_raw)
root_raw->num_chans = num_chans;
for (i = 0; i < num_chans; i++) {
if (root_raw)
root_raw->chan_offset[i] = (start + offset - (uintptr_t)root_raw);
ENCDECDEBUG("encode: channel %d chan_offset[i]=%lu\n", i, start + offset - (uintptr_t)root_raw);
offset += ipc_shm_encode_channel(root_raw, (struct ipc_shm_raw_channel *)(start + offset), num_buffers,
buffer_size);
}
//TODO: pass maximum size and verify we didn't go through
return offset;
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <pthread.h>
#include <semaphore.h>
/* RAW structures */
struct ipc_shm_raw_smpl_buf {
uint64_t timestamp;
uint32_t data_len; /* In samples */
uint16_t samples[0];
};
struct ipc_shm_raw_stream {
pthread_mutex_t lock; /* protects this struct */
pthread_cond_t cf; /* signals fill to reader */
pthread_cond_t ce; /* signals empty nbuf to writer */
uint32_t num_buffers;
uint32_t buffer_size; /* In samples */
uint32_t read_next;
uint32_t write_next;
uint32_t buffer_offset[0];
//struct ipc_shm_smpl_buf buffers[0];
};
struct ipc_shm_raw_channel {
uint32_t dl_buf_offset;
uint32_t ul_buf_offset;
};
struct ipc_shm_raw_region {
uint32_t num_chans;
uint32_t chan_offset[0];
};
/* non-raw, Pointer converted structures */
struct ipc_shm_stream {
uint32_t num_buffers;
uint32_t buffer_size;
volatile struct ipc_shm_raw_stream *raw;
volatile struct ipc_shm_raw_smpl_buf *buffers[0];
};
struct ipc_shm_channel {
struct ipc_shm_stream *dl_stream;
struct ipc_shm_stream *ul_stream;
};
/* Pointer converted structures */
struct ipc_shm_region {
uint32_t num_chans;
struct ipc_shm_channel *channels[0];
};
unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers,
uint32_t buffer_size);
struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw);
/****************************************/
/* UNIX SOCKET API */
/****************************************/
//////////////////
// Master socket
//////////////////
#define IPC_SOCK_API_VERSION 1
/* msg_type */
#define IPC_IF_MSG_GREETING_REQ 0x00
#define IPC_IF_MSG_GREETING_CNF 0x01
#define IPC_IF_MSG_INFO_REQ 0x02
#define IPC_IF_MSG_INFO_CNF 0x03
#define IPC_IF_MSG_OPEN_REQ 0x04
#define IPC_IF_MSG_OPEN_CNF 0x05
#define MAX_NUM_CHANS 30
#define RF_PATH_NAME_SIZE 25
#define MAX_NUM_RF_PATHS 10
#define SHM_NAME_MAX NAME_MAX /* 255 */
#define FEATURE_MASK_CLOCKREF_INTERNAL (0x1 << 0)
#define FEATURE_MASK_CLOCKREF_EXTERNAL (0x1 << 1)
struct ipc_sk_if_info_chan {
char tx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE];
char rx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE];
double min_rx_gain;
double max_rx_gain;
double min_tx_gain;
double max_tx_gain;
double nominal_tx_power; /* dBm */
} __attribute__((packed));
struct ipc_sk_if_open_req_chan {
char tx_path[RF_PATH_NAME_SIZE];
char rx_path[RF_PATH_NAME_SIZE];
} __attribute__((packed));
struct ipc_sk_if_open_cnf_chan {
char chan_ipc_sk_path[108];
} __attribute__((packed));
struct ipc_sk_if_greeting {
uint8_t req_version;
} __attribute__((packed));
struct ipc_sk_if_info_req {
uint8_t spare;
} __attribute__((packed));
struct ipc_sk_if_info_cnf {
uint32_t feature_mask;
double iq_scaling_val_rx; /* for scaling, sample format is 16 bit, but adc/dac might be less */
double iq_scaling_val_tx;
uint32_t max_num_chans;
char dev_desc[200];
struct ipc_sk_if_info_chan chan_info[MAX_NUM_CHANS];
} __attribute__((packed));
struct ipc_sk_if_open_req {
uint32_t num_chans;
uint32_t clockref; /* One of FEATUER_MASK_CLOCKREF_* */
uint32_t rx_sample_freq_num;
uint32_t rx_sample_freq_den;
uint32_t tx_sample_freq_num;
uint32_t tx_sample_freq_den;
uint32_t bandwidth;
struct ipc_sk_if_open_req_chan chan_info[MAX_NUM_CHANS];
} __attribute__((packed));
struct ipc_sk_if_open_cnf {
uint8_t return_code;
uint32_t path_delay;
char shm_name[SHM_NAME_MAX];
struct ipc_sk_if_open_cnf_chan chan_info[MAX_NUM_CHANS];
} __attribute__((packed));
struct ipc_sk_if {
uint8_t msg_type; /* message type */
uint8_t spare[2];
union {
struct ipc_sk_if_greeting greeting_req;
struct ipc_sk_if_greeting greeting_cnf;
struct ipc_sk_if_info_req info_req;
struct ipc_sk_if_info_cnf info_cnf;
struct ipc_sk_if_open_req open_req;
struct ipc_sk_if_open_cnf open_cnf;
} u;
} __attribute__((packed));
//////////////////
// Channel socket
//////////////////
#define IPC_IF_CHAN_MSG_OFFSET 50
#define IPC_IF_MSG_START_REQ IPC_IF_CHAN_MSG_OFFSET + 0
#define IPC_IF_MSG_START_CNF IPC_IF_CHAN_MSG_OFFSET + 1
#define IPC_IF_MSG_STOP_REQ IPC_IF_CHAN_MSG_OFFSET + 2
#define IPC_IF_MSG_STOP_CNF IPC_IF_CHAN_MSG_OFFSET + 3
#define IPC_IF_MSG_SETGAIN_REQ IPC_IF_CHAN_MSG_OFFSET + 4
#define IPC_IF_MSG_SETGAIN_CNF IPC_IF_CHAN_MSG_OFFSET + 5
#define IPC_IF_MSG_SETFREQ_REQ IPC_IF_CHAN_MSG_OFFSET + 6
#define IPC_IF_MSG_SETFREQ_CNF IPC_IF_CHAN_MSG_OFFSET + 7
#define IPC_IF_NOTIFY_UNDERFLOW IPC_IF_CHAN_MSG_OFFSET + 8
#define IPC_IF_NOTIFY_OVERFLOW IPC_IF_CHAN_MSG_OFFSET + 9
#define IPC_IF_MSG_SETTXATTN_REQ IPC_IF_CHAN_MSG_OFFSET + 10
#define IPC_IF_MSG_SETTXATTN_CNF IPC_IF_CHAN_MSG_OFFSET + 11
struct ipc_sk_chan_if_op_void {
// at least one dummy byte, to allow c/c++ compatibility
uint8_t dummy;
} __attribute__((packed));
struct ipc_sk_chan_if_op_rc {
uint8_t return_code;
} __attribute__((packed));
struct ipc_sk_chan_if_gain {
double gain;
uint8_t is_tx;
} __attribute__((packed));
struct ipc_sk_chan_if_freq_req {
double freq;
uint8_t is_tx;
} __attribute__((packed));
struct ipc_sk_chan_if_freq_cnf {
uint8_t return_code;
} __attribute__((packed));
struct ipc_sk_chan_if_notfiy {
uint8_t dummy;
} __attribute__((packed));
struct ipc_sk_chan_if_tx_attenuation {
double attenuation;
} __attribute__((packed));
struct ipc_sk_chan_if {
uint8_t msg_type; /* message type */
uint8_t spare[2];
union {
struct ipc_sk_chan_if_op_void start_req;
struct ipc_sk_chan_if_op_rc start_cnf;
struct ipc_sk_chan_if_op_void stop_req;
struct ipc_sk_chan_if_op_rc stop_cnf;
struct ipc_sk_chan_if_gain set_gain_req;
struct ipc_sk_chan_if_gain set_gain_cnf;
struct ipc_sk_chan_if_freq_req set_freq_req;
struct ipc_sk_chan_if_freq_cnf set_freq_cnf;
struct ipc_sk_chan_if_notfiy notify;
struct ipc_sk_chan_if_tx_attenuation txatten_req;
struct ipc_sk_chan_if_tx_attenuation txatten_cnf;
} u;
} __attribute__((packed));

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
extern "C" {
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include "shm.h"
#include "ipc_shm.h"
#include "ipc-driver-test.h"
}
#include "../uhd/UHDDevice.h"
#include "uhdwrap.h"
#include "Logger.h"
#include "Threads.h"
#include "Utils.h"
int uhd_wrap::open(const std::string &args, int ref, bool swap_channels)
{
int rv = uhd_device::open(args, ref, swap_channels);
samps_per_buff_rx = rx_stream->get_max_num_samps();
samps_per_buff_tx = tx_stream->get_max_num_samps();
channel_count = usrp_dev->get_rx_num_channels();
wrap_rx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * samps_per_buff_rx));
for (size_t i = 0; i < wrap_rx_buffs.size(); i++)
wrap_rx_buf_ptrs.push_back(&wrap_rx_buffs[i].front());
wrap_tx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * 5000));
for (size_t i = 0; i < wrap_tx_buffs.size(); i++)
wrap_tx_buf_ptrs.push_back(&wrap_tx_buffs[i].front());
return rv;
}
uhd_wrap::~uhd_wrap()
{
// drvtest::gshutdown = 1;
//t->join();
}
size_t uhd_wrap::bufsizerx()
{
return samps_per_buff_rx;
}
size_t uhd_wrap::bufsizetx()
{
return samps_per_buff_tx;
}
int uhd_wrap::chancount()
{
return channel_count;
}
int uhd_wrap::wrap_read(TIMESTAMP *timestamp)
{
uhd::rx_metadata_t md;
size_t num_rx_samps = rx_stream->recv(wrap_rx_buf_ptrs, samps_per_buff_rx, md, 0.1, true);
*timestamp = md.time_spec.to_ticks(rx_rate);
return num_rx_samps; //uhd_device::readSamples(bufs, len, overrun, timestamp, underrun);
}
extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req)
{
unsigned int rx_sps, tx_sps;
/* FIXME: dev arg string* */
/* FIXME: rx frontend bw? */
/* FIXME: tx frontend bw? */
ReferenceType cref;
switch (open_req->clockref) {
case FEATURE_MASK_CLOCKREF_EXTERNAL:
cref = ReferenceType::REF_EXTERNAL;
break;
case FEATURE_MASK_CLOCKREF_INTERNAL:
default:
cref = ReferenceType::REF_INTERNAL;
break;
}
std::vector<std::string> tx_paths;
std::vector<std::string> rx_paths;
for (unsigned int i = 0; i < open_req->num_chans; i++) {
tx_paths.push_back(open_req->chan_info[i].tx_path);
rx_paths.push_back(open_req->chan_info[i].rx_path);
}
/* FIXME: this is actually the sps value, not the sample rate!
* sample rate is looked up according to the sps rate by uhd backend */
rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den;
tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den;
uhd_wrap *uhd_wrap_dev =
new uhd_wrap(tx_sps, rx_sps, RadioDevice::NORMAL, open_req->num_chans, 0.0, tx_paths, rx_paths);
uhd_wrap_dev->open("", cref, false);
return uhd_wrap_dev;
}
extern "C" int32_t uhdwrap_get_bufsizerx(void *dev)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->bufsizerx();
}
extern "C" int32_t uhdwrap_get_timingoffset(void *dev)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->getTimingOffset();
}
extern "C" int32_t uhdwrap_read(void *dev, uint32_t num_chans)
{
TIMESTAMP t;
uhd_wrap *d = (uhd_wrap *)dev;
if (num_chans != d->wrap_rx_buf_ptrs.size()) {
perror("omg chans?!");
}
int32_t read = d->wrap_read(&t);
/* multi channel rx on b210 will return 0 due to alignment adventures, do not put 0 samples into a ipc buffer... */
if (read <= 0)
return read;
for (uint32_t i = 0; i < num_chans; i++) {
ipc_shm_enqueue(ios_rx_from_device[i], t, read, (uint16_t *)&d->wrap_rx_buffs[i].front());
}
return read;
}
extern "C" int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun)
{
uhd_wrap *d = (uhd_wrap *)dev;
uint64_t timestamp;
int32_t len = -1;
for (uint32_t i = 0; i < num_chans; i++) {
len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, &timestamp, 1);
if (len < 0)
return 0;
}
return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp);
}
extern "C" double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx)
{
uhd_wrap *d = (uhd_wrap *)dev;
if (for_tx)
return d->setTxFreq(f, chan);
else
return d->setRxFreq(f, chan);
}
extern "C" double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx)
{
uhd_wrap *d = (uhd_wrap *)dev;
// if (for_tx)
// return d->setTxGain(f, chan);
// else
return d->setRxGain(f, chan);
}
extern "C" double uhdwrap_set_txatt(void *dev, double a, size_t chan)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->setPowerAttenuation(a, chan);
}
extern "C" int32_t uhdwrap_start(void *dev, int chan)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->start();
}
extern "C" int32_t uhdwrap_stop(void *dev, int chan)
{
uhd_wrap *d = (uhd_wrap *)dev;
return d->stop();
}
extern "C" void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim)
{
struct ipc_sk_if_info_chan *chan_info;
uhd::device_addr_t args("");
uhd::device_addrs_t devs_found = uhd::device::find(args);
if (devs_found.size() < 1) {
std::cout << "\n No device found!";
exit(0);
}
uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(devs_found[0]);
auto rxchans = usrp->get_rx_num_channels();
auto txchans = usrp->get_tx_num_channels();
auto rx_range = usrp->get_rx_gain_range();
auto tx_range = usrp->get_tx_gain_range();
//auto nboards = usrp->get_num_mboards();
auto refs = usrp->get_clock_sources(0);
auto devname = usrp->get_mboard_name(0);
ipc_prim->u.info_cnf.feature_mask = 0;
if (std::find(refs.begin(), refs.end(), "internal") != refs.end())
ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_INTERNAL;
if (std::find(refs.begin(), refs.end(), "external") != refs.end())
ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_EXTERNAL;
// at least one duplex channel
auto num_chans = rxchans == txchans ? txchans : 1;
ipc_prim->u.info_cnf.iq_scaling_val_rx = 0.3;
ipc_prim->u.info_cnf.iq_scaling_val_tx = 1;
ipc_prim->u.info_cnf.max_num_chans = num_chans;
OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, devname.c_str());
chan_info = ipc_prim->u.info_cnf.chan_info;
for (unsigned int i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) {
auto rxant = usrp->get_rx_antennas(i);
auto txant = usrp->get_tx_antennas(i);
for (unsigned int j = 0; j < txant.size(); j++) {
OSMO_STRLCPY_ARRAY(chan_info->tx_path[j], txant[j].c_str());
}
for (unsigned int j = 0; j < rxant.size(); j++) {
OSMO_STRLCPY_ARRAY(chan_info->rx_path[j], rxant[j].c_str());
}
chan_info->min_rx_gain = rx_range.start();
chan_info->max_rx_gain = rx_range.stop();
chan_info->min_tx_gain = tx_range.start();
chan_info->max_tx_gain = tx_range.stop();
chan_info->nominal_tx_power = 7.5; // FIXME: would require uhd dev + freq info
chan_info++;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef IPC_UHDWRAP_H
#define IPC_UHDWRAP_H
#ifdef __cplusplus
#include "../uhd/UHDDevice.h"
class uhd_wrap : public uhd_device {
public:
// std::thread *t;
size_t samps_per_buff_rx;
size_t samps_per_buff_tx;
int channel_count;
std::vector<std::vector<short> > wrap_rx_buffs;
std::vector<std::vector<short> > wrap_tx_buffs;
std::vector<short *> wrap_rx_buf_ptrs;
std::vector<short *> wrap_tx_buf_ptrs;
template <typename... Args> uhd_wrap(Args... args) : uhd_device(args...)
{
// t = new std::thread(magicthread);
// give the thread some time to start and set up
// std::this_thread::sleep_for(std::chrono::seconds(1));
}
virtual ~uhd_wrap();
// void ipc_sock_close() override {};
int wrap_read(TIMESTAMP *timestamp);
virtual int open(const std::string &args, int ref, bool swap_channels) override;
// bool start() override;
// bool stop() override;
// virtual TIMESTAMP initialWriteTimestamp() override;
// virtual TIMESTAMP initialReadTimestamp() override;
int getTimingOffset()
{
return ts_offset;
}
size_t bufsizerx();
size_t bufsizetx();
int chancount();
};
#else
void *uhdwrap_open(struct ipc_sk_if_open_req *open_req);
int32_t uhdwrap_get_bufsizerx(void *dev);
int32_t uhdwrap_get_timingoffset(void *dev);
int32_t uhdwrap_read(void *dev, uint32_t num_chans);
int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun);
double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx);
double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx);
int32_t uhdwrap_start(void *dev, int chan);
int32_t uhdwrap_stop(void *dev, int chan);
void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim);
double uhdwrap_set_txatt(void *dev, double a, size_t chan);
#endif
#endif // IPC_B210_H

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

@@ -20,6 +20,9 @@
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <map>
#include "Logger.h"
#include "Threads.h"
#include "LMSDevice.h"
@@ -28,6 +31,7 @@
#include <lime/LimeSuite.h>
extern "C" {
#include "trx_vty.h"
#include "osmo_signal.h"
#include <osmocom/core/utils.h>
}
@@ -36,27 +40,117 @@ extern "C" {
#include "config.h"
#endif
using namespace std;
#define MAX_ANTENNA_LIST_SIZE 10
#define LMS_SAMPLE_RATE GSMRATE*32
#define GSM_CARRIER_BW 270000.0 /* 270kHz */
#define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */
#define LMS_CALIBRATE_BW_HZ OSMO_MAX(GSM_CARRIER_BW, LMS_MIN_BW_SUPPORTED)
#define SAMPLE_BUF_SZ (1 << 20) /* Size of Rx timestamp based Ring buffer, in bytes */
LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
/* Device Name Prefixes as presented by LimeSuite API LMS_GetDeviceInfo(): */
#define LMS_DEV_SDR_USB_PREFIX_NAME "LimeSDR-USB"
#define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini"
#define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro"
/* Device parameter descriptor */
struct dev_desc {
/* Does LimeSuite allow switching the clock source for this device?
* LimeSDR-Mini does not have switches but needs soldering to select
* external/internal clock. Any call to LMS_SetClockFreq() will fail.
*/
bool clock_src_switchable;
/* Does LimeSuite allow using REF_INTERNAL for this device?
* LimeNET-Micro does not like selecting internal clock
*/
bool clock_src_int_usable;
/* Sample rate coef (without having TX/RX samples per symbol into account) */
double rate;
/* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */
double rate_multiarfcn;
/* Coefficient multiplied by TX sample rate in order to shift Tx time */
double ts_offset_coef;
/* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */
double ts_offset_coef_multiarfcn;
/* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */
std::string name_prefix;
};
static const std::map<enum lms_dev_type, struct dev_desc> dev_param_map {
{ LMS_DEV_SDR_USB, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } },
{ LMS_DEV_SDR_MINI, { false, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } },
{ LMS_DEV_NET_MICRO, { true, false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } },
{ LMS_DEV_UNKNOWN, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } },
};
typedef std::tuple<lms_dev_type, enum gsm_band> dev_band_key;
typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it;
static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map {
{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2, -6.0 } },
{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8, -6.0 } },
{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5, -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */
{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7, -17.0 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */
{ std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
{ std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
{ std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6, -17.0 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */
{ std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2, -17.0 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */
{ std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8, -6.0 } },
{ std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8, -6.0 } },
{ std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5, -17.0 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */
{ std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3, -17.0 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */
};
/* 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_lms_tx_gain - tx_gain_db);
}
static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm)
{
return desc.nom_lms_tx_gain - (desc.nom_out_tx_power - tx_power_dbm);
}
static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev)
{
std::map<enum lms_dev_type, struct dev_desc>::const_iterator it = dev_param_map.begin();
const lms_dev_info_t* device_info = LMS_GetDeviceInfo(m_lms_dev);
while (it != dev_param_map.end())
{
enum lms_dev_type dev_type = it->first;
struct dev_desc desc = it->second;
if (strncmp(device_info->deviceName, desc.name_prefix.c_str(), desc.name_prefix.length()) == 0) {
LOGC(DDEV, INFO) << "Device identified as " << desc.name_prefix;
return dev_type;
}
it++;
}
return LMS_DEV_UNKNOWN;
}
LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths):
RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths),
m_lms_dev(NULL)
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...";
m_lms_stream_rx.resize(chans);
m_lms_stream_tx.resize(chans);
rx_gains.resize(chans);
tx_gains.resize(chans);
rx_buffers.resize(chans);
/* 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));
}
LMSDevice::~LMSDevice()
@@ -91,7 +185,7 @@ static void lms_log_callback(int lvl, const char *msg)
if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map))
lvl = ARRAY_SIZE(lvl_map)-1;
LOGLV(DLMS, lvl_map[lvl]) << msg;
LOGLV(DDEVDRV, lvl_map[lvl]) << msg;
}
static void print_range(const char* name, lms_range_t *range)
@@ -108,7 +202,7 @@ static void print_range(const char* name, lms_range_t *range)
int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args)
{
unsigned int i, j;
vector<string> filters;
std::vector<std::string> filters;
filters = comma_delimited_to_vector(args.c_str());
@@ -129,24 +223,66 @@ int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::str
return -1;
}
void LMSDevice::assign_band_desc(enum gsm_band req_band)
{
dev_band_map_it it;
it = dev_band_nom_power_param_map.find(dev_band_key(m_dev_type, req_band));
if (it == dev_band_nom_power_param_map.end()) {
dev_desc desc = dev_param_map.at(m_dev_type);
LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device "
<< desc.name_prefix << " on band " << gsm_band_name(req_band)
<< ", using LimeSDR-USB ones as fallback";
it = dev_band_nom_power_param_map.find(dev_band_key(LMS_DEV_SDR_USB, req_band));
}
OSMO_ASSERT(it != dev_band_nom_power_param_map.end());
band_desc = it->second;
}
bool LMSDevice::set_band(enum gsm_band req_band)
{
if (band_ass_curr_sess && req_band != band) {
LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
<< " different from previous band " << gsm_band_name(band);
return false;
}
if (req_band != band) {
band = req_band;
assign_band_desc(band);
}
band_ass_curr_sess = true;
return true;
}
void LMSDevice::get_dev_band_desc(dev_band_desc& desc)
{
if (band == 0) {
LOGC(DDEV, ERROR) << "Power parameters requested before Tx Frequency was set! Providing band 900 by default...";
assign_band_desc(GSM_BAND_900);
}
desc = band_desc;
}
int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
{
lms_info_str_t* info_list;
const lms_dev_info_t* device_info;
lms_range_t range_sr;
float_type sr_host, sr_rf;
unsigned int i, n;
int rc, dev_id;
struct dev_desc dev_desc;
LOGC(DDEV, INFO) << "Opening LMS device..";
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];
@@ -173,19 +309,20 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
delete [] info_list;
device_info = LMS_GetDeviceInfo(m_lms_dev);
m_dev_type = parse_dev_type(m_lms_dev);
dev_desc = dev_param_map.at(m_dev_type);
if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL)){
LOGC(DDEV, ERROR) << "Invalid reference type";
goto out_close;
}
/* if reference clock is external setup must happen _before_ calling LMS_Init */
/* FIXME make external reference frequency configurable */
/* if reference clock is external, setup must happen _before_ calling LMS_Init */
if (ref == REF_EXTERNAL) {
LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz";
/* Assume an external 10 MHz reference clock */
if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
/* FIXME: Assume an external 10 MHz reference clock. make
external reference frequency configurable */
if (!do_clock_src_freq(REF_EXTERNAL, 10000000.0))
goto out_close;
}
@@ -195,22 +332,13 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
goto out_close;
}
/* LimeSDR-Mini does not have switches but needs soldering to select external/internal clock */
/* LimeNET-Micro also does not like selecting internal clock*/
/* also set device specific maximum tx levels selected by phasenoise measurements*/
if (strncmp(device_info->deviceName,"LimeSDR-USB",11) == 0){
/* if reference clock is internal setup must happen _after_ calling LMS_Init */
/* according to lms using LMS_CLOCK_EXTREF with a frequency <= 0 is the correct way to set clock to internal reference*/
if (ref == REF_INTERNAL) {
LOGC(DDEV, INFO) << "Setting Internal clock reference";
if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, -1) < 0)
goto out_close;
}
maxTxGainClamp = 73.0;
} else if (strncmp(device_info->deviceName,"LimeSDR-Mini",12) == 0)
maxTxGainClamp = 66.0;
else
maxTxGainClamp = 71.0; /* "LimeNET-Micro", etc FIXME pciE based LMS boards?*/
/* if reference clock is internal, setup must happen _after_ calling LMS_Init */
if (ref == REF_INTERNAL) {
LOGC(DDEV, INFO) << "Setting Internal clock reference";
/* Internal freq param is not used */
if (!do_clock_src_freq(REF_INTERNAL, 0))
goto out_close;
}
/* enable all used channels */
for (i=0; i<chans; i++) {
@@ -225,16 +353,22 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
goto out_close;
print_range("Sample Rate", &range_sr);
LOGC(DDEV, INFO) << "Setting sample rate to " << GSMRATE*tx_sps << " " << tx_sps;
if (LMS_SetSampleRate(m_lms_dev, GSMRATE*tx_sps, 32) < 0)
if (iface == MULTI_ARFCN)
sr_host = dev_desc.rate_multiarfcn * tx_sps;
else
sr_host = dev_desc.rate * tx_sps;
LOGC(DDEV, INFO) << "Setting sample rate to " << sr_host << " " << tx_sps;
if (LMS_SetSampleRate(m_lms_dev, sr_host, 32) < 0)
goto out_close;
if (LMS_GetSampleRate(m_lms_dev, LMS_CH_RX, 0, &sr_host, &sr_rf))
goto out_close;
LOGC(DDEV, INFO) << "Sample Rate: Host=" << sr_host << " RF=" << sr_rf;
/* FIXME: make this device/model dependent, like UHDDevice:dev_param_map! */
ts_offset = static_cast<TIMESTAMP>(8.9e-5 * GSMRATE * tx_sps); /* time * sample_rate */
if (iface == MULTI_ARFCN)
ts_offset = static_cast<TIMESTAMP>(dev_desc.ts_offset_coef_multiarfcn * sr_host);
else
ts_offset = static_cast<TIMESTAMP>(dev_desc.ts_offset_coef * sr_host);
/* configure antennas */
if (!set_antennas()) {
@@ -242,13 +376,7 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
goto out_close;
}
/* 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));
started = false;
return NORMAL;
return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL;
out_close:
LOGC(DDEV, FATAL) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
@@ -262,17 +390,20 @@ bool LMSDevice::start()
LOGC(DDEV, INFO) << "starting LMS...";
unsigned int i;
dev_band_desc desc;
if (started) {
LOGC(DDEV, ERR) << "Device already started";
return false;
}
get_dev_band_desc(desc);
/* configure the channels/streams */
for (i=0; i<chans; i++) {
/* Set gains for calibration/filter setup */
/* TX gain to maximum */
setTxGain(maxTxGain(), i);
LMS_SetGaindB(m_lms_dev, LMS_CH_TX, i, TxPower2TxGain(desc, desc.nom_out_tx_power));
/* RX gain to midpoint */
setRxGain((minRxGain() + maxRxGain()) / 2, i);
@@ -338,10 +469,49 @@ bool LMSDevice::stop()
LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
}
band_ass_curr_sess = false;
started = false;
return true;
}
bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq)
{
struct dev_desc dev_desc = dev_param_map.at(m_dev_type);
size_t lms_clk_id;
switch (ref) {
case REF_EXTERNAL:
lms_clk_id = LMS_CLOCK_EXTREF;
break;
case REF_INTERNAL:
if (!dev_desc.clock_src_int_usable) {
LOGC(DDEV, ERROR) << "Device type " << dev_desc.name_prefix
<< " doesn't support internal reference clock";
return false;
}
/* According to lms using LMS_CLOCK_EXTREF with a
frequency <= 0 is the correct way to set clock to
internal reference */
lms_clk_id = LMS_CLOCK_EXTREF;
freq = -1;
break;
default:
LOGC(DDEV, ERROR) << "Invalid reference type " << get_value_string(clock_ref_names, ref);
return false;
}
if (dev_desc.clock_src_switchable) {
if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0)
return false;
} else {
LOGC(DDEV, INFO) << "Device type " << dev_desc.name_prefix
<< " doesn't support switching clock source through SW";
}
return true;
}
/* do rx/tx calibration - depends on gain, freq and bw */
bool LMSDevice::do_calib(size_t chan)
{
@@ -380,17 +550,6 @@ bool LMSDevice::do_filters(size_t chan)
return true;
}
double LMSDevice::maxTxGain()
{
return maxTxGainClamp;
}
double LMSDevice::minTxGain()
{
return 0.0;
}
double LMSDevice::maxRxGain()
{
return 73.0;
@@ -401,21 +560,6 @@ double LMSDevice::minRxGain()
return 0.0;
}
double LMSDevice::setTxGain(double dB, size_t chan)
{
if (dB > maxTxGain())
dB = maxTxGain();
if (dB < minTxGain())
dB = minTxGain();
LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB";
if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB";
return dB;
}
double LMSDevice::setRxGain(double dB, size_t chan)
{
if (dB > maxRxGain())
@@ -427,8 +571,80 @@ double LMSDevice::setRxGain(double dB, size_t chan)
if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB";
else
rx_gains[chan] = dB;
return rx_gains[chan];
}
return dB;
double LMSDevice::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 LMSDevice::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);
LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB (~" << tx_power << " dBm)";
if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB (~" << tx_power << " dBm)";
else
tx_gains[chan] = dB;
return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
}
double LMSDevice::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 LMSDevice::getNominalTxPower(size_t chan)
{
dev_band_desc desc;
get_dev_band_desc(desc);
return desc.nom_out_tx_power;
}
void LMSDevice::log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os)
{
lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
int num_names;
int i;
num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, chan, name_list);
for (i = 0; i < num_names; i++) {
if (i)
os << ", ";
os << "'" << name_list[i] << "'";
}
}
int LMSDevice::get_ant_idx(const std::string & name, bool dir_tx, size_t chan)
@@ -484,7 +700,10 @@ bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan)
idx = get_ant_idx(ant, LMS_CH_RX, chan);
if (idx < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna";
std::ostringstream os;
LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna: " << ant;
log_ant_list(LMS_CH_RX, chan, os);
LOGCHAN(chan, DDEV, NOTICE) << "Available Rx Antennas: " << os;
return false;
}
@@ -530,7 +749,10 @@ bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan)
idx = get_ant_idx(ant, LMS_CH_TX, chan);
if (idx < 0) {
LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna";
std::ostringstream os;
LOGCHAN(chan, DDEV, ERROR) << "Invalid Tx Antenna: " << ant;
log_ant_list(LMS_CH_TX, chan, os);
LOGCHAN(chan, DDEV, NOTICE) << "Available Tx Antennas: " << os;
return false;
}
@@ -600,7 +822,7 @@ void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun)
m_ctr[chan].rx_overruns += status.overrun;
/* Dropped packets in Rx are counted when gaps in Rx timestamps are
detected (likely because buffer oveflow in hardware). Value count
detected (likely because buffer overflow in hardware). Value count
since the last call to LMS_GetStreamStatus(stream). */
if (status.droppedPackets) {
changed = true;
@@ -620,7 +842,7 @@ void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun)
// NOTE: Assumes sequential reads
int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
TIMESTAMP timestamp, bool * underrun, unsigned *RSSI)
TIMESTAMP timestamp, bool * underrun)
{
int rc, num_smpls, expect_smpls;
ssize_t avail_smpls;
@@ -687,8 +909,9 @@ int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
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);
LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". "
<< rx_buffers[i]->str_status(timestamp)
<< ", (len=" << len << ")";
return 0;
}
}
@@ -738,8 +961,7 @@ void LMSDevice::update_stream_stats_tx(size_t chan, bool *underrun)
}
int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
bool * underrun, unsigned long long timestamp,
bool isControl)
bool * underrun, unsigned long long timestamp)
{
int rc = 0;
unsigned int i;
@@ -748,11 +970,6 @@ int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
tx_metadata.waitForTimestamp = true;
tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */
if (isControl) {
LOGC(DDEV, ERROR) << "Control packets not supported";
return 0;
}
if (bufs.size() != chans) {
LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
return -1;
@@ -782,8 +999,30 @@ bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
bool LMSDevice::setTxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
enum gsm_band req_band;
if (chan >= chans) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
}
LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100 , 0);
if (req_arfcn == 0xffff) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz";
return false;
}
if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Tx Frequency " << wFreq
<< " Hz (ARFCN " << req_arfcn << " )";
return false;
}
if (!set_band(req_band))
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;
@@ -794,8 +1033,25 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan)
bool LMSDevice::setRxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
enum gsm_band req_band;
LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
if (req_arfcn == 0xffff) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
return false;
}
if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
<< " Hz (ARFCN " << req_arfcn << " )";
return false;
}
if (!set_band(req_band))
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

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under multiple licenses; see the COPYING file in
* the main directory for licensing information for this specific distribuion.
* the main directory for licensing information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -32,6 +32,10 @@
#include <iostream>
#include <lime/LimeSuite.h>
extern "C" {
#include <osmocom/gsm/gsm_utils.h>
}
/* Definition of LIMESDR_TX_AMPL limits maximum amplitude of I and Q
* channels separately. Hence LIMESDR_TX_AMPL value must be 1/sqrt(2) =
* 0.7071.... to get an amplitude of 1 of the complex signal:
@@ -41,6 +45,30 @@
* A^2 = 1 */
#define LIMESDR_TX_AMPL 0.707
enum lms_dev_type {
LMS_DEV_SDR_USB, /* LimeSDR-USB */
LMS_DEV_SDR_MINI, /* LimeSDR-Mini */
LMS_DEV_NET_MICRO, /* LimeNet-micro */
LMS_DEV_UNKNOWN,
};
struct dev_band_desc {
/* Maximum LimeSuite Tx Gain which can be set/used without distorting
the output * signal, and the resulting real output power measured
when that gain is used.
*/
double nom_lms_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.
Correct measured values only provided for LimeSDR-USB so far.
rssiOffset = rxGain + rxgain2rssioffset_rel;
*/
double rxgain2rssioffset_rel; /* dB */
};
/** A class to handle a LimeSuite supported device */
class LMSDevice:public RadioDevice {
@@ -58,20 +86,28 @@ private:
TIMESTAMP ts_initial, ts_offset;
double rxGain;
double maxTxGainClamp;
std::vector<double> tx_gains, rx_gains;
bool band_ass_curr_sess; /* true if "band" was set after last POWEROFF */
enum gsm_band band;
struct dev_band_desc band_desc;
enum lms_dev_type m_dev_type;
bool do_calib(size_t chan);
bool do_filters(size_t chan);
void log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os);
int get_ant_idx(const std::string & name, bool dir_tx, size_t chan);
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 do_clock_src_freq(enum ReferenceType ref, double freq);
void get_dev_band_desc(dev_band_desc& desc);
bool set_band(enum gsm_band req_band);
void assign_band_desc(enum gsm_band req_band);
public:
/** Object constructor */
LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths);
~LMSDevice();
@@ -85,10 +121,6 @@ public:
/** Stop the LMS */
bool stop();
/** Set priority not supported */
void setPriority(float prio = 0.5) {
}
enum TxWindowType getWindowType() {
return TX_WINDOW_LMS1;
}
@@ -100,24 +132,21 @@ public:
@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 LMS does not have data to transmit, e.g. data not being sent fast enough
@param RSSI The received signal strength of the read result
@return The number of samples actually read
*/
int readSamples(std::vector < short *>&buf, int len, bool * overrun,
TIMESTAMP timestamp = 0xffffffff, bool * underrun =
NULL, unsigned *RSSI = NULL);
NULL);
/**
Write samples to the LMS.
@param buf Contains the data to be written.
@param len number of samples to write.
@param underrun Set if LMS 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.
@param isControl Set if data is a control packet, e.g. a ping command
@return The number of samples actually written
*/
int writeSamples(std::vector < short *>&bufs, int len, bool * underrun,
TIMESTAMP timestamp = 0xffffffff, bool isControl =
false);
TIMESTAMP timestamp = 0xffffffff);
/** Update the alignment between the read and write timestamps */
bool updateAlignment(TIMESTAMP timestamp);
@@ -153,7 +182,7 @@ public:
/** get the current receive gain */
double getRxGain(size_t chan = 0) {
return rxGain;
return rx_gains[chan];
}
/** return maximum Rx Gain **/
@@ -162,14 +191,12 @@ public:
/** return minimum Rx Gain **/
double minRxGain(void);
/** sets the transmit chan gain, returns the gain setting **/
double setTxGain(double dB, size_t chan = 0);
double rssiOffset(size_t chan);
/** return maximum Tx Gain **/
double maxTxGain(void);
double setPowerAttenuation(int atten, size_t chan);
double getPowerAttenuation(size_t chan = 0);
/** return minimum Rx Gain **/
double minTxGain(void);
int getNominalTxPower(size_t chan = 0);
/** sets the RX path to use, returns true if successful and false otherwise */
bool setRxAntenna(const std::string & ant, size_t chan = 0);

View File

@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LMS_CFLAGS)
noinst_HEADERS = LMSDevice.h

View File

@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
noinst_HEADERS = UHDDevice.h

View File

@@ -33,11 +33,18 @@
#include "config.h"
#endif
#ifndef USE_UHD_3_11
extern "C" {
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/vty/cpu_sched_vty.h>
}
#ifdef USE_UHD_3_11
#include <uhd/utils/log_add.hpp>
#include <uhd/utils/thread.hpp>
#else
#include <uhd/utils/msg.hpp>
#include <uhd/utils/thread_priority.hpp>
#else
#include <uhd/utils/thread.hpp>
#endif
#define USRP_TX_AMPL 0.3
@@ -122,10 +129,23 @@ static const std::map<dev_key, dev_desc> dev_param_map {
{ std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } },
};
typedef std::tuple<uhd_dev_type, enum gsm_band> dev_band_key;
typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it;
static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map {
{ std::make_tuple(B200, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
{ std::make_tuple(B200, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
{ std::make_tuple(B200, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
{ std::make_tuple(B200, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
{ std::make_tuple(B210, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
{ std::make_tuple(B210, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
{ std::make_tuple(B210, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
{ std::make_tuple(B210, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
};
void *async_event_loop(uhd_device *dev)
{
set_selfthread_name("UHDAsyncEvent");
dev->setPriority(0.43);
osmo_cpu_sched_vty_apply_localthread();
while (1) {
dev->recv_async_msg();
@@ -135,23 +155,52 @@ void *async_event_loop(uhd_device *dev)
return NULL;
}
#ifndef USE_UHD_3_11
#ifdef USE_UHD_3_11
static void uhd_log_handler(const uhd::log::logging_info &info)
{
int level;
switch (info.verbosity)
{
case uhd::log::trace:
case uhd::log::debug:
level = LOGL_DEBUG;
break;
case uhd::log::info:
level = LOGL_INFO;
break;
case uhd::log::warning:
level = LOGL_NOTICE;
break;
case uhd::log::error:
level = LOGL_ERROR;
break;
case uhd::log::fatal:
level = LOGL_FATAL;
break;
default:
level = LOGL_NOTICE;
}
LOGSRC(DDEVDRV, level, info.file.c_str(), info.line) << "[" << info.component << "] " << info.message;
}
#else
/*
Catch and drop underrun 'U' and overrun 'O' messages from stdout
since we already report using the logging facility. Direct
everything else appropriately.
*/
void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
{
switch (type) {
case uhd::msg::status:
LOGC(DDEV, INFO) << msg;
LOGC(DDEVDRV, INFO) << msg;
break;
case uhd::msg::warning:
LOGC(DDEV, WARNING) << msg;
LOGC(DDEVDRV, NOTICE) << msg;
break;
case uhd::msg::error:
LOGC(DDEV, ERROR) << msg;
LOGC(DDEVDRV, ERROR) << msg;
break;
case uhd::msg::fastpath:
break;
@@ -159,14 +208,25 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
}
#endif
/* 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);
}
uhd_device::uhd_device(size_t tx_sps, size_t rx_sps,
InterfaceType iface, size_t chans, double lo_offset,
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, chans, lo_offset, tx_paths, rx_paths),
tx_gain_min(0.0), tx_gain_max(0.0),
rx_gain_min(0.0), rx_gain_max(0.0),
tx_spp(0), rx_spp(0),
: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
rx_gain_min(0.0), rx_gain_max(0.0), band_ass_curr_sess(false),
band((enum gsm_band)0), tx_spp(0), rx_spp(0),
started(false), aligned(false), drop_cnt(0),
prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
{
@@ -180,8 +240,50 @@ uhd_device::~uhd_device()
delete rx_buffers[i];
}
void uhd_device::assign_band_desc(enum gsm_band req_band)
{
dev_band_map_it it;
it = dev_band_nom_power_param_map.find(dev_band_key(dev_type, req_band));
if (it == dev_band_nom_power_param_map.end()) {
dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
LOGC(DDEV, ERROR) << "No Power parameters exist for device "
<< desc.str << " on band " << gsm_band_name(req_band)
<< ", using B210 ones as fallback";
it = dev_band_nom_power_param_map.find(dev_band_key(B210, req_band));
}
OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
band_desc = it->second;
}
bool uhd_device::set_band(enum gsm_band req_band)
{
if (band_ass_curr_sess && req_band != band) {
LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
<< " different from previous band " << gsm_band_name(band);
return false;
}
if (req_band != band) {
band = req_band;
assign_band_desc(band);
}
band_ass_curr_sess = true;
return true;
}
void uhd_device::get_dev_band_desc(dev_band_desc& desc)
{
if (band == 0) {
LOGC(DDEV, ERROR) << "Power parameters requested before Tx Frequency was set! Providing band 900 by default...";
assign_band_desc(GSM_BAND_900);
}
desc = band_desc;
}
void uhd_device::init_gains()
{
double tx_gain_min, tx_gain_max;
uhd::gain_range_t range;
if (dev_type == UMTRX) {
@@ -246,16 +348,59 @@ void uhd_device::set_rates()
LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
}
double uhd_device::setTxGain(double db, size_t chan)
double uhd_device::setRxGain(double db, size_t chan)
{
if (iface == MULTI_ARFCN)
chan = 0;
if (chan >= rx_gains.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return 0.0f;
}
usrp_dev->set_rx_gain(db, chan);
rx_gains[chan] = usrp_dev->get_rx_gain(chan);
LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)";
return rx_gains[chan];
}
double uhd_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 uhd_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 uhd_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);
if (dev_type == UMTRX) {
std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0);
if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") {
@@ -275,40 +420,29 @@ double uhd_device::setTxGain(double db, size_t chan)
tx_gains[chan] = usrp_dev->get_tx_gain(chan);
LOGC(DDEV, INFO) << "Set TX gain to " << tx_gains[chan] << "dB (asked for " << db << "dB)";
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 tx_gains[chan];
return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
}
double uhd_device::setRxGain(double db, size_t chan)
{
if (iface == MULTI_ARFCN)
chan = 0;
if (chan >= rx_gains.size()) {
double uhd_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;
}
usrp_dev->set_rx_gain(db, chan);
rx_gains[chan] = usrp_dev->get_rx_gain(chan);
LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)";
return rx_gains[chan];
get_dev_band_desc(desc);
return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
}
double uhd_device::getRxGain(size_t chan)
int uhd_device::getNominalTxPower(size_t chan)
{
if (iface == MULTI_ARFCN)
chan = 0;
dev_band_desc desc;
get_dev_band_desc(desc);
if (chan >= rx_gains.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return 0.0f;
}
return rx_gains[chan];
return desc.nom_out_tx_power;
}
/*
@@ -382,7 +516,6 @@ void uhd_device::set_channels(bool swap)
if (dev_type != B200 && dev_type != B210)
throw std::invalid_argument("Device does not support MCBTS");
dev_type = B2XX_MCBTS;
chans = 1;
}
if (chans > dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps)).channels)
@@ -418,6 +551,17 @@ 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
uhd::log::add_logger("OsmoTRX", &uhd_log_handler);
uhd::log::set_log_level(uhd::log::debug);
uhd::log::set_console_level(uhd::log::off);
uhd::log::set_logger_level("OsmoTRX", uhd::log::debug);
#else
uhd::msg::register_handler(&uhd_msg_handler);
#endif
// Find UHD devices
uhd::device_addr_t addr(args);
@@ -480,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) {
@@ -527,7 +684,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels)
init_gains();
// Print configuration
LOGC(DDEV, INFO) << "\n" << usrp_dev->get_pp_string();
LOGC(DDEV, INFO) << "Device configuration: " << usrp_dev->get_pp_string();
if (iface == MULTI_ARFCN)
return MULTI_ARFCN;
@@ -605,10 +762,6 @@ bool uhd_device::start()
return false;
}
#ifndef USE_UHD_3_11
// Register msg handler
uhd::msg::register_handler(&uhd_msg_handler);
#endif
// Start asynchronous event (underrun check) loop
async_event_thrd = new Thread();
async_event_thrd->start((void * (*)(void*))async_event_loop, (void*)this);
@@ -639,16 +792,16 @@ bool uhd_device::stop()
async_event_thrd->join();
delete async_event_thrd;
/* 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;
}
void uhd_device::setPriority(float prio)
{
uhd::set_thread_priority_safe(prio);
return;
}
int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
{
if (!num_smpls) {
@@ -692,7 +845,7 @@ int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
}
int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
TIMESTAMP timestamp, bool *underrun)
{
ssize_t rc;
uhd::time_spec_t ts;
@@ -749,7 +902,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
for (size_t i = 0; i < rx_buffers.size(); i++) {
rc = rx_buffers[i]->write((short *) &pkt_bufs[i].front(),
num_smpls,
metadata.time_spec.to_ticks(rx_rate));
ts.to_ticks(rx_rate));
// Continue on local overrun, exit on other errors
if ((rc < 0)) {
@@ -775,7 +928,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
}
int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
unsigned long long timestamp,bool isControl)
unsigned long long timestamp)
{
uhd::tx_metadata_t metadata;
metadata.has_time_spec = true;
@@ -785,12 +938,6 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun
*underrun = false;
// No control packets
if (isControl) {
LOGC(DDEV, ERROR) << "Control packets not supported";
return 0;
}
if (bufs.size() != chans) {
LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
return -1;
@@ -890,15 +1037,18 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
std::vector<double> freqs;
uhd::tune_result_t tres;
uhd::tune_request_t treq = select_freq(freq, chan, tx);
std::string str_dir;
if (tx) {
tres = usrp_dev->set_tx_freq(treq, chan);
tx_freqs[chan] = usrp_dev->get_tx_freq(chan);
str_dir = "Tx";
} else {
tres = usrp_dev->set_rx_freq(treq, chan);
rx_freqs[chan] = usrp_dev->get_rx_freq(chan);
str_dir = "Rx";
}
LOGC(DDEV, INFO) << "\n" << tres.to_pp_string() << std::endl;
LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl;
if ((chans == 1) || ((chans == 2) && dev_type == UMTRX))
return true;
@@ -918,7 +1068,7 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
rx_freqs[!chan] = usrp_dev->get_rx_freq(!chan);
}
LOGC(DDEV, INFO) << "\n" << tres.to_pp_string() << std::endl;
LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl;
}
return true;
@@ -926,23 +1076,60 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx)
bool uhd_device::setTxFreq(double wFreq, size_t chan)
{
uint16_t req_arfcn;
enum gsm_band req_band;
if (chan >= tx_freqs.size()) {
LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
return false;
}
ScopedLock lock(tune_lock);
return set_freq(wFreq, chan, true);
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 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);
}
@@ -974,7 +1161,14 @@ bool uhd_device::setRxAntenna(const std::string &ant, size_t chan)
return false;
}
avail = usrp_dev->get_rx_antennas(chan);
/* UHD may throw a LookupError/IndexError here (see OS#4636) */
try {
avail = usrp_dev->get_rx_antennas(chan);
} catch (const uhd::index_error &e) {
LOGC(DDEV, ALERT) << "UHD Error: " << e.what();
return false;
}
if (std::find(avail.begin(), avail.end(), ant) == avail.end()) {
LOGC(DDEV, ALERT) << "Requested non-existent Rx antenna " << ant << " on channel " << chan;
LOGC(DDEV, INFO) << "Available Rx antennas: ";
@@ -1010,7 +1204,14 @@ bool uhd_device::setTxAntenna(const std::string &ant, size_t chan)
return false;
}
avail = usrp_dev->get_tx_antennas(chan);
/* UHD may throw a LookupError/IndexError here (see OS#4636) */
try {
avail = usrp_dev->get_tx_antennas(chan);
} catch (const uhd::index_error &e) {
LOGC(DDEV, ALERT) << "UHD Error: " << e.what();
return false;
}
if (std::find(avail.begin(), avail.end(), ant) == avail.end()) {
LOGC(DDEV, ALERT) << "Requested non-existent Tx antenna " << ant << " on channel " << chan;
LOGC(DDEV, INFO) << "Available Tx antennas: ";
@@ -1176,6 +1377,7 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata)
return ost.str();
}
#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,
@@ -1183,3 +1385,4 @@ RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
{
return new uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
}
#endif

View File

@@ -37,6 +37,10 @@
#include <uhd/property_tree.hpp>
#include <uhd/usrp/multi_usrp.hpp>
extern "C" {
#include <osmocom/gsm/gsm_utils.h>
}
enum uhd_dev_type {
USRP1,
@@ -52,6 +56,21 @@ enum uhd_dev_type {
LIMESDR,
};
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 */
};
/*
uhd_device - UHD implementation of the Device interface. Timestamped samples
are sent to and received from the device. An intermediate buffer
@@ -62,7 +81,7 @@ enum uhd_dev_type {
class uhd_device : public RadioDevice {
public:
uhd_device(size_t tx_sps, size_t rx_sps, InterfaceType type,
size_t chans, double offset,
size_t chan_num, double offset,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths);
~uhd_device();
@@ -71,14 +90,13 @@ public:
bool start();
bool stop();
bool restart();
void setPriority(float prio);
enum TxWindowType getWindowType() { return tx_window; }
int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI);
TIMESTAMP timestamp, bool *underrun);
int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp, bool isControl);
TIMESTAMP timestamp);
bool updateAlignment(TIMESTAMP timestamp);
@@ -95,10 +113,12 @@ public:
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 setTxGain(double db, size_t chan);
double maxTxGain(void) { return tx_gain_max; }
double minTxGain(void) { return tx_gain_min; }
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);
@@ -127,7 +147,7 @@ public:
ERROR_UNHANDLED = -4,
};
private:
protected:
uhd::usrp::multi_usrp::sptr usrp_dev;
uhd::tx_streamer::sptr tx_stream;
uhd::rx_streamer::sptr rx_stream;
@@ -136,11 +156,13 @@ private:
double tx_rate, rx_rate;
double tx_gain_min, tx_gain_max;
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;
@@ -169,6 +191,9 @@ private:
uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
bool set_freq(double freq, size_t chan, bool tx);
void get_dev_band_desc(dev_band_desc& desc);
bool set_band(enum gsm_band req_band);
void assign_band_desc(enum gsm_band req_band);
Thread *async_event_thrd;
Mutex tune_lock;

View File

@@ -1,7 +1,7 @@
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS)
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(USRP_CFLAGS)
rev2dir = $(datadir)/usrp/rev2
rev4dir = $(datadir)/usrp/rev4

View File

@@ -61,16 +61,17 @@ const dboardConfigType dboardConfig = TXA_RXB;
const double USRPDevice::masterClockRate = 52.0e6;
USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface,
size_t chans, double lo_offset,
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, chans, lo_offset, tx_paths, rx_paths)
RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths)
{
LOGC(DDEV, INFO) << "creating USRP device...";
decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) tx_sps));
actualSampleRate = masterClockRate/decimRate;
rxGain = 0;
txGain = 0;
/*
* Undetermined delay b/w ping response timestamp and true
@@ -204,8 +205,8 @@ bool USRPDevice::start()
writeLock.unlock();
// Set gains to midpoint
setTxGain((minTxGain() + maxTxGain()) / 2);
setRxGain((minRxGain() + maxRxGain()) / 2);
setTxGain((m_dbTx->gain_min() + m_dbTx->gain_max()) / 2);
setRxGain((m_dbTx->gain_min() + m_dbTx->gain_max()) / 2);
data = new short[currDataSize];
dataStart = 0;
@@ -242,16 +243,6 @@ bool USRPDevice::stop()
#endif
}
double USRPDevice::maxTxGain()
{
return m_dbTx->gain_max();
}
double USRPDevice::minTxGain()
{
return m_dbTx->gain_min();
}
double USRPDevice::maxRxGain()
{
return m_dbRx->gain_max();
@@ -270,19 +261,20 @@ double USRPDevice::setTxGain(double dB, size_t chan)
}
writeLock.lock();
if (dB > maxTxGain())
dB = maxTxGain();
if (dB < minTxGain())
dB = minTxGain();
if (dB > m_dbTx->gain_max())
dB = m_dbTx->gain_max();
if (dB < m_dbTx->gain_min())
dB = m_dbTx->gain_min();
LOGC(DDEV, NOTICE) << "Setting TX gain to " << dB << " dB.";
if (!m_dbTx->set_gain(dB))
LOGC(DDEV, ERR) << "Error setting TX gain";
else
txGain = dB;
writeLock.unlock();
return dB;
return txGain;
}
@@ -305,10 +297,28 @@ double USRPDevice::setRxGain(double dB, size_t chan)
if (!m_dbRx->set_gain(dB))
LOGC(DDEV, ERR) << "Error setting RX gain";
else
rxGain = dB;
writeLock.unlock();
return dB;
return rxGain;
}
double USRPDevice::setPowerAttenuation(int atten, size_t chan) {
double rfGain;
rfGain = setTxGain(m_dbTx->gain_max() - atten, chan);
return m_dbTx->gain_max() - rfGain;
}
double USRPDevice::getPowerAttenuation(size_t chan) {
return m_dbTx->gain_max() - getTxGain(chan);
}
int USRPDevice::getNominalTxPower(size_t chan)
{
/* TODO: return value based on some experimentally generated table depending on
* band/arfcn, which is known here thanks to TXTUNE
*/
return 23;
}
bool USRPDevice::setRxAntenna(const std::string &ant, size_t chan)
@@ -362,7 +372,7 @@ GSM::Time USRPDevice::minLatency() {
// NOTE: Assumes sequential reads
int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
TIMESTAMP timestamp, bool *underrun)
{
#ifndef SWLOOPBACK
if (!m_uRx)
@@ -430,8 +440,10 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
*underrun = true;
LOGC(DDEV, DEBUG) << "UNDERRUN in TRX->USRP interface";
}
if (RSSI) *RSSI = (word0 >> 21) & 0x3f;
#if 0
/* FIXME: Do something with this ? */
unsigned RSSI = (word0 >> 21) & 0x3f;
#endif
if (!isAligned) continue;
unsigned cursorStart = pktTimestamp - timeStart + dataStart;
@@ -510,9 +522,8 @@ int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
#endif
}
int USRPDevice::writeSamples(std::vector<short *> &bufs, int len,
bool *underrun, unsigned long long timestamp,
bool isControl)
int USRPDevice::writeSamplesControl(std::vector<short *> &bufs, int len,
bool *underrun, unsigned long long timestamp, bool isControl)
{
writeLock.lock();
@@ -566,6 +577,12 @@ int USRPDevice::writeSamples(std::vector<short *> &bufs, int len,
#endif
}
int USRPDevice::writeSamples(std::vector<short *> &bufs, int len,
bool *underrun, unsigned long long timestamp)
{
return writeSamplesControl(bufs, len, underrun, timestamp, false);
}
bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
{
#ifndef SWLOOPBACK
@@ -575,7 +592,7 @@ bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
bool tmpUnderrun;
std::vector<short *> buf(1, data);
if (writeSamples(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) {
if (writeSamplesControl(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) {
pingTimestamp = timestamp;
return true;
}

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0+
*
* This software is distributed under multiple licenses; see the COPYING file in
* the main directory for licensing information for this specific distribuion.
* the main directory for licensing information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -80,6 +80,16 @@ private:
unsigned long lastPktTimestamp;
double rxGain;
double txGain;
int writeSamplesControl(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp = 0xffffffff, bool isControl = false);
/** sets the transmit chan gain, returns the gain setting **/
double setTxGain(double dB, size_t chan = 0);
/** get transmit gain */
double getTxGain(size_t chan = 0) { return txGain; }
#ifdef SWLOOPBACK
short loopbackBuffer[1000000];
@@ -94,7 +104,7 @@ private:
public:
/** Object constructor */
USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
const std::vector<std::string>& tx_paths,
const std::vector<std::string>& rx_paths);
@@ -107,9 +117,6 @@ private:
/** Stop the USRP */
bool stop();
/** Set priority not supported */
void setPriority(float prio = 0.5) { }
enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; }
/**
@@ -119,23 +126,20 @@ private:
@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 USRP does not have data to transmit, e.g. data not being sent fast enough
@param RSSI The received signal strength of the read result
@return The number of samples actually read
*/
int readSamples(std::vector<short *> &buf, int len, bool *overrun,
TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL,
unsigned *RSSI = NULL);
TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL);
/**
Write samples to the USRP.
@param buf Contains the data to be written.
@param len number of samples to write.
@param underrun Set if USRP 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.
@param isControl Set if data is a control packet, e.g. a ping command
@return The number of samples actually written
*/
int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
TIMESTAMP timestamp = 0xffffffff, bool isControl = false);
TIMESTAMP timestamp = 0xffffffff);
/** Update the alignment between the read and write timestamps */
bool updateAlignment(TIMESTAMP timestamp);
@@ -170,14 +174,12 @@ private:
/** return minimum Rx Gain **/
double minRxGain(void);
/** sets the transmit chan gain, returns the gain setting **/
double setTxGain(double dB, size_t chan = 0);
double rssiOffset(size_t chan) { return 0.0f; } /* FIXME: not implemented */
/** return maximum Tx Gain **/
double maxTxGain(void);
double setPowerAttenuation(int atten, size_t chan);
double getPowerAttenuation(size_t chan=0);
/** return minimum Rx Gain **/
double minTxGain(void);
int getNominalTxPower(size_t chan = 0);
/** sets the RX path to use, returns true if successful and false otherwise */
bool setRxAntenna(const std::string &ant, size_t chan = 0);

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

Some files were not shown because too many files have changed in this diff Show More