aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore53
-rw-r--r--.gitreview3
-rw-r--r--AUTHORS127
-rw-r--r--COPYING708
-rw-r--r--ChangeLog0
-rw-r--r--CommonLibs/BitVector.cpp369
-rw-r--r--CommonLibs/BitVector.h256
-rw-r--r--CommonLibs/Interthread.h696
-rw-r--r--CommonLibs/LinkedLists.cpp83
-rw-r--r--CommonLibs/LinkedLists.h175
-rw-r--r--CommonLibs/Logger.cpp64
-rw-r--r--CommonLibs/Logger.h94
-rw-r--r--CommonLibs/Makefile.am54
-rw-r--r--CommonLibs/PRBS.h110
-rw-r--r--CommonLibs/Sockets.cpp287
-rw-r--r--CommonLibs/Sockets.h173
-rw-r--r--CommonLibs/Threads.cpp140
-rw-r--r--CommonLibs/Threads.h192
-rw-r--r--CommonLibs/Timeval.cpp98
-rw-r--r--CommonLibs/Timeval.h105
-rw-r--r--CommonLibs/Vector.h308
-rw-r--r--CommonLibs/config_defs.h20
-rw-r--r--CommonLibs/debug.c30
-rw-r--r--CommonLibs/debug.h10
-rw-r--r--CommonLibs/osmo_signal.h35
-rw-r--r--CommonLibs/trx_vty.c576
-rw-r--r--CommonLibs/trx_vty.h68
-rw-r--r--GSM/GSMCommon.cpp93
-rw-r--r--GSM/GSMCommon.h253
-rw-r--r--GSM/Makefile.am32
-rw-r--r--INSTALLATION19
-rw-r--r--LEGAL60
-rw-r--r--Makefile.am55
-rw-r--r--Makefile.common38
-rw-r--r--NEWS0
-rw-r--r--README116
-rw-r--r--Transceiver52M/Channelizer.cpp107
-rw-r--r--Transceiver52M/Channelizer.h34
-rw-r--r--Transceiver52M/ChannelizerBase.cpp251
-rw-r--r--Transceiver52M/ChannelizerBase.h39
-rw-r--r--Transceiver52M/Complex.h266
-rw-r--r--Transceiver52M/Makefile.am111
-rw-r--r--Transceiver52M/README35
-rw-r--r--Transceiver52M/README.DFEsymbolspaced14
-rw-r--r--Transceiver52M/Resampler.cpp187
-rw-r--r--Transceiver52M/Resampler.h76
-rw-r--r--Transceiver52M/Synthesis.cpp121
-rw-r--r--Transceiver52M/Synthesis.h35
-rw-r--r--Transceiver52M/Transceiver.cpp1128
-rw-r--r--Transceiver52M/Transceiver.h282
-rw-r--r--Transceiver52M/arch/Makefile.am8
-rw-r--r--Transceiver52M/arch/arm/Makefile.am22
-rw-r--r--Transceiver52M/arch/arm/convert.c85
-rw-r--r--Transceiver52M/arch/arm/convert_neon.S51
-rw-r--r--Transceiver52M/arch/arm/convolve.c146
-rw-r--r--Transceiver52M/arch/arm/convolve_neon.S277
-rw-r--r--Transceiver52M/arch/arm/mult.c56
-rw-r--r--Transceiver52M/arch/arm/mult_neon.S42
-rw-r--r--Transceiver52M/arch/arm/scale.c56
-rw-r--r--Transceiver52M/arch/arm/scale_neon.S50
-rw-r--r--Transceiver52M/arch/common/Makefile.am15
-rw-r--r--Transceiver52M/arch/common/convert.h15
-rw-r--r--Transceiver52M/arch/common/convert_base.c34
-rw-r--r--Transceiver52M/arch/common/convolve.h32
-rw-r--r--Transceiver52M/arch/common/convolve_base.c156
-rw-r--r--Transceiver52M/arch/common/fft.c112
-rw-r--r--Transceiver52M/arch/common/fft.h13
-rw-r--r--Transceiver52M/arch/common/mult.h6
-rw-r--r--Transceiver52M/arch/common/scale.h6
-rw-r--r--Transceiver52M/arch/x86/Makefile.am33
-rw-r--r--Transceiver52M/arch/x86/convert.c83
-rw-r--r--Transceiver52M/arch/x86/convert_sse_3.c107
-rw-r--r--Transceiver52M/arch/x86/convert_sse_3.h34
-rw-r--r--Transceiver52M/arch/x86/convert_sse_4_1.c77
-rw-r--r--Transceiver52M/arch/x86/convert_sse_4_1.h28
-rw-r--r--Transceiver52M/arch/x86/convolve.c172
-rw-r--r--Transceiver52M/arch/x86/convolve_sse_3.c542
-rw-r--r--Transceiver52M/arch/x86/convolve_sse_3.h68
-rw-r--r--Transceiver52M/device/Makefile.am17
-rw-r--r--Transceiver52M/device/lms/LMSDevice.cpp637
-rw-r--r--Transceiver52M/device/lms/LMSDevice.h202
-rw-r--r--Transceiver52M/device/lms/Makefile.am10
-rw-r--r--Transceiver52M/device/radioDevice.h210
-rw-r--r--Transceiver52M/device/uhd/Makefile.am8
-rw-r--r--Transceiver52M/device/uhd/UHDDevice.cpp1561
-rw-r--r--Transceiver52M/device/usrp1/Makefile.am10
-rw-r--r--Transceiver52M/device/usrp1/USRPDevice.cpp671
-rw-r--r--Transceiver52M/device/usrp1/USRPDevice.h209
-rw-r--r--Transceiver52M/inband-signaling-usb314
-rw-r--r--Transceiver52M/laurent.m83
-rw-r--r--Transceiver52M/osmo-trx.cpp605
-rw-r--r--Transceiver52M/pulseApproximate.m15
-rw-r--r--Transceiver52M/radioBuffer.cpp228
-rw-r--r--Transceiver52M/radioBuffer.h45
-rw-r--r--Transceiver52M/radioClock.cpp49
-rw-r--r--Transceiver52M/radioClock.h40
-rw-r--r--Transceiver52M/radioInterface.cpp360
-rw-r--r--Transceiver52M/radioInterface.h188
-rw-r--r--Transceiver52M/radioInterfaceMulti.cpp405
-rw-r--r--Transceiver52M/radioInterfaceResamp.cpp235
-rw-r--r--Transceiver52M/radioVector.cpp155
-rw-r--r--Transceiver52M/radioVector.h68
-rw-r--r--Transceiver52M/sigProcLib.cpp1892
-rw-r--r--Transceiver52M/sigProcLib.h130
-rw-r--r--Transceiver52M/signalVector.cpp107
-rw-r--r--Transceiver52M/signalVector.h55
-rwxr-xr-xTransceiver52M/std_inband.rbfbin0 -> 176507 bytes
-rwxr-xr-xautogen.sh1578
-rw-r--r--config/ax_check_compile_flag.m472
-rw-r--r--config/ax_cxx_compile_stdcxx.m4982
-rw-r--r--config/ax_cxx_compile_stdcxx_11.m439
-rw-r--r--config/ax_sse.m471
-rw-r--r--configure.ac270
-rw-r--r--contrib/Makefile.am1
-rwxr-xr-xcontrib/jenkins.sh87
-rw-r--r--contrib/systemd/Makefile.am18
-rw-r--r--contrib/systemd/osmo-trx-lms.service11
-rw-r--r--contrib/systemd/osmo-trx-uhd.service11
-rw-r--r--contrib/systemd/osmo-trx-usrp1.service11
-rw-r--r--debian/changelog300
-rw-r--r--debian/compat1
-rw-r--r--debian/control91
-rw-r--r--debian/copyright147
-rw-r--r--debian/osmo-trx-lms.install4
-rw-r--r--debian/osmo-trx-uhd.install6
-rw-r--r--debian/osmo-trx-usrp1.install4
-rw-r--r--debian/osmo-trx.install0
-rw-r--r--debian/patches/build-for-debian8.patch57
-rw-r--r--debian/patches/series1
-rwxr-xr-xdebian/rules15
-rw-r--r--debian/source/format1
-rw-r--r--doc/Makefile.am3
-rw-r--r--doc/examples/Makefile.am41
-rw-r--r--doc/examples/osmo-trx-lms/osmo-trx-limesdr.cfg22
l---------doc/examples/osmo-trx-lms/osmo-trx-lms.cfg1
-rw-r--r--doc/examples/osmo-trx-uhd/osmo-trx-limesdr.cfg22
l---------doc/examples/osmo-trx-uhd/osmo-trx-uhd.cfg1
-rw-r--r--doc/examples/osmo-trx-uhd/osmo-trx-umtrx.cfg23
-rw-r--r--doc/examples/osmo-trx-uhd/osmo-trx-usrp_b200.cfg22
-rwxr-xr-xgit-version-gen151
-rw-r--r--tests/CommonLibs/BitVectorTest.cpp60
-rw-r--r--tests/CommonLibs/BitVectorTest.ok8
-rw-r--r--tests/CommonLibs/InterthreadTest.cpp137
-rw-r--r--tests/CommonLibs/InterthreadTest.ok1
-rw-r--r--tests/CommonLibs/LogTest.cpp69
-rw-r--r--tests/CommonLibs/LogTest.err3
-rw-r--r--tests/CommonLibs/LogTest.ok0
-rw-r--r--tests/CommonLibs/Makefile.am46
-rw-r--r--tests/CommonLibs/PRBSTest.cpp42
-rw-r--r--tests/CommonLibs/PRBSTest.ok4
-rw-r--r--tests/CommonLibs/SocketsTest.cpp101
-rw-r--r--tests/CommonLibs/SocketsTest.ok1
-rw-r--r--tests/CommonLibs/TimevalTest.cpp64
-rw-r--r--tests/CommonLibs/TimevalTest.ok1
-rw-r--r--tests/CommonLibs/VectorTest.cpp63
-rw-r--r--tests/CommonLibs/VectorTest.ok10
-rw-r--r--tests/Makefile.am41
-rw-r--r--tests/Transceiver52M/Makefile.am18
-rw-r--r--tests/Transceiver52M/convolve_test.c150
-rw-r--r--tests/Transceiver52M/convolve_test.ok72
-rw-r--r--tests/testsuite.at53
-rwxr-xr-xutils/clockdump.sh3
162 files changed, 24032 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..83c59d9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,53 @@
+# build results
+*.o
+*.lo
+*.la
+Transceiver52M/osmo-trx-uhd
+Transceiver52M/osmo-trx-usrp1
+Transceiver52M/osmo-trx-lms
+
+# tests
+tests/CommonLibs/BitVectorTest
+tests/CommonLibs/F16Test
+tests/CommonLibs/InterthreadTest
+tests/CommonLibs/LogTest
+tests/CommonLibs/RegexpTest
+tests/CommonLibs/SocketsTest
+tests/CommonLibs/TimevalTest
+tests/CommonLibs/URLEncodeTest
+tests/CommonLibs/VectorTest
+tests/CommonLibs/PRBSTest
+tests/Transceiver52M/convolve_test
+
+# automake/autoconf
+*.in
+.deps
+.libs
+.dirstamp
+*~
+Makefile
+config.log
+config.status
+config.h
+config.guess
+config.sub
+config/*
+configure
+compile
+aclocal.m4
+autom4te.cache
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+INSTALL
+tests/package.m4
+tests/testsuite
+tests/atconfig
+tests/testsuite.dir
+tests/testsuite.log
+
+# vim
+*.sw?
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..4521e98
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,3 @@
+[gerrit]
+host=gerrit.osmocom.org
+project=osmo-trx
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..53a3c7d
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,127 @@
+#
+# Copyright 2008, 2009 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio 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.
+#
+# GNU Radio 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+David A. Burgess, dburgess@kestrelsp.com:
+ CLI/CLI.cpp
+ CLI/CLI.h
+ CommonLibs/Assert.h
+ CommonLibs/BitVector.cpp
+ CommonLibs/Interthread.h
+ CommonLibs/LinkedLists.cpp
+ CommonLibs/LinkedLists.h
+ CommonLibs/Regexp.h
+ CommonLibs/Sockets.cpp
+ CommonLibs/Sockets.h
+ CommonLibs/Threads.cpp
+ CommonLibs/Threads.h
+ CommonLibs/Timeval.cpp
+ CommonLibs/Timeval.h
+ CommonLibs/Vector.h
+ GSM/GSM610Tables.cpp
+ GSM/GSM610Tables.h
+ GSM/GSMCommon.cpp
+ GSM/GSMCommon.h
+ GSM/GSMConfig.h
+ GSM/GSML1FEC.cpp
+ GSM/GSML1FEC.h
+ GSM/GSML2LAPDm.cpp
+ GSM/GSML2LAPDm.h
+ GSM/GSML3CCElements.cpp
+ GSM/GSML3CCElements.h
+ GSM/GSML3CCMessages.cpp
+ GSM/GSML3CCMessages.h
+ GSM/GSML3CommonElements.cpp
+ GSM/GSML3CommonElements.h
+ GSM/GSML3MMElements.cpp
+ GSM/GSML3MMElements.h
+ GSM/GSML3MMMessages.cpp
+ GSM/GSML3MMMessages.h
+ GSM/GSML3Message.cpp
+ GSM/GSML3Message.h
+ GSM/GSML3RRElements.cpp
+ GSM/GSML3RRElements.h
+ GSM/GSML3RRMessages.cpp
+ GSM/GSML3RRMessages.h
+ GSM/GSMLogicalChannel.h
+ GSM/GSMTDMA.cpp
+ GSM/GSMTDMA.h
+ GSM/GSMTransfer.cpp
+ GSM/GSMTransfer.h
+ LICENSEBLOCK
+ TRXManager/TRXManager.cpp
+ Transceiver/Complex.h
+ tests/CommonLibs/BitVectorTest.cpp
+ tests/CommonLibs/InterthreadTest.cpp
+ tests/CommonLibs/SocketsTest.cpp
+ tests/CommonLibs/TimevalTest.cpp
+ tests/CommonLibs/VectorTest.cpp
+
+Harvind S. Samra, hssamra@kestrelsp.com:
+ GSM/GSMConfig.h
+ GSM/GSMTransfer.h
+ LICENSEBLOCK
+ Transceiver/ComplexTest.cpp
+ Transceiver/Transceiver.cpp
+ Transceiver/Transceiver.h
+ Transceiver/USRPDevice.cpp
+ Transceiver/USRPDevice.h
+ Transceiver/USRPping.cpp
+ Transceiver/radioInterface.cpp
+ Transceiver/radioInterface.h
+ Transceiver/rcvLPF_651.h
+ Transceiver/runTransceiver.cpp
+ Transceiver/sendLPF_961.h
+ Transceiver/sigProcLib.cpp
+ Transceiver/sigProcLib.h
+ Transceiver/sigProcLibTest.cpp
+ Transceiver/sweepGenerator.cpp
+ Transceiver/testRadio.cpp
+
+Raffi Sevlian, raffisev@gmail.com:
+ GSM/GSMCommon.h
+ GSM/GSMConfig.h
+ GSM/GSML1FEC.h
+ GSM/GSML3CCElements.cpp
+ GSM/GSML3CCElements.h
+ GSM/GSML3CCMessages.cpp
+ GSM/GSML3CCMessages.h
+ GSM/GSML3CommonElements.cpp
+ GSM/GSML3CommonElements.h
+ GSM/GSML3MMElements.cpp
+ GSM/GSML3MMElements.h
+ GSM/GSML3MMMessages.cpp
+ GSM/GSML3MMMessages.h
+ GSM/GSML3Message.cpp
+ GSM/GSML3Message.h
+ GSM/GSML3RRElements.cpp
+ GSM/GSML3RRElements.h
+ GSM/GSML3RRMessages.cpp
+ GSM/GSML3RRMessages.h
+ GSM/GSMLogicalChannel.h
+ GSM/GSMSAPMux.cpp
+ GSM/GSMSAPMux.h
+ GSM/GSMTransfer.h
+ LICENSEBLOCK
+ TRXManager/TRXManager.h
+
+Alon Levy, alonlevy1@gmail.com
+ RRLPMessages.cpp
+ RRLPMessages.h
+ RRLPTest.cpp
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..879341e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,708 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
+
+
+
+
+=========================================================================
+
+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
+actual AGPL text and not part of an attempt to create a deriviative work based
+on the AGPLv3 text.
+
+=========================================================================
+
+
+ADDITIONAL TERMS TO THE AGPLv3 LICENSE FOR OsmoTRX
+
+
+Permissive Terms Supplementing the License
+
+1. Remote Interaction Through IP Networks.
+
+OsmoTRX is an implementation of the GSM network cellular air interface,
+as well as other interfaces to IP networks. The interaction of cellular
+handsets with the OsmoTRX software is considered "remote network interaction"
+for the purposes of the Affero General Public License and cellular users are
+subject to the source code access requirements of Section 13 of AGPLv3 ("Remote
+Network Interaction; Use with the GNU General Public License").
+
+Remote interactions through interfaces other than the GSM air interface are, at
+your option, exempted from the requirements of Section 13 ("Remote Network
+Interaction; Use with the GNU General Public License"). This exemption of
+interfaces other than the GSM air interface from the requirements of Section 13
+is an additional permission granted to you.
+
+
+END OF ADDITIONAL TERMS
+
+
+
+How to comply with Section 13 of the AGPLv3 license.
+
+The recommended method for compliance with Section 13 of the AGPLv3 license is
+to deliver a text message to each handset that attaches to the cellular
+network which uses OsmoTRX. At a minimum, that text message should include the string
+"OsmoTRX AGPLv3" and a URL that can be used to access the OsmoBTS source code. This
+message need not be delivered to handsets that are denied registration with the
+network, since those handsets have been denied service.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog
diff --git a/CommonLibs/BitVector.cpp b/CommonLibs/BitVector.cpp
new file mode 100644
index 0000000..cf408cd
--- /dev/null
+++ b/CommonLibs/BitVector.cpp
@@ -0,0 +1,369 @@
+/*
+* Copyright 2008, 2009 Free Software Foundation, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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 "BitVector.h"
+#include <iostream>
+#include <stdio.h>
+#include <sstream>
+#include <math.h>
+
+using namespace std;
+
+
+/**
+ Apply a Galois polymonial to a binary seqeunce.
+ @param val The input sequence.
+ @param poly The polynomial.
+ @param order The order of the polynomial.
+ @return Single-bit result.
+*/
+unsigned applyPoly(uint64_t val, uint64_t poly, unsigned order)
+{
+ uint64_t prod = val & poly;
+ unsigned sum = prod;
+ for (unsigned i=1; i<order; i++) sum ^= prod>>i;
+ return sum & 0x01;
+}
+
+
+
+
+
+
+BitVector::BitVector(const char *valString)
+ :Vector<char>(strlen(valString))
+{
+ uint32_t accum = 0;
+ for (size_t i=0; i<size(); i++) {
+ accum <<= 1;
+ if (valString[i]=='1') accum |= 0x01;
+ mStart[i] = accum;
+ }
+}
+
+
+
+
+
+uint64_t BitVector::peekField(size_t readIndex, unsigned length) const
+{
+ uint64_t accum = 0;
+ char *dp = mStart + readIndex;
+ assert(dp+length <= mEnd);
+ for (unsigned i=0; i<length; i++) {
+ accum = (accum<<1) | ((*dp++) & 0x01);
+ }
+ return accum;
+}
+
+
+
+
+uint64_t BitVector::peekFieldReversed(size_t readIndex, unsigned length) const
+{
+ uint64_t accum = 0;
+ char *dp = mStart + readIndex + length - 1;
+ assert(dp<mEnd);
+ for (int i=(length-1); i>=0; i--) {
+ accum = (accum<<1) | ((*dp--) & 0x01);
+ }
+ return accum;
+}
+
+
+
+
+uint64_t BitVector::readField(size_t& readIndex, unsigned length) const
+{
+ const uint64_t retVal = peekField(readIndex,length);
+ readIndex += length;
+ return retVal;
+}
+
+
+uint64_t BitVector::readFieldReversed(size_t& readIndex, unsigned length) const
+{
+ const uint64_t retVal = peekFieldReversed(readIndex,length);
+ readIndex += length;
+ return retVal;
+}
+
+
+
+
+
+void BitVector::fillField(size_t writeIndex, uint64_t value, unsigned length)
+{
+ char *dpBase = mStart + writeIndex;
+ char *dp = dpBase + length - 1;
+ assert(dp < mEnd);
+ while (dp>=dpBase) {
+ *dp-- = value & 0x01;
+ value >>= 1;
+ }
+}
+
+
+void BitVector::fillFieldReversed(size_t writeIndex, uint64_t value, unsigned length)
+{
+ char *dp = mStart + writeIndex;
+ char *dpEnd = dp + length - 1;
+ assert(dpEnd < mEnd);
+ while (dp<=dpEnd) {
+ *dp++ = value & 0x01;
+ value >>= 1;
+ }
+}
+
+
+
+
+void BitVector::writeField(size_t& writeIndex, uint64_t value, unsigned length)
+{
+ fillField(writeIndex,value,length);
+ writeIndex += length;
+}
+
+
+void BitVector::writeFieldReversed(size_t& writeIndex, uint64_t value, unsigned length)
+{
+ fillFieldReversed(writeIndex,value,length);
+ writeIndex += length;
+}
+
+
+void BitVector::invert()
+{
+ for (size_t i=0; i<size(); i++) {
+ mStart[i] = ~mStart[i];
+ }
+}
+
+
+
+
+void BitVector::reverse8()
+{
+ assert(size()>=8);
+
+ char tmp0 = mStart[0];
+ mStart[0] = mStart[7];
+ mStart[7] = tmp0;
+
+ char tmp1 = mStart[1];
+ mStart[1] = mStart[6];
+ mStart[6] = tmp1;
+
+ char tmp2 = mStart[2];
+ mStart[2] = mStart[5];
+ mStart[5] = tmp2;
+
+ char tmp3 = mStart[3];
+ mStart[3] = mStart[4];
+ mStart[4] = tmp3;
+}
+
+
+
+void BitVector::LSB8MSB()
+{
+ if (size()<8) return;
+ size_t size8 = 8*(size()/8);
+ size_t iTop = size8 - 8;
+ for (size_t i=0; i<=iTop; i+=8) segment(i,8).reverse8();
+}
+
+
+
+unsigned BitVector::sum() const
+{
+ unsigned sum = 0;
+ for (size_t i=0; i<size(); i++) sum += mStart[i] & 0x01;
+ return sum;
+}
+
+
+
+
+void BitVector::map(const unsigned *map, size_t mapSize, BitVector& dest) const
+{
+ for (unsigned i=0; i<mapSize; i++) {
+ dest.mStart[i] = mStart[map[i]];
+ }
+}
+
+
+
+
+void BitVector::unmap(const unsigned *map, size_t mapSize, BitVector& dest) const
+{
+ for (unsigned i=0; i<mapSize; i++) {
+ dest.mStart[map[i]] = mStart[i];
+ }
+}
+
+
+
+
+
+
+
+ostream& operator<<(ostream& os, const BitVector& hv)
+{
+ for (size_t i=0; i<hv.size(); i++) {
+ if (hv.bit(i)) os << '1';
+ else os << '0';
+ }
+ return os;
+}
+
+
+
+
+SoftVector::SoftVector(const BitVector& source)
+{
+ resize(source.size());
+ for (size_t i=0; i<size(); i++) {
+ if (source.bit(i)) mStart[i]=1.0F;
+ else mStart[i]=-1.0F;
+ }
+}
+
+
+BitVector SoftVector::sliced() const
+{
+ size_t sz = size();
+ BitVector newSig(sz);
+ for (size_t i=0; i<sz; i++) {
+ if (mStart[i]>0.0F) newSig[i]=1;
+ else newSig[i] = 0;
+ }
+ return newSig;
+}
+
+
+float SoftVector::getEnergy(float *plow) const
+{
+ const SoftVector &vec = *this;
+ int len = vec.size();
+ float avg = 0; float low = 1;
+ for (int i = 0; i < len; i++) {
+ float energy = fabsf(vec[i]);
+ if (energy < low) low = energy;
+ avg += energy/len;
+ }
+ if (plow) { *plow = low; }
+ return avg;
+}
+
+
+ostream& operator<<(ostream& os, const SoftVector& sv)
+{
+ for (size_t i=0; i<sv.size(); i++) {
+ if (sv[i]<-0.5) os << "0";
+ else if (sv[i]<-0.25) os << "o";
+ else if (sv[i]<0.0) os << ".";
+ else if (sv[i]>0.5) os << "1";
+ else if (sv[i]>0.25) os << "|";
+ else if (sv[i]>0.0) os << "'";
+ else os << "-";
+ }
+ return os;
+}
+
+
+
+void BitVector::pack(unsigned char* targ) const
+{
+ // Assumes MSB-first packing.
+ unsigned bytes = size()/8;
+ for (unsigned i=0; i<bytes; i++) {
+ targ[i] = peekField(i*8,8);
+ }
+ unsigned whole = bytes*8;
+ unsigned rem = size() - whole;
+ if (rem==0) return;
+ targ[bytes] = peekField(whole,rem) << (8-rem);
+}
+
+
+void BitVector::unpack(const unsigned char* src)
+{
+ // Assumes MSB-first packing.
+ unsigned bytes = size()/8;
+ for (unsigned i=0; i<bytes; i++) {
+ fillField(i*8,src[i],8);
+ }
+ unsigned whole = bytes*8;
+ unsigned rem = size() - whole;
+ if (rem==0) return;
+ fillField(whole,src[bytes] >> (8-rem),rem);
+}
+
+void BitVector::hex(ostream& os) const
+{
+ os << std::hex;
+ unsigned digits = size()/4;
+ size_t wp=0;
+ for (unsigned i=0; i<digits; i++) {
+ os << readField(wp,4);
+ }
+ os << std::dec;
+}
+
+std::string BitVector::hexstr() const
+{
+ std::ostringstream ss;
+ hex(ss);
+ return ss.str();
+}
+
+
+bool BitVector::unhex(const char* src)
+{
+ // Assumes MSB-first packing.
+ unsigned int val;
+ unsigned digits = size()/4;
+ for (unsigned i=0; i<digits; i++) {
+ if (sscanf(src+i, "%1x", &val) < 1) {
+ return false;
+ }
+ fillField(i*4,val,4);
+ }
+ unsigned whole = digits*4;
+ unsigned rem = size() - whole;
+ if (rem>0) {
+ if (sscanf(src+digits, "%1x", &val) < 1) {
+ return false;
+ }
+ fillField(whole,val,rem);
+ }
+ return true;
+}
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/BitVector.h b/CommonLibs/BitVector.h
new file mode 100644
index 0000000..559dd99
--- /dev/null
+++ b/CommonLibs/BitVector.h
@@ -0,0 +1,256 @@
+/*
+* Copyright 2008, 2009 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+#ifndef FECVECTORS_H
+#define FECVECTORS_H
+
+#include "Vector.h"
+#include <stdint.h>
+
+
+class BitVector : public Vector<char> {
+
+
+ public:
+
+ /**@name Constructors. */
+ //@{
+
+ /**@name Casts of Vector constructors. */
+ //@{
+ BitVector(char* wData, char* wStart, char* wEnd)
+ :Vector<char>(wData,wStart,wEnd)
+ { }
+ BitVector(size_t len=0):Vector<char>(len) {}
+ BitVector(const Vector<char>& source):Vector<char>(source) {}
+ BitVector(Vector<char>& source):Vector<char>(source) {}
+ BitVector(const Vector<char>& source1, const Vector<char> source2):Vector<char>(source1,source2) {}
+ //@}
+
+ /** Construct from a string of "0" and "1". */
+ BitVector(const char* valString);
+ //@}
+
+ /** Index a single bit. */
+ bool bit(size_t index) const
+ {
+ // We put this code in .h for fast inlining.
+ const char *dp = mStart+index;
+ assert(dp<mEnd);
+ return (*dp) & 0x01;
+ }
+
+ /**@name Casts and overrides of Vector operators. */
+ //@{
+ BitVector segment(size_t start, size_t span)
+ {
+ char* wStart = mStart + start;
+ char* wEnd = wStart + span;
+ assert(wEnd<=mEnd);
+ return BitVector(NULL,wStart,wEnd);
+ }
+
+ BitVector alias()
+ { return segment(0,size()); }
+
+ const BitVector segment(size_t start, size_t span) const
+ { return (BitVector)(Vector<char>::segment(start,span)); }
+
+ BitVector head(size_t span) { return segment(0,span); }
+ const BitVector head(size_t span) const { return segment(0,span); }
+ BitVector tail(size_t start) { return segment(start,size()-start); }
+ const BitVector tail(size_t start) const { return segment(start,size()-start); }
+ //@}
+
+
+ void zero() { fill(0); }
+
+
+ /** Invert 0<->1. */
+ void invert();
+
+ /**@name Byte-wise operations. */
+ //@{
+ /** Reverse an 8-bit vector. */
+ void reverse8();
+ /** Reverse groups of 8 within the vector (byte reversal). */
+ void LSB8MSB();
+ //@}
+
+ /**@name Serialization and deserialization. */
+ //@{
+ uint64_t peekField(size_t readIndex, unsigned length) const;
+ uint64_t peekFieldReversed(size_t readIndex, unsigned length) const;
+ uint64_t readField(size_t& readIndex, unsigned length) const;
+ uint64_t readFieldReversed(size_t& readIndex, unsigned length) const;
+ void fillField(size_t writeIndex, uint64_t value, unsigned length);
+ void fillFieldReversed(size_t writeIndex, uint64_t value, unsigned length);
+ void writeField(size_t& writeIndex, uint64_t value, unsigned length);
+ void writeFieldReversed(size_t& writeIndex, uint64_t value, unsigned length);
+ void write0(size_t& writeIndex) { writeField(writeIndex,0,1); }
+ void write1(size_t& writeIndex) { writeField(writeIndex,1,1); }
+
+ //@}
+
+ /** Sum of bits. */
+ unsigned sum() const;
+
+ /** Reorder bits, dest[i] = this[map[i]]. */
+ void map(const unsigned *map, size_t mapSize, BitVector& dest) const;
+
+ /** Reorder bits, dest[map[i]] = this[i]. */
+ void unmap(const unsigned *map, size_t mapSize, BitVector& dest) const;
+
+ /** Pack into a char array. */
+ void pack(unsigned char*) const;
+
+ /** Unpack from a char array. */
+ void unpack(const unsigned char*);
+
+ /** Make a hexdump string. */
+ void hex(std::ostream&) const;
+ std::string hexstr() const;
+
+ /** Unpack from a hexdump string.
+ * @returns true on success, false on error. */
+ bool unhex(const char*);
+
+ void set(BitVector other) // That's right. No ampersand.
+ {
+ clear();
+ mData=other.mData;
+ mStart=other.mStart;
+ mEnd=other.mEnd;
+ other.mData=NULL;
+ }
+
+ void settfb(int i, int j) const
+ {
+ mStart[i] = j;
+ }
+
+};
+
+
+
+std::ostream& operator<<(std::ostream&, const BitVector&);
+
+
+
+
+
+
+/**
+ The SoftVector class is used to represent a soft-decision signal.
+ Values 0..1 represent probabilities that a bit is "true".
+ */
+class SoftVector: public Vector<float> {
+
+ public:
+
+ /** Build a SoftVector of a given length. */
+ SoftVector(size_t wSize=0):Vector<float>(wSize) {}
+
+ /** Construct a SoftVector from a C string of "0", "1", and "X". */
+ SoftVector(const char* valString);
+
+ /** Construct a SoftVector from a BitVector. */
+ SoftVector(const BitVector& source);
+
+ /**
+ Wrap a SoftVector around a block of floats.
+ The block will be delete[]ed upon desctuction.
+ */
+ SoftVector(float *wData, unsigned length)
+ :Vector<float>(wData,length)
+ {}
+
+ SoftVector(float* wData, float* wStart, float* wEnd)
+ :Vector<float>(wData,wStart,wEnd)
+ { }
+
+ /**
+ Casting from a Vector<float>.
+ Note that this is NOT pass-by-reference.
+ */
+ SoftVector(Vector<float> source)
+ :Vector<float>(source)
+ {}
+
+
+ /**@name Casts and overrides of Vector operators. */
+ //@{
+ SoftVector segment(size_t start, size_t span)
+ {
+ float* wStart = mStart + start;
+ float* wEnd = wStart + span;
+ assert(wEnd<=mEnd);
+ return SoftVector(NULL,wStart,wEnd);
+ }
+
+ SoftVector alias()
+ { return segment(0,size()); }
+
+ const SoftVector segment(size_t start, size_t span) const
+ { return (SoftVector)(Vector<float>::segment(start,span)); }
+
+ SoftVector head(size_t span) { return segment(0,span); }
+ const SoftVector head(size_t span) const { return segment(0,span); }
+ SoftVector tail(size_t start) { return segment(start,size()-start); }
+ const SoftVector tail(size_t start) const { return segment(start,size()-start); }
+ //@}
+
+ // How good is the SoftVector in the sense of the bits being solid?
+ // Result of 1 is perfect and 0 means all the bits were 0.0
+ // If plow is non-NULL, also return the lowest energy bit.
+ float getEnergy(float *low=0) const;
+
+ /** Fill with "unknown" values. */
+ void unknown() { fill(0.0F); }
+
+ /** Return a hard bit value from a given index by slicing. */
+ bool bit(size_t index) const
+ {
+ const float *dp = mStart+index;
+ assert(dp<mEnd);
+ return (*dp)>0.0F;
+ }
+
+ /** Slice the whole signal into bits. */
+ BitVector sliced() const;
+
+};
+
+
+
+std::ostream& operator<<(std::ostream&, const SoftVector&);
+
+
+
+
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Interthread.h b/CommonLibs/Interthread.h
new file mode 100644
index 0000000..42e6f7f
--- /dev/null
+++ b/CommonLibs/Interthread.h
@@ -0,0 +1,696 @@
+/*
+* Copyright 2008, 2011 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+#ifndef INTERTHREAD_H
+#define INTERTHREAD_H
+
+#include "Timeval.h"
+#include "Threads.h"
+#include "LinkedLists.h"
+#include <map>
+#include <vector>
+#include <queue>
+
+
+
+
+
+/**@defgroup Templates for interthread mechanisms. */
+//@{
+
+
+/** Pointer FIFO for interthread operations. */
+// (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,
+// 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*).
+template <class T, class Fifo=PointerFIFO> class InterthreadQueue {
+
+ protected:
+
+ Fifo mQ;
+ mutable Mutex mLock;
+ mutable Signal mWriteSignal;
+
+ public:
+
+ /** Delete contents. */
+ void clear()
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>0) delete (T*)mQ.get();
+ }
+
+ /** Empty the queue, but don't delete. */
+ void flushNoDelete()
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>0) mQ.get();
+ }
+
+
+ ~InterthreadQueue()
+ { clear(); }
+
+
+ size_t size() const
+ {
+ ScopedLock lock(mLock);
+ return mQ.size();
+ }
+
+ size_t totalSize() const // pat added
+ {
+ ScopedLock lock(mLock);
+ return mQ.totalSize();
+ }
+
+ /**
+ Blocking read.
+ @return Pointer to object (will not be NULL).
+ */
+ T* read()
+ {
+ ScopedLock lock(mLock);
+ T* retVal = (T*)mQ.get();
+ while (retVal==NULL) {
+ mWriteSignal.wait(mLock);
+ retVal = (T*)mQ.get();
+ }
+ return retVal;
+ }
+
+ /** Non-blocking peek at the first element; returns NULL if empty. */
+ T* front()
+ {
+ ScopedLock lock(mLock);
+ return (T*) mQ.front();
+ }
+
+ /**
+ Blocking read with a timeout.
+ @param timeout The read timeout in ms.
+ @return Pointer to object or NULL on timeout.
+ */
+ T* read(unsigned timeout)
+ {
+ if (timeout==0) return readNoBlock();
+ Timeval waitTime(timeout);
+ ScopedLock lock(mLock);
+ while ((mQ.size()==0) && (!waitTime.passed()))
+ mWriteSignal.wait(mLock,waitTime.remaining());
+ T* retVal = (T*)mQ.get();
+ return retVal;
+ }
+
+ /**
+ Non-blocking read.
+ @return Pointer to object or NULL if FIFO is empty.
+ */
+ T* readNoBlock()
+ {
+ ScopedLock lock(mLock);
+ return (T*)mQ.get();
+ }
+
+ /** Non-blocking write. */
+ void write(T* val)
+ {
+ ScopedLock lock(mLock);
+ mQ.put(val);
+ mWriteSignal.signal();
+ }
+
+ /** Non-block write to the front of the queue. */
+ void write_front(T* val) // pat added
+ {
+ ScopedLock lock(mLock);
+ mQ.push_front(val);
+ mWriteSignal.signal();
+ }
+};
+
+// (pat) Identical to above but with the threading problem fixed.
+template <class T, class Fifo=PointerFIFO> class InterthreadQueue2 {
+
+ protected:
+
+ Fifo mQ;
+ mutable Mutex mLock;
+ mutable Signal mWriteSignal;
+
+ public:
+
+ /** Delete contents. */
+ void clear()
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>0) delete (T*)mQ.get();
+ }
+
+ /** Empty the queue, but don't delete. */
+ void flushNoDelete()
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>0) mQ.get();
+ }
+
+
+ ~InterthreadQueue2()
+ { clear(); }
+
+
+ size_t size() const
+ {
+ ScopedLock lock(mLock);
+ return mQ.size();
+ }
+
+ size_t totalSize() const // pat added
+ {
+ ScopedLock lock(mLock);
+ return mQ.totalSize();
+ }
+
+ /**
+ Blocking read.
+ @return Pointer to object (will not be NULL).
+ */
+ T* read()
+ {
+ ScopedLock lock(mLock);
+ T* retVal = (T*)mQ.get();
+ while (retVal==NULL) {
+ mWriteSignal.wait(mLock);
+ retVal = (T*)mQ.get();
+ }
+ return retVal;
+ }
+
+ /** Non-blocking peek at the first element; returns NULL if empty. */
+ T* front()
+ {
+ ScopedLock lock(mLock);
+ return (T*) mQ.front();
+ }
+
+ /**
+ Blocking read with a timeout.
+ @param timeout The read timeout in ms.
+ @return Pointer to object or NULL on timeout.
+ */
+ T* read(unsigned timeout)
+ {
+ if (timeout==0) return readNoBlock();
+ Timeval waitTime(timeout);
+ ScopedLock lock(mLock);
+ while ((mQ.size()==0) && (!waitTime.passed()))
+ mWriteSignal.wait(mLock,waitTime.remaining());
+ T* retVal = (T*)mQ.get();
+ return retVal;
+ }
+
+ /**
+ Non-blocking read.
+ @return Pointer to object or NULL if FIFO is empty.
+ */
+ T* readNoBlock()
+ {
+ ScopedLock lock(mLock);
+ return (T*)mQ.get();
+ }
+
+ /** Non-blocking write. */
+ void write(T* val)
+ {
+ // (pat) The Mutex mLock must be released before signaling the mWriteSignal condition.
+ // This is an implicit requirement of pthread_cond_wait() called from signal().
+ // If you do not do that, the InterthreadQueue read() function cannot start
+ // because the mutex is still locked by the thread calling the write(),
+ // so the read() thread yields its immediate execution opportunity.
+ // This recurs (and the InterthreadQueue fills up with data)
+ // until the read thread's accumulated temporary priority causes it to
+ // get a second pre-emptive activation over the writing thread,
+ // resulting in bursts of activity by the read thread.
+ { ScopedLock lock(mLock);
+ mQ.put(val);
+ }
+ mWriteSignal.signal();
+ }
+
+ /** Non-block write to the front of the queue. */
+ void write_front(T* val) // pat added
+ {
+ // (pat) See comments above.
+ { ScopedLock lock(mLock);
+ mQ.push_front(val);
+ }
+ mWriteSignal.signal();
+ }
+};
+
+
+
+/** Pointer FIFO for interthread operations. */
+template <class T> class InterthreadQueueWithWait {
+
+ protected:
+
+ PointerFIFO mQ;
+ mutable Mutex mLock;
+ mutable Signal mWriteSignal;
+ mutable Signal mReadSignal;
+
+ virtual void freeElement(T* element) const { delete element; };
+
+ public:
+
+ /** Delete contents. */
+ void clear()
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>0) freeElement((T*)mQ.get());
+ mReadSignal.signal();
+ }
+
+
+
+ virtual ~InterthreadQueueWithWait()
+ { clear(); }
+
+
+ size_t size() const
+ {
+ ScopedLock lock(mLock);
+ return mQ.size();
+ }
+
+ /**
+ Blocking read.
+ @return Pointer to object (will not be NULL).
+ */
+ T* read()
+ {
+ ScopedLock lock(mLock);
+ T* retVal = (T*)mQ.get();
+ while (retVal==NULL) {
+ mWriteSignal.wait(mLock);
+ retVal = (T*)mQ.get();
+ }
+ mReadSignal.signal();
+ return retVal;
+ }
+
+ /**
+ Blocking read with a timeout.
+ @param timeout The read timeout in ms.
+ @return Pointer to object or NULL on timeout.
+ */
+ T* read(unsigned timeout)
+ {
+ if (timeout==0) return readNoBlock();
+ Timeval waitTime(timeout);
+ ScopedLock lock(mLock);
+ while ((mQ.size()==0) && (!waitTime.passed()))
+ mWriteSignal.wait(mLock,waitTime.remaining());
+ T* retVal = (T*)mQ.get();
+ if (retVal!=NULL) mReadSignal.signal();
+ return retVal;
+ }
+
+ /**
+ Non-blocking read.
+ @return Pointer to object or NULL if FIFO is empty.
+ */
+ T* readNoBlock()
+ {
+ ScopedLock lock(mLock);
+ T* retVal = (T*)mQ.get();
+ if (retVal!=NULL) mReadSignal.signal();
+ return retVal;
+ }
+
+ /** Non-blocking write. */
+ void write(T* val)
+ {
+ // (pat) 8-14: Taking out the threading problem fix temporarily for David to use in the field.
+ ScopedLock lock(mLock);
+ mQ.put(val);
+ mWriteSignal.signal();
+ }
+
+ /** Wait until the queue falls below a low water mark. */
+ // (pat) This function suffers from the same problem as documented
+ // at InterthreadQueue.write(), but I am not fixing it because I cannot test it.
+ // The caller of this function will eventually get to run, just not immediately
+ // after the mReadSignal condition is fulfilled.
+ void wait(size_t sz=0)
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>sz) mReadSignal.wait(mLock);
+ }
+
+};
+
+
+
+
+
+/** Thread-safe map of pointers to class D, keyed by class K. */
+template <class K, class D > class InterthreadMap {
+
+protected:
+
+ typedef std::map<K,D*> Map;
+ Map mMap;
+ mutable Mutex mLock;
+ Signal mWriteSignal;
+
+public:
+
+ void clear()
+ {
+ // Delete everything in the map.
+ ScopedLock lock(mLock);
+ typename Map::iterator iter = mMap.begin();
+ while (iter != mMap.end()) {
+ delete iter->second;
+ ++iter;
+ }
+ mMap.clear();
+ }
+
+ ~InterthreadMap() { clear(); }
+
+ /**
+ Non-blocking write.
+ @param key The index to write to.
+ @param wData Pointer to data, not to be deleted until removed from the map.
+ */
+ void write(const K &key, D * wData)
+ {
+ ScopedLock lock(mLock);
+ typename Map::iterator iter = mMap.find(key);
+ if (iter!=mMap.end()) {
+ delete iter->second;
+ iter->second = wData;
+ } else {
+ mMap[key] = wData;
+ }
+ mWriteSignal.broadcast();
+ }
+
+ /**
+ Non-blocking read with element removal.
+ @param key Key to read from.
+ @return Pointer at key or NULL if key not found, to be deleted by caller.
+ */
+ D* getNoBlock(const K& key)
+ {
+ ScopedLock lock(mLock);
+ typename Map::iterator iter = mMap.find(key);
+ if (iter==mMap.end()) return NULL;
+ D* retVal = iter->second;
+ mMap.erase(iter);
+ return retVal;
+ }
+
+ /**
+ Blocking read with a timeout and element removal.
+ @param key The key to read from.
+ @param timeout The blocking timeout in ms.
+ @return Pointer at key or NULL on timeout, to be deleted by caller.
+ */
+ D* get(const K &key, unsigned timeout)
+ {
+ if (timeout==0) return getNoBlock(key);
+ Timeval waitTime(timeout);
+ ScopedLock lock(mLock);
+ typename Map::iterator iter = mMap.find(key);
+ while ((iter==mMap.end()) && (!waitTime.passed())) {
+ mWriteSignal.wait(mLock,waitTime.remaining());
+ iter = mMap.find(key);
+ }
+ if (iter==mMap.end()) return NULL;
+ D* retVal = iter->second;
+ mMap.erase(iter);
+ return retVal;
+ }
+
+ /**
+ Blocking read with and element removal.
+ @param key The key to read from.
+ @return Pointer at key, to be deleted by caller.
+ */
+ D* get(const K &key)
+ {
+ ScopedLock lock(mLock);
+ typename Map::iterator iter = mMap.find(key);
+ while (iter==mMap.end()) {
+ mWriteSignal.wait(mLock);
+ iter = mMap.find(key);
+ }
+ D* retVal = iter->second;
+ mMap.erase(iter);
+ return retVal;
+ }
+
+
+ /**
+ Remove an entry and delete it.
+ @param key The key of the entry to delete.
+ @return True if it was actually found and deleted.
+ */
+ bool remove(const K &key )
+ {
+ D* val = getNoBlock(key);
+ if (!val) return false;
+ delete val;
+ return true;
+ }
+
+
+ /**
+ Non-blocking read.
+ @param key Key to read from.
+ @return Pointer at key or NULL if key not found.
+ */
+ D* readNoBlock(const K& key) const
+ {
+ D* retVal=NULL;
+ ScopedLock lock(mLock);
+ typename Map::const_iterator iter = mMap.find(key);
+ if (iter!=mMap.end()) retVal = iter->second;
+ return retVal;
+ }
+
+ /**
+ Blocking read with a timeout.
+ @param key The key to read from.
+ @param timeout The blocking timeout in ms.
+ @return Pointer at key or NULL on timeout.
+ */
+ D* read(const K &key, unsigned timeout) const
+ {
+ if (timeout==0) return readNoBlock(key);
+ ScopedLock lock(mLock);
+ Timeval waitTime(timeout);
+ typename Map::const_iterator iter = mMap.find(key);
+ while ((iter==mMap.end()) && (!waitTime.passed())) {
+ mWriteSignal.wait(mLock,waitTime.remaining());
+ iter = mMap.find(key);
+ }
+ if (iter==mMap.end()) return NULL;
+ D* retVal = iter->second;
+ return retVal;
+ }
+
+ /**
+ Blocking read.
+ @param key The key to read from.
+ @return Pointer at key.
+ */
+ D* read(const K &key) const
+ {
+ ScopedLock lock(mLock);
+ typename Map::const_iterator iter = mMap.find(key);
+ while (iter==mMap.end()) {
+ mWriteSignal.wait(mLock);
+ iter = mMap.find(key);
+ }
+ D* retVal = iter->second;
+ return retVal;
+ }
+
+};
+
+
+
+
+
+
+
+/** This class is used to provide pointer-based comparison in priority_queues. */
+template <class T> class PointerCompare {
+
+ public:
+
+ /** Compare the objects pointed to, not the pointers themselves. */
+ bool operator()(const T *v1, const T *v2)
+ { return (*v1)>(*v2); }
+
+};
+
+
+
+/**
+ Priority queue for interthread operations.
+ Passes pointers to objects.
+*/
+template <class T, class C = std::vector<T*>, class Cmp = PointerCompare<T> > class InterthreadPriorityQueue {
+
+ protected:
+
+ std::priority_queue<T*,C,Cmp> mQ;
+ mutable Mutex mLock;
+ mutable Signal mWriteSignal;
+
+ public:
+
+
+ /** Clear the FIFO. */
+ void clear()
+ {
+ ScopedLock lock(mLock);
+ while (mQ.size()>0) {
+ T* ptr = mQ.top();
+ mQ.pop();
+ delete ptr;
+ }
+ }
+
+
+ ~InterthreadPriorityQueue()
+ {
+ clear();
+ }
+
+ size_t size() const
+ {
+ ScopedLock lock(mLock);
+ return mQ.size();
+ }
+
+
+ /** Non-blocking read. */
+ T* readNoBlock()
+ {
+ ScopedLock lock(mLock);
+ T* retVal = NULL;
+ if (mQ.size()!=0) {
+ retVal = mQ.top();
+ mQ.pop();
+ }
+ return retVal;
+ }
+
+ /** Blocking read. */
+ T* read()
+ {
+ ScopedLock lock(mLock);
+ T* retVal;
+ while (mQ.size()==0) mWriteSignal.wait(mLock);
+ retVal = mQ.top();
+ mQ.pop();
+ return retVal;
+ }
+
+ /** Non-blocking write. */
+ void write(T* val)
+ {
+ // (pat) 8-14: Taking out the threading problem fix temporarily for David to use in the field.
+ ScopedLock lock(mLock);
+ mQ.push(val);
+ mWriteSignal.signal();
+ }
+
+};
+
+
+
+
+
+class Semaphore {
+
+ private:
+
+ bool mFlag;
+ Signal mSignal;
+ mutable Mutex mLock;
+
+ public:
+
+ Semaphore()
+ :mFlag(false)
+ { }
+
+ void post()
+ {
+ ScopedLock lock(mLock);
+ mFlag=true;
+ mSignal.signal();
+ }
+
+ void get()
+ {
+ ScopedLock lock(mLock);
+ while (!mFlag) mSignal.wait(mLock);
+ mFlag=false;
+ }
+
+ bool semtry()
+ {
+ ScopedLock lock(mLock);
+ bool retVal = mFlag;
+ mFlag = false;
+ return retVal;
+ }
+
+};
+
+
+
+
+
+//@}
+
+
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/LinkedLists.cpp b/CommonLibs/LinkedLists.cpp
new file mode 100644
index 0000000..35a8541
--- /dev/null
+++ b/CommonLibs/LinkedLists.cpp
@@ -0,0 +1,83 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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 "LinkedLists.h"
+
+
+void PointerFIFO::push_front(void* val) // by pat
+{
+ // Pat added this routine for completeness, but never used or tested.
+ // The first person to use this routine should remove this assert.
+ ListNode *node = allocate();
+ node->data(val);
+ node->next(mHead);
+ mHead = node;
+ if (!mTail) mTail=node;
+ mSize++;
+}
+
+void PointerFIFO::put(void* val)
+{
+ ListNode *node = allocate();
+ node->data(val);
+ node->next(NULL);
+ if (mTail!=NULL) mTail->next(node);
+ mTail=node;
+ if (mHead==NULL) mHead=node;
+ mSize++;
+}
+
+/** Take an item from the FIFO. */
+void* PointerFIFO::get()
+{
+ // empty list?
+ if (mHead==NULL) return NULL;
+ // normal case
+ ListNode* next = mHead->next();
+ void* retVal = mHead->data();
+ release(mHead);
+ mHead = next;
+ if (next==NULL) mTail=NULL;
+ mSize--;
+ return retVal;
+}
+
+
+ListNode *PointerFIFO::allocate()
+{
+ if (mFreeList==NULL) return new ListNode;
+ ListNode* retVal = mFreeList;
+ mFreeList = mFreeList->next();
+ return retVal;
+}
+
+void PointerFIFO::release(ListNode* wNode)
+{
+ wNode->next(mFreeList);
+ mFreeList = wNode;
+}
diff --git a/CommonLibs/LinkedLists.h b/CommonLibs/LinkedLists.h
new file mode 100644
index 0000000..31fb9c5
--- /dev/null
+++ b/CommonLibs/LinkedLists.h
@@ -0,0 +1,175 @@
+/*
+* 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 the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+
+#ifndef LINKEDLISTS_H
+#define LINKEDLISTS_H
+
+#include <stdlib.h>
+#include <assert.h>
+
+
+
+/** This node class is used to build singly-linked lists. */
+class ListNode {
+
+ private:
+
+ ListNode* mNext;
+ void* mData;
+
+ public:
+
+ ListNode* next() { return mNext; }
+ void next(ListNode* wNext) { mNext=wNext; }
+
+ void* data() { return mData; }
+ void data(void* wData) { mData=wData; }
+};
+
+
+
+
+/** A fast FIFO for pointer-based storage. */
+class PointerFIFO {
+
+ protected:
+
+ ListNode* mHead; ///< points to next item out
+ ListNode* mTail; ///< points to last item in
+ ListNode* mFreeList; ///< pool of previously-allocated nodes
+ unsigned mSize; ///< number of items in the FIFO
+
+ public:
+
+ PointerFIFO()
+ :mHead(NULL),mTail(NULL),mFreeList(NULL),
+ mSize(0)
+ {}
+
+ unsigned size() const { return mSize; }
+ unsigned totalSize() const { return 0; } // Not used in this version.
+
+ /** Put an item into the FIFO at the back of the queue. */
+ void put(void* val);
+ /** Push an item on the front of the FIFO. */
+ void push_front(void*val); // pat added.
+
+ /**
+ Take an item from the FIFO.
+ Returns NULL for empty list.
+ */
+ void* get();
+
+ /** Peek at front item without removal. */
+ void *front() { return mHead ? mHead->data() : 0; } // pat added
+
+
+ private:
+
+ /** Allocate a new node to extend the FIFO. */
+ ListNode *allocate();
+
+ /** Release a node to the free pool after removal from the FIFO. */
+ void release(ListNode* wNode);
+
+};
+
+// This is the default type for SingleLinkList Node element;
+// You can derive your class directly from this, but then you must add type casts
+// all over the place.
+class SingleLinkListNode
+{ public:
+ SingleLinkListNode *mNext;
+ SingleLinkListNode *next() {return mNext;}
+ void setNext(SingleLinkListNode *item) {mNext=item;}
+ SingleLinkListNode() : mNext(0) {}
+ virtual unsigned size() { return 0; }
+};
+
+// A single-linked lists of elements with internal pointers.
+// The methods must match those from SingleLinkListNode.
+// This class also assumes the Node has a size() method, and accumulates
+// the total size of elements in the list in totalSize().
+template<class Node=SingleLinkListNode>
+class SingleLinkList
+{
+ Node *mHead, *mTail;
+ unsigned mSize; // Number of elements in list.
+ unsigned mTotalSize; // Total of size() method of elements in list.
+
+ public:
+ SingleLinkList() : mHead(0), mTail(0), mSize(0), mTotalSize(0) {}
+ unsigned size() const { return mSize; }
+ unsigned totalSize() const { return mTotalSize; }
+
+ Node *pop_back() { assert(0); } // Not efficient with this type of list.
+
+ Node *pop_front()
+ {
+ if (!mHead) return NULL;
+ Node *result = mHead;
+ mHead = mHead->next();
+ if (mTail == result) { mTail = NULL; assert(mHead == NULL); }
+ result->setNext(NULL); // be neat
+ mSize--;
+ mTotalSize -= result->size();
+ return result;
+ }
+
+ void push_front(Node *item)
+ {
+ item->setNext(mHead);
+ mHead = item;
+ if (!mTail) { mTail = item; }
+ mSize++;
+ mTotalSize += item->size();
+ }
+
+ void push_back(Node *item)
+ {
+ item->setNext(NULL);
+ if (mTail) { mTail->setNext(item); }
+ mTail = item;
+ if (!mHead) mHead = item;
+ mSize++;
+ mTotalSize += item->size();
+ }
+ Node *front() { return mHead; }
+ Node *back() { return mTail; }
+
+ // Interface to InterthreadQueue so it can used SingleLinkList as the Fifo.
+ void put(void *val) { push_back((Node*)val); }
+ void *get() { return pop_front(); }
+};
+
+
+
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Logger.cpp b/CommonLibs/Logger.cpp
new file mode 100644
index 0000000..171c635
--- /dev/null
+++ b/CommonLibs/Logger.cpp
@@ -0,0 +1,64 @@
+/*
+* Copyright (C) 2018 sysmocom - s.f.m.c. GmbH
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <string.h>
+#include <cstdio>
+#include <fstream>
+#include <string>
+#include <stdarg.h>
+#include <sys/time.h> // For gettimeofday
+
+#include "Logger.h"
+#include "Threads.h" // pat added
+
+using namespace std;
+
+Mutex gLogToLock;
+
+std::ostream& operator<<(std::ostream& os, std::ostringstream& ss)
+{
+ return os << ss.str();
+}
+
+Log::~Log()
+{
+ int old_state;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
+ int mlen = mStream.str().size();
+ int neednl = (mlen==0 || mStream.str()[mlen-1] != '\n');
+ const char *fmt = neednl ? "%s\n" : "%s";
+ ScopedLock lock(gLogToLock);
+ // The COUT() macro prevents messages from stomping each other but adds uninteresting thread numbers,
+ // so just use std::cout.
+ LOGPSRC(mCategory, mPriority, filename, line, fmt, mStream.str().c_str());
+ pthread_setcancelstate(old_state, NULL);
+}
+
+ostringstream& Log::get()
+{
+ return mStream;
+}
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Logger.h b/CommonLibs/Logger.h
new file mode 100644
index 0000000..e18ecfb
--- /dev/null
+++ b/CommonLibs/Logger.h
@@ -0,0 +1,94 @@
+/*
+* Copyright 2009, 2010 Free Software Foundation, Inc.
+* Copyright 2010 Kestrel Signal Processing, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <sstream>
+#include <string>
+
+extern "C" {
+#include <osmocom/core/logging.h>
+#include "debug.h"
+}
+
+/* Translation for old log statements */
+#ifndef LOGL_ALERT
+#define LOGL_ALERT LOGL_FATAL
+#endif
+#ifndef LOGL_ERR
+#define LOGL_ERR LOGL_ERROR
+#endif
+#ifndef LOGL_WARNING
+#define LOGL_WARNING LOGL_NOTICE
+#endif
+
+#define LOG(level) \
+ Log(DMAIN, LOGL_##level, __BASE_FILE__, __LINE__).get() << "[tid=" << pthread_self() << "] "
+
+#define LOGC(category, level) \
+ Log(category, LOGL_##level, __BASE_FILE__, __LINE__).get() << "[tid=" << pthread_self() << "] "
+
+#define LOGLV(category, level) \
+ Log(category, level, __BASE_FILE__, __LINE__).get() << "[tid=" << pthread_self() << "] "
+
+/**
+ A C++ stream-based thread-safe logger.
+ This object is NOT the global logger;
+ every log record is an object of this class.
+*/
+class Log {
+
+ public:
+
+ protected:
+
+ std::ostringstream mStream; ///< This is where we buffer up the log entry.
+ int mCategory; ///< Priority of current report.
+ int mPriority; ///< Category of current report.
+ const char *filename; ///< Source File Name of current report.
+ int line; ///< Line number in source file of current report.
+
+ public:
+
+ Log(int wCategory, int wPriority, const char* filename, int line)
+ : mCategory(wCategory), mPriority(wPriority),
+ filename(filename), line(line)
+ { }
+
+ // Most of the work is in the destructor.
+ /** The destructor actually generates the log entry. */
+ ~Log();
+
+ std::ostringstream& get();
+};
+
+std::ostream& operator<<(std::ostream& os, std::ostringstream& ss);
+
+#endif
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Makefile.am b/CommonLibs/Makefile.am
new file mode 100644
index 0000000..9fabcf1
--- /dev/null
+++ b/CommonLibs/Makefile.am
@@ -0,0 +1,54 @@
+#
+# Copyright 2008, 2009 Free Software Foundation, Inc.
+# Copyright 2011, 2012 Range Networks, Inc.
+#
+# This software is distributed under the terms of the GNU Public License.
+# See the COPYING file in the main directory for details.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
+AM_CXXFLAGS = -Wall -O3 -g -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
+AM_CFLAGS = $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
+
+noinst_LTLIBRARIES = libcommon.la
+
+libcommon_la_SOURCES = \
+ BitVector.cpp \
+ LinkedLists.cpp \
+ Sockets.cpp \
+ Threads.cpp \
+ Timeval.cpp \
+ Logger.cpp \
+ trx_vty.c \
+ debug.c
+libcommon_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS)
+
+noinst_HEADERS = \
+ BitVector.h \
+ PRBS.h \
+ Interthread.h \
+ LinkedLists.h \
+ Sockets.h \
+ Threads.h \
+ Timeval.h \
+ Vector.h \
+ Logger.h \
+ trx_vty.h \
+ debug.h \
+ osmo_signal.h \
+ config_defs.h
diff --git a/CommonLibs/PRBS.h b/CommonLibs/PRBS.h
new file mode 100644
index 0000000..0b7bbc3
--- /dev/null
+++ b/CommonLibs/PRBS.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+#define PRBS_H
+
+#include <stdint.h>
+#include <assert.h>
+
+/** Pseudo-random binary sequence (PRBS) generator (a Galois LFSR implementation). */
+class PRBS {
+public:
+
+ PRBS(unsigned wLen, uint64_t wCoeff, uint64_t wState = 0x01)
+ : mCoeff(wCoeff), mStartState(wState), mState(wState), mLen(wLen)
+ { assert(wLen<=64); }
+
+ /**@name Accessors */
+ //@{
+ uint64_t coeff() const { return mCoeff; }
+ uint64_t state() const { return mState; }
+ void state(uint64_t state) { mState = state & mask(); }
+ unsigned size() const { return mLen; }
+ //@}
+
+ /**
+ Calculate one bit of a PRBS
+ */
+ unsigned generateBit()
+ {
+ const unsigned result = mState & 0x01;
+ processBit(result);
+ return result;
+ }
+
+ /**
+ Update the generator state by one bit.
+ If you want to synchronize your PRBS to a known state, call this function
+ size() times passing your PRBS to it bit by bit.
+ */
+ void processBit(unsigned inBit)
+ {
+ mState >>= 1;
+ if (inBit) mState ^= mCoeff;
+ }
+
+ /** Return true when PRBS is wrapping through initial state */
+ bool isFinished() const { return mStartState == mState; }
+
+protected:
+
+ uint64_t mCoeff; ///< polynomial coefficients. LSB is zero exponent.
+ uint64_t mStartState; ///< initial shift register state.
+ uint64_t mState; ///< shift register state.
+ unsigned mLen; ///< number of bits used in shift register
+
+ /** Return mask for the state register */
+ uint64_t mask() const { return (mLen==64)?0xFFFFFFFFFFFFFFFFUL:((1<<mLen)-1); }
+
+};
+
+/**
+ A standard 9-bit based pseudorandom binary sequence (PRBS) generator.
+ Polynomial: x^9 + x^5 + 1
+*/
+class PRBS9 : public PRBS {
+ public:
+ PRBS9(uint64_t wState = 0x01)
+ : PRBS(9, 0x0110, wState)
+ {}
+};
+
+/**
+ A standard 15-bit based pseudorandom binary sequence (PRBS) generator.
+ Polynomial: x^15 + x^14 + 1
+*/
+class PRBS15 : public PRBS {
+public:
+ PRBS15(uint64_t wState = 0x01)
+ : PRBS(15, 0x6000, wState)
+ {}
+};
+
+/**
+ A standard 64-bit based pseudorandom binary sequence (PRBS) generator.
+ Polynomial: x^64 + x^63 + x^61 + x^60 + 1
+*/
+class PRBS64 : public PRBS {
+public:
+ PRBS64(uint64_t wState = 0x01)
+ : PRBS(64, 0xD800000000000000ULL, wState)
+ {}
+};
+
+#endif // PRBS_H
diff --git a/CommonLibs/Sockets.cpp b/CommonLibs/Sockets.cpp
new file mode 100644
index 0000000..ce8e3d5
--- /dev/null
+++ b/CommonLibs/Sockets.cpp
@@ -0,0 +1,287 @@
+/*
+* Copyright 2008, 2010 Free Software Foundation, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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 <config.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cstdio>
+#include <sys/select.h>
+
+#include "Threads.h"
+#include "Sockets.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+
+
+
+
+
+bool resolveAddress(struct sockaddr_in *address, const char *hostAndPort)
+{
+ assert(address);
+ assert(hostAndPort);
+ char *copy = strdup(hostAndPort);
+ char *colon = strchr(copy,':');
+ if (!colon) return false;
+ *colon = '\0';
+ char *host = copy;
+ unsigned port = strtol(colon+1,NULL,10);
+ bool retVal = resolveAddress(address,host,port);
+ free(copy);
+ return retVal;
+}
+
+bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port)
+{
+ assert(address);
+ assert(host);
+ // FIXME -- Need to ignore leading/trailing spaces in hostname.
+ struct hostent *hp;
+ int h_errno_local;
+#ifdef HAVE_GETHOSTBYNAME2_R
+ struct hostent hostData;
+ char tmpBuffer[2048];
+
+ // There are different flavors of gethostbyname_r(), but
+ // latest Linux use the following form:
+ if (gethostbyname2_r(host, AF_INET, &hostData, tmpBuffer, sizeof(tmpBuffer), &hp, &h_errno_local)!=0) {
+ CERR("WARNING -- gethostbyname2_r() failed for " << host << ", " << hstrerror(h_errno_local));
+ return false;
+ }
+#else
+ static Mutex sGethostbynameMutex;
+ // gethostbyname() is NOT thread-safe, so we should use a mutex here.
+ // Ideally it should be a global mutex for all non thread-safe socket
+ // operations and it should protect access to variables such as
+ // global h_errno.
+ sGethostbynameMutex.lock();
+ hp = gethostbyname(host);
+ h_errno_local = h_errno;
+ sGethostbynameMutex.unlock();
+#endif
+ if (hp==NULL) {
+ CERR("WARNING -- gethostbyname() failed for " << host << ", " << hstrerror(h_errno_local));
+ return false;
+ }
+ if (hp->h_addrtype != AF_INET) {
+ CERR("WARNING -- gethostbyname() resolved " << host << " to something other then AF_INET");
+ return false;
+ }
+ address->sin_family = hp->h_addrtype;
+ assert(sizeof(address->sin_addr) == hp->h_length);
+ memcpy(&(address->sin_addr), hp->h_addr_list[0], hp->h_length);
+ address->sin_port = htons(port);
+ return true;
+}
+
+
+
+DatagramSocket::DatagramSocket()
+{
+ memset(mDestination, 0, sizeof(mDestination));
+}
+
+
+
+
+
+void DatagramSocket::nonblocking()
+{
+ fcntl(mSocketFD,F_SETFL,O_NONBLOCK);
+}
+
+void DatagramSocket::blocking()
+{
+ fcntl(mSocketFD,F_SETFL,0);
+}
+
+void DatagramSocket::close()
+{
+ ::close(mSocketFD);
+}
+
+
+DatagramSocket::~DatagramSocket()
+{
+ close();
+}
+
+
+
+
+
+int DatagramSocket::write( const char * message, size_t length )
+{
+ assert(length<=MAX_UDP_LENGTH);
+ int retVal = sendto(mSocketFD, message, length, 0,
+ (struct sockaddr *)mDestination, addressSize());
+ if (retVal == -1 ) perror("DatagramSocket::write() failed");
+ return retVal;
+}
+
+int DatagramSocket::writeBack( const char * message, size_t length )
+{
+ assert(length<=MAX_UDP_LENGTH);
+ int retVal = sendto(mSocketFD, message, length, 0,
+ (struct sockaddr *)mSource, addressSize());
+ if (retVal == -1 ) perror("DatagramSocket::write() failed");
+ return retVal;
+}
+
+
+
+int DatagramSocket::write( const char * message)
+{
+ size_t length=strlen(message)+1;
+ return write(message,length);
+}
+
+int DatagramSocket::writeBack( const char * message)
+{
+ size_t length=strlen(message)+1;
+ return writeBack(message,length);
+}
+
+
+
+int DatagramSocket::send(const struct sockaddr* dest, const char * message, size_t length )
+{
+ assert(length<=MAX_UDP_LENGTH);
+ int retVal = sendto(mSocketFD, message, length, 0, dest, addressSize());
+ if (retVal == -1 ) perror("DatagramSocket::send() failed");
+ return retVal;
+}
+
+int DatagramSocket::send(const struct sockaddr* dest, const char * message)
+{
+ size_t length=strlen(message)+1;
+ return send(dest,message,length);
+}
+
+int DatagramSocket::read(char* buffer, size_t length)
+{
+ socklen_t addr_len = sizeof(mSource);
+ int rd_length = recvfrom(mSocketFD, (void *) buffer, length, 0,
+ (struct sockaddr*) &mSource, &addr_len);
+
+ if ((rd_length==-1) && (errno!=EAGAIN)) {
+ perror("DatagramSocket::read() failed");
+ throw SocketError();
+ }
+ return rd_length;
+}
+
+int DatagramSocket::read(char* buffer, size_t length, unsigned timeout)
+{
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(mSocketFD,&fds);
+ struct timeval tv;
+ tv.tv_sec = timeout/1000;
+ tv.tv_usec = (timeout%1000)*1000;
+ int sel = select(mSocketFD+1,&fds,NULL,NULL,&tv);
+ if (sel<0) {
+ perror("DatagramSocket::read() select() failed");
+ throw SocketError();
+ }
+ if (sel==0) return -1;
+ if (FD_ISSET(mSocketFD,&fds)) return read(buffer, length);
+ return -1;
+}
+
+
+
+
+
+
+UDPSocket::UDPSocket(const char *wSrcIP, unsigned short wSrcPort)
+ :DatagramSocket()
+{
+ open(wSrcPort, wSrcIP);
+}
+
+
+UDPSocket::UDPSocket(const char *wSrcIP, unsigned short wSrcPort,
+ const char *wDestIP, unsigned short wDestPort)
+ :DatagramSocket()
+{
+ open(wSrcPort, wSrcIP);
+ destination(wDestPort, wDestIP);
+}
+
+
+
+void UDPSocket::destination( unsigned short wDestPort, const char * wDestIP )
+{
+ resolveAddress((sockaddr_in*)mDestination, wDestIP, wDestPort );
+}
+
+
+void UDPSocket::open(unsigned short localPort, const char *wlocalIP)
+{
+ // create
+ mSocketFD = socket(AF_INET,SOCK_DGRAM,0);
+ if (mSocketFD<0) {
+ perror("socket() failed");
+ throw SocketError();
+ }
+
+ // pat added: This lets the socket be reused immediately, which is needed if OpenBTS crashes.
+ int on = 1;
+ setsockopt(mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+
+ // bind
+ struct sockaddr_in address;
+ size_t length = sizeof(address);
+ bzero(&address,length);
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = inet_addr(wlocalIP);
+ address.sin_port = htons(localPort);
+ if (bind(mSocketFD,(struct sockaddr*)&address,length)<0) {
+ perror("bind() failed");
+ throw SocketError();
+ }
+}
+
+
+
+unsigned short UDPSocket::port() const
+{
+ struct sockaddr_in name;
+ socklen_t nameSize = sizeof(name);
+ int retVal = getsockname(mSocketFD, (struct sockaddr*)&name, &nameSize);
+ if (retVal==-1) throw SocketError();
+ return ntohs(name.sin_port);
+}
+
+// vim:ts=4:sw=4
diff --git a/CommonLibs/Sockets.h b/CommonLibs/Sockets.h
new file mode 100644
index 0000000..71b8b22
--- /dev/null
+++ b/CommonLibs/Sockets.h
@@ -0,0 +1,173 @@
+/*
+* Copyright 2008, 2010 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+#ifndef SOCKETS_H
+#define SOCKETS_H
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <list>
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+
+
+
+
+
+#define MAX_UDP_LENGTH 1500
+
+/** A function to resolve IP host names. */
+bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port);
+
+/** Resolve an address of the form "<host>:<port>". */
+bool resolveAddress(struct sockaddr_in *address, const char *hostAndPort);
+
+/** An exception to throw when a critical socket operation fails. */
+class SocketError {};
+#define SOCKET_ERROR {throw SocketError(); }
+
+/** Abstract class for connectionless sockets. */
+class DatagramSocket {
+
+protected:
+
+ int mSocketFD; ///< underlying file descriptor
+ char mDestination[256]; ///< address to which packets are sent
+ char mSource[256]; ///< return address of most recent received packet
+
+public:
+
+ /** An almost-does-nothing constructor. */
+ DatagramSocket();
+
+ virtual ~DatagramSocket();
+
+ /** Return the address structure size for this socket type. */
+ virtual size_t addressSize() const = 0;
+
+ /**
+ Send a binary packet.
+ @param buffer The data bytes to send to mDestination.
+ @param length Number of bytes to send, or strlen(buffer) if defaulted to -1.
+ @return number of bytes written, or -1 on error.
+ */
+ int write( const char * buffer, size_t length);
+
+ /**
+ Send a C-style string packet.
+ @param buffer The data bytes to send to mDestination.
+ @return number of bytes written, or -1 on error.
+ */
+ int write( const char * buffer);
+
+ /**
+ Send a binary packet.
+ @param buffer The data bytes to send to mSource.
+ @param length Number of bytes to send, or strlen(buffer) if defaulted to -1.
+ @return number of bytes written, or -1 on error.
+ */
+ int writeBack(const char * buffer, size_t length);
+
+ /**
+ Send a C-style string packet.
+ @param buffer The data bytes to send to mSource.
+ @return number of bytes written, or -1 on error.
+ */
+ int writeBack(const char * buffer);
+
+
+ /**
+ Receive a packet.
+ @param buffer A char[MAX_UDP_LENGTH] procured by the caller.
+ @return The number of bytes received or -1 on non-blocking pass.
+ */
+ int read(char* buffer, size_t length);
+
+ /**
+ Receive a packet with a timeout.
+ @param buffer A char[MAX_UDP_LENGTH] procured by the caller.
+ @param maximum wait time in milliseconds
+ @return The number of bytes received or -1 on timeout.
+ */
+ int read(char* buffer, size_t length, unsigned timeout);
+
+
+ /** Send a packet to a given destination, other than the default. */
+ int send(const struct sockaddr *dest, const char * buffer, size_t length);
+
+ /** Send a C-style string to a given destination, other than the default. */
+ int send(const struct sockaddr *dest, const char * buffer);
+
+ /** Make the socket non-blocking. */
+ void nonblocking();
+
+ /** Make the socket blocking (the default). */
+ void blocking();
+
+ /** Close the socket. */
+ void close();
+
+};
+
+
+
+/** UDP/IP User Datagram Socket */
+class UDPSocket : public DatagramSocket {
+
+public:
+
+ /** Open a USP socket with an OS-assigned port and no default destination. */
+ UDPSocket(const char *localIP, unsigned short localPort);
+
+ /** Given a full specification, open the socket and set the dest address. */
+ UDPSocket(const char *localIP, unsigned short localPort,
+ const char *remoteIP, unsigned short remotePort);
+
+ /** Set the destination port. */
+ void destination( unsigned short wDestPort, const char * wDestIP );
+
+ /** Return the actual port number in use. */
+ unsigned short port() const;
+
+ /** Open and bind the UDP socket to a local port. */
+ void open(unsigned short localPort=0, const char *wlocalIP="127.0.0.1");
+
+ /** Give the return address of the most recently received packet. */
+ const struct sockaddr_in* source() const { return (const struct sockaddr_in*)mSource; }
+
+ size_t addressSize() const { return sizeof(struct sockaddr_in); }
+
+};
+
+#endif
+
+
+
+// vim:ts=4:sw=4
diff --git a/CommonLibs/Threads.cpp b/CommonLibs/Threads.cpp
new file mode 100644
index 0000000..2988e12
--- /dev/null
+++ b/CommonLibs/Threads.cpp
@@ -0,0 +1,140 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "Threads.h"
+#include "Timeval.h"
+#include "Logger.h"
+
+#ifndef gettid
+#include <sys/syscall.h>
+#define gettid() syscall(SYS_gettid)
+#endif
+
+
+using namespace std;
+
+
+
+
+Mutex gStreamLock; ///< Global lock to control access to cout and cerr.
+
+void lockCout()
+{
+ gStreamLock.lock();
+ Timeval entryTime;
+ cout << entryTime << " " << pthread_self() << ": ";
+}
+
+
+void unlockCout()
+{
+ cout << dec << endl << flush;
+ gStreamLock.unlock();
+}
+
+
+void lockCerr()
+{
+ gStreamLock.lock();
+ Timeval entryTime;
+ cerr << entryTime << " " << pthread_self() << ": ";
+}
+
+void unlockCerr()
+{
+ cerr << dec << endl << flush;
+ gStreamLock.unlock();
+}
+
+
+
+
+
+
+
+Mutex::Mutex()
+{
+ bool res;
+ res = pthread_mutexattr_init(&mAttribs);
+ assert(!res);
+ res = pthread_mutexattr_settype(&mAttribs,PTHREAD_MUTEX_RECURSIVE);
+ assert(!res);
+ res = pthread_mutex_init(&mMutex,&mAttribs);
+ assert(!res);
+}
+
+
+Mutex::~Mutex()
+{
+ pthread_mutex_destroy(&mMutex);
+ bool res = pthread_mutexattr_destroy(&mAttribs);
+ assert(!res);
+}
+
+
+
+
+/** Block for the signal up to the cancellation timeout. */
+void Signal::wait(Mutex& wMutex, unsigned timeout) const
+{
+ Timeval then(timeout);
+ struct timespec waitTime = then.timespec();
+ pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
+}
+
+void set_selfthread_name(const char *name)
+{
+ pthread_t selfid = pthread_self();
+ pid_t tid = gettid();
+ if (pthread_setname_np(selfid, name) == 0) {
+ LOG(INFO) << "Thread "<< selfid << " (task " << tid << ") set name: " << name;
+ } else {
+ char buf[256];
+ int err = errno;
+ char* err_str = strerror_r(err, buf, sizeof(buf));
+ LOG(NOTICE) << "Thread "<< selfid << " (task " << tid << ") set name \"" << name << "\" failed: (" << err << ") " << err_str;
+ }
+}
+
+void Thread::start(void *(*task)(void*), void *arg)
+{
+ assert(mThread==((pthread_t)0));
+ bool res;
+ // (pat) Moved initialization to constructor to avoid crash in destructor.
+ //res = pthread_attr_init(&mAttrib);
+ //assert(!res);
+ res = pthread_attr_setstacksize(&mAttrib, mStackSize);
+ assert(!res);
+ res = pthread_create(&mThread, &mAttrib, task, arg);
+ assert(!res);
+}
+
+
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Threads.h b/CommonLibs/Threads.h
new file mode 100644
index 0000000..857c5d9
--- /dev/null
+++ b/CommonLibs/Threads.h
@@ -0,0 +1,192 @@
+/*
+* Copyright 2008, 2011 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+#ifndef THREADS_H
+#define THREADS_H
+
+#include <pthread.h>
+#include <iostream>
+#include <assert.h>
+#include <unistd.h>
+
+class Mutex;
+
+
+/**@name Multithreaded access for standard streams. */
+//@{
+
+/**@name Functions for gStreamLock. */
+//@{
+extern Mutex gStreamLock; ///< global lock for cout and cerr
+void lockCerr(); ///< call prior to writing cerr
+void unlockCerr(); ///< call after writing cerr
+void lockCout(); ///< call prior to writing cout
+void unlockCout(); ///< call after writing cout
+//@}
+
+/**@name Macros for standard messages. */
+//@{
+#define COUT(text) { lockCout(); std::cout << text; unlockCout(); }
+#define CERR(text) { lockCerr(); std::cerr << __FILE__ << ":" << __LINE__ << ": " << text; unlockCerr(); }
+#ifdef NDEBUG
+#define DCOUT(text) {}
+#define OBJDCOUT(text) {}
+#else
+#define DCOUT(text) { COUT(__FILE__ << ":" << __LINE__ << " " << text); }
+#define OBJDCOUT(text) { DCOUT(this << " " << text); }
+#endif
+//@}
+//@}
+
+
+
+/**@defgroup C++ wrappers for pthread mechanisms. */
+//@{
+
+/** A class for recursive mutexes based on pthread_mutex. */
+class Mutex {
+
+ private:
+
+ pthread_mutex_t mMutex;
+ pthread_mutexattr_t mAttribs;
+
+ public:
+
+ Mutex();
+
+ ~Mutex();
+
+ void lock() { pthread_mutex_lock(&mMutex); }
+
+ bool trylock() { return pthread_mutex_trylock(&mMutex)==0; }
+
+ void unlock() { pthread_mutex_unlock(&mMutex); }
+
+ friend class Signal;
+
+};
+
+
+class ScopedLock {
+
+ private:
+ Mutex& mMutex;
+
+ public:
+ ScopedLock(Mutex& wMutex) :mMutex(wMutex) { mMutex.lock(); }
+ ~ScopedLock() { mMutex.unlock(); }
+
+};
+
+
+
+
+/** A C++ interthread signal based on pthread condition variables. */
+class Signal {
+
+ private:
+
+ mutable pthread_cond_t mSignal;
+
+ public:
+
+ Signal() { int s = pthread_cond_init(&mSignal,NULL); assert(!s); }
+
+ ~Signal() { pthread_cond_destroy(&mSignal); }
+
+ /**
+ Block for the signal up to the cancellation timeout.
+ Under Linux, spurious returns are possible.
+ */
+ void wait(Mutex& wMutex, unsigned timeout) const;
+
+ /**
+ Block for the signal.
+ Under Linux, spurious returns are possible.
+ */
+ void wait(Mutex& wMutex) const
+ { pthread_cond_wait(&mSignal,&wMutex.mMutex); }
+
+ void signal() { pthread_cond_signal(&mSignal); }
+
+ void broadcast() { pthread_cond_broadcast(&mSignal); }
+
+};
+
+
+
+#define START_THREAD(thread,function,argument) \
+ thread.start((void *(*)(void*))function, (void*)argument);
+
+void set_selfthread_name(const char *name);
+
+/** A C++ wrapper for pthread threads. */
+class Thread {
+
+ private:
+
+ pthread_t mThread;
+ pthread_attr_t mAttrib;
+ // FIXME -- Can this be reduced now?
+ size_t mStackSize;
+
+
+ public:
+
+ /** Create a thread in a non-running state. */
+ Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) {
+ pthread_attr_init(&mAttrib); // (pat) moved this here.
+ mStackSize=wStackSize;
+ }
+
+ /**
+ Destroy the Thread.
+ It should be stopped and joined.
+ */
+ // (pat) If the Thread is destroyed without being started, then mAttrib is undefined. Oops.
+ ~Thread() { pthread_attr_destroy(&mAttrib); }
+
+
+ /** Start the thread on a task. */
+ void start(void *(*task)(void*), void *arg);
+
+ /** Join a thread that will stop on its own. */
+ void join() {
+ if (mThread) {
+ int s = pthread_join(mThread, NULL);
+ assert(!s);
+ }
+ }
+
+ /** Send cancelation to thread */
+ void cancel() { pthread_cancel(mThread); }
+};
+
+
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Timeval.cpp b/CommonLibs/Timeval.cpp
new file mode 100644
index 0000000..50ce05d
--- /dev/null
+++ b/CommonLibs/Timeval.cpp
@@ -0,0 +1,98 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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 "Timeval.h"
+
+using namespace std;
+
+void Timeval::future(unsigned offset)
+{
+ now();
+ unsigned sec = offset/1000;
+ unsigned msec = offset%1000;
+ mTimeval.tv_usec += msec*1000;
+ mTimeval.tv_sec += sec;
+ if (mTimeval.tv_usec>1000000) {
+ mTimeval.tv_usec -= 1000000;
+ mTimeval.tv_sec += 1;
+ }
+}
+
+
+struct timespec Timeval::timespec() const
+{
+ struct timespec retVal;
+ retVal.tv_sec = mTimeval.tv_sec;
+ retVal.tv_nsec = 1000 * (long)mTimeval.tv_usec;
+ return retVal;
+}
+
+
+bool Timeval::passed() const
+{
+ Timeval nowTime;
+ if (nowTime.mTimeval.tv_sec < mTimeval.tv_sec) return false;
+ if (nowTime.mTimeval.tv_sec > mTimeval.tv_sec) return true;
+ if (nowTime.mTimeval.tv_usec > mTimeval.tv_usec) return true;
+ return false;
+}
+
+double Timeval::seconds() const
+{
+ return ((double)mTimeval.tv_sec) + 1e-6*((double)mTimeval.tv_usec);
+}
+
+
+
+long Timeval::delta(const Timeval& other) const
+{
+ // 2^31 milliseconds is just over 4 years.
+ int32_t deltaS = other.sec() - sec();
+ int32_t deltaUs = other.usec() - usec();
+ return 1000*deltaS + deltaUs/1000;
+}
+
+
+
+
+ostream& operator<<(ostream& os, const Timeval& tv)
+{
+ os.setf( ios::fixed, ios::floatfield );
+ os << tv.seconds();
+ return os;
+}
+
+
+ostream& operator<<(ostream& os, const struct timespec& ts)
+{
+ os << ts.tv_sec << "," << ts.tv_nsec;
+ return os;
+}
+
+
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Timeval.h b/CommonLibs/Timeval.h
new file mode 100644
index 0000000..c497864
--- /dev/null
+++ b/CommonLibs/Timeval.h
@@ -0,0 +1,105 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+#ifndef TIMEVAL_H
+#define TIMEVAL_H
+
+#include <stdint.h>
+#include "sys/time.h"
+#include <iostream>
+#include <unistd.h>
+
+
+
+/** A wrapper on usleep to sleep for milliseconds. */
+inline void msleep(long v) { usleep(v*1000); }
+
+
+/** A C++ wrapper for struct timeval. */
+class Timeval {
+
+ private:
+
+ struct timeval mTimeval;
+
+ public:
+
+ /** Set the value to gettimeofday. */
+ void now() { gettimeofday(&mTimeval,NULL); }
+
+ /** Set the value to gettimeofday plus an offset. */
+ void future(unsigned ms);
+
+ //@{
+ Timeval(unsigned sec, unsigned usec)
+ {
+ mTimeval.tv_sec = sec;
+ mTimeval.tv_usec = usec;
+ }
+
+ Timeval(const struct timeval& wTimeval)
+ :mTimeval(wTimeval)
+ {}
+
+ /**
+ Create a Timeval offset into the future.
+ @param offset milliseconds
+ */
+ Timeval(unsigned offset=0) { future(offset); }
+ //@}
+
+ /** Convert to a struct timespec. */
+ struct timespec timespec() const;
+
+ /** Return total seconds. */
+ double seconds() const;
+
+ uint32_t sec() const { return mTimeval.tv_sec; }
+ uint32_t usec() const { return mTimeval.tv_usec; }
+
+ /** Return differnce from other (other-self), in ms. */
+ long delta(const Timeval& other) const;
+
+ /** Elapsed time in ms. */
+ long elapsed() const { return delta(Timeval()); }
+
+ /** Remaining time in ms. */
+ long remaining() const { return -elapsed(); }
+
+ /** Return true if the time has passed, as per gettimeofday. */
+ bool passed() const;
+
+ /** Add a given number of minutes to the time. */
+ void addMinutes(unsigned minutes) { mTimeval.tv_sec += minutes*60; }
+
+};
+
+std::ostream& operator<<(std::ostream& os, const Timeval&);
+
+std::ostream& operator<<(std::ostream& os, const struct timespec&);
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Vector.h b/CommonLibs/Vector.h
new file mode 100644
index 0000000..9119683
--- /dev/null
+++ b/CommonLibs/Vector.h
@@ -0,0 +1,308 @@
+/**@file Simplified Vector template with aliases. */
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+
+
+#ifndef VECTOR_H
+#define VECTOR_H
+
+#include <string.h>
+#include <iostream>
+#include <assert.h>
+// We cant use Logger.h in this file...
+extern int gVectorDebug;
+#define BVDEBUG(msg) if (gVectorDebug) {std::cout << msg;}
+
+
+
+/**
+ A simplified Vector template with aliases.
+ Unlike std::vector, this class does not support dynamic resizing.
+ Unlike std::vector, this class does support "aliases" and subvectors.
+*/
+template <class T> class Vector {
+
+ // TODO -- Replace memcpy calls with for-loops.
+
+ public:
+
+ /**@name Iterator types. */
+ //@{
+ typedef T* iterator;
+ typedef const T* const_iterator;
+ //@}
+
+ protected:
+
+ T* mData; ///< allocated data block, if any
+ T* mStart; ///< start of useful data
+ T* mEnd; ///< end of useful data + 1
+
+ public:
+
+ /****
+ char *inspect() {
+ static char buf[100];
+ sprintf(buf," mData=%p mStart=%p mEnd=%p ",mData,mStart,mEnd);
+ return buf;
+ }
+ ***/
+
+ /** Return the size of the Vector. */
+ size_t size() const
+ {
+ assert(mStart>=mData);
+ assert(mEnd>=mStart);
+ return mEnd - mStart;
+ }
+
+ /** Return size in bytes. */
+ size_t bytes() const { return size()*sizeof(T); }
+
+ /** Change the size of the Vector, discarding content. */
+ void resize(size_t newSize)
+ {
+ if (mData!=NULL) delete[] mData;
+ if (newSize==0) mData=NULL;
+ else mData = new T[newSize];
+ mStart = mData;
+ mEnd = mStart + newSize;
+ }
+
+ /** Reduce addressable size of the Vector, keeping content. */
+ void shrink(size_t newSize)
+ {
+ assert(newSize <= mEnd - mStart);
+ mEnd = mStart + newSize;
+ }
+
+ /** Release memory and clear pointers. */
+ void clear() { resize(0); }
+
+
+ /** Copy data from another vector. */
+ void clone(const Vector<T>& other)
+ {
+ resize(other.size());
+ other.copyTo(*this);
+ }
+
+
+
+
+ //@{
+
+ /** Build an empty Vector of a given size. */
+ Vector(size_t wSize=0):mData(NULL) { resize(wSize); }
+
+ /** Build a Vector by moving another. */
+ Vector(Vector<T>&& other)
+ :mData(other.mData),mStart(other.mStart),mEnd(other.mEnd)
+ { other.mData=NULL; }
+
+ /** Build a Vector by copying another. */
+ Vector(const Vector<T>& other):mData(NULL) { clone(other); }
+
+ /** Build a Vector with explicit values. */
+ Vector(T* wData, T* wStart, T* wEnd)
+ :mData(wData),mStart(wStart),mEnd(wEnd)
+ { }
+
+ /** Build a vector from an existing block, NOT to be deleted upon destruction. */
+ Vector(T* wStart, size_t span)
+ :mData(NULL),mStart(wStart),mEnd(wStart+span)
+ { }
+
+ /** Build a Vector by concatenation. */
+ Vector(const Vector<T>& other1, const Vector<T>& other2)
+ :mData(NULL)
+ {
+ resize(other1.size()+other2.size());
+ memcpy(mStart, other1.mStart, other1.bytes());
+ memcpy(mStart+other1.size(), other2.mStart, other2.bytes());
+ }
+
+ //@}
+
+ /** Destroy a Vector, deleting held memory. */
+ ~Vector() { clear(); }
+
+
+
+
+ //@{
+
+ /** Assign from another Vector, shifting ownership. */
+ void operator=(Vector<T>& other)
+ {
+ clear();
+ mData=other.mData;
+ mStart=other.mStart;
+ mEnd=other.mEnd;
+ other.mData=NULL;
+ }
+
+ /** Assign from another Vector, copying. */
+ void operator=(const Vector<T>& other) { clone(other); }
+
+ //@}
+
+
+ //@{
+
+ /** Return an alias to a segment of this Vector. */
+ Vector<T> segment(size_t start, size_t span)
+ {
+ T* wStart = mStart + start;
+ T* wEnd = wStart + span;
+ assert(wEnd<=mEnd);
+ return Vector<T>(NULL,wStart,wEnd);
+ }
+
+ /** Return an alias to a segment of this Vector. */
+ const Vector<T> segment(size_t start, size_t span) const
+ {
+ T* wStart = mStart + start;
+ T* wEnd = wStart + span;
+ assert(wEnd<=mEnd);
+ return Vector<T>(NULL,wStart,wEnd);
+ }
+
+ Vector<T> head(size_t span) { return segment(0,span); }
+ const Vector<T> head(size_t span) const { return segment(0,span); }
+ Vector<T> tail(size_t start) { return segment(start,size()-start); }
+ const Vector<T> tail(size_t start) const { return segment(start,size()-start); }
+
+ /**
+ Copy part of this Vector to a segment of another Vector.
+ @param other The other vector.
+ @param start The start point in the other vector.
+ @param span The number of elements to copy.
+ */
+ void copyToSegment(Vector<T>& other, size_t start, size_t span) const
+ {
+ unsigned int i;
+ T* dst = other.mStart + start;
+ T* src = mStart;
+ assert(dst+span<=other.mEnd);
+ assert(mStart+span<=mEnd);
+ for (i = 0; i < span; i++, src++, dst++)
+ *dst = *src;
+ /*TODO if not non-trivially copyable type class, optimize:
+ memcpy(dst,mStart,span*sizeof(T)); */
+ }
+
+ /** Copy all of this Vector to a segment of another Vector. */
+ void copyToSegment(Vector<T>& other, size_t start=0) const { copyToSegment(other,start,size()); }
+
+ void copyTo(Vector<T>& other) const { copyToSegment(other,0,size()); }
+
+ /**
+ Copy a segment of this vector into another.
+ @param other The other vector (to copt into starting at 0.)
+ @param start The start point in this vector.
+ @param span The number of elements to copy.
+ */
+ 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);
+ memcpy(other.mStart,base,span*sizeof(T));
+ }
+
+ /**
+ Move (copy) a segment of this vector into a different position in the vector
+ @param from Start point from which to copy.
+ @param to Start point to which to copy.
+ @param span The number of elements to copy.
+ */
+ void segmentMove(size_t from, size_t to, size_t span)
+ {
+ const T* baseFrom = mStart + from;
+ T* baseTo = mStart + to;
+ assert(baseFrom+span<=mEnd);
+ assert(baseTo+span<=mEnd);
+ memmove(baseTo,baseFrom,span*sizeof(T));
+ }
+
+ void fill(const T& val)
+ {
+ T* dp=mStart;
+ while (dp<mEnd) *dp++=val;
+ }
+
+ void fill(const T& val, unsigned start, unsigned length)
+ {
+ T* dp=mStart+start;
+ T* end=dp+length;
+ assert(end<=mEnd);
+ while (dp<end) *dp++=val;
+ }
+
+
+ //@}
+
+
+ //@{
+
+ T& operator[](size_t index)
+ {
+ assert(mStart+index<mEnd);
+ return mStart[index];
+ }
+
+ const T& operator[](size_t index) const
+ {
+ assert(mStart+index<mEnd);
+ return mStart[index];
+ }
+
+ const T* begin() const { return mStart; }
+ T* begin() { return mStart; }
+ const T* end() const { return mEnd; }
+ T* end() { return mEnd; }
+ bool isOwner() { return !!mData; } // Do we own any memory ourselves?
+ //@}
+
+
+};
+
+
+
+
+/** Basic print operator for Vector objects. */
+template <class T>
+std::ostream& operator<<(std::ostream& os, const Vector<T>& v)
+{
+ for (unsigned i=0; i<v.size(); i++) os << v[i] << " ";
+ return os;
+}
+
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/config_defs.h b/CommonLibs/config_defs.h
new file mode 100644
index 0000000..8626166
--- /dev/null
+++ b/CommonLibs/config_defs.h
@@ -0,0 +1,20 @@
+#pragma once
+
+/*
+ * This file contains structures used by both VTY (C, dir CommonLibs) and
+ * osmo-trx (CXX, dir Transceiver52)
+ */
+
+enum FillerType {
+ FILLER_DUMMY,
+ FILLER_ZERO,
+ FILLER_NORM_RAND,
+ FILLER_EDGE_RAND,
+ FILLER_ACCESS_RAND,
+};
+
+enum ReferenceType {
+ REF_INTERNAL,
+ REF_EXTERNAL,
+ REF_GPS,
+};
diff --git a/CommonLibs/debug.c b/CommonLibs/debug.c
new file mode 100644
index 0000000..c6de21a
--- /dev/null
+++ b/CommonLibs/debug.c
@@ -0,0 +1,30 @@
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include "debug.h"
+
+/* default categories */
+static const struct log_info_cat default_categories[] = {
+ [DMAIN] = {
+ .name = "DMAIN",
+ .description = "Main generic category",
+ .color = NULL,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DDEV] = {
+ .name = "DDEV",
+ .description = "Device/Driver specific code",
+ .color = NULL,
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DLMS] = {
+ .name = "DLMS",
+ .description = "Logging from within LimeSuite itself",
+ .color = NULL,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+};
+
+const struct log_info log_info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
diff --git a/CommonLibs/debug.h b/CommonLibs/debug.h
new file mode 100644
index 0000000..f8f6239
--- /dev/null
+++ b/CommonLibs/debug.h
@@ -0,0 +1,10 @@
+#pragma once
+
+extern const struct log_info log_info;
+
+/* Debug Areas of the code */
+enum {
+ DMAIN,
+ DDEV,
+ DLMS,
+};
diff --git a/CommonLibs/osmo_signal.h b/CommonLibs/osmo_signal.h
new file mode 100644
index 0000000..00b8097
--- /dev/null
+++ b/CommonLibs/osmo_signal.h
@@ -0,0 +1,35 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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 <osmocom/core/signal.h>
+
+/* Signalling subsystems */
+enum signal_subsystems {
+ SS_TRANSC,
+};
+
+/* SS_TRANSC signals */
+enum SS_TRANSC {
+ S_TRANSC_STOP_REQUIRED, /* Transceiver fatal error, it should be stopped */
+};
diff --git a/CommonLibs/trx_vty.c b/CommonLibs/trx_vty.c
new file mode 100644
index 0000000..45b58eb
--- /dev/null
+++ b/CommonLibs/trx_vty.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2012-2017 sysmocom - s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+
+#include "trx_vty.h"
+#include "../config.h"
+
+static struct trx_ctx* g_trx_ctx;
+
+static 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" },
+ { 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
+ * to this function, in case we'd like to retrieve the global
+ * trx instance from the vty at some point in the future. But
+ * until then, just return the global pointer, which should have been
+ * initialized by trx_vty_init().
+ */
+ OSMO_ASSERT(g_trx_ctx);
+ return g_trx_ctx;
+}
+
+enum trx_vty_node {
+ TRX_NODE = _LAST_OSMOVTY_NODE + 1,
+ CHAN_NODE,
+};
+
+static struct cmd_node trx_node = {
+ TRX_NODE,
+ "%s(config-trx)# ",
+ 1,
+};
+
+static struct cmd_node chan_node = {
+ CHAN_NODE,
+ "%s(config-trx-chan)# ",
+ 1,
+};
+
+DEFUN(cfg_trx, cfg_trx_cmd,
+ "trx",
+ "Configure the TRX\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ if (!trx)
+ return CMD_WARNING;
+
+ vty->node = TRX_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bind_ip, cfg_bind_ip_cmd,
+ "bind-ip A.B.C.D",
+ "Set the IP address for the local bind\n"
+ "IPv4 Address\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ osmo_talloc_replace_string(trx, &trx->cfg.bind_addr, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_remote_ip, cfg_remote_ip_cmd,
+ "remote-ip A.B.C.D",
+ "Set the IP address for the remote BTS\n"
+ "IPv4 Address\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ osmo_talloc_replace_string(trx, &trx->cfg.remote_addr, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_base_port, cfg_base_port_cmd,
+ "base-port <1-65535>",
+ "Set the TRX Base Port\n"
+ "TRX Base Port\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.base_port = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_dev_args, cfg_dev_args_cmd,
+ "dev-args DESC",
+ "Set the device-specific arguments to pass to the device\n"
+ "Device-specific arguments\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ osmo_talloc_replace_string(trx, &trx->cfg.dev_args, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_tx_sps, cfg_tx_sps_cmd,
+ "tx-sps (1|4)",
+ "Set the Tx Samples-per-Symbol\n"
+ "Tx Samples-per-Symbol\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.tx_sps = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_rx_sps, cfg_rx_sps_cmd,
+ "rx-sps (1|4)",
+ "Set the Rx Samples-per-Symbol\n"
+ "Rx Samples-per-Symbol\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.rx_sps = atoi(argv[0]);
+
+ 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 external 10 MHz reference\n"
+ "Enable GPSDO reference\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.clock_ref = get_string_value(clock_ref_names, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_multi_arfcn, cfg_multi_arfcn_cmd,
+ "multi-arfcn (disable|enable)",
+ "Enable multi-ARFCN transceiver (default=disable)\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ if (strcmp("disable", argv[0]) == 0) {
+ trx->cfg.multi_arfcn = false;
+ } else if (strcmp("enable", argv[0]) == 0) {
+ trx->cfg.multi_arfcn = true;
+ } else {
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_offset, cfg_offset_cmd,
+ "offset FLOAT",
+ "Set the baseband frequency offset (default=0, auto)\n"
+ "Baseband Frequency Offset\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.offset = atof(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_rssi_offset, cfg_rssi_offset_cmd,
+ "rssi-offset FLOAT",
+ "Set the RSSI to dBm offset in dB (default=0)\n"
+ "RSSI to dBm offset in dB\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.rssi_offset = atof(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_swap_channels, cfg_swap_channels_cmd,
+ "swap-channels (disable|enable)",
+ "Swap channels (default=disable)\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ if (strcmp("disable", argv[0]) == 0) {
+ trx->cfg.swap_channels = false;
+ } else if (strcmp("enable", argv[0]) == 0) {
+ trx->cfg.swap_channels = true;
+ } else {
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_egprs, cfg_egprs_cmd,
+ "egprs (disable|enable)",
+ "Enable EDGE receiver (default=disable)\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ if (strcmp("disable", argv[0]) == 0) {
+ 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;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_rt_prio, cfg_rt_prio_cmd,
+ "rt-prio <1-32>",
+ "Set the SCHED_RR real-time priority\n"
+ "Real time priority\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.sched_rr = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_filler, cfg_filler_cmd,
+ "filler dummy",
+ "Enable C0 filler table\n"
+ "Dummy method\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx->cfg.filler = FILLER_DUMMY;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_chan, cfg_chan_cmd,
+ "chan <0-100>",
+ "Select a channel to configure\n"
+ "Channel index\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+ int idx = atoi(argv[0]);
+
+ if (idx >= TRX_CHAN_MAX) {
+ vty_out(vty, "Chan list full.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (trx->cfg.num_chans < idx) { /* Unexisting or creating non-consecutive */
+ vty_out(vty, "Non-existent or non-consecutive chan %d.%s",
+ idx, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (trx->cfg.num_chans == idx) { /* creating it */
+ trx->cfg.num_chans++;
+ trx->cfg.chans[idx].trx = trx;
+ trx->cfg.chans[idx].idx = idx;
+ }
+
+ vty->node = CHAN_NODE;
+ vty->index = &trx->cfg.chans[idx];
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_chan_rx_path, cfg_chan_rx_path_cmd,
+ "rx-path NAME",
+ "Set the Rx Path\n"
+ "Rx Path name\n")
+{
+ struct trx_chan *chan = vty->index;
+
+ osmo_talloc_replace_string(chan->trx, &chan->rx_path, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_chan_tx_path, cfg_chan_tx_path_cmd,
+ "tx-path NAME",
+ "Set the Tx Path\n"
+ "Tx Path name\n")
+{
+ struct trx_chan *chan = vty->index;
+
+ osmo_talloc_replace_string(chan->trx, &chan->tx_path, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+static int dummy_config_write(struct vty *v)
+{
+ return CMD_SUCCESS;
+}
+
+static int config_write_trx(struct vty *vty)
+{
+ struct trx_chan *chan;
+ int i;
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ vty_out(vty, "trx%s", VTY_NEWLINE);
+ if (trx->cfg.bind_addr)
+ vty_out(vty, " bind-ip %s%s", trx->cfg.bind_addr, VTY_NEWLINE);
+ if (trx->cfg.remote_addr)
+ vty_out(vty, " remote-ip %s%s", trx->cfg.remote_addr, VTY_NEWLINE);
+ if (trx->cfg.base_port != DEFAULT_TRX_PORT)
+ vty_out(vty, " base-port %u%s", trx->cfg.base_port, VTY_NEWLINE);
+ if (trx->cfg.dev_args)
+ vty_out(vty, " dev-args %s%s", trx->cfg.dev_args, VTY_NEWLINE);
+ if (trx->cfg.tx_sps != DEFAULT_TX_SPS)
+ vty_out(vty, " tx-sps %u%s", trx->cfg.tx_sps, VTY_NEWLINE);
+ 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);
+ 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);
+ if (trx->cfg.sched_rr != 0)
+ vty_out(vty, " rt-prio %u%s", trx->cfg.sched_rr, VTY_NEWLINE);
+
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ chan = &trx->cfg.chans[i];
+ vty_out(vty, " chan %u%s", chan->idx, VTY_NEWLINE);
+ if (chan->rx_path)
+ vty_out(vty, " rx-path %s%s", chan->rx_path, VTY_NEWLINE);
+ if (chan->tx_path)
+ vty_out(vty, " tx-path %s%s", chan->tx_path, VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct trx_ctx *trx)
+{
+ struct trx_chan *chan;
+ int i;
+ vty_out(vty, "TRX Config:%s", VTY_NEWLINE);
+ vty_out(vty, " Local IP: %s%s", trx->cfg.bind_addr, VTY_NEWLINE);
+ vty_out(vty, " Remote IP: %s%s", trx->cfg.remote_addr, VTY_NEWLINE);
+ vty_out(vty, " TRX Base Port: %u%s", trx->cfg.base_port, VTY_NEWLINE);
+ 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, " 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);
+ vty_out(vty, " RSSI to dBm offset: %f%s", trx->cfg.rssi_offset, VTY_NEWLINE);
+ vty_out(vty, " Swap channels: %s%s", trx->cfg.swap_channels ? "Enabled" : "Disabled", VTY_NEWLINE);
+ vty_out(vty, " EDGE support: %s%s", trx->cfg.egprs ? "Enabled" : "Disabled", VTY_NEWLINE);
+ vty_out(vty, " Real Time Priority: %u (%s)%s", trx->cfg.sched_rr,
+ trx->cfg.sched_rr ? "Enabled" : "Disabled", VTY_NEWLINE);
+ vty_out(vty, " Channels: %u%s", trx->cfg.num_chans, VTY_NEWLINE);
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ chan = &trx->cfg.chans[i];
+ vty_out(vty, " Channel %u:%s", chan->idx, VTY_NEWLINE);
+ if (chan->rx_path)
+ vty_out(vty, " Rx Path: %s%s", chan->rx_path, VTY_NEWLINE);
+ if (chan->tx_path)
+ vty_out(vty, " Tx Path: %s%s", chan->tx_path, VTY_NEWLINE);
+ }
+}
+
+DEFUN(show_trx, show_trx_cmd,
+ "show trx",
+ SHOW_STR "Display information on the TRX\n")
+{
+ struct trx_ctx *trx = trx_from_vty(vty);
+
+ trx_dump_vty(vty, trx);
+
+ return CMD_SUCCESS;
+}
+
+static int trx_vty_is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case TRX_NODE:
+ case CHAN_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int trx_vty_go_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case TRX_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ vty->index_sub = NULL;
+ break;
+ case CHAN_NODE:
+ vty->node = TRX_NODE;
+ vty->index = NULL;
+ vty->index_sub = NULL;
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ vty->index_sub = NULL;
+ }
+
+ return vty->node;
+}
+
+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) 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"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+struct vty_app_info g_vty_info = {
+ .name = "OsmoTRX",
+ .version = PACKAGE_VERSION,
+ .copyright = trx_copyright,
+ .go_parent_cb = trx_vty_go_parent,
+ .is_config_node = trx_vty_is_config_node,
+};
+
+struct trx_ctx *vty_trx_ctx_alloc(void *talloc_ctx)
+{
+ struct trx_ctx * trx = talloc_zero(talloc_ctx, struct trx_ctx);
+
+ trx->cfg.bind_addr = talloc_strdup(trx, DEFAULT_TRX_IP);
+ trx->cfg.remote_addr = talloc_strdup(trx, DEFAULT_TRX_IP);
+ trx->cfg.base_port = DEFAULT_TRX_PORT;
+ trx->cfg.tx_sps = DEFAULT_TX_SPS;
+ trx->cfg.rx_sps = DEFAULT_RX_SPS;
+ trx->cfg.filler = FILLER_ZERO;
+
+ return trx;
+}
+
+int trx_vty_init(struct trx_ctx* trx)
+{
+ g_trx_ctx = trx;
+ install_element_ve(&show_trx_cmd);
+
+ install_element(CONFIG_NODE, &cfg_trx_cmd);
+
+ install_node(&trx_node, config_write_trx);
+ install_element(TRX_NODE, &cfg_bind_ip_cmd);
+ install_element(TRX_NODE, &cfg_remote_ip_cmd);
+ install_element(TRX_NODE, &cfg_base_port_cmd);
+ 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_rssi_offset_cmd);
+ install_element(TRX_NODE, &cfg_swap_channels_cmd);
+ install_element(TRX_NODE, &cfg_egprs_cmd);
+ install_element(TRX_NODE, &cfg_rt_prio_cmd);
+ install_element(TRX_NODE, &cfg_filler_cmd);
+
+ install_element(TRX_NODE, &cfg_chan_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);
+
+ return 0;
+}
diff --git a/CommonLibs/trx_vty.h b/CommonLibs/trx_vty.h
new file mode 100644
index 0000000..c921722
--- /dev/null
+++ b/CommonLibs/trx_vty.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <osmocom/vty/command.h>
+
+#include "config_defs.h"
+
+extern struct vty_app_info g_vty_info;
+
+#define TRX_CHAN_MAX 8
+
+/* Samples-per-symbol for downlink path
+ * 4 - Uses precision modulator (more computation, less distortion)
+ * 1 - Uses minimized modulator (less computation, more distortion)
+ *
+ * Other values are invalid. Receive path (uplink) is always
+ * downsampled to 1 sps. Default to 4 sps for all cases.
+ */
+#define DEFAULT_TX_SPS 4
+
+/*
+ * Samples-per-symbol for uplink (receiver) path
+ * Do not modify this value. EDGE configures 4 sps automatically on
+ * B200/B210 devices only. Use of 4 sps on the receive path for other
+ * configurations is not supported.
+ */
+#define DEFAULT_RX_SPS 1
+
+/* Default configuration parameters */
+#define DEFAULT_TRX_PORT 5700
+#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 egprs;
+ unsigned int sched_rr;
+ unsigned int num_chans;
+ struct trx_chan chans[TRX_CHAN_MAX];
+ } cfg;
+};
+
+int trx_vty_init(struct trx_ctx* trx);
+struct trx_ctx *vty_trx_ctx_alloc(void *talloc_ctx);
diff --git a/GSM/GSMCommon.cpp b/GSM/GSMCommon.cpp
new file mode 100644
index 0000000..711ca70
--- /dev/null
+++ b/GSM/GSMCommon.cpp
@@ -0,0 +1,93 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+* Copyright 2011 Range Networks, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "GSMCommon.h"
+
+using namespace GSM;
+using namespace std;
+
+
+const BitVector GSM::gTrainingSequence[] = {
+ BitVector("00100101110000100010010111"),
+ BitVector("00101101110111100010110111"),
+ BitVector("01000011101110100100001110"),
+ BitVector("01000111101101000100011110"),
+ BitVector("00011010111001000001101011"),
+ BitVector("01001110101100000100111010"),
+ BitVector("10100111110110001010011111"),
+ BitVector("11101111000100101110111100"),
+};
+
+const BitVector GSM::gEdgeTrainingSequence[] = {
+ BitVector("111111001111111001111001001001111111111111001111111111001111111001111001001001"),
+ BitVector("111111001111001001111001001001111001001001001111111111001111001001111001001001"),
+ BitVector("111001111111111111001001001111001001001111001111111001111111111111001001001111"),
+ BitVector("111001111111111001001001001111001001111001111111111001111111111001001001001111"),
+ BitVector("111111111001001111001111001001001111111001111111111111111001001111001111001001"),
+ BitVector("111001111111001001001111001111001001111111111111111001111111001001001111001111"),
+ BitVector("001111001111111001001001001001111001001111111111001111001111111001001001001001"),
+ BitVector("001001001111001001001001111111111001111111001111001001001111001001001001111111"),
+};
+
+const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
+
+/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
+const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000"); /* GSM, GMSK (default) */
+const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101"); /* EGPRS, 8-PSK */
+const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111"); /* EGPRS, GMSK */
+
+// |-head-||---------midamble----------------------||--------------data----------------||t|
+const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");
+
+
+int32_t GSM::FNDelta(int32_t v1, int32_t v2)
+{
+ static const int32_t halfModulus = gHyperframe/2;
+ int32_t delta = v1-v2;
+ if (delta>=halfModulus) delta -= gHyperframe;
+ else if (delta<-halfModulus) delta += gHyperframe;
+ return (int32_t) delta;
+}
+
+int GSM::FNCompare(int32_t v1, int32_t v2)
+{
+ int32_t delta = FNDelta(v1,v2);
+ if (delta>0) return 1;
+ if (delta<0) return -1;
+ return 0;
+}
+
+
+
+
+ostream& GSM::operator<<(ostream& os, const Time& t)
+{
+ os << t.TN() << ":" << t.FN();
+ return os;
+}
+
+
+// vim: ts=4 sw=4
diff --git a/GSM/GSMCommon.h b/GSM/GSMCommon.h
new file mode 100644
index 0000000..f703c30
--- /dev/null
+++ b/GSM/GSMCommon.h
@@ -0,0 +1,253 @@
+/**@file Common-use GSM declarations, most from the GSM 04.xx and 05.xx series. */
+/*
+* Copyright 2008-2011 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+
+#ifndef GSMCOMMON_H
+#define GSMCOMMON_H
+
+#include <stdlib.h>
+#include <sys/time.h>
+#include <ostream>
+#include <vector>
+
+#include <Threads.h>
+#include <Timeval.h>
+#include <BitVector.h>
+
+
+
+
+namespace GSM {
+
+/**@namespace GSM This namespace covers L1 FEC, L2 and L3 message translation. */
+
+/** GSM Training sequences from GSM 05.02 5.2.3. */
+extern const BitVector gTrainingSequence[];
+extern const BitVector gEdgeTrainingSequence[];
+
+/** C0T0 filler burst, GSM 05.02, 5.2.6 */
+extern const BitVector gDummyBurst;
+
+/** Random access burst synch. sequence */
+extern const BitVector gRACHSynchSequenceTS0;
+extern const BitVector gRACHSynchSequenceTS1;
+extern const BitVector gRACHSynchSequenceTS2;
+/** Random access burst synch. sequence, GSM 05.02 5.2.7 */
+extern const BitVector gRACHBurst;
+
+
+/**@name Modulus operations for frame numbers. */
+//@{
+/** The GSM hyperframe is largest time period in the GSM system, GSM 05.02 4.3.3. */
+const uint32_t gHyperframe = 2048UL * 26UL * 51UL;
+
+/** Get a clock difference, within the modulus, v1-v2. */
+int32_t FNDelta(int32_t v1, int32_t v2);
+
+/**
+ Compare two frame clock values.
+ @return 1 if v1>v2, -1 if v1<v2, 0 if v1==v2
+*/
+int FNCompare(int32_t v1, int32_t v2);
+
+
+//@}
+
+
+/**
+ GSM frame clock value. GSM 05.02 4.3
+ No internal thread sync.
+*/
+class Time {
+
+ private:
+
+ int mFN; ///< frame number in the hyperframe
+ int mTN; ///< timeslot number
+
+ public:
+
+ Time(int wFN=0, int wTN=0)
+ :mFN(wFN),mTN(wTN)
+ { }
+
+
+ /** Move the time forward to a given position in a given modulus. */
+ void rollForward(unsigned wFN, unsigned modulus)
+ {
+ assert(modulus<gHyperframe);
+ while ((mFN % modulus) != wFN) mFN=(mFN+1)%gHyperframe;
+ }
+
+ /**@name Accessors. */
+ //@{
+ int FN() const { return mFN; }
+ void FN(unsigned wFN) { mFN = wFN; }
+ unsigned TN() const { return mTN; }
+ void TN(unsigned wTN) { mTN=wTN; }
+ //@}
+
+ /**@name Arithmetic. */
+ //@{
+
+ Time& operator++()
+ {
+ mFN = (mFN+1) % gHyperframe;
+ return *this;
+ }
+
+ Time& decTN(unsigned step=1)
+ {
+ assert(step<=8);
+ mTN -= step;
+ if (mTN<0) {
+ mTN+=8;
+ mFN-=1;
+ if (mFN<0) mFN+=gHyperframe;
+ }
+ return *this;
+ }
+
+ Time& incTN(unsigned step=1)
+ {
+ assert(step<=8);
+ mTN += step;
+ if (mTN>7) {
+ mTN-=8;
+ mFN = (mFN+1) % gHyperframe;
+ }
+ return *this;
+ }
+
+ Time& operator+=(int step)
+ {
+ // Remember the step might be negative.
+ mFN += step;
+ if (mFN<0) mFN+=gHyperframe;
+ mFN = mFN % gHyperframe;
+ return *this;
+ }
+
+ Time operator-(int step) const
+ { return operator+(-step); }
+
+ Time operator+(int step) const
+ {
+ Time newVal = *this;
+ newVal += step;
+ return newVal;
+ }
+
+ Time operator+(const Time& other) const
+ {
+ unsigned newTN = (mTN + other.mTN) % 8;
+ uint64_t newFN = (mFN+other.mFN + (mTN + other.mTN)/8) % gHyperframe;
+ return Time(newFN,newTN);
+ }
+
+ int operator-(const Time& other) const
+ {
+ return FNDelta(mFN,other.mFN);
+ }
+
+ //@}
+
+
+ /**@name Comparisons. */
+ //@{
+
+ bool operator<(const Time& other) const
+ {
+ if (mFN==other.mFN) return (mTN<other.mTN);
+ return FNCompare(mFN,other.mFN)<0;
+ }
+
+ bool operator>(const Time& other) const
+ {
+ if (mFN==other.mFN) return (mTN>other.mTN);
+ return FNCompare(mFN,other.mFN)>0;
+ }
+
+ bool operator<=(const Time& other) const
+ {
+ if (mFN==other.mFN) return (mTN<=other.mTN);
+ return FNCompare(mFN,other.mFN)<=0;
+ }
+
+ bool operator>=(const Time& other) const
+ {
+ if (mFN==other.mFN) return (mTN>=other.mTN);
+ return FNCompare(mFN,other.mFN)>=0;
+ }
+
+ bool operator==(const Time& other) const
+ {
+ return (mFN == other.mFN) && (mTN==other.mTN);
+ }
+
+ //@}
+
+
+
+ /**@name Standard derivations. */
+ //@{
+
+ /** GSM 05.02 3.3.2.2.1 */
+ unsigned SFN() const { return mFN / (26*51); }
+
+ /** GSM 05.02 3.3.2.2.1 */
+ unsigned T1() const { return SFN() % 2048; }
+
+ /** GSM 05.02 3.3.2.2.1 */
+ unsigned T2() const { return mFN % 26; }
+
+ /** GSM 05.02 3.3.2.2.1 */
+ unsigned T3() const { return mFN % 51; }
+
+ /** GSM 05.02 3.3.2.2.1. */
+ unsigned T3p() const { return (T3()-1)/10; }
+
+ /** GSM 05.02 6.3.1.3. */
+ unsigned TC() const { return (FN()/51) % 8; }
+
+ /** GSM 04.08 10.5.2.30. */
+ unsigned T1p() const { return SFN() % 32; }
+
+ /** GSM 05.02 6.2.3 */
+ unsigned T1R() const { return T1() % 64; }
+
+ //@}
+};
+
+
+std::ostream& operator<<(std::ostream& os, const Time& ts);
+
+}; // namespace GSM
+
+
+#endif
+
+// vim: ts=4 sw=4
diff --git a/GSM/Makefile.am b/GSM/Makefile.am
new file mode 100644
index 0000000..a2f5db0
--- /dev/null
+++ b/GSM/Makefile.am
@@ -0,0 +1,32 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+#
+# This software is distributed under the terms of the GNU Public License.
+# See the COPYING file in the main directory for details.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
+#AM_CXXFLAGS = -O2 -g
+
+noinst_LTLIBRARIES = libGSM.la
+
+libGSM_la_SOURCES = \
+ GSMCommon.cpp
+
+noinst_HEADERS = \
+ GSMCommon.h
diff --git a/INSTALLATION b/INSTALLATION
new file mode 100644
index 0000000..f87b6cc
--- /dev/null
+++ b/INSTALLATION
@@ -0,0 +1,19 @@
+Installation Requirements
+
+
+
+osmo-trx compiles to a simple Unix binary and does not require special
+installation.
+
+One some systems (Ubuntu), you will need to define LIBS = -lpthread prior to
+running configure.
+
+To run osmo-trx, the following should be installed:
+ libuhd (https://gnuradio.org).
+ This is part of the GNURadio installation.
+
+For information on specific executables, see tests/README.tests and
+apps/README.apps.
+
+See https://osmocom.org/projects/osmotrx/wiki/OsmoTRX for more
+information.
diff --git a/LEGAL b/LEGAL
new file mode 100644
index 0000000..a8b75f2
--- /dev/null
+++ b/LEGAL
@@ -0,0 +1,60 @@
+OpenBTS
+
+The OsmoTRX project is direved from OpenBTS transceiver code. See http://openbts.org/ for details.
+
+The related copyrights:
+Most parts copyright 2008-2011 Free Software Foundation.
+Some parts copyright 2010 Kestrel Signal Processing, Inc.
+Some parts copyright 2011 Range Networks, Inc.
+
+
+Patent Laws
+
+The use of this software to provide GSM services may result in the use of
+patented technologies. The user of this software is required to take whatever
+actions are necessary to avoid patent infringement.
+
+
+Telecom and Radio Spectrum Laws
+
+The primary function of OsmoTRX is the provision of telecommunications service
+over a radio link. This activity is heavily regulated nearly everywhere in
+the world. Users of this software are expected to comply with local and national
+regulations in the jurisdictions where this sortware is used with radio equipment.
+
+
+Legal Summary
+
+The user of this software is expected to comply with all applicable laws and
+regulations, including patent laws, copyright laws, and telecommunications
+regulations.
+
+The legal restrictions listed here are not necessarily exhaustive.
+
+
+Note to US Government Users
+
+The OsmoTRX software applications and associated documentation are "Commercial
+Item(s)," as that term is defined at 48 C.F.R. Section 2.101, consisting of
+"Commercial Computer Software" and "Commercial Computer Software Documentation,"
+as such terms are used in 48 C.F.R. 12.212 or 48 C.F.R. 227.7202, as
+applicable. Consistent with 48 C.F.R. 12.212 or 48 C.F.R. Sections 227.7202-1
+through 227.7202-4, as applicable, the Commercial Computer Software and
+Commercial Computer Software Documentation are being licensed to U.S. Government
+end users (a) only as Commercial Items and (b) with only those rights as are
+granted to all other end users pursuant to the terms and conditions of GPLv3
+and AGPLv3.
+
+
+Note to US Government Contractors
+
+GPL is not compatible with "government purpose rights" (GPR). If you receive
+OsmoTRX software under a GPL and deliver it under GPR, you will be in violation
+of GPL and possibly subject to enforcement actions by the original authors and
+copyright holders, including the Free Software Foundation, Inc.
+
+
+Software Licensing and Distribution
+
+The OsmoTRX is distributed publicly under AGPLv3. See the COPYING file
+for more information on the license for this distribution.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..38cd88d
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,55 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+#
+# This software is distributed under the terms of the GNU Public License.
+# See the COPYING file in the main directory for details.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+include $(top_srcdir)/Makefile.common
+
+ACLOCAL_AMFLAGS = -I config
+AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USB_INCLUDES) $(WITH_INCLUDES)
+AM_CXXFLAGS = -Wall -pthread
+#AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread
+#AM_CFLAGS = -Wall -O2 -NDEBUG -pthread
+
+# Order must be preserved
+SUBDIRS = \
+ doc \
+ CommonLibs \
+ GSM \
+ Transceiver52M \
+ contrib \
+ tests
+
+EXTRA_DIST = \
+ autogen.sh \
+ INSTALLATION \
+ LEGAL \
+ COPYING \
+ README
+
+DISTCHECK_CONFIGURE_FLAGS = \
+ --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+.PHONY: release
+
+@RELMAKE@
+
+dox: FORCE
+ doxygen doxconfig
+
+FORCE:
diff --git a/Makefile.common b/Makefile.common
new file mode 100644
index 0000000..1de9733
--- /dev/null
+++ b/Makefile.common
@@ -0,0 +1,38 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+#
+# This software is distributed under the terms of the GNU Public License.
+# See the COPING file in the main directory for details.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+COMMON_INCLUDEDIR = $(top_srcdir)/CommonLibs
+GSM_INCLUDEDIR = $(top_srcdir)/GSM
+
+STD_DEFINES_AND_INCLUDES = \
+ $(SVNDEV) \
+ -I$(COMMON_INCLUDEDIR) \
+ -I$(GSM_INCLUDEDIR)
+
+COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la
+GSM_LA = $(top_builddir)/GSM/libGSM.la
+
+if ARCH_ARM
+ARCH_LA = $(top_builddir)/Transceiver52M/arch/arm/libarch.la
+else
+ARCH_LA = $(top_builddir)/Transceiver52M/arch/x86/libarch.la
+endif
+
+MOSTLYCLEANFILES = *~
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..502d661
--- /dev/null
+++ b/README
@@ -0,0 +1,116 @@
+This is the interface to the transcevier.
+
+Each TRX Manager UDP socket interface represents a single ARFCN.
+Each of these per-ARFCN interfaces is a pair of UDP sockets, one for control and one for data.
+Give a base port B (5700), the master clock interface is at port P=B.
+The TRX-side control interface for C(N) is on port P=B+2N+1 and the data interface is on an odd numbered port P=B+2N+2.
+The corresponding core-side interface for every socket is at P+100.
+For any given build, the number of ARFCN interfaces can be fixed.
+
+
+
+Indications on the Master Clock Interface
+
+The master clock interface is output only (from the radio).
+Messages are "indications".
+
+CLOCK gives the current value of the transceiver clock to be used by the core.
+This message is sent whenever a trasmission packet arrives that is too late or too early. The clock value is NOT the current transceiver time. It is a time setting the the core should use to give better packet arrival times.
+IND CLOCK <totalFrames>
+
+
+
+Commands on the Per-ARFCN Control Interface
+
+The per-ARFCN control interface uses a command-reponse protocol.
+Commands are NULL-terminated ASCII strings, one per UDP socket.
+Each command has a corresponding response.
+Every command is of the form:
+
+CMD <cmdtype> [params]
+
+The <cmdtype> is the actual command.
+Parameters are optional depending on the commands type.
+Every response is of the form:
+
+RSP <cmdtype> <status> [result]
+
+The <status> is 0 for success and a non-zero error code for failure.
+Successful responses may include results, depending on the command type.
+
+
+Power Control
+
+POWEROFF shuts off transmitter power and stops the demodulator.
+CMD POWEROFF
+RSP POWEROFF <status>
+
+POWERON starts the transmitter and starts the demodulator. Initial power level is very low.
+This command fails if the transmitter and receiver are not yet tuned.
+This command fails if the transmit or receive frequency creates a conflict with another ARFCN that is already runnng.
+If the transceiver is already on, it response with success to this command.
+CMD POWERON
+RSP POWERON <status>
+
+SETPOWER sets output power in dB wrt full scale.
+This command fails if the transmitter and receiver are not running.
+CMD SETPOWER <dB>
+RSP SETPOWER <status> <dB>
+
+ADJPOWER adjusts power by the given dB step. Response returns resulting power level wrt full scale.
+This command fails if the transmitter and receiver are not running.
+CMD ADJPOWER <dBStep>
+RSP ADJPOWER <status> <dBLevel>
+
+
+Tuning Control
+
+RXTUNE tunes the receiver to a given frequency in kHz.
+This command fails if the receiver is already running.
+(To re-tune you stop the radio, re-tune, and restart.)
+This command fails if the transmit or receive frequency creates a conflict with another ARFCN that is already runnng.
+CMD RXTUNE <kHz>
+RSP RXTUNE <status> <kHz>
+
+TXTUNE tunes the transmitter to a given frequency in kHz.
+This command fails if the transmitter is already running.
+(To re-tune you stop the radio, re-tune, and restart.)
+This command fails if the transmit or receive frequency creates a conflict with another ARFCN that is already runnng.
+CMD TXTUNE <kHz>
+RSP TXTUNE <status> <kHz>
+
+
+Timeslot Control
+
+SETSLOT sets the format of the uplink timeslots in the ARFCN.
+The <timeslot> indicates the timeslot of interest.
+The <chantype> indicates the type of channel that occupies the timeslot.
+A chantype of zero indicates the timeslot is off.
+CMD SETSLOT <timeslot> <chantype>
+RSP SETSLOT <status> <timeslot> <chantype>
+
+
+Messages on the per-ARFCN Data Interface
+
+Messages on the data interface carry one radio burst per UDP message.
+
+
+Received Data Burst
+
+1 byte timeslot index
+4 bytes GSM frame number, big endian
+1 byte RSSI in -dBm
+2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, big endian
+148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1"
+
+
+Transmit Data Burst
+
+1 byte timeslot index
+4 bytes GSM frame number, big endian
+1 byte transmit level wrt ARFCN max, -dB (attenuation)
+148 bytes output symbol values, 0 & 1
+
+
+
+
diff --git a/Transceiver52M/Channelizer.cpp b/Transceiver52M/Channelizer.cpp
new file mode 100644
index 0000000..2d817b0
--- /dev/null
+++ b/Transceiver52M/Channelizer.cpp
@@ -0,0 +1,107 @@
+/*
+ * Polyphase channelizer
+ *
+ * Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * 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 <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+#include <cstdio>
+
+#include "Channelizer.h"
+
+extern "C" {
+#include "fft.h"
+#include "convolve.h"
+}
+
+static void deinterleave(const float *in, size_t ilen,
+ float **out, size_t olen, size_t m)
+{
+ size_t i, n;
+
+ for (i = 0; i < olen; i++) {
+ for (n = 0; n < m; n++) {
+ out[m - 1 - n][2 * i + 0] = in[2 * (i * m + n) + 0];
+ out[m - 1 - n][2 * i + 1] = in[2 * (i * m + n) + 1];
+ }
+ }
+}
+
+size_t Channelizer::inputLen() const
+{
+ return blockLen * m;
+}
+
+size_t Channelizer::outputLen() const
+{
+ return blockLen;
+}
+
+float *Channelizer::outputBuffer(size_t chan) const
+{
+ if (chan >= m)
+ return NULL;
+
+ return hInputs[chan];
+}
+
+/*
+ * Implementation based on material found in:
+ *
+ * "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
+ * Prentice Hall, 2006."
+ */
+bool Channelizer::rotate(const float *in, size_t len)
+{
+ size_t hSize = 2 * hLen * sizeof(float);
+
+ if (!checkLen(blockLen, len))
+ return false;
+
+ deinterleave(in, len, hInputs, blockLen, m);
+
+ /*
+ * Convolve through filterbank while applying and saving sample history
+ */
+ for (size_t i = 0; i < m; i++) {
+ memcpy(&hInputs[i][2 * -hLen], hist[i], hSize);
+ memcpy(hist[i], &hInputs[i][2 * (blockLen - hLen)], hSize);
+
+ convolve_real(hInputs[i], blockLen,
+ subFilters[i], hLen,
+ hOutputs[i], blockLen,
+ 0, blockLen, 1, 0);
+ }
+
+ cxvec_fft(fftHandle);
+
+ return true;
+}
+
+/* Setup channelizer paramaters */
+Channelizer::Channelizer(size_t m, size_t blockLen, size_t hLen)
+ : ChannelizerBase(m, blockLen, hLen)
+{
+}
+
+Channelizer::~Channelizer()
+{
+}
diff --git a/Transceiver52M/Channelizer.h b/Transceiver52M/Channelizer.h
new file mode 100644
index 0000000..fdd6779
--- /dev/null
+++ b/Transceiver52M/Channelizer.h
@@ -0,0 +1,34 @@
+#ifndef _CHANNELIZER_RX_H_
+#define _CHANNELIZER_RX_H_
+
+#include "ChannelizerBase.h"
+
+class Channelizer : public ChannelizerBase {
+public:
+ /** Constructor for channelizing filter bank
+ @param m number of physical channels
+ @param blockLen number of samples per output of each iteration
+ @param hLen number of taps in each constituent filter path
+ */
+ Channelizer(size_t m, size_t blockLen, size_t hLen = 16);
+ ~Channelizer();
+
+ /* Return required input and output buffer lengths */
+ size_t inputLen() const;
+ size_t outputLen() const;
+
+ /** Rotate "input commutator" and drive samples through filterbank
+ @param in complex input vector
+ @param iLen number of samples in buffer (must match block length)
+ @return false on error and true otherwise
+ */
+ bool rotate(const float *in, size_t iLen);
+
+ /** Get buffer for an output path
+ @param chan channel number of filterbank
+ @return NULL on error and pointer to buffer otherwise
+ */
+ float *outputBuffer(size_t chan) const;
+};
+
+#endif /* _CHANNELIZER_RX_H_ */
diff --git a/Transceiver52M/ChannelizerBase.cpp b/Transceiver52M/ChannelizerBase.cpp
new file mode 100644
index 0000000..430e260
--- /dev/null
+++ b/Transceiver52M/ChannelizerBase.cpp
@@ -0,0 +1,251 @@
+/*
+ * Polyphase channelizer
+ *
+ * Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * 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 <malloc.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+#include <cstdio>
+
+#include "Logger.h"
+#include "ChannelizerBase.h"
+
+extern "C" {
+#include "fft.h"
+}
+
+static float sinc(float x)
+{
+ if (x == 0.0f)
+ return 0.999999999999f;
+
+ return sin(M_PI * x) / (M_PI * x);
+}
+
+/*
+ * There are more efficient reversal algorithms, but we only reverse at
+ * initialization so we don't care.
+ */
+static void reverse(float *buf, size_t len)
+{
+ float tmp[2 * len];
+ memcpy(tmp, buf, 2 * len * sizeof(float));
+
+ for (size_t i = 0; i < len; i++) {
+ buf[2 * i + 0] = tmp[2 * (len - 1 - i) + 0];
+ buf[2 * i + 1] = tmp[2 * (len - 1 - i) + 1];
+ }
+}
+
+/*
+ * Create polyphase filterbank
+ *
+ * Implementation based material found in,
+ *
+ * "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
+ * Prentice Hall, 2006."
+ */
+bool ChannelizerBase::initFilters()
+{
+ size_t protoLen = m * hLen;
+ float *proto;
+ float sum = 0.0f, scale = 0.0f;
+ float midpt = (float) (protoLen - 1.0) / 2.0;
+
+ /*
+ * Allocate 'M' partition filters and the temporary prototype
+ * filter. Coefficients are real only and must be 16-byte memory
+ * aligned for SSE usage.
+ */
+ proto = new float[protoLen];
+ if (!proto)
+ return false;
+
+ subFilters = (float **) malloc(sizeof(float *) * m);
+ if (!subFilters) {
+ delete[] proto;
+ return false;
+ }
+
+ for (size_t i = 0; i < m; i++) {
+ subFilters[i] = (float *)
+ memalign(16, hLen * 2 * sizeof(float));
+ }
+
+ /*
+ * Generate the prototype filter with a Blackman-harris window.
+ * Scale coefficients with DC filter gain set to unity divided
+ * by the number of channels.
+ */
+ float a0 = 0.35875;
+ float a1 = 0.48829;
+ float a2 = 0.14128;
+ float a3 = 0.01168;
+
+ for (size_t i = 0; i < protoLen; i++) {
+ proto[i] = sinc(((float) i - midpt) / (float) m);
+ proto[i] *= a0 -
+ a1 * cos(2 * M_PI * i / (protoLen - 1)) +
+ a2 * cos(4 * M_PI * i / (protoLen - 1)) -
+ a3 * cos(6 * M_PI * i / (protoLen - 1));
+ sum += proto[i];
+ }
+ scale = (float) m / sum;
+
+ /*
+ * Populate partition filters and reverse the coefficients per
+ * convolution requirements.
+ */
+ for (size_t i = 0; i < hLen; i++) {
+ for (size_t n = 0; n < m; n++) {
+ subFilters[n][2 * i + 0] = proto[i * m + n] * scale;
+ subFilters[n][2 * i + 1] = 0.0f;
+ }
+ }
+
+ for (size_t i = 0; i < m; i++)
+ reverse(subFilters[i], hLen);
+
+ delete[] proto;
+
+ return true;
+}
+
+bool ChannelizerBase::initFFT()
+{
+ size_t size;
+
+ if (fftInput || fftOutput || fftHandle)
+ return false;
+
+ size = blockLen * m * 2 * sizeof(float);
+ fftInput = (float *) fft_malloc(size);
+ memset(fftInput, 0, size);
+
+ size = (blockLen + hLen) * m * 2 * sizeof(float);
+ fftOutput = (float *) fft_malloc(size);
+ memset(fftOutput, 0, size);
+
+ if (!fftInput | !fftOutput) {
+ LOG(ALERT) << "Memory allocation error";
+ return false;
+ }
+
+ fftHandle = init_fft(0, m, blockLen, blockLen + hLen,
+ fftInput, fftOutput, hLen);
+ return true;
+}
+
+bool ChannelizerBase::mapBuffers()
+{
+ if (!fftHandle) {
+ LOG(ALERT) << "FFT buffers not initialized";
+ return false;
+ }
+
+ hInputs = (float **) malloc(sizeof(float *) * m);
+ hOutputs = (float **) malloc(sizeof(float *) * m);
+ if (!hInputs | !hOutputs)
+ return false;
+
+ for (size_t i = 0; i < m; i++) {
+ hInputs[i] = &fftOutput[2 * (i * (blockLen + hLen) + hLen)];
+ hOutputs[i] = &fftInput[2 * (i * blockLen)];
+ }
+
+ return true;
+}
+
+/*
+ * Setup filterbank internals
+ */
+bool ChannelizerBase::init()
+{
+ /*
+ * Filterbank coefficients, fft plan, history, and output sample
+ * rate conversion blocks
+ */
+ if (!initFilters()) {
+ LOG(ALERT) << "Failed to initialize channelizing filter";
+ return false;
+ }
+
+ hist = (float **) malloc(sizeof(float *) * m);
+ for (size_t i = 0; i < m; i++) {
+ hist[i] = new float[2 * hLen];
+ memset(hist[i], 0, 2 * hLen * sizeof(float));
+ }
+
+ if (!initFFT()) {
+ LOG(ALERT) << "Failed to initialize FFT";
+ return false;
+ }
+
+ mapBuffers();
+
+ return true;
+}
+
+/* Check vector length validity */
+bool ChannelizerBase::checkLen(size_t innerLen, size_t outerLen)
+{
+ if (outerLen != innerLen * m) {
+ LOG(ALERT) << "Invalid outer length " << innerLen
+ << " is not multiple of " << blockLen;
+ return false;
+ }
+
+ if (innerLen != blockLen) {
+ LOG(ALERT) << "Invalid inner length " << outerLen
+ << " does not equal " << blockLen;
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Setup channelizer paramaters
+ */
+ChannelizerBase::ChannelizerBase(size_t m, size_t blockLen, size_t hLen)
+ : fftInput(NULL), fftOutput(NULL), fftHandle(NULL)
+{
+ this->m = m;
+ this->hLen = hLen;
+ this->blockLen = blockLen;
+}
+
+ChannelizerBase::~ChannelizerBase()
+{
+ free_fft(fftHandle);
+
+ for (size_t i = 0; i < m; i++) {
+ free(subFilters[i]);
+ delete[] hist[i];
+ }
+
+ fft_free(fftInput);
+ fft_free(fftOutput);
+
+ free(hInputs);
+ free(hOutputs);
+ free(hist);
+}
diff --git a/Transceiver52M/ChannelizerBase.h b/Transceiver52M/ChannelizerBase.h
new file mode 100644
index 0000000..7da506b
--- /dev/null
+++ b/Transceiver52M/ChannelizerBase.h
@@ -0,0 +1,39 @@
+#ifndef _CHANNELIZER_BASE_H_
+#define _CHANNELIZER_BASE_H_
+
+class ChannelizerBase {
+protected:
+ ChannelizerBase(size_t m, size_t blockLen, size_t hLen);
+ ~ChannelizerBase();
+
+ /* Channelizer parameters */
+ size_t m;
+ size_t hLen;
+ size_t blockLen;
+
+ /* Channelizer filterbank sub-filters */
+ float **subFilters;
+
+ /* Input/Output buffers */
+ float **hInputs, **hOutputs, **hist;
+ float *fftInput, *fftOutput;
+
+ /* Pointer to opaque FFT instance */
+ struct fft_hdl *fftHandle;
+
+ /* Initializer internals */
+ bool initFilters();
+ bool initFFT();
+ void releaseFilters();
+
+ /* Map overlapped FFT and filter I/O buffers */
+ bool mapBuffers();
+
+ /* Buffer length validity checking */
+ bool checkLen(size_t innerLen, size_t outerLen);
+public:
+ /* Initilize channelizer/synthesis filter internals */
+ bool init();
+};
+
+#endif /* _CHANNELIZER_BASE_H_ */
diff --git a/Transceiver52M/Complex.h b/Transceiver52M/Complex.h
new file mode 100644
index 0000000..d02944b
--- /dev/null
+++ b/Transceiver52M/Complex.h
@@ -0,0 +1,266 @@
+/**@file templates for Complex classes
+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 use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+
+
+
+#ifndef COMPLEXCPP_H
+#define COMPLEXCPP_H
+
+#include <math.h>
+#include <ostream>
+
+
+template<class Real> class Complex {
+
+public:
+
+ Real r, i;
+
+ /**@name constructors */
+ //@{
+ /**@name from real */
+ //@{
+ Complex(Real real, Real imag) {r=real; i=imag;} // x=complex(a,b)
+ Complex(Real real) {r=real; i=0;} // x=complex(a)
+ //@}
+ /**@name from nothing */
+ //@{
+ Complex() {r=(Real)0; i=(Real)0;} // x=complex()
+ //@}
+ /**@name from other complex */
+ //@{
+ Complex(const Complex<float>& z) {r=z.r; i=z.i;} // x=complex(z)
+ Complex(const Complex<double>& z) {r=z.r; i=z.i;} // x=complex(z)
+ Complex(const Complex<long double>& z) {r=z.r; i=z.i;} // x=complex(z)
+ //@}
+ //@}
+
+ /**@name casting up from basic numeric types */
+ //@{
+ Complex& operator=(char a) { r=(Real)a; i=(Real)0; return *this; }
+ Complex& operator=(int a) { r=(Real)a; i=(Real)0; return *this; }
+ Complex& operator=(long int a) { r=(Real)a; i=(Real)0; return *this; }
+ Complex& operator=(short a) { r=(Real)a; i=(Real)0; return *this; }
+ Complex& operator=(float a) { r=(Real)a; i=(Real)0; return *this; }
+ Complex& operator=(double a) { r=(Real)a; i=(Real)0; return *this; }
+ Complex& operator=(long double a) { r=(Real)a; i=(Real)0; return *this; }
+ //@}
+
+ /**@name arithmetic */
+ //@{
+ /**@ binary operators */
+ //@{
+ Complex operator+(const Complex<Real>& a) const { return Complex<Real>(r+a.r, i+a.i); }
+ Complex operator+(Real a) const { return Complex<Real>(r+a,i); }
+ Complex operator-(const Complex<Real>& a) const { return Complex<Real>(r-a.r, i-a.i); }
+ Complex operator-(Real a) const { return Complex<Real>(r-a,i); }
+ Complex operator*(const Complex<Real>& a) const { return Complex<Real>(r*a.r-i*a.i, r*a.i+i*a.r); }
+ Complex operator*(Real a) const { return Complex<Real>(r*a, i*a); }
+ Complex operator/(const Complex<Real>& a) const { return operator*(a.inv()); }
+ Complex operator/(Real a) const { return Complex<Real>(r/a, i/a); }
+ //@}
+ /*@name component-wise product */
+ //@{
+ Complex operator&(const Complex<Real>& a) const { return Complex<Real>(r*a.r, i*a.i); }
+ //@}
+ /*@name inplace operations */
+ //@{
+ Complex& operator+=(const Complex<Real>&);
+ Complex& operator-=(const Complex<Real>&);
+ Complex& operator*=(const Complex<Real>&);
+ Complex& operator/=(const Complex<Real>&);
+ Complex& operator+=(Real);
+ Complex& operator-=(Real);
+ Complex& operator*=(Real);
+ Complex& operator/=(Real);
+ //@}
+ //@}
+
+ /**@name comparisons */
+ //@{
+ bool operator==(const Complex<Real>& a) const { return ((i==a.i)&&(r==a.r)); }
+ bool operator!=(const Complex<Real>& a) const { return ((i!=a.i)||(r!=a.r)); }
+ bool operator<(const Complex<Real>& a) const { return norm2()<a.norm2(); }
+ bool operator>(const Complex<Real>& a) const { return norm2()>a.norm2(); }
+ //@}
+
+ /// reciprocation
+ Complex inv() const;
+
+ // unary functions -- inlined
+ /**@name unary functions */
+ //@{
+ /**@name inlined */
+ //@{
+ Complex conj() const { return Complex<Real>(r,-i); }
+ Real norm2() const { return i*i+r*r; }
+ Complex flip() const { return Complex<Real>(i,r); }
+ Real real() const { return r;}
+ Real imag() const { return i;}
+ Complex neg() const { return Complex<Real>(-r, -i); }
+ bool isZero() const { return ((r==(Real)0) && (i==(Real)0)); }
+ //@}
+ /**@name not inlined due to outside calls */
+ //@{
+ Real abs() const { return ::sqrt(norm2()); }
+ Real arg() const { return ::atan2(i,r); }
+ float dB() const { return 10.0*log10(norm2()); }
+ Complex exp() const { return expj(i)*(::exp(r)); }
+ Complex unit() const; ///< unit phasor with same angle
+ Complex log() const { return Complex(::log(abs()),arg()); }
+ Complex pow(double n) const { return expj(arg()*n)*(::pow(abs(),n)); }
+ Complex sqrt() const { return pow(0.5); }
+ //@}
+ //@}
+
+};
+
+
+/**@name standard Complex manifestations */
+//@{
+typedef Complex<float> complex;
+typedef Complex<double> dcomplex;
+typedef Complex<short> complex16;
+typedef Complex<long> complex32;
+//@}
+
+
+template<class Real> inline Complex<Real> Complex<Real>::inv() const
+{
+ Real nVal;
+
+ nVal = norm2();
+ return Complex<Real>(r/nVal, -i/nVal);
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator+=(const Complex<Real>& a)
+{
+ r += a.r;
+ i += a.i;
+ return *this;
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator*=(const Complex<Real>& a)
+{
+ operator*(a);
+ return *this;
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator-=(const Complex<Real>& a)
+{
+ r -= a.r;
+ i -= a.i;
+ return *this;
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator/=(const Complex<Real>& a)
+{
+ operator/(a);
+ return *this;
+}
+
+
+/* op= style operations with reals */
+
+template<class Real> Complex<Real>& Complex<Real>::operator+=(Real a)
+{
+ r += a;
+ return *this;
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator*=(Real a)
+{
+ r *=a;
+ i *=a;
+ return *this;
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator-=(Real a)
+{
+ r -= a;
+ return *this;
+}
+
+template<class Real> Complex<Real>& Complex<Real>::operator/=(Real a)
+{
+ r /= a;
+ i /= a;
+ return *this;
+}
+
+
+template<class Real> Complex<Real> Complex<Real>::unit() const
+{
+ Real absVal = abs();
+ return (Complex<Real>(r/absVal, i/absVal));
+}
+
+
+
+/**@name complex functions outside of the Complex<> class. */
+//@{
+
+/** this allows type-commutative multiplication */
+template<class Real> Complex<Real> operator*(Real a, const Complex<Real>& z)
+{
+ return Complex<Real>(z.r*a, z.i*a);
+}
+
+
+/** this allows type-commutative addition */
+template<class Real> Complex<Real> operator+(Real a, const Complex<Real>& z)
+{
+ return Complex<Real>(z.r+a, z.i);
+}
+
+
+/** this allows type-commutative subtraction */
+template<class Real> Complex<Real> operator-(Real a, const Complex<Real>& z)
+{
+ return Complex<Real>(z.r-a, z.i);
+}
+
+
+
+/// e^jphi
+template<class Real> Complex<Real> expj(Real phi)
+{
+ return Complex<Real>(cos(phi),sin(phi));
+}
+
+/// phasor expression of a complex number
+template<class Real> Complex<Real> phasor(Real C, Real phi)
+{
+ return (expj(phi)*C);
+}
+
+/// formatted stream output
+template<class Real> std::ostream& operator<<(std::ostream& os, const Complex<Real>& z)
+{
+ os << z.r << ' ';
+ //os << z.r << ", ";
+ //if (z.i>=0) { os << "+"; }
+ os << z.i << "j";
+ return os;
+}
+
+//@}
+
+
+#endif
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
new file mode 100644
index 0000000..28c47ab
--- /dev/null
+++ b/Transceiver52M/Makefile.am
@@ -0,0 +1,111 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+# Copyright 2010 Range Networks, Inc.
+#
+# This software is distributed under the terms of the GNU Public License.
+# See the COPYING file in the main directory for details.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+include $(top_srcdir)/Makefile.common
+
+SUBDIRS = arch device
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
+
+rev2dir = $(datadir)/usrp/rev2
+rev4dir = $(datadir)/usrp/rev4
+
+dist_rev2_DATA = std_inband.rbf
+dist_rev4_DATA = std_inband.rbf
+
+EXTRA_DIST = README
+
+noinst_LTLIBRARIES = libtransceiver_common.la
+
+COMMON_SOURCES = \
+ radioInterface.cpp \
+ radioVector.cpp \
+ radioClock.cpp \
+ radioBuffer.cpp \
+ sigProcLib.cpp \
+ signalVector.cpp \
+ Transceiver.cpp \
+ ChannelizerBase.cpp \
+ Channelizer.cpp \
+ Synthesis.cpp
+
+libtransceiver_common_la_SOURCES = \
+ $(COMMON_SOURCES) \
+ Resampler.cpp \
+ radioInterfaceResamp.cpp \
+ radioInterfaceMulti.cpp
+
+noinst_HEADERS = \
+ Complex.h \
+ radioInterface.h \
+ radioVector.h \
+ radioClock.h \
+ radioBuffer.h \
+ sigProcLib.h \
+ signalVector.h \
+ Transceiver.h \
+ Resampler.h \
+ ChannelizerBase.h \
+ Channelizer.h \
+ Synthesis.h
+
+COMMON_LDADD = \
+ libtransceiver_common.la \
+ $(ARCH_LA) \
+ $(GSM_LA) \
+ $(COMMON_LA) \
+ $(FFTWF_LIBS) \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOVTY_LIBS)
+
+bin_PROGRAMS =
+
+if DEVICE_UHD
+bin_PROGRAMS += osmo-trx-uhd
+osmo_trx_uhd_SOURCES = osmo-trx.cpp
+osmo_trx_uhd_LDADD = \
+ $(builddir)/device/uhd/libdevice.la \
+ $(COMMON_LDADD) \
+ $(UHD_LIBS)
+osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
+endif
+
+if DEVICE_USRP1
+bin_PROGRAMS += osmo-trx-usrp1
+osmo_trx_usrp1_SOURCES = osmo-trx.cpp
+osmo_trx_usrp1_LDADD = \
+ $(builddir)/device/usrp1/libdevice.la \
+ $(COMMON_LDADD) \
+ $(USRP_LIBS)
+osmo_trx_usrp1_CPPFLAGS = $(AM_CPPFLAGS) $(USRP_CFLAGS)
+endif
+
+if DEVICE_LMS
+bin_PROGRAMS += osmo-trx-lms
+osmo_trx_lms_SOURCES = osmo-trx.cpp
+osmo_trx_lms_LDADD = \
+ $(builddir)/device/lms/libdevice.la \
+ $(COMMON_LDADD) \
+ $(LMS_LIBS)
+osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS)
+endif
diff --git a/Transceiver52M/README b/Transceiver52M/README
new file mode 100644
index 0000000..491693c
--- /dev/null
+++ b/Transceiver52M/README
@@ -0,0 +1,35 @@
+The Transceiver
+
+The transceiver consists of three modules:
+ --- transceiver
+ --- radioInterface
+ --- USRPDevice
+
+The USRPDevice module is basically a driver that reads/writes
+packets to a USRP with two RFX900 daughterboards, board
+A is the Tx chain and board B is the Rx chain.
+
+The radioInterface module is basically an interface b/w the
+transceiver and the USRP. It operates the basestation clock
+based upon the sample count of received USRP samples. Packets
+from the USRP are queued and segmented into GSM bursts that are
+passed up to the transceiver; bursts from the transceiver are
+passed down to the USRP.
+
+The transceiver basically operates "layer 0" of the GSM stack,
+performing the modulation, detection, and demodulation of GSM
+bursts. It communicates with the GSM stack via three UDP sockets,
+one socket for data, one for control messages, and one socket to
+pass clocking information. The transceiver contains a priority
+queue to sort to-be-transmitted bursts, and a filler table to fill
+in timeslots that do not have bursts in the priority queue. The
+transceiver tries to stay ahead of the basestation clock, adapting
+its latency when underruns are reported by the radioInterface/USRP.
+Received bursts (from the radioInterface) pass through a simple
+energy detector, a RACH or midamble correlator, and a DFE-based demodulator.
+
+NOTE: There's a SWLOOPBACK #define statement, where the USRP is replaced
+with a memory buffer. In this mode, data written to the USRP is actually stored
+in a buffer, and read commands to the USRP simply pull data from this buffer.
+This was very useful in early testing, and still may be useful in testing basic
+Transceiver and radioInterface functionality.
diff --git a/Transceiver52M/README.DFEsymbolspaced b/Transceiver52M/README.DFEsymbolspaced
new file mode 100644
index 0000000..f1ab479
--- /dev/null
+++ b/Transceiver52M/README.DFEsymbolspaced
@@ -0,0 +1,14 @@
+signalVectors G0, G1. i.e. G0(D) = 1 +2D + 3D^2 = [1 2 3]
+G0(D) = 1/sqrt(SNR).
+G1(D) = [h0 h1D .. h_(N-1)D^(N-1)]
+for i = 0,1,...,N_f-1,
+ d = |G0(0)|^2+|G1(0)|^2
+ l_i(D) = D^i ( G0(D)*G0'(0) + G1(D)*G1'(0) )/d
+ k = G1(0)/G0(0)
+ G0n(D) = G0(D)+k'*G1(D)
+ G1n(D) = (-G0(D)*k+G1(D))/D
+ G0(D) = G0n(D)/sqrt(1+k*k')
+ G1(D) = G1n(D)/sqrt(1+k*k')
+end
+
+
diff --git a/Transceiver52M/Resampler.cpp b/Transceiver52M/Resampler.cpp
new file mode 100644
index 0000000..6b9621c
--- /dev/null
+++ b/Transceiver52M/Resampler.cpp
@@ -0,0 +1,187 @@
+/*
+ * Rational Sample Rate Conversion
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <math.h>
+#include <string.h>
+#include <malloc.h>
+#include <iostream>
+#include <algorithm>
+
+#include "Resampler.h"
+
+extern "C" {
+#include "convolve.h"
+}
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846264338327f
+#endif
+
+#define MAX_OUTPUT_LEN 4096
+
+using namespace std;
+
+static float sinc(float x)
+{
+ if (x == 0.0)
+ return 0.9999999999;
+
+ return sin(M_PI * x) / (M_PI * x);
+}
+
+void Resampler::initFilters(float bw)
+{
+ float cutoff;
+ float sum = 0.0f, scale = 0.0f;
+
+ /*
+ * Allocate partition filters and the temporary prototype filter
+ * according to numerator of the rational rate. Coefficients are
+ * real only and must be 16-byte memory aligned for SSE usage.
+ */
+ auto proto = vector<float>(p * filt_len);
+ for (auto &part : partitions)
+ part = (complex<float> *) memalign(16, filt_len * sizeof(complex<float>));
+
+ /*
+ * Generate the prototype filter with a Blackman-harris window.
+ * Scale coefficients with DC filter gain set to unity divided
+ * by the number of filter partitions.
+ */
+ float a0 = 0.35875;
+ float a1 = 0.48829;
+ float a2 = 0.14128;
+ float a3 = 0.01168;
+
+ if (p > q)
+ cutoff = (float) p;
+ else
+ cutoff = (float) q;
+
+ float midpt = (proto.size() - 1) / 2.0;
+ for (size_t i = 0; i < proto.size(); i++) {
+ proto[i] = sinc(((float) i - midpt) / cutoff * bw);
+ proto[i] *= a0 -
+ a1 * cos(2 * M_PI * i / (proto.size() - 1)) +
+ a2 * cos(4 * M_PI * i / (proto.size() - 1)) -
+ a3 * cos(6 * M_PI * i / (proto.size() - 1));
+ sum += proto[i];
+ }
+ scale = p / sum;
+
+ /* Populate filter partitions from the prototype filter */
+ for (size_t i = 0; i < filt_len; i++) {
+ for (size_t n = 0; n < p; n++)
+ partitions[n][i] = complex<float>(proto[i * p + n] * scale);
+ }
+
+ /* Store filter taps in reverse */
+ for (auto &part : partitions)
+ reverse(&part[0], &part[filt_len]);
+}
+
+static bool check_vec_len(int in_len, int out_len, int p, int q)
+{
+ if (in_len % q) {
+ std::cerr << "Invalid input length " << in_len
+ << " is not multiple of " << q << std::endl;
+ return false;
+ }
+
+ if (out_len % p) {
+ std::cerr << "Invalid output length " << out_len
+ << " is not multiple of " << p << std::endl;
+ return false;
+ }
+
+ if ((in_len / q) != (out_len / p)) {
+ std::cerr << "Input/output block length mismatch" << std::endl;
+ std::cerr << "P = " << p << ", Q = " << q << std::endl;
+ std::cerr << "Input len: " << in_len << std::endl;
+ std::cerr << "Output len: " << out_len << std::endl;
+ return false;
+ }
+
+ if (out_len > MAX_OUTPUT_LEN) {
+ std::cerr << "Block length of " << out_len
+ << " exceeds max of " << MAX_OUTPUT_LEN << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len)
+{
+ int n, path;
+
+ if (!check_vec_len(in_len, out_len, p, q))
+ return -1;
+
+ /* Generate output from precomputed input/output paths */
+ for (size_t i = 0; i < out_len; i++) {
+ n = in_index[i];
+ path = out_path[i];
+
+ convolve_real(in, in_len,
+ reinterpret_cast<float *>(partitions[path]),
+ filt_len, &out[2 * i], out_len - i,
+ n, 1, 1, 0);
+ }
+
+ return out_len;
+}
+
+bool Resampler::init(float bw)
+{
+ if (p == 0 || q == 0 || filt_len == 0) return false;
+
+ /* Filterbank filter internals */
+ initFilters(bw);
+
+ /* Precompute filterbank paths */
+ int i = 0;
+ for (auto &index : in_index)
+ index = (q * i++) / p;
+ i = 0;
+ for (auto &path : out_path)
+ path = (q * i++) % p;
+
+ return true;
+}
+
+size_t Resampler::len()
+{
+ return filt_len;
+}
+
+Resampler::Resampler(size_t p, size_t q, size_t filt_len)
+ : in_index(MAX_OUTPUT_LEN), out_path(MAX_OUTPUT_LEN), partitions(p)
+{
+ this->p = p;
+ this->q = q;
+ this->filt_len = filt_len;
+}
+
+Resampler::~Resampler()
+{
+ for (auto &part : partitions)
+ free(part);
+}
diff --git a/Transceiver52M/Resampler.h b/Transceiver52M/Resampler.h
new file mode 100644
index 0000000..caffc08
--- /dev/null
+++ b/Transceiver52M/Resampler.h
@@ -0,0 +1,76 @@
+/*
+ * Rational Sample Rate Conversion
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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_
+#define _RESAMPLER_H_
+
+#include <vector>
+#include <complex>
+
+class Resampler {
+public:
+ /* Constructor for rational sample rate conversion
+ * @param p numerator of resampling ratio
+ * @param q denominator of resampling ratio
+ * @param filt_len length of each polyphase subfilter
+ */
+ Resampler(size_t p, size_t q, size_t filt_len = 16);
+ ~Resampler();
+
+ /* Initilize 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
+ * 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.
+ */
+ bool init(float bw = 1.0f);
+
+ /* Rotate "commutator" and drive samples through filterbank
+ * @param in continuous buffer of input complex float values
+ * @param in_len input buffer length
+ * @param out continuous buffer of output complex float values
+ * @param out_len output buffer length
+ * @return number of samples outputted, negative on error
+ *
+ * Input and output vector lengths must of be equal multiples of the
+ * rational conversion rate denominator and numerator respectively.
+ */
+ int rotate(const float *in, size_t in_len, float *out, size_t out_len);
+
+ /* Get filter length
+ * @return number of taps in each filter partition
+ */
+ size_t len();
+
+private:
+ size_t p;
+ size_t q;
+ size_t filt_len;
+ std::vector<size_t> in_index;
+ std::vector<size_t> out_path;
+ std::vector<std::complex<float> *> partitions;
+
+ void initFilters(float bw);
+};
+
+#endif /* _RESAMPLER_H_ */
diff --git a/Transceiver52M/Synthesis.cpp b/Transceiver52M/Synthesis.cpp
new file mode 100644
index 0000000..262c638
--- /dev/null
+++ b/Transceiver52M/Synthesis.cpp
@@ -0,0 +1,121 @@
+/*
+ * Polyphase synthesis filter
+ *
+ * Copyright (C) 2012-2014 Tom Tsou <tom@tsou.cc>
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * 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 <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+#include <cstdio>
+#include <iostream>
+
+#include "Synthesis.h"
+
+extern "C" {
+#include "fft.h"
+#include "convolve.h"
+}
+
+static void interleave(float **in, size_t ilen,
+ float *out, size_t m)
+{
+ size_t i, n;
+
+ for (i = 0; i < ilen; i++) {
+ for (n = 0; n < m; n++) {
+ out[2 * (i * m + n) + 0] = in[n][2 * i + 0];
+ out[2 * (i * m + n) + 1] = in[n][2 * i + 1];
+ }
+ }
+}
+
+size_t Synthesis::inputLen() const
+{
+ return blockLen;
+}
+
+size_t Synthesis::outputLen() const
+{
+ return blockLen * m;
+}
+
+float *Synthesis::inputBuffer(size_t chan) const
+{
+ if (chan >= m)
+ return NULL;
+
+ return hOutputs[chan];
+}
+
+bool Synthesis::resetBuffer(size_t chan)
+{
+ if (chan >= m)
+ return false;
+
+ memset(hOutputs[chan], 0, blockLen * 2 * sizeof(float));
+
+ return true;
+}
+
+/*
+ * Implementation based on material found in:
+ *
+ * "harris, fred, Multirate Signal Processing, Upper Saddle River, NJ,
+ * Prentice Hall, 2006."
+ */
+bool Synthesis::rotate(float *out, size_t len)
+{
+ size_t hSize = 2 * hLen * sizeof(float);
+
+ if (!checkLen(blockLen, len)) {
+ std::cout << "Length fail" << std::endl;
+ exit(1);
+ return false;
+ }
+
+ cxvec_fft(fftHandle);
+
+ /*
+ * Convolve through filterbank while applying and saving sample history
+ */
+ for (size_t i = 0; i < m; i++) {
+ memcpy(&hInputs[i][2 * -hLen], hist[i], hSize);
+ memcpy(hist[i], &hInputs[i][2 * (blockLen - hLen)], hSize);
+
+ convolve_real(hInputs[i], blockLen,
+ subFilters[i], hLen,
+ hOutputs[i], blockLen,
+ 0, blockLen, 1, 0);
+ }
+
+ /* Interleave into output vector */
+ interleave(hOutputs, blockLen, out, m);
+
+ return true;
+}
+
+Synthesis::Synthesis(size_t m, size_t blockLen, size_t hLen)
+ : ChannelizerBase(m, blockLen, hLen)
+{
+}
+
+Synthesis::~Synthesis()
+{
+}
diff --git a/Transceiver52M/Synthesis.h b/Transceiver52M/Synthesis.h
new file mode 100644
index 0000000..4100283
--- /dev/null
+++ b/Transceiver52M/Synthesis.h
@@ -0,0 +1,35 @@
+#ifndef _SYNTHESIS_H_
+#define _SYNTHESIS_H_
+
+#include "ChannelizerBase.h"
+
+class Synthesis : public ChannelizerBase {
+public:
+ /** Constructor for synthesis filterbank
+ @param m number of physical channels
+ @param blockLen number of samples per output of each iteration
+ @param hLen number of taps in each constituent filter path
+ */
+ Synthesis(size_t m, size_t blockLen, size_t hLen = 16);
+ ~Synthesis();
+
+ /* Return required input and output buffer lengths */
+ size_t inputLen() const;
+ size_t outputLen() const;
+
+ /** Rotate "output commutator" and drive samples through filterbank
+ @param out complex output vector
+ @param oLen number of samples in buffer (must match block length * m)
+ @return false on error and true otherwise
+ */
+ bool rotate(float *out, size_t oLen);
+
+ /** Get buffer for an input path
+ @param chan channel number of filterbank
+ @return NULL on error and pointer to buffer otherwise
+ */
+ float *inputBuffer(size_t chan) const;
+ bool resetBuffer(size_t chan);
+};
+
+#endif /* _SYNTHESIS_H_ */
diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp
new file mode 100644
index 0000000..d6ddce8
--- /dev/null
+++ b/Transceiver52M/Transceiver.cpp
@@ -0,0 +1,1128 @@
+/*
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <iomanip> // std::setprecision
+#include <fstream>
+#include "Transceiver.h"
+#include <Logger.h>
+
+extern "C" {
+#include "osmo_signal.h"
+}
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+using namespace GSM;
+
+#define USB_LATENCY_INTRVL 10,0
+
+/* Number of running values use in noise average */
+#define NOISE_CNT 20
+
+TransceiverState::TransceiverState()
+ : mRetrans(false), mNoiseLev(0.0), mNoises(NOISE_CNT), mPower(0.0)
+{
+ for (int i = 0; i < 8; i++) {
+ chanType[i] = Transceiver::NONE;
+ fillerModulus[i] = 26;
+ chanResponse[i] = NULL;
+ DFEForward[i] = NULL;
+ DFEFeedback[i] = NULL;
+
+ for (int n = 0; n < 102; n++)
+ fillerTable[n][i] = NULL;
+ }
+}
+
+TransceiverState::~TransceiverState()
+{
+ for (int i = 0; i < 8; i++) {
+ delete chanResponse[i];
+ delete DFEForward[i];
+ delete DFEFeedback[i];
+
+ for (int n = 0; n < 102; n++)
+ delete fillerTable[n][i];
+ }
+}
+
+bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t rtsc, unsigned rach_delay)
+{
+ signalVector *burst;
+
+ if ((sps != 1) && (sps != 4))
+ return false;
+
+ for (size_t n = 0; n < 8; n++) {
+ for (size_t i = 0; i < 102; i++) {
+ switch (filler) {
+ case FILLER_DUMMY:
+ burst = generateDummyBurst(sps, n);
+ break;
+ case FILLER_NORM_RAND:
+ burst = genRandNormalBurst(rtsc, sps, n);
+ break;
+ case FILLER_EDGE_RAND:
+ burst = generateEdgeBurst(rtsc);
+ break;
+ case FILLER_ACCESS_RAND:
+ burst = genRandAccessBurst(rach_delay, sps, n);
+ break;
+ case FILLER_ZERO:
+ default:
+ burst = generateEmptyBurst(sps, n);
+ }
+
+ scaleVector(*burst, scale);
+ fillerTable[i][n] = burst;
+ }
+
+ if ((filler == FILLER_NORM_RAND) ||
+ (filler == FILLER_EDGE_RAND)) {
+ chanType[n] = TSC;
+ }
+ }
+
+ return false;
+}
+
+Transceiver::Transceiver(int wBasePort,
+ const char *TRXAddress,
+ const char *GSMcoreAddress,
+ size_t tx_sps, size_t rx_sps, size_t chans,
+ GSM::Time wTransmitLatency,
+ RadioInterface *wRadioInterface,
+ double wRssiOffset)
+ : mBasePort(wBasePort), mLocalAddr(TRXAddress), mRemoteAddr(GSMcoreAddress),
+ mClockSocket(TRXAddress, wBasePort, GSMcoreAddress, wBasePort + 100),
+ mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
+ rssiOffset(wRssiOffset), sig_cbfn(NULL),
+ mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mEdge(false), mOn(false), mForceClockInterface(false),
+ mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0),
+ mWriteBurstToDiskMask(0)
+{
+ txFullScale = mRadioInterface->fullScaleInputValue();
+ rxFullScale = mRadioInterface->fullScaleOutputValue();
+
+ for (int i = 0; i < 8; i++) {
+ for (int j = 0; j < 8; j++)
+ mHandover[i][j] = false;
+ }
+}
+
+Transceiver::~Transceiver()
+{
+ stop();
+
+ sigProcLibDestroy();
+
+ for (size_t i = 0; i < mChans; i++) {
+ mControlServiceLoopThreads[i]->cancel();
+ mControlServiceLoopThreads[i]->join();
+ delete mControlServiceLoopThreads[i];
+
+ mTxPriorityQueues[i].clear();
+ delete mCtrlSockets[i];
+ delete mDataSockets[i];
+ }
+}
+
+/*
+ * Initialize transceiver
+ *
+ * Start or restart the control loop. Any further control is handled through the
+ * socket API. Randomize the central radio clock set the downlink burst
+ * counters. Note that the clock will not update until the radio starts, but we
+ * are still expected to report clock indications through control channel
+ * activity.
+ */
+bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, bool edge)
+{
+ int d_srcport, d_dstport, c_srcport, c_dstport;
+
+ if (!mChans) {
+ LOG(ALERT) << "No channels assigned";
+ return false;
+ }
+
+ if (!sigProcLibSetup()) {
+ LOG(ALERT) << "Failed to initialize signal processing library";
+ return false;
+ }
+
+ mEdge = edge;
+
+ mDataSockets.resize(mChans);
+ mCtrlSockets.resize(mChans);
+ mControlServiceLoopThreads.resize(mChans);
+ mTxPriorityQueueServiceLoopThreads.resize(mChans);
+ mRxServiceLoopThreads.resize(mChans);
+
+ mTxPriorityQueues.resize(mChans);
+ mReceiveFIFO.resize(mChans);
+ mStates.resize(mChans);
+
+ /* Filler table retransmissions - support only on channel 0 */
+ if (filler == FILLER_DUMMY)
+ mStates[0].mRetrans = true;
+
+ /* Setup sockets */
+ for (size_t i = 0; i < mChans; i++) {
+ c_srcport = mBasePort + 2 * i + 1;
+ c_dstport = mBasePort + 2 * i + 101;
+ d_srcport = mBasePort + 2 * i + 2;
+ d_dstport = mBasePort + 2 * i + 102;
+
+ mCtrlSockets[i] = new UDPSocket(mLocalAddr.c_str(), c_srcport, mRemoteAddr.c_str(), c_dstport);
+ mDataSockets[i] = new UDPSocket(mLocalAddr.c_str(), d_srcport, mRemoteAddr.c_str(), d_dstport);
+ }
+
+ /* Randomize the central clock */
+ GSM::Time startTime(random() % gHyperframe, 0);
+ mRadioInterface->getClock()->set(startTime);
+ mTransmitDeadlineClock = startTime;
+ mLastClockUpdateTime = startTime;
+ mLatencyUpdateTime = startTime;
+
+ /* Start control threads */
+ for (size_t i = 0; i < mChans; i++) {
+ TransceiverChannel *chan = new TransceiverChannel(this, i);
+ mControlServiceLoopThreads[i] = new Thread(32768);
+ mControlServiceLoopThreads[i]->start((void * (*)(void*))
+ ControlServiceLoopAdapter, (void*) chan);
+
+ if (i && filler == FILLER_DUMMY)
+ filler = FILLER_ZERO;
+
+ mStates[i].init(filler, mSPSTx, txFullScale, rtsc, rach_delay);
+ }
+
+ return true;
+}
+
+void Transceiver::setSignalHandler(osmo_signal_cbfn cbfn)
+{
+ if (this->sig_cbfn)
+ osmo_signal_unregister_handler(SS_TRANSC, this->sig_cbfn, NULL);
+
+ if (cbfn) {
+ this->sig_cbfn = cbfn;
+ osmo_signal_register_handler(SS_TRANSC, this->sig_cbfn, NULL);
+ }
+}
+
+/*
+ * Start the transceiver
+ *
+ * Submit command(s) to the radio device to commence streaming samples and
+ * launch threads to handle sample I/O. Re-synchronize the transmit burst
+ * counters to the central radio clock here as well.
+ */
+bool Transceiver::start()
+{
+ ScopedLock lock(mLock);
+
+ if (mOn) {
+ LOG(ERR) << "Transceiver already running";
+ return true;
+ }
+
+ LOG(NOTICE) << "Starting the transceiver";
+
+ GSM::Time time = mRadioInterface->getClock()->get();
+ mTransmitDeadlineClock = time;
+ mLastClockUpdateTime = time;
+ mLatencyUpdateTime = time;
+
+ if (!mRadioInterface->start()) {
+ LOG(ALERT) << "Device failed to start";
+ return false;
+ }
+
+ /* Device is running - launch I/O threads */
+ mRxLowerLoopThread = new Thread(32768);
+ mTxLowerLoopThread = new Thread(32768);
+ mTxLowerLoopThread->start((void * (*)(void*))
+ TxLowerLoopAdapter,(void*) this);
+ mRxLowerLoopThread->start((void * (*)(void*))
+ RxLowerLoopAdapter,(void*) this);
+
+ /* Launch uplink and downlink burst processing threads */
+ for (size_t i = 0; i < mChans; i++) {
+ TransceiverChannel *chan = new TransceiverChannel(this, i);
+ mRxServiceLoopThreads[i] = new Thread(32768);
+ mRxServiceLoopThreads[i]->start((void * (*)(void*))
+ RxUpperLoopAdapter, (void*) chan);
+
+ chan = new TransceiverChannel(this, i);
+ mTxPriorityQueueServiceLoopThreads[i] = new Thread(32768);
+ mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*))
+ TxUpperLoopAdapter, (void*) chan);
+ }
+
+ mForceClockInterface = true;
+ mOn = true;
+ return true;
+}
+
+/*
+ * Stop the transceiver
+ *
+ * Perform stopping by disabling receive streaming and issuing cancellation
+ * requests to running threads. Most threads will timeout and terminate once
+ * device is disabled, but the transmit loop may block waiting on the central
+ * UMTS clock. Explicitly signal the clock to make sure that the transmit loop
+ * makes it to the thread cancellation point.
+ */
+void Transceiver::stop()
+{
+ ScopedLock lock(mLock);
+
+ if (!mOn)
+ return;
+
+ LOG(NOTICE) << "Stopping the transceiver";
+ mTxLowerLoopThread->cancel();
+ mRxLowerLoopThread->cancel();
+ mTxLowerLoopThread->join();
+ mRxLowerLoopThread->join();
+ delete mTxLowerLoopThread;
+ delete mRxLowerLoopThread;
+
+ for (size_t i = 0; i < mChans; i++) {
+ mRxServiceLoopThreads[i]->cancel();
+ mTxPriorityQueueServiceLoopThreads[i]->cancel();
+ }
+
+ LOG(INFO) << "Stopping the device";
+ mRadioInterface->stop();
+
+ for (size_t i = 0; i < mChans; i++) {
+ mRxServiceLoopThreads[i]->join();
+ mTxPriorityQueueServiceLoopThreads[i]->join();
+ delete mRxServiceLoopThreads[i];
+ delete mTxPriorityQueueServiceLoopThreads[i];
+
+ mTxPriorityQueues[i].clear();
+ }
+
+ mOn = false;
+ LOG(NOTICE) << "Transceiver stopped";
+}
+
+void Transceiver::addRadioVector(size_t chan, BitVector &bits,
+ int RSSI, GSM::Time &wTime)
+{
+ signalVector *burst;
+ radioVector *radio_burst;
+
+ if (chan >= mTxPriorityQueues.size()) {
+ LOG(ALERT) << "Invalid channel " << chan;
+ return;
+ }
+
+ if (wTime.TN() > 7) {
+ LOG(ALERT) << "Received burst with invalid slot " << wTime.TN();
+ return;
+ }
+
+ /* Use the number of bits as the EDGE burst indicator */
+ if (bits.size() == EDGE_BURST_NBITS)
+ burst = modulateEdgeBurst(bits, mSPSTx);
+ else
+ burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), mSPSTx);
+
+ scaleVector(*burst, txFullScale * pow(10, -RSSI / 10));
+
+ radio_burst = new radioVector(wTime, burst);
+
+ mTxPriorityQueues[chan].write(radio_burst);
+}
+
+void Transceiver::updateFillerTable(size_t chan, radioVector *burst)
+{
+ int TN, modFN;
+ TransceiverState *state = &mStates[chan];
+
+ TN = burst->getTime().TN();
+ modFN = burst->getTime().FN() % state->fillerModulus[TN];
+
+ delete state->fillerTable[modFN][TN];
+ state->fillerTable[modFN][TN] = burst->getVector();
+ burst->setVector(NULL);
+}
+
+void Transceiver::pushRadioVector(GSM::Time &nowTime)
+{
+ int TN, modFN;
+ radioVector *burst;
+ TransceiverState *state;
+ std::vector<signalVector *> bursts(mChans);
+ std::vector<bool> zeros(mChans);
+ std::vector<bool> filler(mChans, true);
+
+ for (size_t i = 0; i < mChans; i ++) {
+ state = &mStates[i];
+
+ while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) {
+ LOG(NOTICE) << "chan " << i << " dumping STALE burst in TRX->USRP interface ("
+ << burst->getTime() <<" vs " << nowTime << "), retrans=" << state->mRetrans;
+ if (state->mRetrans)
+ updateFillerTable(i, burst);
+ delete burst;
+ }
+
+ TN = nowTime.TN();
+ modFN = nowTime.FN() % state->fillerModulus[TN];
+
+ bursts[i] = state->fillerTable[modFN][TN];
+ zeros[i] = state->chanType[TN] == NONE;
+
+ if ((burst = mTxPriorityQueues[i].getCurrentBurst(nowTime))) {
+ bursts[i] = burst->getVector();
+
+ if (state->mRetrans) {
+ updateFillerTable(i, burst);
+ } else {
+ burst->setVector(NULL);
+ filler[i] = false;
+ }
+
+ delete burst;
+ }
+ }
+
+ mRadioInterface->driveTransmitRadio(bursts, zeros);
+
+ for (size_t i = 0; i < mChans; i++) {
+ if (!filler[i])
+ delete bursts[i];
+ }
+}
+
+void Transceiver::setModulus(size_t timeslot, size_t chan)
+{
+ TransceiverState *state = &mStates[chan];
+
+ switch (state->chanType[timeslot]) {
+ case NONE:
+ case I:
+ case II:
+ case III:
+ case FILL:
+ state->fillerModulus[timeslot] = 26;
+ break;
+ case IV:
+ case VI:
+ case V:
+ state->fillerModulus[timeslot] = 51;
+ break;
+ //case V:
+ case VII:
+ state->fillerModulus[timeslot] = 102;
+ break;
+ case XIII:
+ state->fillerModulus[timeslot] = 52;
+ break;
+ default:
+ break;
+ }
+}
+
+
+CorrType Transceiver::expectedCorrType(GSM::Time currTime,
+ size_t chan)
+{
+ static int tchh_subslot[26] = { 0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,1 };
+ static int sdcch4_subslot[102] = { 3,3,3,3,0,0,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2,2,2,
+ 3,3,3,3,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2,2,2 };
+ static int sdcch8_subslot[102] = { 5,5,5,5,6,6,6,6,7,7,7,7,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,0,0,0,0,
+ 1,1,1,1,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,4,4,4,4 };
+ TransceiverState *state = &mStates[chan];
+ unsigned burstTN = currTime.TN();
+ unsigned burstFN = currTime.FN();
+ int subch;
+
+ switch (state->chanType[burstTN]) {
+ case NONE:
+ return OFF;
+ break;
+ case FILL:
+ return IDLE;
+ break;
+ case I:
+ // TODO: Are we expecting RACH on an IDLE frame?
+/* if (burstFN % 26 == 25)
+ return IDLE;*/
+ if (mHandover[burstTN][0])
+ return RACH;
+ return TSC;
+ break;
+ case II:
+ subch = tchh_subslot[burstFN % 26];
+ if (subch == 1)
+ return IDLE;
+ if (mHandover[burstTN][0])
+ return RACH;
+ return TSC;
+ break;
+ case III:
+ subch = tchh_subslot[burstFN % 26];
+ if (mHandover[burstTN][subch])
+ return RACH;
+ return TSC;
+ break;
+ case IV:
+ case VI:
+ return RACH;
+ break;
+ case V: {
+ int mod51 = burstFN % 51;
+ if ((mod51 <= 36) && (mod51 >= 14))
+ return RACH;
+ else if ((mod51 == 4) || (mod51 == 5))
+ return RACH;
+ else if ((mod51 == 45) || (mod51 == 46))
+ return RACH;
+ else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]])
+ return RACH;
+ else
+ return TSC;
+ break;
+ }
+ case VII:
+ if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
+ return IDLE;
+ else if (mHandover[burstTN][sdcch8_subslot[burstFN % 102]])
+ return RACH;
+ else
+ return TSC;
+ break;
+ case XIII: {
+ int mod52 = burstFN % 52;
+ if ((mod52 == 12) || (mod52 == 38))
+ return RACH;
+ else if ((mod52 == 25) || (mod52 == 51))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ }
+ case LOOPBACK:
+ if ((burstFN % 51 <= 50) && (burstFN % 51 >=48))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ default:
+ return OFF;
+ break;
+ }
+}
+
+void writeToFile(radioVector *radio_burst, size_t chan)
+{
+ GSM::Time time = radio_burst->getTime();
+ std::ostringstream fname;
+ fname << chan << "_" << time.FN() << "_" << time.TN() << ".fc";
+ std::ofstream outfile (fname.str().c_str(), std::ofstream::binary);
+ outfile.write((char*)radio_burst->getVector()->begin(), radio_burst->getVector()->size() * 2 * sizeof(float));
+ outfile.close();
+}
+
+/*
+ * Pull bursts from the FIFO and handle according to the slot
+ * and burst correlation type. Equalzation is currently disabled.
+ */
+SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime, double &RSSI, bool &isRssiValid,
+ double &timingOffset, double &noise,
+ size_t chan)
+{
+ int rc;
+ complex amp;
+ float toa, max = -1.0, avg = 0.0;
+ int max_i = -1;
+ signalVector *burst;
+ SoftVector *bits = NULL;
+ TransceiverState *state = &mStates[chan];
+ isRssiValid = false;
+
+ /* Blocking FIFO read */
+ radioVector *radio_burst = mReceiveFIFO[chan]->read();
+ if (!radio_burst)
+ return NULL;
+
+ /* Set time and determine correlation type */
+ GSM::Time time = radio_burst->getTime();
+ CorrType type = expectedCorrType(time, chan);
+
+ /* Enable 8-PSK burst detection if EDGE is enabled */
+ if (mEdge && (type == TSC))
+ type = EDGE;
+
+ /* Debug: dump bursts to disk */
+ /* bits 0-7 - chan 0 timeslots
+ * bits 8-15 - chan 1 timeslots */
+ if (mWriteBurstToDiskMask & ((1<<time.TN()) << (8*chan)))
+ writeToFile(radio_burst, chan);
+
+ /* No processing if the timeslot is off.
+ * Not even power level or noise calculation. */
+ if (type == OFF) {
+ delete radio_burst;
+ return NULL;
+ }
+
+ /* Select the diversity channel with highest energy */
+ for (size_t i = 0; i < radio_burst->chans(); i++) {
+ float pow = energyDetect(*radio_burst->getVector(i), 20 * mSPSRx);
+ if (pow > max) {
+ max = pow;
+ max_i = i;
+ }
+ avg += pow;
+ }
+
+ if (max_i < 0) {
+ LOG(ALERT) << "Received empty burst";
+ delete radio_burst;
+ return NULL;
+ }
+
+ /* Average noise on diversity paths and update global levels */
+ burst = radio_burst->getVector(max_i);
+ avg = sqrt(avg / radio_burst->chans());
+
+ wTime = time;
+ RSSI = 20.0 * log10(rxFullScale / avg);
+
+ /* RSSI estimation are valid */
+ isRssiValid = true;
+
+ if (type == IDLE) {
+ /* Update noise levels */
+ state->mNoises.insert(avg);
+ state->mNoiseLev = state->mNoises.avg();
+ noise = 20.0 * log10(rxFullScale / state->mNoiseLev);
+
+ delete radio_burst;
+ return NULL;
+ } else {
+ /* Do not update noise levels */
+ noise = 20.0 * log10(rxFullScale / state->mNoiseLev);
+ }
+
+ /* Detect normal or RACH bursts */
+ rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, amp, toa,
+ (type==RACH)?mMaxExpectedDelayAB:mMaxExpectedDelayNB);
+
+ if (rc > 0) {
+ type = (CorrType) rc;
+ } else if (rc <= 0) {
+ if (rc == -SIGERR_CLIP) {
+ LOG(WARNING) << "Clipping detected on received RACH or Normal Burst";
+ } else if (rc != SIGERR_NONE) {
+ LOG(WARNING) << "Unhandled RACH or Normal Burst detection error";
+ }
+
+ delete radio_burst;
+ return NULL;
+ }
+
+ timingOffset = toa;
+
+ bits = demodAnyBurst(*burst, mSPSRx, amp, toa, type);
+
+ delete radio_burst;
+ return bits;
+}
+
+void Transceiver::reset()
+{
+ for (size_t i = 0; i < mTxPriorityQueues.size(); i++)
+ mTxPriorityQueues[i].clear();
+}
+
+
+#define MAX_PACKET_LENGTH 100
+
+/**
+ * Matches a buffer with a command.
+ * @param buf a buffer to look command in
+ * @param cmd a command to look in buffer
+ * @param params pointer to arguments, or NULL
+ * @return true if command matches, otherwise false
+ */
+static bool match_cmd(char *buf,
+ const char *cmd, char **params)
+{
+ size_t cmd_len = strlen(cmd);
+
+ /* Check a command itself */
+ if (strncmp(buf, cmd, cmd_len))
+ return false;
+
+ /* A command has arguments */
+ if (params != NULL) {
+ /* Make sure there is a space */
+ if (buf[cmd_len] != ' ')
+ return false;
+
+ /* Update external pointer */
+ *params = buf + cmd_len + 1;
+ }
+
+ return true;
+}
+
+void Transceiver::driveControl(size_t chan)
+{
+ char buffer[MAX_PACKET_LENGTH + 1];
+ char response[MAX_PACKET_LENGTH + 1];
+ char *command, *params;
+ int msgLen;
+
+ /* Attempt to read from control socket */
+ msgLen = mCtrlSockets[chan]->read(buffer, MAX_PACKET_LENGTH);
+ if (msgLen < 1)
+ return;
+
+ /* Zero-terminate received string */
+ buffer[msgLen] = '\0';
+
+ /* Verify a command signature */
+ if (strncmp(buffer, "CMD ", 4)) {
+ LOG(WARNING) << "bogus message on control interface";
+ return;
+ }
+
+ /* Set command pointer */
+ command = buffer + 4;
+ LOG(INFO) << "command is " << command;
+
+ if (match_cmd(command, "POWEROFF", NULL)) {
+ stop();
+ sprintf(response,"RSP POWEROFF 0");
+ } else if (match_cmd(command, "POWERON", NULL)) {
+ if (!start()) {
+ sprintf(response,"RSP POWERON 1");
+ } else {
+ sprintf(response,"RSP POWERON 0");
+ for (int i = 0; i < 8; i++) {
+ for (int j = 0; j < 8; j++)
+ mHandover[i][j] = false;
+ }
+ }
+ } else if (match_cmd(command, "HANDOVER", &params)) {
+ unsigned ts = 0, ss = 0;
+ sscanf(params, "%u %u", &ts, &ss);
+ if (ts > 7 || ss > 7) {
+ sprintf(response, "RSP NOHANDOVER 1 %u %u", ts, ss);
+ } else {
+ mHandover[ts][ss] = true;
+ sprintf(response, "RSP HANDOVER 0 %u %u", ts, ss);
+ }
+ } else if (match_cmd(command, "NOHANDOVER", &params)) {
+ unsigned ts = 0, ss = 0;
+ sscanf(params, "%u %u", &ts, &ss);
+ if (ts > 7 || ss > 7) {
+ sprintf(response, "RSP NOHANDOVER 1 %u %u", ts, ss);
+ } else {
+ mHandover[ts][ss] = false;
+ sprintf(response, "RSP NOHANDOVER 0 %u %u", ts, ss);
+ }
+ } else if (match_cmd(command, "SETMAXDLY", &params)) {
+ //set expected maximum time-of-arrival
+ int maxDelay;
+ sscanf(params, "%d", &maxDelay);
+ mMaxExpectedDelayAB = maxDelay; // 1 GSM symbol is approx. 1 km
+ sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay);
+ } else if (match_cmd(command, "SETMAXDLYNB", &params)) {
+ //set expected maximum time-of-arrival
+ int maxDelay;
+ sscanf(params, "%d", &maxDelay);
+ mMaxExpectedDelayNB = maxDelay; // 1 GSM symbol is approx. 1 km
+ sprintf(response,"RSP SETMAXDLYNB 0 %d",maxDelay);
+ } else if (match_cmd(command, "SETRXGAIN", &params)) {
+ //set expected maximum time-of-arrival
+ int newGain;
+ sscanf(params, "%d", &newGain);
+ newGain = mRadioInterface->setRxGain(newGain, chan);
+ sprintf(response,"RSP SETRXGAIN 0 %d",newGain);
+ } else if (match_cmd(command, "NOISELEV", NULL)) {
+ if (mOn) {
+ float lev = mStates[chan].mNoiseLev;
+ sprintf(response,"RSP NOISELEV 0 %d",
+ (int) round(20.0 * log10(rxFullScale / lev)));
+ }
+ else {
+ sprintf(response,"RSP NOISELEV 1 0");
+ }
+ } else if (match_cmd(command, "SETPOWER", &params)) {
+ int power;
+ sscanf(params, "%d", &power);
+ power = mRadioInterface->setPowerAttenuation(power, chan);
+ mStates[chan].mPower = power;
+ sprintf(response, "RSP SETPOWER 0 %d", power);
+ } else if (match_cmd(command, "ADJPOWER", &params)) {
+ int power, step;
+ sscanf(params, "%d", &step);
+ power = mStates[chan].mPower + step;
+ power = mRadioInterface->setPowerAttenuation(power, chan);
+ mStates[chan].mPower = power;
+ sprintf(response, "RSP ADJPOWER 0 %d", power);
+ } else if (match_cmd(command, "RXTUNE", &params)) {
+ // tune receiver
+ int freqKhz;
+ sscanf(params, "%d", &freqKhz);
+ mRxFreq = freqKhz * 1e3;
+ if (!mRadioInterface->tuneRx(mRxFreq, chan)) {
+ LOG(ALERT) << "RX failed to tune";
+ sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
+ }
+ else
+ sprintf(response,"RSP RXTUNE 0 %d",freqKhz);
+ } else if (match_cmd(command, "TXTUNE", &params)) {
+ // tune txmtr
+ int freqKhz;
+ sscanf(params, "%d", &freqKhz);
+ mTxFreq = freqKhz * 1e3;
+ if (!mRadioInterface->tuneTx(mTxFreq, chan)) {
+ LOG(ALERT) << "TX failed to tune";
+ sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
+ }
+ else
+ sprintf(response,"RSP TXTUNE 0 %d",freqKhz);
+ } else if (match_cmd(command, "SETTSC", &params)) {
+ // set TSC
+ unsigned TSC;
+ sscanf(params, "%u", &TSC);
+ if (TSC > 7) {
+ sprintf(response, "RSP SETTSC 1 %d", TSC);
+ } else {
+ LOG(NOTICE) << "Changing TSC from " << mTSC << " to " << TSC;
+ mTSC = TSC;
+ sprintf(response,"RSP SETTSC 0 %d", TSC);
+ }
+ } else if (match_cmd(command, "SETSLOT", &params)) {
+ // set slot type
+ int corrCode;
+ int timeslot;
+ sscanf(params, "%d %d", &timeslot, &corrCode);
+ if ((timeslot < 0) || (timeslot > 7)) {
+ LOG(WARNING) << "bogus message on control interface";
+ sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode);
+ return;
+ }
+ mStates[chan].chanType[timeslot] = (ChannelCombination) corrCode;
+ setModulus(timeslot, chan);
+ sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode);
+ } else if (match_cmd(command, "_SETBURSTTODISKMASK", &params)) {
+ // debug command! may change or disapear without notice
+ // set a mask which bursts to dump to disk
+ int mask;
+ sscanf(params, "%d", &mask);
+ mWriteBurstToDiskMask = mask;
+ sprintf(response,"RSP _SETBURSTTODISKMASK 0 %d",mask);
+ } else {
+ LOG(WARNING) << "bogus command " << command << " on control interface.";
+ sprintf(response,"RSP ERR 1");
+ }
+
+ mCtrlSockets[chan]->write(response, strlen(response) + 1);
+}
+
+bool Transceiver::driveTxPriorityQueue(size_t chan)
+{
+ int burstLen;
+ char buffer[EDGE_BURST_NBITS + 50];
+
+ // check data socket
+ size_t msgLen = mDataSockets[chan]->read(buffer, sizeof(buffer));
+
+ if (msgLen == gSlotLen + 1 + 4 + 1) {
+ burstLen = gSlotLen;
+ } else if (msgLen == EDGE_BURST_NBITS + 1 + 4 + 1) {
+ if (mSPSTx != 4)
+ return false;
+
+ burstLen = EDGE_BURST_NBITS;
+ } else {
+ LOG(ERR) << "badly formatted packet on GSM->TRX interface";
+ return false;
+ }
+
+ int timeSlot = (int) buffer[0];
+ uint64_t frameNum = 0;
+ for (int i = 0; i < 4; i++)
+ frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]);
+
+ LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot);
+
+ int RSSI = (int) buffer[5];
+ BitVector newBurst(burstLen);
+ BitVector::iterator itr = newBurst.begin();
+ char *bufferItr = buffer+6;
+ while (itr < newBurst.end())
+ *itr++ = *bufferItr++;
+
+ GSM::Time currTime = GSM::Time(frameNum,timeSlot);
+
+ addRadioVector(chan, newBurst, RSSI, currTime);
+
+ return true;
+
+
+}
+
+void Transceiver::driveReceiveRadio()
+{
+ int rc = mRadioInterface->driveReceiveRadio();
+ if (rc == 0) {
+ usleep(100000);
+ } else if (rc < 0) {
+ LOG(FATAL) << "radio Interface receive failed, requesting stop.";
+ osmo_signal_dispatch(SS_TRANSC, S_TRANSC_STOP_REQUIRED, this);
+ } else if (mForceClockInterface || mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0)) {
+ mForceClockInterface = false;
+ writeClockInterface();
+ }
+}
+
+void Transceiver::logRxBurst(size_t chan, SoftVector *burst, GSM::Time time, double dbm,
+ double rssi, double noise, double toa)
+{
+ LOG(DEBUG) << std::fixed << std::right
+ << " chan: " << chan
+ << " time: " << time
+ << " RSSI: " << std::setw(5) << std::setprecision(1) << rssi
+ << "dBFS/" << std::setw(6) << -dbm << "dBm"
+ << " noise: " << std::setw(5) << std::setprecision(1) << noise
+ << "dBFS/" << std::setw(6) << -(noise + rssiOffset) << "dBm"
+ << " TOA: " << std::setw(5) << std::setprecision(2) << toa
+ << " bits: " << *burst;
+}
+
+void Transceiver::driveReceiveFIFO(size_t chan)
+{
+ SoftVector *rxBurst = NULL;
+ double RSSI; // in dBFS
+ double dBm; // in dBm
+ double TOA; // in symbols
+ int TOAint; // in 1/256 symbols
+ double noise; // noise level in dBFS
+ GSM::Time burstTime;
+ bool isRssiValid; // are RSSI, noise and burstTime valid
+ unsigned nbits = gSlotLen;
+
+ rxBurst = pullRadioVector(burstTime, RSSI, isRssiValid, TOA, noise, chan);
+ if (!rxBurst)
+ return;
+
+ // Convert -1..+1 soft bits to 0..1 soft bits
+ vectorSlicer(rxBurst);
+
+ /*
+ * EDGE demodulator returns 444 (148 * 3) bits
+ */
+ if (rxBurst->size() == gSlotLen * 3)
+ nbits = gSlotLen * 3;
+
+ dBm = RSSI + rssiOffset;
+ logRxBurst(chan, rxBurst, burstTime, dBm, RSSI, noise, TOA);
+
+ TOAint = (int) (TOA * 256.0 + 0.5); // round to closest integer
+
+ char burstString[nbits + 10];
+ burstString[0] = burstTime.TN();
+ for (int i = 0; i < 4; i++)
+ burstString[1+i] = (burstTime.FN() >> ((3-i)*8)) & 0x0ff;
+ burstString[5] = (int)dBm;
+ burstString[6] = (TOAint >> 8) & 0x0ff;
+ burstString[7] = TOAint & 0x0ff;
+ SoftVector::iterator burstItr = rxBurst->begin();
+
+ for (unsigned i = 0; i < nbits; i++)
+ burstString[8 + i] = (char) round((*burstItr++) * 255.0);
+
+ burstString[nbits + 9] = '\0';
+ delete rxBurst;
+
+ mDataSockets[chan]->write(burstString, nbits + 10);
+}
+
+void Transceiver::driveTxFIFO()
+{
+
+ /**
+ Features a carefully controlled latency mechanism, to
+ assure that transmit packets arrive at the radio/USRP
+ before they need to be transmitted.
+
+ Deadline clock indicates the burst that needs to be
+ pushed into the FIFO right NOW. If transmit queue does
+ not have a burst, stick in filler data.
+ */
+
+
+ RadioClock *radioClock = (mRadioInterface->getClock());
+
+ if (mOn) {
+ //radioClock->wait(); // wait until clock updates
+ LOG(DEBUG) << "radio clock " << radioClock->get();
+ while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
+ // if underrun, then we're not providing bursts to radio/USRP fast
+ // enough. Need to increase latency by one GSM frame.
+ if (mRadioInterface->getWindowType() == RadioDevice::TX_WINDOW_USRP1) {
+ if (mRadioInterface->isUnderrun()) {
+ // only update latency at the defined frame interval
+ if (radioClock->get() > mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL)) {
+ mTransmitLatency = mTransmitLatency + GSM::Time(1,0);
+ LOG(INFO) << "new latency: " << mTransmitLatency << " (underrun "
+ << radioClock->get() << " vs " << mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL) << ")";
+ mLatencyUpdateTime = radioClock->get();
+ }
+ }
+ else {
+ // if underrun hasn't occurred in the last sec (216 frames) drop
+ // transmit latency by a timeslot
+ if (mTransmitLatency > mRadioInterface->minLatency()) {
+ if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) {
+ mTransmitLatency.decTN();
+ LOG(INFO) << "reduced latency: " << mTransmitLatency;
+ mLatencyUpdateTime = radioClock->get();
+ }
+ }
+ }
+ }
+ // time to push burst to transmit FIFO
+ pushRadioVector(mTransmitDeadlineClock);
+ mTransmitDeadlineClock.incTN();
+ }
+ }
+
+ radioClock->wait();
+}
+
+
+
+void Transceiver::writeClockInterface()
+{
+ char command[50];
+ // FIXME -- This should be adaptive.
+ sprintf(command,"IND CLOCK %llu",(unsigned long long) (mTransmitDeadlineClock.FN()+2));
+
+ LOG(INFO) << "ClockInterface: sending " << command;
+
+ mClockSocket.write(command, strlen(command) + 1);
+
+ mLastClockUpdateTime = mTransmitDeadlineClock;
+
+}
+
+void *RxUpperLoopAdapter(TransceiverChannel *chan)
+{
+ char thread_name[16];
+ Transceiver *trx = chan->trx;
+ size_t num = chan->num;
+
+ delete chan;
+
+ snprintf(thread_name, 16, "RxUpper%zu", num);
+ set_selfthread_name(thread_name);
+
+ trx->setPriority(0.42);
+
+ while (1) {
+ trx->driveReceiveFIFO(num);
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *RxLowerLoopAdapter(Transceiver *transceiver)
+{
+ set_selfthread_name("RxLower");
+
+ transceiver->setPriority(0.45);
+
+ while (1) {
+ transceiver->driveReceiveRadio();
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *TxLowerLoopAdapter(Transceiver *transceiver)
+{
+ set_selfthread_name("TxLower");
+
+ transceiver->setPriority(0.44);
+
+ while (1) {
+ transceiver->driveTxFIFO();
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *ControlServiceLoopAdapter(TransceiverChannel *chan)
+{
+ char thread_name[16];
+ Transceiver *trx = chan->trx;
+ size_t num = chan->num;
+
+ delete chan;
+
+ snprintf(thread_name, 16, "CtrlService%zu", num);
+ set_selfthread_name(thread_name);
+
+ while (1) {
+ trx->driveControl(num);
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void *TxUpperLoopAdapter(TransceiverChannel *chan)
+{
+ char thread_name[16];
+ Transceiver *trx = chan->trx;
+ size_t num = chan->num;
+
+ delete chan;
+
+ snprintf(thread_name, 16, "TxUpper%zu", num);
+ set_selfthread_name(thread_name);
+
+ trx->setPriority(0.40);
+
+ while (1) {
+ trx->driveTxPriorityQueue(num);
+ pthread_testcancel();
+ }
+ return NULL;
+}
diff --git a/Transceiver52M/Transceiver.h b/Transceiver52M/Transceiver.h
new file mode 100644
index 0000000..e250adc
--- /dev/null
+++ b/Transceiver52M/Transceiver.h
@@ -0,0 +1,282 @@
+/*
+* Copyright 2008 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "radioInterface.h"
+#include "Interthread.h"
+#include "GSMCommon.h"
+#include "Sockets.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+extern "C" {
+#include <osmocom/core/signal.h>
+#include "config_defs.h"
+}
+
+class Transceiver;
+
+/** Channel descriptor for transceiver object and channel number pair */
+struct TransceiverChannel {
+ TransceiverChannel(Transceiver *trx, int num)
+ {
+ this->trx = trx;
+ this->num = num;
+ }
+
+ ~TransceiverChannel()
+ {
+ }
+
+ Transceiver *trx;
+ size_t num;
+};
+
+/** Internal transceiver state variables */
+struct TransceiverState {
+ TransceiverState();
+ ~TransceiverState();
+
+ /* Initialize a multiframe slot in the filler table */
+ bool init(FillerType filler, size_t sps, float scale, size_t rtsc, unsigned rach_delay);
+
+ int chanType[8];
+
+ /* Last timestamp of each timeslot's channel estimate */
+ GSM::Time chanEstimateTime[8];
+
+ /* The filler table */
+ signalVector *fillerTable[102][8];
+ int fillerModulus[8];
+ bool mRetrans;
+
+ /* Most recent channel estimate of all timeslots */
+ signalVector *chanResponse[8];
+
+ /* Most recent DFE feedback filter of all timeslots */
+ signalVector *DFEForward[8];
+ signalVector *DFEFeedback[8];
+
+ /* Most recent SNR, timing, and channel amplitude estimates */
+ float SNRestimate[8];
+ float chanRespOffset[8];
+ complex chanRespAmplitude[8];
+
+ /* Received noise energy levels */
+ float mNoiseLev;
+ noiseVector mNoises;
+
+ /* Shadowed downlink attenuation */
+ int mPower;
+};
+
+/** 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 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,
+ GSM::Time wTransmitLatency,
+ RadioInterface *wRadioInterface,
+ double wRssiOffset);
+
+ /** Destructor */
+ ~Transceiver();
+
+ /** Start the control loop */
+ bool init(FillerType filler, size_t rtsc, unsigned rach_delay, bool edge);
+
+ /** attach the radioInterface receive FIFO */
+ bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
+ {
+ if (chan >= mReceiveFIFO.size())
+ return false;
+
+ mReceiveFIFO[chan] = wFIFO;
+ return true;
+ }
+
+ /** accessor for number of channels */
+ size_t numChans() const { return mChans; };
+
+ void setSignalHandler(osmo_signal_cbfn cbfn);
+
+ /** Codes for channel combinations */
+ typedef enum {
+ FILL, ///< Channel is transmitted, but unused
+ I, ///< TCH/FS
+ II, ///< TCH/HS, idle every other slot
+ III, ///< TCH/HS
+ IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH
+ V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4
+ VI, ///< CCCH+BCCH, uplink RACH
+ VII, ///< SDCCH/8 + SACCH/8
+ VIII, ///< TCH/F + FACCH/F + SACCH/M
+ IX, ///< TCH/F + SACCH/M
+ X, ///< TCH/FD + SACCH/MD
+ XI, ///< PBCCH+PCCCH+PDTCH+PACCH+PTCCH
+ XII, ///< PCCCH+PDTCH+PACCH+PTCCH
+ XIII, ///< PDTCH+PACCH+PTCCH
+ NONE, ///< Channel is inactive, default
+ LOOPBACK ///< similar go VII, used in loopback testing
+ } ChannelCombination;
+
+private:
+ int mBasePort;
+ std::string mLocalAddr;
+ std::string mRemoteAddr;
+
+ std::vector<UDPSocket *> mDataSockets; ///< socket for writing to/reading from GSM core
+ std::vector<UDPSocket *> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
+ UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core
+
+ std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core
+ std::vector<VectorFIFO *> mReceiveFIFO; ///< radioInterface FIFO of receive bursts
+
+ 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
+ GSM::Time mLatencyUpdateTime; ///< last time latency was updated
+ GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO
+ GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core
+
+ RadioInterface *mRadioInterface; ///< associated radioInterface object
+ double txFullScale; ///< full scale input to radio
+ double rxFullScale; ///< full scale output to radio
+
+ double rssiOffset; ///< RSSI to dBm conversion offset
+
+ osmo_signal_cbfn *sig_cbfn; ///< Registered Signal Handler to announce events.
+
+ /** modulate and add a burst to the transmit queue */
+ void addRadioVector(size_t chan, BitVector &bits,
+ int RSSI, GSM::Time &wTime);
+
+ /** Update filler table */
+ void updateFillerTable(size_t chan, radioVector *burst);
+
+ /** Push modulated burst into transmit FIFO corresponding to a particular timestamp */
+ void pushRadioVector(GSM::Time &nowTime);
+
+ /** Pull and demodulate a burst from the receive FIFO */
+ SoftVector *pullRadioVector(GSM::Time &wTime, double &RSSI, bool &isRssiValid,
+ double &timingOffset, double &noise,
+ size_t chan = 0);
+
+ /** Set modulus for specific timeslot */
+ void setModulus(size_t timeslot, size_t chan);
+
+ /** return the expected burst type for the specified timestamp */
+ CorrType expectedCorrType(GSM::Time currTime, size_t chan);
+
+ /** send messages over the clock socket */
+ void writeClockInterface(void);
+
+ int mSPSTx; ///< number of samples per Tx symbol
+ int mSPSRx; ///< number of samples per Rx symbol
+ size_t mChans;
+
+ 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
+ double mTxFreq; ///< the transmit frequency
+ double mRxFreq; ///< the receive frequency
+ unsigned mTSC; ///< the midamble sequence code
+ unsigned mMaxExpectedDelayAB; ///< maximum expected time-of-arrival offset in GSM symbols for Access Bursts (RACH)
+ unsigned mMaxExpectedDelayNB; ///< maximum expected time-of-arrival offset in GSM symbols for Normal Bursts
+ unsigned mWriteBurstToDiskMask; ///< debug: bitmask to indicate which timeslots to dump to disk
+
+ std::vector<TransceiverState> mStates;
+
+ /** Start and stop I/O threads through the control socket API */
+ bool start();
+ void stop();
+
+ /** Protect destructor accessable stop call */
+ Mutex mLock;
+
+protected:
+ /** drive lower receive I/O and burst generation */
+ void driveReceiveRadio();
+
+ /** drive demodulation of GSM bursts */
+ void 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
+ */
+ bool driveTxPriorityQueue(size_t chan);
+
+ friend void *RxUpperLoopAdapter(TransceiverChannel *);
+
+ friend void *TxUpperLoopAdapter(TransceiverChannel *);
+
+ friend void *RxLowerLoopAdapter(Transceiver *);
+
+ friend void *TxLowerLoopAdapter(Transceiver *);
+
+ friend void *ControlServiceLoopAdapter(TransceiverChannel *);
+
+
+ void reset();
+
+ /** set priority on current thread */
+ void setPriority(float prio = 0.5) { mRadioInterface->setPriority(prio); }
+
+ void logRxBurst(size_t chan, SoftVector *burst, GSM::Time time, double dbm,
+ double rssi, double noise, double toa);
+};
+
+void *RxUpperLoopAdapter(TransceiverChannel *);
+
+/** Main drive threads */
+void *RxLowerLoopAdapter(Transceiver *);
+void *TxLowerLoopAdapter(Transceiver *);
+
+/** control message handler thread loop */
+void *ControlServiceLoopAdapter(TransceiverChannel *);
+
+/** transmit queueing thread loop */
+void *TxUpperLoopAdapter(TransceiverChannel *);
diff --git a/Transceiver52M/arch/Makefile.am b/Transceiver52M/arch/Makefile.am
new file mode 100644
index 0000000..14e6c82
--- /dev/null
+++ b/Transceiver52M/arch/Makefile.am
@@ -0,0 +1,8 @@
+include $(top_srcdir)/Makefile.common
+
+SUBDIRS = common
+if ARCH_ARM
+SUBDIRS += arm
+else
+SUBDIRS += x86
+endif
diff --git a/Transceiver52M/arch/arm/Makefile.am b/Transceiver52M/arch/arm/Makefile.am
new file mode 100644
index 0000000..bc6da72
--- /dev/null
+++ b/Transceiver52M/arch/arm/Makefile.am
@@ -0,0 +1,22 @@
+if ARCH_ARM_A15
+ARCH_FLAGS = -mfpu=neon-vfpv4
+else
+ARCH_FLAGS = -mfpu=neon
+endif
+
+AM_CFLAGS = -Wall $(ARCH_FLAGS) -std=gnu99 -I${srcdir}/../common
+AM_CCASFLAGS = $(ARCH_FLAGS)
+
+noinst_LTLIBRARIES = libarch.la
+
+libarch_la_LIBADD = $(top_builddir)/Transceiver52M/arch/common/libarch_common.la
+
+libarch_la_SOURCES = \
+ convert.c \
+ convert_neon.S \
+ convolve.c \
+ convolve_neon.S \
+ scale.c \
+ scale_neon.S \
+ mult.c \
+ mult_neon.S
diff --git a/Transceiver52M/arch/arm/convert.c b/Transceiver52M/arch/arm/convert.c
new file mode 100644
index 0000000..c94a3d7
--- /dev/null
+++ b/Transceiver52M/arch/arm/convert.c
@@ -0,0 +1,85 @@
+/*
+ * NEON type conversions
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include "convert.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+void neon_convert_ps_si16_4n(short *, const float *, const float *, int);
+void neon_convert_si16_ps_4n(float *, const short *, int);
+
+void convert_init(void) {
+}
+
+/* 4*N 16-bit signed integer conversion with remainder */
+static void neon_convert_si16_ps(float *out,
+ const short *in,
+ int len)
+{
+ int start = len / 4 * 4;
+
+ neon_convert_si16_ps_4n(out, in, len >> 2);
+
+ for (int i = 0; i < len % 4; i++)
+ out[start + i] = (float) in[start + i];
+}
+
+/* 4*N 16-bit signed integer conversion with remainder */
+static void neon_convert_ps_si16(short *out,
+ const float *in,
+ const float *scale,
+ int len)
+{
+ int start = len / 4 * 4;
+
+ neon_convert_ps_si16_4n(out, in, scale, len >> 2);
+
+ for (int i = 0; i < len % 4; i++)
+ out[start + i] = (short) (in[start + i] * (*scale));
+}
+
+void convert_float_short(short *out, const float *in, float scale, int len)
+{
+#ifdef HAVE_NEON
+ float q[4] = { scale, scale, scale, scale };
+
+ if (len % 4)
+ neon_convert_ps_si16(out, in, q, len);
+ else
+ neon_convert_ps_si16_4n(out, in, q, len >> 2);
+#else
+ base_convert_float_short(out, in, scale, len);
+#endif
+}
+
+void convert_short_float(float *out, const short *in, int len)
+{
+#ifdef HAVE_NEON
+ if (len % 4)
+ neon_convert_si16_ps(out, in, len);
+ else
+ neon_convert_si16_ps_4n(out, in, len >> 2);
+#else
+ base_convert_short_float(out, in, len);
+#endif
+}
diff --git a/Transceiver52M/arch/arm/convert_neon.S b/Transceiver52M/arch/arm/convert_neon.S
new file mode 100644
index 0000000..842ed9f
--- /dev/null
+++ b/Transceiver52M/arch/arm/convert_neon.S
@@ -0,0 +1,51 @@
+/*
+ * NEON type conversions
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+ .text
+ .align 2
+ .global neon_convert_ps_si16_4n
+ .type neon_convert_ps_si16_4n, %function
+neon_convert_ps_si16_4n:
+ vld1.32 {q1}, [r2]
+.loop_fltint:
+ vld1.64 {d0-d1}, [r1]!
+ vmul.f32 q0, q1
+ vcvt.s32.f32 q2, q0
+ vqmovn.s32 d0, q2
+ vst1.64 {d0}, [r0]!
+ subs r3, #1
+ bne .loop_fltint
+ bx lr
+ .size neon_convert_ps_si16_4n, .-neon_convert_ps_si16_4n
+ .text
+ .align 2
+ .global neon_convert_si16_ps_4n
+ .type neon_convert_si16_ps_4n, %function
+neon_convert_si16_ps_4n:
+.loop_intflt:
+ vld1.64 {d0}, [r1]!
+ vmovl.s16 q1, d0
+ vcvt.f32.s32 q0, q1
+ vst1.64 {q0}, [r0]!
+ subs r2, #1
+ bne .loop_intflt
+ bx lr
+ .size neon_convert_si16_ps_4n, .-neon_convert_si16_ps_4n
+ .section .note.GNU-stack,"",%progbits
diff --git a/Transceiver52M/arch/arm/convolve.c b/Transceiver52M/arch/arm/convolve.c
new file mode 100644
index 0000000..912d0c2
--- /dev/null
+++ b/Transceiver52M/arch/arm/convolve.c
@@ -0,0 +1,146 @@
+/*
+ * NEON Convolution
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Forward declarations from base implementation */
+int _base_convolve_real(float *x, int x_len,
+ float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int _base_convolve_complex(float *x, int x_len,
+ float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int bounds_check(int x_len, int h_len, int y_len,
+ int start, int len, int step);
+
+#ifdef HAVE_NEON
+/* Calls into NEON assembler */
+void neon_conv_real4(float *x, float *h, float *y, int len);
+void neon_conv_real8(float *x, float *h, float *y, int len);
+void neon_conv_real12(float *x, float *h, float *y, int len);
+void neon_conv_real16(float *x, float *h, float *y, int len);
+void neon_conv_real20(float *x, float *h, float *y, int len);
+void mac_cx_neon4(float *x, float *h, float *y, int len);
+
+/* Complex-complex convolution */
+static void neon_conv_cmplx_4n(float *x, float *h, float *y, int h_len, int len)
+{
+ for (int i = 0; i < len; i++)
+ mac_cx_neon4(&x[2 * i], h, &y[2 * i], h_len >> 2);
+}
+#endif
+
+/* API: Initalize convolve module */
+void convolve_init(void)
+{
+ /* Stub */
+ return;
+}
+
+/* API: Aligned complex-real */
+int convolve_real(float *x, int x_len,
+ float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset)
+{
+ void (*conv_func)(float *, float *, float *, int) = NULL;
+
+ if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
+ return -1;
+
+ memset(y, 0, len * 2 * sizeof(float));
+
+#ifdef HAVE_NEON
+ if (step <= 4) {
+ switch (h_len) {
+ case 4:
+ conv_func = neon_conv_real4;
+ break;
+ case 8:
+ conv_func = neon_conv_real8;
+ break;
+ case 12:
+ conv_func = neon_conv_real12;
+ break;
+ case 16:
+ conv_func = neon_conv_real16;
+ break;
+ case 20:
+ conv_func = neon_conv_real20;
+ break;
+ }
+ }
+#endif
+ if (conv_func) {
+ conv_func(&x[2 * (-(h_len - 1) + start)],
+ h, y, len);
+ } else {
+ _base_convolve_real(x, x_len,
+ h, h_len,
+ y, y_len,
+ start, len, step, offset);
+ }
+
+ return len;
+}
+
+
+/* API: Aligned complex-complex */
+int convolve_complex(float *x, int x_len,
+ float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset)
+{
+ void (*conv_func)(float *, float *, float *, int, int) = NULL;
+
+ if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
+ return -1;
+
+ memset(y, 0, len * 2 * sizeof(float));
+
+#ifdef HAVE_NEON
+ if (step <= 4 && !(h_len % 4))
+ conv_func = neon_conv_cmplx_4n;
+#endif
+ if (conv_func) {
+ conv_func(&x[2 * (-(h_len - 1) + start)],
+ h, y, h_len, len);
+ } else {
+ _base_convolve_complex(x, x_len,
+ h, h_len,
+ y, y_len,
+ start, len, step, offset);
+ }
+
+ return len;
+}
diff --git a/Transceiver52M/arch/arm/convolve_neon.S b/Transceiver52M/arch/arm/convolve_neon.S
new file mode 100644
index 0000000..637d150
--- /dev/null
+++ b/Transceiver52M/arch/arm/convolve_neon.S
@@ -0,0 +1,277 @@
+/*
+ * NEON Convolution
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+#include "config.h"
+#endif
+
+ .syntax unified
+ .text
+ .align 2
+ .global neon_conv_real4
+ .type neon_conv_real4, %function
+neon_conv_real4:
+ push {r4, lr}
+ vpush {q4-q7}
+ vld2.32 {q0-q1}, [r1]
+ ldr r4, =8
+.neon_conv_loop4:
+ vld2.32 {q2-q3}, [r0], r4
+ vmul.f32 q4, q2, q0
+ vmul.f32 q5, q3, q0
+ vpadd.f32 d12, d8, d9
+ vpadd.f32 d13, d10, d11
+ vpadd.f32 d14, d12, d13
+ vst1.64 {d14}, [r2]!
+ subs r3, r3, #1
+ bne .neon_conv_loop4
+ vpop {q4-q7}
+ pop {r4, pc}
+ .size neon_conv_real4, .-neon_conv_real4
+ .align 2
+ .p2align 4,,15
+ .global neon_conv_real8
+ .type neon_conv_real8, %function
+neon_conv_real8:
+ push {r4-r5, lr}
+ vpush {q4-q7}
+ vld2.32 {q0-q1}, [r1]!
+ vld2.32 {q2-q3}, [r1]
+ add r4, r0, #32
+ ldr r5, =8
+.neon_conv_loop8:
+ vld2.32 {q4-q5}, [r0], r5
+ vld2.32 {q6-q7}, [r4], r5
+ vmul.f32 q8, q4, q0
+ vmul.f32 q9, q5, q0
+ vmul.f32 q10, q6, q2
+ vmul.f32 q11, q7, q2
+
+ vadd.f32 q12, q8, q10
+ vadd.f32 q13, q9, q11
+
+ vpadd.f32 d22, d24, d25
+ vpadd.f32 d23, d26, d27
+ vpadd.f32 d24, d22, d23
+ vst1.64 {d24}, [r2]!
+ subs r3, r3, #1
+ bne .neon_conv_loop8
+ vpop {q4-q7}
+ pop {r4-r5, pc}
+ .size neon_conv_real8, .-neon_conv_real8
+ .align 2
+ .global neon_conv_real12
+ .type neon_conv_real12, %function
+neon_conv_real12:
+ push {r4-r6, lr}
+ vpush {q4-q7}
+ vld2.32 {q0-q1}, [r1]!
+ vld2.32 {q2-q3}, [r1]!
+ vld2.32 {q4-q5}, [r1]!
+ add r4, r0, #32
+ add r5, r0, #64
+ ldr r6, =8
+.neon_conv_loop12:
+ vld2.32 {q6-q7}, [r0], r6
+ vld2.32 {q8-q9}, [r4], r6
+ vld2.32 {q10-q11}, [r5], r6
+#ifdef HAVE_NEON_FMA
+ vfma.f32 q1, q6, q0
+ vfma.f32 q3, q7, q0
+ vfma.f32 q1, q8, q2
+ vfma.f32 q3, q9, q2
+ vfma.f32 q1, q10, q4
+ vfma.f32 q3, q11, q4
+#else
+ vmul.f32 q12, q6, q0
+ vmul.f32 q13, q7, q0
+ vmul.f32 q14, q8, q2
+ vmul.f32 q15, q9, q2
+ vmul.f32 q1, q10, q4
+ vmul.f32 q3, q11, q4
+
+ vadd.f32 q5, q12, q14
+ vadd.f32 q6, q13, q15
+ vadd.f32 q1, q5, q1
+ vadd.f32 q3, q6, q3
+#endif
+ vpadd.f32 d2, d2, d3
+ vpadd.f32 d3, d6, d7
+ vpadd.f32 d6, d2, d3
+ vst1.64 {d6}, [r2]!
+ subs r3, r3, #1
+ bne .neon_conv_loop12
+ vpop {q4-q7}
+ pop {r4-r6, pc}
+ .size neon_conv_real12, .-neon_conv_real12
+ .align 2
+ .global neon_conv_real16
+ .type neon_conv_real16, %function
+neon_conv_real16:
+ push {r4-r7, lr}
+ vpush {q4-q7}
+ vld2.32 {q0-q1}, [r1]!
+ vld2.32 {q2-q3}, [r1]!
+ vld2.32 {q4-q5}, [r1]!
+ vld2.32 {q6-q7}, [r1]
+ add r4, r0, #32
+ add r5, r0, #64
+ add r6, r0, #96
+ ldr r7, =8
+.neon_conv_loop16:
+ vld2.32 {q8-q9}, [r0], r7
+ vld2.32 {q10-q11}, [r4], r7
+ vld2.32 {q12-q13}, [r5], r7
+ vld2.32 {q14-q15}, [r6], r7
+#ifdef HAVE_NEON_FMA
+ vmul.f32 q1, q8, q0
+ vmul.f32 q3, q9, q0
+ vfma.f32 q1, q10, q2
+ vfma.f32 q3, q11, q2
+ vfma.f32 q1, q12, q4
+ vfma.f32 q3, q13, q4
+ vfma.f32 q1, q14, q6
+ vfma.f32 q3, q15, q6
+#else
+ vmul.f32 q1, q8, q0
+ vmul.f32 q3, q9, q0
+ vmul.f32 q5, q10, q2
+ vmul.f32 q7, q11, q2
+ vmul.f32 q8, q12, q4
+ vmul.f32 q9, q13, q4
+ vmul.f32 q10, q14, q6
+ vmul.f32 q11, q15, q6
+
+ vadd.f32 q1, q1, q5
+ vadd.f32 q3, q3, q7
+ vadd.f32 q5, q8, q10
+ vadd.f32 q7, q9, q11
+ vadd.f32 q1, q1, q5
+ vadd.f32 q3, q3, q7
+#endif
+ vpadd.f32 d2, d2, d3
+ vpadd.f32 d3, d6, d7
+ vpadd.f32 d6, d2, d3
+ vst1.64 {d6}, [r2]!
+ subs r3, r3, #1
+ bne .neon_conv_loop16
+ vpop {q4-q7}
+ pop {r4-r7, pc}
+ .size neon_conv_real16, .-neon_conv_real16
+ .align 2
+ .global neon_conv_real20
+ .type neon_conv_real20, %function
+neon_conv_real20:
+ push {r4-r8, lr}
+ vpush {q4-q7}
+ vld2.32 {q0-q1}, [r1]!
+ vld2.32 {q2-q3}, [r1]!
+ vld2.32 {q4-q5}, [r1]!
+ vld2.32 {q6-q7}, [r1]!
+ vld2.32 {q8-q9}, [r1]
+ add r4, r0, #32
+ add r5, r0, #64
+ add r6, r0, #96
+ add r7, r0, #128
+ ldr r8, =8
+.neon_conv_loop20:
+ vld2.32 {q10-q11}, [r0], r8
+ vld2.32 {q12-q13}, [r4], r8
+ vld2.32 {q14-q15}, [r5], r8
+#ifdef HAVE_NEON_FMA
+ vmul.f32 q1, q10, q0
+ vfma.f32 q1, q12, q2
+ vfma.f32 q1, q14, q4
+ vmul.f32 q3, q11, q0
+ vfma.f32 q3, q13, q2
+ vfma.f32 q3, q15, q4
+
+ vld2.32 {q12-q13}, [r6], r8
+ vld2.32 {q14-q15}, [r7], r8
+
+ vfma.f32 q1, q12, q6
+ vfma.f32 q3, q13, q6
+ vfma.f32 q1, q14, q8
+ vfma.f32 q3, q15, q8
+#else
+ vmul.f32 q1, q10, q0
+ vmul.f32 q3, q12, q2
+ vmul.f32 q5, q14, q4
+ vmul.f32 q7, q11, q0
+ vmul.f32 q9, q13, q2
+ vmul.f32 q10, q15, q4
+ vadd.f32 q1, q1, q3
+ vadd.f32 q3, q7, q9
+ vadd.f32 q9, q1, q5
+ vadd.f32 q10, q3, q10
+
+ vld2.32 {q12-q13}, [r6], r8
+ vld2.32 {q14-q15}, [r7], r8
+
+ vmul.f32 q1, q12, q6
+ vmul.f32 q3, q13, q6
+ vmul.f32 q5, q14, q8
+ vmul.f32 q7, q15, q8
+ vadd.f32 q12, q1, q9
+ vadd.f32 q14, q3, q10
+ vadd.f32 q1, q12, q5
+ vadd.f32 q3, q14, q7
+#endif
+ vpadd.f32 d2, d2, d3
+ vpadd.f32 d3, d6, d7
+ vpadd.f32 d6, d2, d3
+ vst1.64 {d6}, [r2]!
+ subs r3, r3, #1
+ bne .neon_conv_loop20
+ vpop {q4-q7}
+ pop {r4-r8, pc}
+ .size neon_conv_real20, .-neon_conv_real20
+ .align 2
+ .global mac_cx_neon4
+ .type mac_cx_neon4, %function
+mac_cx_neon4:
+ push {r4, lr}
+ ldr r4, =32
+ veor q14, q14
+ veor q15, q15
+.neon_conv_loop_mac4:
+ vld2.32 {q0-q1}, [r0], r4
+ vld2.32 {q2-q3}, [r1]!
+
+ vmul.f32 q10, q0, q2
+ vmul.f32 q11, q1, q3
+ vmul.f32 q12, q0, q3
+ vmul.f32 q13, q2, q1
+ vsub.f32 q8, q10, q11
+ vadd.f32 q9, q12, q13
+
+ vadd.f32 q14, q8
+ vadd.f32 q15, q9
+ subs r3, #1
+ bne .neon_conv_loop_mac4
+
+ vld1.64 d0, [r2]
+ vpadd.f32 d28, d28, d29
+ vpadd.f32 d30, d30, d31
+ vpadd.f32 d1, d28, d30
+ vadd.f32 d1, d0
+ vst1.64 d1, [r2]
+ pop {r4, pc}
+ .size mac_cx_neon4, .-mac_cx_neon4
+ .section .note.GNU-stack,"",%progbits
diff --git a/Transceiver52M/arch/arm/mult.c b/Transceiver52M/arch/arm/mult.c
new file mode 100644
index 0000000..245be50
--- /dev/null
+++ b/Transceiver52M/arch/arm/mult.c
@@ -0,0 +1,56 @@
+/*
+ * NEON scaling
+ * Copyright (C) 2012,2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include <mult.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+void neon_cmplx_mul_4n(float *, float *, float *, int);
+
+static void cmplx_mul_ps(float *out, float *a, float *b, int len)
+{
+ float ai, aq, bi, bq;
+
+ for (int i = 0; i < len; i++) {
+ ai = a[2 * i + 0];
+ aq = a[2 * i + 1];
+
+ bi = b[2 * i + 0];
+ bq = b[2 * i + 1];
+
+ out[2 * i + 0] = ai * bi - aq * bq;
+ out[2 * i + 1] = ai * bq + aq * bi;
+ }
+}
+
+void mul_complex(float *out, float *a, float *b, int len)
+{
+#ifdef HAVE_NEON
+ if (len % 4)
+ cmplx_mul_ps(out, a, b, len);
+ else
+ neon_cmplx_mul_4n(out, a, b, len >> 2);
+#else
+ cmplx_mul_ps(out, a, b, len);
+#endif
+}
diff --git a/Transceiver52M/arch/arm/mult_neon.S b/Transceiver52M/arch/arm/mult_neon.S
new file mode 100644
index 0000000..162846e
--- /dev/null
+++ b/Transceiver52M/arch/arm/mult_neon.S
@@ -0,0 +1,42 @@
+/*
+ * NEON complex multiplication
+ * Copyright (C) 2012,2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+ .text
+ .align 2
+ .global neon_cmplx_mul_4n
+ .type neon_cmplx_mul_4n, %function
+neon_cmplx_mul_4n:
+ vpush {q4-q7}
+.loop_mul:
+ vld2.32 {q0-q1}, [r1]!
+ vld2.32 {q2-q3}, [r2]!
+ vmul.f32 q4, q0, q2
+ vmul.f32 q5, q1, q3
+ vmul.f32 q6, q0, q3
+ vmul.f32 q7, q2, q1
+ vsub.f32 q8, q4, q5
+ vadd.f32 q9, q6, q7
+ vst2.32 {q8-q9}, [r0]!
+ subs r3, #1
+ bne .loop_mul
+ vpop {q4-q7}
+ bx lr
+ .size neon_cmplx_mul_4n, .-neon_cmplx_mul_4n
+ .section .note.GNU-stack,"",%progbits
diff --git a/Transceiver52M/arch/arm/scale.c b/Transceiver52M/arch/arm/scale.c
new file mode 100644
index 0000000..2de13ff
--- /dev/null
+++ b/Transceiver52M/arch/arm/scale.c
@@ -0,0 +1,56 @@
+/*
+ * NEON scaling
+ * Copyright (C) 2012,2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include <scale.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+void neon_scale_4n(float *, float *, float *, int);
+
+static void scale_ps(float *out, float *in, float *scale, int len)
+{
+ float ai, aq, bi, bq;
+
+ bi = scale[0];
+ bq = scale[1];
+
+ for (int i = 0; i < len; i++) {
+ ai = in[2 * i + 0];
+ aq = in[2 * i + 1];
+
+ out[2 * i + 0] = ai * bi - aq * bq;
+ out[2 * i + 1] = ai * bq + aq * bi;
+ }
+}
+
+void scale_complex(float *out, float *in, float* scale, int len)
+{
+#ifdef HAVE_NEON
+ if (len % 4)
+ scale_ps(out, in, scale, len);
+ else
+ neon_scale_4n(in, scale, out, len >> 2);
+#else
+ scale_ps(out, in, scale, len);
+#endif
+}
diff --git a/Transceiver52M/arch/arm/scale_neon.S b/Transceiver52M/arch/arm/scale_neon.S
new file mode 100644
index 0000000..a66fbe5
--- /dev/null
+++ b/Transceiver52M/arch/arm/scale_neon.S
@@ -0,0 +1,50 @@
+/*
+ * ARM NEON Scaling
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+ .text
+ .align 2
+ .global neon_scale_4n
+ .type neon_scale_4n, %function
+neon_scale_4n:
+ push {r4, lr}
+ ldr r4, =32
+
+ vld1.64 d0, [r1]
+ vmov.32 s4, s1
+ vmov.32 s1, s0
+ vmov.64 d1, d0
+ vmov.32 s5, s4
+ vmov.64 d3, d2
+.loop_mul_const:
+ vld2.32 {q2-q3}, [r0], r4
+
+ vmul.f32 q8, q0, q2
+ vmul.f32 q9, q1, q3
+ vmul.f32 q10, q0, q3
+ vmul.f32 q11, q1, q2
+ vsub.f32 q8, q8, q9
+ vadd.f32 q9, q10, q11
+
+ vst2.32 {q8-q9}, [r2]!
+ subs r3, #1
+ bne .loop_mul_const
+ pop {r4, pc}
+ .size neon_scale_4n, .-neon_scale_4n
+ .section .note.GNU-stack,"",%progbits
diff --git a/Transceiver52M/arch/common/Makefile.am b/Transceiver52M/arch/common/Makefile.am
new file mode 100644
index 0000000..6b37906
--- /dev/null
+++ b/Transceiver52M/arch/common/Makefile.am
@@ -0,0 +1,15 @@
+AM_CFLAGS = -Wall -std=gnu99
+
+noinst_LTLIBRARIES = libarch_common.la
+
+noinst_HEADERS = \
+ convolve.h \
+ convert.h \
+ scale.h \
+ mult.h \
+ fft.h
+
+libarch_common_la_SOURCES = \
+ convolve_base.c \
+ convert_base.c \
+ fft.c
diff --git a/Transceiver52M/arch/common/convert.h b/Transceiver52M/arch/common/convert.h
new file mode 100644
index 0000000..73402b0
--- /dev/null
+++ b/Transceiver52M/arch/common/convert.h
@@ -0,0 +1,15 @@
+#ifndef _CONVERT_H_
+#define _CONVERT_H_
+
+void convert_float_short(short *out, const float *in, float scale, int len);
+
+void convert_short_float(float *out, const short *in, int len);
+
+void base_convert_float_short(short *out, const float *in,
+ float scale, int len);
+
+void base_convert_short_float(float *out, const short *in, int len);
+
+void convert_init(void);
+
+#endif /* _CONVERT_H_ */
diff --git a/Transceiver52M/arch/common/convert_base.c b/Transceiver52M/arch/common/convert_base.c
new file mode 100644
index 0000000..5251fb8
--- /dev/null
+++ b/Transceiver52M/arch/common/convert_base.c
@@ -0,0 +1,34 @@
+/*
+ * Conversion
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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"
+
+void base_convert_float_short(short *out, const float *in,
+ float scale, int len)
+{
+ for (int i = 0; i < len; i++)
+ out[i] = in[i] * scale;
+}
+
+void base_convert_short_float(float *out, const short *in, int len)
+{
+ for (int i = 0; i < len; i++)
+ out[i] = in[i];
+}
+
diff --git a/Transceiver52M/arch/common/convolve.h b/Transceiver52M/arch/common/convolve.h
new file mode 100644
index 0000000..43db577
--- /dev/null
+++ b/Transceiver52M/arch/common/convolve.h
@@ -0,0 +1,32 @@
+#ifndef _CONVOLVE_H_
+#define _CONVOLVE_H_
+
+void *convolve_h_alloc(int num);
+
+int convolve_real(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int convolve_complex(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int base_convolve_real(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int base_convolve_complex(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+void convolve_init(void);
+
+#endif /* _CONVOLVE_H_ */
diff --git a/Transceiver52M/arch/common/convolve_base.c b/Transceiver52M/arch/common/convolve_base.c
new file mode 100644
index 0000000..71453a1
--- /dev/null
+++ b/Transceiver52M/arch/common/convolve_base.c
@@ -0,0 +1,156 @@
+/*
+ * Convolution
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Base multiply and accumulate complex-real */
+static void mac_real(const float *x, const float *h, float *y)
+{
+ y[0] += x[0] * h[0];
+ y[1] += x[1] * h[0];
+}
+
+/* Base multiply and accumulate complex-complex */
+static void mac_cmplx(const float *x, const float *h, float *y)
+{
+ y[0] += x[0] * h[0] - x[1] * h[1];
+ y[1] += x[0] * h[1] + x[1] * h[0];
+}
+
+/* Base vector complex-complex multiply and accumulate */
+static void mac_real_vec_n(const float *x, const float *h, float *y,
+ int len, int step, int offset)
+{
+ for (int i = offset; i < len; i += step)
+ mac_real(&x[2 * i], &h[2 * i], y);
+}
+
+/* Base vector complex-complex multiply and accumulate */
+static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
+ int len, int step, int offset)
+{
+ for (int i = offset; i < len; i += step)
+ mac_cmplx(&x[2 * i], &h[2 * i], y);
+}
+
+/* Base complex-real convolution */
+int _base_convolve_real(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset)
+{
+ for (int i = 0; i < len; i++) {
+ mac_real_vec_n(&x[2 * (i - (h_len - 1) + start)],
+ h,
+ &y[2 * i], h_len,
+ step, offset);
+ }
+
+ return len;
+}
+
+/* Base complex-complex convolution */
+int _base_convolve_complex(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset)
+{
+ for (int i = 0; i < len; i++) {
+ mac_cmplx_vec_n(&x[2 * (i - (h_len - 1) + start)],
+ h,
+ &y[2 * i],
+ h_len, step, offset);
+ }
+
+ return len;
+}
+
+/* Buffer validity checks */
+int bounds_check(int x_len, int h_len, int y_len,
+ int start, int len, int step)
+{
+ if ((x_len < 1) || (h_len < 1) ||
+ (y_len < 1) || (len < 1) || (step < 1)) {
+ fprintf(stderr, "Convolve: Invalid input\n");
+ return -1;
+ }
+
+ if ((start + len > x_len) || (len > y_len) || (x_len < h_len)) {
+ fprintf(stderr, "Convolve: Boundary exception\n");
+ fprintf(stderr, "start: %i, len: %i, x: %i, h: %i, y: %i\n",
+ start, len, x_len, h_len, y_len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* API: Non-aligned (no SSE) complex-real */
+int base_convolve_real(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset)
+{
+ if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
+ return -1;
+
+ memset(y, 0, len * 2 * sizeof(float));
+
+ return _base_convolve_real(x, x_len,
+ h, h_len,
+ y, y_len,
+ start, len, step, offset);
+}
+
+/* API: Non-aligned (no SSE) complex-complex */
+int base_convolve_complex(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset)
+{
+ if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
+ return -1;
+
+ memset(y, 0, len * 2 * sizeof(float));
+
+ return _base_convolve_complex(x, x_len,
+ h, h_len,
+ y, y_len,
+ start, len, step, offset);
+}
+
+/* Aligned filter tap allocation */
+void *convolve_h_alloc(int len)
+{
+#ifdef HAVE_SSE3
+ return memalign(16, len * 2 * sizeof(float));
+#else
+ return malloc(len * 2 * sizeof(float));
+#endif
+}
diff --git a/Transceiver52M/arch/common/fft.c b/Transceiver52M/arch/common/fft.c
new file mode 100644
index 0000000..18b2de7
--- /dev/null
+++ b/Transceiver52M/arch/common/fft.c
@@ -0,0 +1,112 @@
+/*
+ * Fast Fourier transform
+ *
+ * Copyright (C) 2012 Tom Tsou <tom@tsou.cc>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <fftw3.h>
+
+#include "fft.h"
+
+struct fft_hdl {
+ float *fft_in;
+ float *fft_out;
+ int len;
+ fftwf_plan fft_plan;
+};
+
+/*! \brief Initialize FFT backend
+ * \param[in] reverse FFT direction
+ * \param[in] m FFT length
+ * \param[in] istride input stride count
+ * \param[in] ostride output stride count
+ * \param[in] in input buffer (FFTW aligned)
+ * \param[in] out output buffer (FFTW aligned)
+ * \param[in] ooffset initial offset into output buffer
+ *
+ * If the reverse is non-NULL, then an inverse FFT will be used. This is a
+ * wrapper for advanced non-contiguous FFTW usage. See FFTW documentation for
+ * further details.
+ *
+ * http://www.fftw.org/doc/Advanced-Complex-DFTs.html
+ *
+ * It is currently unknown how the offset of the output buffer affects FFTW
+ * memory alignment.
+ */
+struct fft_hdl *init_fft(int reverse, int m, int istride, int ostride,
+ float *in, float *out, int ooffset)
+{
+ int rank = 1;
+ int n[] = { m };
+ int howmany = istride;
+ int idist = 1;
+ int odist = 1;
+ int *inembed = n;
+ int *onembed = n;
+ fftwf_complex *obuffer, *ibuffer;
+
+ struct fft_hdl *hdl = (struct fft_hdl *) malloc(sizeof(struct fft_hdl));
+ if (!hdl)
+ return NULL;
+
+ int direction = FFTW_FORWARD;
+ if (reverse)
+ direction = FFTW_BACKWARD;
+
+ ibuffer = (fftwf_complex *) in;
+ obuffer = (fftwf_complex *) out + ooffset;
+
+ hdl->fft_in = in;
+ hdl->fft_out = out;
+ hdl->fft_plan = fftwf_plan_many_dft(rank, n, howmany,
+ ibuffer, inembed, istride, idist,
+ obuffer, onembed, ostride, odist,
+ direction, FFTW_MEASURE);
+ return hdl;
+}
+
+void *fft_malloc(size_t size)
+{
+ return fftwf_malloc(size);
+}
+
+void fft_free(void *ptr)
+{
+ free(ptr);
+}
+
+/*! \brief Free FFT backend resources
+ */
+void free_fft(struct fft_hdl *hdl)
+{
+ fftwf_destroy_plan(hdl->fft_plan);
+ free(hdl);
+}
+
+/*! \brief Run multiple DFT operations with the initialized plan
+ * \param[in] hdl handle to an intitialized fft struct
+ *
+ * Input and output buffers are configured with init_fft().
+ */
+int cxvec_fft(struct fft_hdl *hdl)
+{
+ fftwf_execute(hdl->fft_plan);
+ return 0;
+}
diff --git a/Transceiver52M/arch/common/fft.h b/Transceiver52M/arch/common/fft.h
new file mode 100644
index 0000000..fb7bede
--- /dev/null
+++ b/Transceiver52M/arch/common/fft.h
@@ -0,0 +1,13 @@
+#ifndef _FFT_H_
+#define _FFT_H_
+
+struct fft_hdl;
+
+struct fft_hdl *init_fft(int reverse, int m, int istride, int ostride,
+ float *in, float *out, int ooffset);
+void *fft_malloc(size_t size);
+void fft_free(void *ptr);
+void free_fft(struct fft_hdl *hdl);
+int cxvec_fft(struct fft_hdl *hdl);
+
+#endif /* _FFT_H_ */
diff --git a/Transceiver52M/arch/common/mult.h b/Transceiver52M/arch/common/mult.h
new file mode 100644
index 0000000..4d96efb
--- /dev/null
+++ b/Transceiver52M/arch/common/mult.h
@@ -0,0 +1,6 @@
+#ifndef _MULT_H_
+#define _MULT_H_
+
+void mul_complex(float *out, float *a, float *b, int len);
+
+#endif /* _MULT_H_ */
diff --git a/Transceiver52M/arch/common/scale.h b/Transceiver52M/arch/common/scale.h
new file mode 100644
index 0000000..da867e7
--- /dev/null
+++ b/Transceiver52M/arch/common/scale.h
@@ -0,0 +1,6 @@
+#ifndef _SCALE_H_
+#define _SCALE_H_
+
+void scale_complex(float *out, float *in, float *scale, int len);
+
+#endif /* _SCALE_H_ */
diff --git a/Transceiver52M/arch/x86/Makefile.am b/Transceiver52M/arch/x86/Makefile.am
new file mode 100644
index 0000000..a79b80a
--- /dev/null
+++ b/Transceiver52M/arch/x86/Makefile.am
@@ -0,0 +1,33 @@
+AM_CFLAGS = -Wall -std=gnu99 -I${srcdir}/../common
+
+noinst_LTLIBRARIES = libarch.la
+noinst_LTLIBRARIES += libarch_sse_3.la
+noinst_LTLIBRARIES += libarch_sse_4_1.la
+
+noinst_HEADERS = \
+ convert_sse_3.h \
+ convert_sse_4_1.h \
+ convolve_sse_3.h
+
+libarch_la_LIBADD = $(top_builddir)/Transceiver52M/arch/common/libarch_common.la
+
+# SSE 3 specific code
+if HAVE_SSE3
+libarch_sse_3_la_SOURCES = \
+ convert_sse_3.c \
+ convolve_sse_3.c
+libarch_sse_3_la_CFLAGS = $(AM_CFLAGS) -msse3
+libarch_la_LIBADD += libarch_sse_3.la
+endif
+
+# SSE 4.1 specific code
+if HAVE_SSE4_1
+libarch_sse_4_1_la_SOURCES = \
+ convert_sse_4_1.c
+libarch_sse_4_1_la_CFLAGS = $(AM_CFLAGS) -msse4.1
+libarch_la_LIBADD += libarch_sse_4_1.la
+endif
+
+libarch_la_SOURCES = \
+ convert.c \
+ convolve.c
diff --git a/Transceiver52M/arch/x86/convert.c b/Transceiver52M/arch/x86/convert.c
new file mode 100644
index 0000000..07cdf59
--- /dev/null
+++ b/Transceiver52M/arch/x86/convert.c
@@ -0,0 +1,83 @@
+/*
+ * SSE type conversions
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include "convert.h"
+#include "convert_sse_3.h"
+#include "convert_sse_4_1.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Architecture dependant function pointers */
+struct convert_cpu_context {
+ void (*convert_si16_ps_16n) (float *, const short *, int);
+ void (*convert_si16_ps) (float *, const short *, int);
+ void (*convert_scale_ps_si16_16n)(short *, const float *, float, int);
+ void (*convert_scale_ps_si16_8n)(short *, const float *, float, int);
+ void (*convert_scale_ps_si16)(short *, const float *, float, int);
+};
+
+static struct convert_cpu_context c;
+
+void convert_init(void)
+{
+ c.convert_scale_ps_si16_16n = base_convert_float_short;
+ c.convert_scale_ps_si16_8n = base_convert_float_short;
+ c.convert_scale_ps_si16 = base_convert_float_short;
+ c.convert_si16_ps_16n = base_convert_short_float;
+ c.convert_si16_ps = base_convert_short_float;
+
+#ifdef HAVE___BUILTIN_CPU_SUPPORTS
+#ifdef HAVE_SSE4_1
+ if (__builtin_cpu_supports("sse4.1")) {
+ c.convert_si16_ps_16n = &_sse_convert_si16_ps_16n;
+ c.convert_si16_ps = &_sse_convert_si16_ps;
+ }
+#endif
+
+#ifdef HAVE_SSE3
+ if (__builtin_cpu_supports("sse3")) {
+ c.convert_scale_ps_si16_16n = _sse_convert_scale_ps_si16_16n;
+ c.convert_scale_ps_si16_8n = _sse_convert_scale_ps_si16_8n;
+ c.convert_scale_ps_si16 = _sse_convert_scale_ps_si16;
+ }
+#endif
+#endif
+}
+
+void convert_float_short(short *out, const float *in, float scale, int len)
+{
+ if (!(len % 16))
+ c.convert_scale_ps_si16_16n(out, in, scale, len);
+ else if (!(len % 8))
+ c.convert_scale_ps_si16_8n(out, in, scale, len);
+ else
+ c.convert_scale_ps_si16(out, in, scale, len);
+}
+
+void convert_short_float(float *out, const short *in, int len)
+{
+ if (!(len % 16))
+ c.convert_si16_ps_16n(out, in, len);
+ else
+ c.convert_si16_ps(out, in, len);
+}
diff --git a/Transceiver52M/arch/x86/convert_sse_3.c b/Transceiver52M/arch/x86/convert_sse_3.c
new file mode 100644
index 0000000..255db67
--- /dev/null
+++ b/Transceiver52M/arch/x86/convert_sse_3.c
@@ -0,0 +1,107 @@
+/*
+ * SSE type conversions
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include "convert_sse_3.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SSE3
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+/* 8*N single precision floats scaled and converted to 16-bit signed integer */
+void _sse_convert_scale_ps_si16_8n(short *restrict out,
+ const float *restrict in,
+ float scale, int len)
+{
+ __m128 m0, m1, m2;
+ __m128i m4, m5;
+
+ for (int i = 0; i < len / 8; i++) {
+ /* Load (unaligned) packed floats */
+ m0 = _mm_loadu_ps(&in[8 * i + 0]);
+ m1 = _mm_loadu_ps(&in[8 * i + 4]);
+ m2 = _mm_load1_ps(&scale);
+
+ /* Scale */
+ m0 = _mm_mul_ps(m0, m2);
+ m1 = _mm_mul_ps(m1, m2);
+
+ /* Convert */
+ m4 = _mm_cvtps_epi32(m0);
+ m5 = _mm_cvtps_epi32(m1);
+
+ /* Pack and store */
+ m5 = _mm_packs_epi32(m4, m5);
+ _mm_storeu_si128((__m128i *) & out[8 * i], m5);
+ }
+}
+
+/* 8*N single precision floats scaled and converted with remainder */
+void _sse_convert_scale_ps_si16(short *restrict out,
+ const float *restrict in, float scale, int len)
+{
+ int start = len / 8 * 8;
+
+ _sse_convert_scale_ps_si16_8n(out, in, scale, len);
+
+ for (int i = 0; i < len % 8; i++)
+ out[start + i] = in[start + i] * scale;
+}
+
+/* 16*N single precision floats scaled and converted to 16-bit signed integer */
+void _sse_convert_scale_ps_si16_16n(short *restrict out,
+ const float *restrict in,
+ float scale, int len)
+{
+ __m128 m0, m1, m2, m3, m4;
+ __m128i m5, m6, m7, m8;
+
+ for (int i = 0; i < len / 16; i++) {
+ /* Load (unaligned) packed floats */
+ m0 = _mm_loadu_ps(&in[16 * i + 0]);
+ m1 = _mm_loadu_ps(&in[16 * i + 4]);
+ m2 = _mm_loadu_ps(&in[16 * i + 8]);
+ m3 = _mm_loadu_ps(&in[16 * i + 12]);
+ m4 = _mm_load1_ps(&scale);
+
+ /* Scale */
+ m0 = _mm_mul_ps(m0, m4);
+ m1 = _mm_mul_ps(m1, m4);
+ m2 = _mm_mul_ps(m2, m4);
+ m3 = _mm_mul_ps(m3, m4);
+
+ /* Convert */
+ m5 = _mm_cvtps_epi32(m0);
+ m6 = _mm_cvtps_epi32(m1);
+ m7 = _mm_cvtps_epi32(m2);
+ m8 = _mm_cvtps_epi32(m3);
+
+ /* Pack and store */
+ m5 = _mm_packs_epi32(m5, m6);
+ m7 = _mm_packs_epi32(m7, m8);
+ _mm_storeu_si128((__m128i *) & out[16 * i + 0], m5);
+ _mm_storeu_si128((__m128i *) & out[16 * i + 8], m7);
+ }
+}
+#endif
diff --git a/Transceiver52M/arch/x86/convert_sse_3.h b/Transceiver52M/arch/x86/convert_sse_3.h
new file mode 100644
index 0000000..c2f87d7
--- /dev/null
+++ b/Transceiver52M/arch/x86/convert_sse_3.h
@@ -0,0 +1,34 @@
+/*
+ * SSE type conversions
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+
+/* 8*N single precision floats scaled and converted to 16-bit signed integer */
+void _sse_convert_scale_ps_si16_8n(short *restrict out,
+ const float *restrict in,
+ float scale, int len);
+
+/* 8*N single precision floats scaled and converted with remainder */
+void _sse_convert_scale_ps_si16(short *restrict out,
+ const float *restrict in, float scale, int len);
+
+/* 16*N single precision floats scaled and converted to 16-bit signed integer */
+void _sse_convert_scale_ps_si16_16n(short *restrict out,
+ const float *restrict in,
+ float scale, int len);
diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.c b/Transceiver52M/arch/x86/convert_sse_4_1.c
new file mode 100644
index 0000000..42a235c
--- /dev/null
+++ b/Transceiver52M/arch/x86/convert_sse_4_1.c
@@ -0,0 +1,77 @@
+/*
+ * SSE type conversions
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include "convert_sse_4_1.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SSE4_1
+#include <smmintrin.h>
+
+/* 16*N 16-bit signed integer converted to single precision floats */
+void _sse_convert_si16_ps_16n(float *restrict out,
+ const short *restrict in, int len)
+{
+ __m128i m0, m1, m2, m3, m4, m5;
+ __m128 m6, m7, m8, m9;
+
+ for (int i = 0; i < len / 16; i++) {
+ /* Load (unaligned) packed floats */
+ m0 = _mm_loadu_si128((__m128i *) & in[16 * i + 0]);
+ m1 = _mm_loadu_si128((__m128i *) & in[16 * i + 8]);
+
+ /* Unpack */
+ m2 = _mm_cvtepi16_epi32(m0);
+ m4 = _mm_cvtepi16_epi32(m1);
+ m0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1, 0, 3, 2));
+ m1 = _mm_shuffle_epi32(m1, _MM_SHUFFLE(1, 0, 3, 2));
+ m3 = _mm_cvtepi16_epi32(m0);
+ m5 = _mm_cvtepi16_epi32(m1);
+
+ /* Convert */
+ m6 = _mm_cvtepi32_ps(m2);
+ m7 = _mm_cvtepi32_ps(m3);
+ m8 = _mm_cvtepi32_ps(m4);
+ m9 = _mm_cvtepi32_ps(m5);
+
+ /* Store */
+ _mm_storeu_ps(&out[16 * i + 0], m6);
+ _mm_storeu_ps(&out[16 * i + 4], m7);
+ _mm_storeu_ps(&out[16 * i + 8], m8);
+ _mm_storeu_ps(&out[16 * i + 12], m9);
+ }
+}
+
+/* 16*N 16-bit signed integer conversion with remainder */
+void _sse_convert_si16_ps(float *restrict out,
+ const short *restrict in, int len)
+{
+ int start = len / 16 * 16;
+
+ _sse_convert_si16_ps_16n(out, in, len);
+
+ for (int i = 0; i < len % 16; i++)
+ out[start + i] = in[start + i];
+}
+
+#endif
diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.h b/Transceiver52M/arch/x86/convert_sse_4_1.h
new file mode 100644
index 0000000..57a5efb
--- /dev/null
+++ b/Transceiver52M/arch/x86/convert_sse_4_1.h
@@ -0,0 +1,28 @@
+/*
+ * SSE type conversions
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+
+/* 16*N 16-bit signed integer converted to single precision floats */
+void _sse_convert_si16_ps_16n(float *restrict out,
+ const short *restrict in, int len);
+
+/* 16*N 16-bit signed integer conversion with remainder */
+void _sse_convert_si16_ps(float *restrict out,
+ const short *restrict in, int len);
diff --git a/Transceiver52M/arch/x86/convolve.c b/Transceiver52M/arch/x86/convolve.c
new file mode 100644
index 0000000..eb38f64
--- /dev/null
+++ b/Transceiver52M/arch/x86/convolve.c
@@ -0,0 +1,172 @@
+/*
+ * SSE Convolution
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include <stdio.h>
+#include "convolve.h"
+#include "convolve_sse_3.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Architecture dependant function pointers */
+struct convolve_cpu_context {
+ void (*conv_cmplx_4n) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_cmplx_8n) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_cmplx) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real4) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real8) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real12) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real16) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real20) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real4n) (const float *, int, const float *, int, float *,
+ int, int, int, int, int);
+ void (*conv_real) (const float *, int, const float *, int, float *, int,
+ int, int, int, int);
+};
+static struct convolve_cpu_context c;
+
+/* Forward declarations from base implementation */
+int _base_convolve_real(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int _base_convolve_complex(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len,
+ int step, int offset);
+
+int bounds_check(int x_len, int h_len, int y_len,
+ int start, int len, int step);
+
+/* API: Initalize convolve module */
+void convolve_init(void)
+{
+ c.conv_cmplx_4n = (void *)_base_convolve_complex;
+ c.conv_cmplx_8n = (void *)_base_convolve_complex;
+ c.conv_cmplx = (void *)_base_convolve_complex;
+ c.conv_real4 = (void *)_base_convolve_real;
+ c.conv_real8 = (void *)_base_convolve_real;
+ c.conv_real12 = (void *)_base_convolve_real;
+ c.conv_real16 = (void *)_base_convolve_real;
+ c.conv_real20 = (void *)_base_convolve_real;
+ c.conv_real4n = (void *)_base_convolve_real;
+ c.conv_real = (void *)_base_convolve_real;
+
+#if defined(HAVE_SSE3) && defined(HAVE___BUILTIN_CPU_SUPPORTS)
+ if (__builtin_cpu_supports("sse3")) {
+ c.conv_cmplx_4n = sse_conv_cmplx_4n;
+ c.conv_cmplx_8n = sse_conv_cmplx_8n;
+ c.conv_real4 = sse_conv_real4;
+ c.conv_real8 = sse_conv_real8;
+ c.conv_real12 = sse_conv_real12;
+ c.conv_real16 = sse_conv_real16;
+ c.conv_real20 = sse_conv_real20;
+ c.conv_real4n = sse_conv_real4n;
+ }
+#endif
+}
+
+/* API: Aligned complex-real */
+int convolve_real(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len, int start, int len, int step, int offset)
+{
+ if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
+ return -1;
+
+ memset(y, 0, len * 2 * sizeof(float));
+
+ if (step <= 4) {
+ switch (h_len) {
+ case 4:
+ c.conv_real4(x, x_len, h, h_len, y, y_len, start, len,
+ step, offset);
+ break;
+ case 8:
+ c.conv_real8(x, x_len, h, h_len, y, y_len, start, len,
+ step, offset);
+ break;
+ case 12:
+ c.conv_real12(x, x_len, h, h_len, y, y_len, start, len,
+ step, offset);
+ break;
+ case 16:
+ c.conv_real16(x, x_len, h, h_len, y, y_len, start, len,
+ step, offset);
+ break;
+ case 20:
+ c.conv_real20(x, x_len, h, h_len, y, y_len, start, len,
+ step, offset);
+ break;
+ default:
+ if (!(h_len % 4))
+ c.conv_real4n(x, x_len, h, h_len, y, y_len,
+ start, len, step, offset);
+ else
+ c.conv_real(x, x_len, h, h_len, y, y_len, start,
+ len, step, offset);
+ }
+ } else
+ c.conv_real(x, x_len, h, h_len, y, y_len, start, len, step,
+ offset);
+
+ return len;
+}
+
+/* API: Aligned complex-complex */
+int convolve_complex(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
+ return -1;
+
+ memset(y, 0, len * 2 * sizeof(float));
+
+ if (step <= 4) {
+ if (!(h_len % 8))
+ c.conv_cmplx_8n(x, x_len, h, h_len, y, y_len, start,
+ len, step, offset);
+ else if (!(h_len % 4))
+ c.conv_cmplx_4n(x, x_len, h, h_len, y, y_len, start,
+ len, step, offset);
+ else
+ c.conv_cmplx(x, x_len, h, h_len, y, y_len, start, len,
+ step, offset);
+ } else
+ c.conv_cmplx(x, x_len, h, h_len, y, y_len, start, len, step,
+ offset);
+
+ return len;
+}
diff --git a/Transceiver52M/arch/x86/convolve_sse_3.c b/Transceiver52M/arch/x86/convolve_sse_3.c
new file mode 100644
index 0000000..dbee3cc
--- /dev/null
+++ b/Transceiver52M/arch/x86/convolve_sse_3.c
@@ -0,0 +1,542 @@
+/*
+ * SSE Convolution
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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>
+#include <string.h>
+#include <stdio.h>
+#include "convolve_sse_3.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SSE3
+#include <xmmintrin.h>
+#include <pmmintrin.h>
+
+/* 4-tap SSE complex-real convolution */
+void sse_conv_real4(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* NOTE: The parameter list of this function has to match the parameter
+ * list of _base_convolve_real() in convolve_base.c. This specific
+ * implementation, ignores some of the parameters of
+ * _base_convolve_complex(), which are: x_len, y_len, offset, step */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[0]);
+ m1 = _mm_load_ps(&h[4]);
+ m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+
+ for (int i = 0; i < len; i++) {
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 4]);
+ m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m3 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m4 = _mm_mul_ps(m2, m7);
+ m5 = _mm_mul_ps(m3, m7);
+
+ /* Sum and store */
+ m6 = _mm_hadd_ps(m4, m5);
+ m0 = _mm_hadd_ps(m6, m6);
+
+ _mm_store_ss(&y[2 * i + 0], m0);
+ m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m0);
+ }
+}
+
+/* 8-tap SSE complex-real convolution */
+void sse_conv_real8(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* See NOTE in sse_conv_real4() */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7, m8, m9;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[0]);
+ m1 = _mm_load_ps(&h[4]);
+ m2 = _mm_load_ps(&h[8]);
+ m3 = _mm_load_ps(&h[12]);
+
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+
+ for (int i = 0; i < len; i++) {
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 4]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 8]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 12]);
+
+ m6 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m8 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m9 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m6 = _mm_mul_ps(m6, m4);
+ m7 = _mm_mul_ps(m7, m4);
+ m8 = _mm_mul_ps(m8, m5);
+ m9 = _mm_mul_ps(m9, m5);
+
+ /* Sum and store */
+ m6 = _mm_add_ps(m6, m8);
+ m7 = _mm_add_ps(m7, m9);
+ m6 = _mm_hadd_ps(m6, m7);
+ m6 = _mm_hadd_ps(m6, m6);
+
+ _mm_store_ss(&y[2 * i + 0], m6);
+ m6 = _mm_shuffle_ps(m6, m6, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m6);
+ }
+}
+
+/* 12-tap SSE complex-real convolution */
+void sse_conv_real12(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* See NOTE in sse_conv_real4() */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7;
+ __m128 m8, m9, m10, m11, m12, m13, m14;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[0]);
+ m1 = _mm_load_ps(&h[4]);
+ m2 = _mm_load_ps(&h[8]);
+ m3 = _mm_load_ps(&h[12]);
+ m4 = _mm_load_ps(&h[16]);
+ m5 = _mm_load_ps(&h[20]);
+
+ m12 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m13 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m14 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
+
+ for (int i = 0; i < len; i++) {
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 4]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 8]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 12]);
+
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ m0 = _mm_loadu_ps(&_x[2 * i + 16]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 20]);
+
+ m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m0 = _mm_mul_ps(m4, m12);
+ m1 = _mm_mul_ps(m5, m12);
+ m2 = _mm_mul_ps(m6, m13);
+ m3 = _mm_mul_ps(m7, m13);
+ m4 = _mm_mul_ps(m8, m14);
+ m5 = _mm_mul_ps(m9, m14);
+
+ /* Sum and store */
+ m8 = _mm_add_ps(m0, m2);
+ m9 = _mm_add_ps(m1, m3);
+ m10 = _mm_add_ps(m8, m4);
+ m11 = _mm_add_ps(m9, m5);
+
+ m2 = _mm_hadd_ps(m10, m11);
+ m3 = _mm_hadd_ps(m2, m2);
+
+ _mm_store_ss(&y[2 * i + 0], m3);
+ m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m3);
+ }
+}
+
+/* 16-tap SSE complex-real convolution */
+void sse_conv_real16(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* See NOTE in sse_conv_real4() */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7;
+ __m128 m8, m9, m10, m11, m12, m13, m14, m15;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[0]);
+ m1 = _mm_load_ps(&h[4]);
+ m2 = _mm_load_ps(&h[8]);
+ m3 = _mm_load_ps(&h[12]);
+
+ m4 = _mm_load_ps(&h[16]);
+ m5 = _mm_load_ps(&h[20]);
+ m6 = _mm_load_ps(&h[24]);
+ m7 = _mm_load_ps(&h[28]);
+
+ m12 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m13 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m14 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
+ m15 = _mm_shuffle_ps(m6, m7, _MM_SHUFFLE(0, 2, 0, 2));
+
+ for (int i = 0; i < len; i++) {
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 4]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 8]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 12]);
+
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ m0 = _mm_loadu_ps(&_x[2 * i + 16]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 20]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 24]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 28]);
+
+ m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m10 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m11 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m0 = _mm_mul_ps(m4, m12);
+ m1 = _mm_mul_ps(m5, m12);
+ m2 = _mm_mul_ps(m6, m13);
+ m3 = _mm_mul_ps(m7, m13);
+
+ m4 = _mm_mul_ps(m8, m14);
+ m5 = _mm_mul_ps(m9, m14);
+ m6 = _mm_mul_ps(m10, m15);
+ m7 = _mm_mul_ps(m11, m15);
+
+ /* Sum and store */
+ m8 = _mm_add_ps(m0, m2);
+ m9 = _mm_add_ps(m1, m3);
+ m10 = _mm_add_ps(m4, m6);
+ m11 = _mm_add_ps(m5, m7);
+
+ m0 = _mm_add_ps(m8, m10);
+ m1 = _mm_add_ps(m9, m11);
+ m2 = _mm_hadd_ps(m0, m1);
+ m3 = _mm_hadd_ps(m2, m2);
+
+ _mm_store_ss(&y[2 * i + 0], m3);
+ m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m3);
+ }
+}
+
+/* 20-tap SSE complex-real convolution */
+void sse_conv_real20(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* See NOTE in sse_conv_real4() */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7;
+ __m128 m8, m9, m11, m12, m13, m14, m15;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[0]);
+ m1 = _mm_load_ps(&h[4]);
+ m2 = _mm_load_ps(&h[8]);
+ m3 = _mm_load_ps(&h[12]);
+ m4 = _mm_load_ps(&h[16]);
+ m5 = _mm_load_ps(&h[20]);
+ m6 = _mm_load_ps(&h[24]);
+ m7 = _mm_load_ps(&h[28]);
+ m8 = _mm_load_ps(&h[32]);
+ m9 = _mm_load_ps(&h[36]);
+
+ m11 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m12 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m13 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
+ m14 = _mm_shuffle_ps(m6, m7, _MM_SHUFFLE(0, 2, 0, 2));
+ m15 = _mm_shuffle_ps(m8, m9, _MM_SHUFFLE(0, 2, 0, 2));
+
+ for (int i = 0; i < len; i++) {
+ /* Multiply-accumulate first 12 taps */
+ m0 = _mm_loadu_ps(&_x[2 * i + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 4]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 8]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 12]);
+ m4 = _mm_loadu_ps(&_x[2 * i + 16]);
+ m5 = _mm_loadu_ps(&_x[2 * i + 20]);
+
+ m6 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m8 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m9 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+ m0 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
+ m1 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(1, 3, 1, 3));
+
+ m2 = _mm_mul_ps(m6, m11);
+ m3 = _mm_mul_ps(m7, m11);
+ m4 = _mm_mul_ps(m8, m12);
+ m5 = _mm_mul_ps(m9, m12);
+ m6 = _mm_mul_ps(m0, m13);
+ m7 = _mm_mul_ps(m1, m13);
+
+ m0 = _mm_add_ps(m2, m4);
+ m1 = _mm_add_ps(m3, m5);
+ m8 = _mm_add_ps(m0, m6);
+ m9 = _mm_add_ps(m1, m7);
+
+ /* Multiply-accumulate last 8 taps */
+ m0 = _mm_loadu_ps(&_x[2 * i + 24]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 28]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 32]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 36]);
+
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ m0 = _mm_mul_ps(m4, m14);
+ m1 = _mm_mul_ps(m5, m14);
+ m2 = _mm_mul_ps(m6, m15);
+ m3 = _mm_mul_ps(m7, m15);
+
+ m4 = _mm_add_ps(m0, m2);
+ m5 = _mm_add_ps(m1, m3);
+
+ /* Final sum and store */
+ m0 = _mm_add_ps(m8, m4);
+ m1 = _mm_add_ps(m9, m5);
+ m2 = _mm_hadd_ps(m0, m1);
+ m3 = _mm_hadd_ps(m2, m2);
+
+ _mm_store_ss(&y[2 * i + 0], m3);
+ m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m3);
+ }
+}
+
+/* 4*N-tap SSE complex-real convolution */
+void sse_conv_real4n(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* See NOTE in sse_conv_real4() */
+
+ __m128 m0, m1, m2, m4, m5, m6, m7;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ for (int i = 0; i < len; i++) {
+ /* Zero */
+ m6 = _mm_setzero_ps();
+ m7 = _mm_setzero_ps();
+
+ for (int n = 0; n < h_len / 4; n++) {
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[8 * n + 0]);
+ m1 = _mm_load_ps(&h[8 * n + 4]);
+ m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 8 * n + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 8 * n + 4]);
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m0 = _mm_mul_ps(m2, m4);
+ m1 = _mm_mul_ps(m2, m5);
+
+ /* Accumulate */
+ m6 = _mm_add_ps(m6, m0);
+ m7 = _mm_add_ps(m7, m1);
+ }
+
+ m0 = _mm_hadd_ps(m6, m7);
+ m0 = _mm_hadd_ps(m0, m0);
+
+ _mm_store_ss(&y[2 * i + 0], m0);
+ m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m0);
+ }
+}
+
+/* 4*N-tap SSE complex-complex convolution */
+void sse_conv_cmplx_4n(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* NOTE: The parameter list of this function has to match the parameter
+ * list of _base_convolve_complex() in convolve_base.c. This specific
+ * implementation, ignores some of the parameters of
+ * _base_convolve_complex(), which are: x_len, y_len, offset, step. */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ for (int i = 0; i < len; i++) {
+ /* Zero */
+ m6 = _mm_setzero_ps();
+ m7 = _mm_setzero_ps();
+
+ for (int n = 0; n < h_len / 4; n++) {
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[8 * n + 0]);
+ m1 = _mm_load_ps(&h[8 * n + 4]);
+ m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m3 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 8 * n + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 8 * n + 4]);
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m0 = _mm_mul_ps(m2, m4);
+ m1 = _mm_mul_ps(m3, m5);
+
+ m2 = _mm_mul_ps(m2, m5);
+ m3 = _mm_mul_ps(m3, m4);
+
+ /* Sum */
+ m0 = _mm_sub_ps(m0, m1);
+ m2 = _mm_add_ps(m2, m3);
+
+ /* Accumulate */
+ m6 = _mm_add_ps(m6, m0);
+ m7 = _mm_add_ps(m7, m2);
+ }
+
+ m0 = _mm_hadd_ps(m6, m7);
+ m0 = _mm_hadd_ps(m0, m0);
+
+ _mm_store_ss(&y[2 * i + 0], m0);
+ m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m0);
+ }
+}
+
+/* 8*N-tap SSE complex-complex convolution */
+void sse_conv_cmplx_8n(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset)
+{
+ /* See NOTE in sse_conv_cmplx_4n() */
+
+ __m128 m0, m1, m2, m3, m4, m5, m6, m7;
+ __m128 m8, m9, m10, m11, m12, m13, m14, m15;
+
+ const float *_x = &x[2 * (-(h_len - 1) + start)];
+
+ for (int i = 0; i < len; i++) {
+ /* Zero */
+ m12 = _mm_setzero_ps();
+ m13 = _mm_setzero_ps();
+ m14 = _mm_setzero_ps();
+ m15 = _mm_setzero_ps();
+
+ for (int n = 0; n < h_len / 8; n++) {
+ /* Load (aligned) filter taps */
+ m0 = _mm_load_ps(&h[16 * n + 0]);
+ m1 = _mm_load_ps(&h[16 * n + 4]);
+ m2 = _mm_load_ps(&h[16 * n + 8]);
+ m3 = _mm_load_ps(&h[16 * n + 12]);
+
+ m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Load (unaligned) input data */
+ m0 = _mm_loadu_ps(&_x[2 * i + 16 * n + 0]);
+ m1 = _mm_loadu_ps(&_x[2 * i + 16 * n + 4]);
+ m2 = _mm_loadu_ps(&_x[2 * i + 16 * n + 8]);
+ m3 = _mm_loadu_ps(&_x[2 * i + 16 * n + 12]);
+
+ m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
+ m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
+ m10 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
+ m11 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
+
+ /* Quad multiply */
+ m0 = _mm_mul_ps(m4, m8);
+ m1 = _mm_mul_ps(m5, m9);
+ m2 = _mm_mul_ps(m6, m10);
+ m3 = _mm_mul_ps(m7, m11);
+
+ m4 = _mm_mul_ps(m4, m9);
+ m5 = _mm_mul_ps(m5, m8);
+ m6 = _mm_mul_ps(m6, m11);
+ m7 = _mm_mul_ps(m7, m10);
+
+ /* Sum */
+ m0 = _mm_sub_ps(m0, m1);
+ m2 = _mm_sub_ps(m2, m3);
+ m4 = _mm_add_ps(m4, m5);
+ m6 = _mm_add_ps(m6, m7);
+
+ /* Accumulate */
+ m12 = _mm_add_ps(m12, m0);
+ m13 = _mm_add_ps(m13, m2);
+ m14 = _mm_add_ps(m14, m4);
+ m15 = _mm_add_ps(m15, m6);
+ }
+
+ m0 = _mm_add_ps(m12, m13);
+ m1 = _mm_add_ps(m14, m15);
+ m2 = _mm_hadd_ps(m0, m1);
+ m2 = _mm_hadd_ps(m2, m2);
+
+ _mm_store_ss(&y[2 * i + 0], m2);
+ m2 = _mm_shuffle_ps(m2, m2, _MM_SHUFFLE(0, 3, 2, 1));
+ _mm_store_ss(&y[2 * i + 1], m2);
+ }
+}
+#endif
diff --git a/Transceiver52M/arch/x86/convolve_sse_3.h b/Transceiver52M/arch/x86/convolve_sse_3.h
new file mode 100644
index 0000000..ac30ca5
--- /dev/null
+++ b/Transceiver52M/arch/x86/convolve_sse_3.h
@@ -0,0 +1,68 @@
+/*
+ * SSE Convolution
+ * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+
+/* 4-tap SSE complex-real convolution */
+void sse_conv_real4(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 8-tap SSE complex-real convolution */
+void sse_conv_real8(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 12-tap SSE complex-real convolution */
+void sse_conv_real12(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 16-tap SSE complex-real convolution */
+void sse_conv_real16(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 20-tap SSE complex-real convolution */
+void sse_conv_real20(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 4*N-tap SSE complex-real convolution */
+void sse_conv_real4n(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 4*N-tap SSE complex-complex convolution */
+void sse_conv_cmplx_4n(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
+
+/* 8*N-tap SSE complex-complex convolution */
+void sse_conv_cmplx_8n(const float *x, int x_len,
+ const float *h, int h_len,
+ float *y, int y_len,
+ int start, int len, int step, int offset);
diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am
new file mode 100644
index 0000000..1a2d077
--- /dev/null
+++ b/Transceiver52M/device/Makefile.am
@@ -0,0 +1,17 @@
+include $(top_srcdir)/Makefile.common
+
+noinst_HEADERS = radioDevice.h
+
+SUBDIRS =
+
+if DEVICE_USRP1
+SUBDIRS += usrp1
+endif
+
+if DEVICE_UHD
+SUBDIRS += uhd
+endif
+
+if DEVICE_LMS
+SUBDIRS += lms
+endif
diff --git a/Transceiver52M/device/lms/LMSDevice.cpp b/Transceiver52M/device/lms/LMSDevice.cpp
new file mode 100644
index 0000000..0a6c4a2
--- /dev/null
+++ b/Transceiver52M/device/lms/LMSDevice.cpp
@@ -0,0 +1,637 @@
+/*
+* Copyright 2018 sysmocom - s.f.m.c. GmbH
+*
+ 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 <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include "Logger.h"
+#include "Threads.h"
+#include "LMSDevice.h"
+
+#include <lime/LimeSuite.h>
+
+#include <osmocom/core/utils.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+using namespace std;
+
+constexpr double LMSDevice::masterClockRate;
+
+#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)
+
+LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths):
+ RadioDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths),
+ m_lms_dev(NULL)
+{
+ LOGC(DDEV, INFO) << "creating LMS device...";
+
+ m_lms_stream_rx.resize(chans);
+ m_lms_stream_tx.resize(chans);
+
+ m_last_rx_underruns.resize(chans, 0);
+ m_last_rx_overruns.resize(chans, 0);
+ m_last_tx_underruns.resize(chans, 0);
+ m_last_tx_overruns.resize(chans, 0);
+}
+
+static void lms_log_callback(int lvl, const char *msg)
+{
+ /* map lime specific log levels */
+ static const int lvl_map[5] = {
+ [0] = LOGL_FATAL,
+ [LMS_LOG_ERROR] = LOGL_ERROR,
+ [LMS_LOG_WARNING] = LOGL_NOTICE,
+ [LMS_LOG_INFO] = LOGL_INFO,
+ [LMS_LOG_DEBUG] = LOGL_DEBUG,
+ };
+ /* protect against future higher log level values (lower importance) */
+ if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map))
+ lvl = ARRAY_SIZE(lvl_map)-1;
+
+ LOGLV(DLMS, lvl_map[lvl]) << msg;
+}
+
+static void thread_enable_cancel(bool cancel)
+{
+ cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+}
+
+static void print_range(const char* name, lms_range_t *range)
+{
+ LOGC(DDEV, INFO) << name << ": Min=" << range->min << " Max=" << range->max
+ << " Step=" << range->step;
+}
+
+int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
+{
+ //lms_info_str_t dev_str;
+ lms_info_str_t* info_list;
+ lms_range_t range_lpfbw_rx, range_lpfbw_tx, range_sr;
+ float_type sr_host, sr_rf, lpfbw_rx, lpfbw_tx;
+ uint16_t dac_val;
+ unsigned int i, n;
+ int rc;
+
+ LOGC(DDEV, INFO) << "Opening LMS device..";
+
+ LMS_RegisterLogHandler(&lms_log_callback);
+
+ if ((n = LMS_GetDeviceList(NULL)) < 0)
+ LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed";
+ LOGC(DDEV, INFO) << "Devices found: " << n;
+ if (n < 1)
+ return -1;
+
+ info_list = new lms_info_str_t[n];
+
+ if (LMS_GetDeviceList(info_list) < 0)
+ LOGC(DDEV, ERROR) << "LMS_GetDeviceList(info_list) failed";
+
+ for (i = 0; i < n; i++)
+ LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i];
+
+ rc = LMS_Open(&m_lms_dev, info_list[0], NULL);
+ if (rc != 0) {
+ LOGC(DDEV, ERROR) << "LMS_GetDeviceList() failed)";
+ delete [] info_list;
+ return -1;
+ }
+
+ delete [] info_list;
+
+ LOGC(DDEV, INFO) << "Init LMS device";
+ if (LMS_Init(m_lms_dev) != 0) {
+ LOGC(DDEV, ERROR) << "LMS_Init() failed";
+ return -1;
+ }
+
+ if (LMS_GetSampleRateRange(m_lms_dev, LMS_CH_RX, &range_sr))
+ 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)
+ 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 */
+
+ switch (ref) {
+ case REF_INTERNAL:
+ LOGC(DDEV, INFO) << "Setting Internal clock reference";
+ /* Ugly API: Selecting clock source implicit by writing to VCTCXO DAC ?!? */
+ if (LMS_VCTCXORead(m_lms_dev, &dac_val) < 0)
+ goto out_close;
+ LOGC(DDEV, INFO) << "Setting VCTCXO to " << dac_val;
+ if (LMS_VCTCXOWrite(m_lms_dev, dac_val) < 0)
+ goto out_close;
+ break;
+ case REF_EXTERNAL:
+ LOGC(DDEV, INFO) << "Setting External clock reference to " << 10000000.0;
+ /* Assume an external 10 MHz reference clock */
+ if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
+ goto out_close;
+ break;
+ default:
+ LOGC(DDEV, ALERT) << "Invalid reference type";
+ goto out_close;
+ }
+
+ if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
+ goto out_close;
+ print_range("LPFBWRange Rx", &range_lpfbw_rx);
+ if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
+ goto out_close;
+ print_range("LPFBWRange Tx", &range_lpfbw_tx);
+ lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
+ lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
+
+ LOGC(DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
+
+ if (!set_antennas()) {
+ LOGC(DDEV, ALERT) << "LMS antenna setting failed";
+ return -1;
+ }
+
+ /* Perform Rx and Tx calibration */
+ for (i=0; i<chans; i++) {
+ LOGC(DDEV, INFO) << "Setting LPFBW chan " << i;
+ if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, i, lpfbw_rx) < 0)
+ goto out_close;
+ if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, i, lpfbw_tx) < 0)
+ goto out_close;
+ LOGC(DDEV, INFO) << "Calibrating chan " << i;
+ if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, i, LMS_CALIBRATE_BW_HZ, 0) < 0)
+ goto out_close;
+ if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, i, LMS_CALIBRATE_BW_HZ, 0) < 0)
+ goto out_close;
+ }
+
+ samplesRead = 0;
+ samplesWritten = 0;
+ started = false;
+
+ return NORMAL;
+
+out_close:
+ LOGC(DDEV, ALERT) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
+ LMS_Close(m_lms_dev);
+ return -1;
+}
+
+bool LMSDevice::start()
+{
+ LOGC(DDEV, INFO) << "starting LMS...";
+
+ unsigned int i;
+
+ /* configure the channels/streams */
+ for (i=0; i<chans; i++) {
+ if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
+ return false;
+
+ if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
+ return false;
+
+ // Set gains to midpoint
+ setTxGain((minTxGain() + maxTxGain()) / 2, i);
+ setRxGain(34.0, i);
+
+ m_lms_stream_rx[i] = {};
+ m_lms_stream_rx[i].isTx = false;
+ m_lms_stream_rx[i].channel = i;
+ m_lms_stream_rx[i].fifoSize = 1024 * 1024;
+ m_lms_stream_rx[i].throughputVsLatency = 0.3;
+ m_lms_stream_rx[i].dataFmt = lms_stream_t::LMS_FMT_I16;
+
+ m_lms_stream_tx[i] = {};
+ m_lms_stream_tx[i].isTx = true;
+ m_lms_stream_tx[i].channel = i;
+ m_lms_stream_tx[i].fifoSize = 1024 * 1024;
+ m_lms_stream_tx[i].throughputVsLatency = 0.3;
+ m_lms_stream_tx[i].dataFmt = lms_stream_t::LMS_FMT_I16;
+
+ if (LMS_SetupStream(m_lms_dev, &m_lms_stream_rx[i]) < 0)
+ return false;
+
+ if (LMS_SetupStream(m_lms_dev, &m_lms_stream_tx[i]) < 0)
+ return false;
+ }
+
+ /* now start the streams in a second loop, as we can no longer call
+ * LMS_SetupStream() after LMS_StartStream() of the first stream */
+ for (i = 0; i < chans; i++) {
+ if (LMS_StartStream(&m_lms_stream_rx[i]) < 0)
+ return false;
+
+ if (LMS_StartStream(&m_lms_stream_tx[i]) < 0)
+ return false;
+ }
+
+ flush_recv(10);
+
+ started = true;
+ return true;
+}
+
+bool LMSDevice::stop()
+{
+ unsigned int i;
+
+ if (!started)
+ return true;
+
+ for (i=0; i<chans; i++) {
+ LMS_StopStream(&m_lms_stream_tx[i]);
+ LMS_StopStream(&m_lms_stream_rx[i]);
+
+ LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
+ LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
+ }
+
+ return true;
+}
+
+double LMSDevice::maxTxGain()
+{
+ return 73.0;
+}
+
+double LMSDevice::minTxGain()
+{
+ return 0.0;
+}
+
+double LMSDevice::maxRxGain()
+{
+ return 73.0;
+}
+
+double LMSDevice::minRxGain()
+{
+ return 0.0;
+}
+
+double LMSDevice::setTxGain(double dB, size_t chan)
+{
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return 0.0;
+ }
+
+ if (dB > maxTxGain())
+ dB = maxTxGain();
+ if (dB < minTxGain())
+ dB = minTxGain();
+
+ LOGC(DDEV, NOTICE) << "Setting TX gain to " << dB << " dB.";
+
+ if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
+ LOGC(DDEV, ERR) << "Error setting TX gain";
+
+ return dB;
+}
+
+double LMSDevice::setRxGain(double dB, size_t chan)
+{
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return 0.0;
+ }
+
+ if (dB > maxRxGain())
+ dB = maxRxGain();
+ if (dB < minRxGain())
+ dB = minRxGain();
+
+ LOGC(DDEV, NOTICE) << "Setting RX gain to " << dB << " dB.";
+
+ if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
+ LOGC(DDEV, ERR) << "Error setting RX gain";
+
+ return dB;
+}
+
+int LMSDevice::get_ant_idx(const std::string & name, bool dir_tx, size_t chan)
+{
+ lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
+ const char* c_name = name.c_str();
+ 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 (!strcmp(c_name, name_list[i]))
+ return i;
+ }
+ return -1;
+}
+
+bool LMSDevice::flush_recv(size_t num_pkts)
+{
+ #define CHUNK 625
+ int len = CHUNK * tx_sps;
+ short *buffer = new short[len * 2];
+ int rc;
+ lms_stream_meta_t rx_metadata = {};
+ rx_metadata.flushPartialPacket = false;
+ rx_metadata.waitForTimestamp = false;
+
+ ts_initial = 0;
+
+ while (!ts_initial || (num_pkts-- > 0)) {
+ rc = LMS_RecvStream(&m_lms_stream_rx[0], &buffer[0], len, &rx_metadata, 100);
+ LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp;
+ if (rc != len) {
+ LOGC(DDEV, ALERT) << "LMS: Device receive timed out";
+ delete[] buffer;
+ return false;
+ }
+
+ ts_initial = rx_metadata.timestamp + len;
+ }
+
+ LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
+ delete[] buffer;
+ return true;
+}
+
+bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan)
+{
+ int idx;
+
+ if (chan >= rx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+
+ idx = get_ant_idx(ant, LMS_CH_RX, chan);
+ if (idx < 0) {
+ LOGC(DDEV, ALERT) << "Invalid Rx Antenna";
+ return false;
+ }
+
+ if (LMS_SetAntenna(m_lms_dev, LMS_CH_RX, chan, idx) < 0) {
+ LOGC(DDEV, ALERT) << "Unable to set Rx Antenna";
+ }
+
+ return true;
+}
+
+std::string LMSDevice::getRxAntenna(size_t chan)
+{
+ lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
+ int idx;
+
+ if (chan >= rx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return "";
+ }
+
+ idx = LMS_GetAntenna(m_lms_dev, LMS_CH_RX, chan);
+ if (idx < 0) {
+ LOGC(DDEV, ALERT) << "Error getting Rx Antenna";
+ return "";
+ }
+
+ if (LMS_GetAntennaList(m_lms_dev, LMS_CH_RX, chan, name_list) < idx) {
+ LOGC(DDEV, ALERT) << "Error getting Rx Antenna List";
+ return "";
+ }
+
+ return name_list[idx];
+}
+
+bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan)
+{
+ int idx;
+
+ if (chan >= tx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+
+ idx = get_ant_idx(ant, LMS_CH_TX, chan);
+ if (idx < 0) {
+ LOGC(DDEV, ALERT) << "Invalid Rx Antenna";
+ return false;
+ }
+
+ if (LMS_SetAntenna(m_lms_dev, LMS_CH_TX, chan, idx) < 0) {
+ LOGC(DDEV, ALERT) << "Unable to set Rx Antenna";
+ }
+
+ return true;
+}
+
+std::string LMSDevice::getTxAntenna(size_t chan)
+{
+ lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
+ int idx;
+
+ if (chan >= tx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return "";
+ }
+
+ idx = LMS_GetAntenna(m_lms_dev, LMS_CH_TX, chan);
+ if (idx < 0) {
+ LOGC(DDEV, ALERT) << "Error getting Tx Antenna";
+ return "";
+ }
+
+ if (LMS_GetAntennaList(m_lms_dev, LMS_CH_TX, chan, name_list) < idx) {
+ LOGC(DDEV, ALERT) << "Error getting Tx Antenna List";
+ return "";
+ }
+
+ return name_list[idx];
+}
+
+bool LMSDevice::requiresRadioAlign()
+{
+ return false;
+}
+
+GSM::Time LMSDevice::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);
+}
+
+// NOTE: Assumes sequential reads
+int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
+ TIMESTAMP timestamp, bool * underrun, unsigned *RSSI)
+{
+ int rc = 0;
+ unsigned int i;
+ lms_stream_status_t status;
+ lms_stream_meta_t rx_metadata = {};
+ rx_metadata.flushPartialPacket = false;
+ rx_metadata.waitForTimestamp = false;
+ rx_metadata.timestamp = 0;
+
+ if (bufs.size() != chans) {
+ LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
+ return -1;
+ }
+
+ *overrun = false;
+ *underrun = false;
+ for (i = 0; i<chans; i++) {
+ thread_enable_cancel(false);
+ rc = LMS_RecvStream(&m_lms_stream_rx[i], bufs[i], len, &rx_metadata, 100);
+ if (rc != len) {
+ LOGC(DDEV, ALERT) << "LMS: Device receive timed out (" << rc << " vs exp " << len << ").";
+ thread_enable_cancel(true);
+ return -1;
+ }
+ if (timestamp != (TIMESTAMP)rx_metadata.timestamp)
+ LOGC(DDEV, ALERT) << "chan "<< i << " recv buffer of len " << rc << " expect " << std::hex << timestamp << " got " << std::hex << (TIMESTAMP)rx_metadata.timestamp << " (" << std::hex << rx_metadata.timestamp <<") diff=" << rx_metadata.timestamp - timestamp;
+
+ if (LMS_GetStreamStatus(&m_lms_stream_rx[i], &status) == 0) {
+ if (status.underrun > m_last_rx_underruns[i])
+ *underrun = true;
+ m_last_rx_underruns[i] = status.underrun;
+
+ if (status.overrun > m_last_rx_overruns[i])
+ *overrun = true;
+ m_last_rx_overruns[i] = status.overrun;
+ }
+ thread_enable_cancel(true);
+ }
+
+ samplesRead += rc;
+
+ if (((TIMESTAMP) rx_metadata.timestamp) < timestamp)
+ rc = 0;
+
+ return rc;
+}
+
+int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
+ bool * underrun, unsigned long long timestamp,
+ bool isControl)
+{
+ int rc = 0;
+ unsigned int i;
+ lms_stream_status_t status;
+ lms_stream_meta_t tx_metadata = {};
+ tx_metadata.flushPartialPacket = false;
+ tx_metadata.waitForTimestamp = true;
+ tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */
+
+ if (isControl) {
+ LOGC(DDEV, ERR) << "Control packets not supported";
+ return 0;
+ }
+
+ if (bufs.size() != chans) {
+ LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
+ return -1;
+ }
+
+ *underrun = false;
+
+ for (i = 0; i<chans; i++) {
+ LOGC(DDEV, DEBUG) << "chan "<< i << " send buffer of len " << len << " timestamp " << std::hex << tx_metadata.timestamp;
+ thread_enable_cancel(false);
+ rc = LMS_SendStream(&m_lms_stream_tx[i], bufs[i], len, &tx_metadata, 100);
+ if (rc != len) {
+ LOGC(DDEV, ALERT) << "LMS: Device send timed out";
+ }
+
+ if (LMS_GetStreamStatus(&m_lms_stream_tx[i], &status) == 0) {
+ if (status.underrun > m_last_tx_underruns[i])
+ *underrun = true;
+ m_last_tx_underruns[i] = status.underrun;
+ }
+ thread_enable_cancel(true);
+ }
+
+ samplesWritten += rc;
+
+ return rc;
+}
+
+bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
+{
+ return true;
+}
+
+bool LMSDevice::setTxFreq(double wFreq, size_t chan)
+{
+
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return false;
+ }
+
+ if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
+ LOGC(DDEV, ALERT) << "set Tx: " << wFreq << " failed!";
+ return false;
+ }
+
+ return true;
+}
+
+bool LMSDevice::setRxFreq(double wFreq, size_t chan)
+{
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return false;
+ }
+
+ if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
+ LOGC(DDEV, ALERT) << "set Rx: " << wFreq << " failed!";
+ return false;
+ }
+
+ 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) << "LMS Requires tx_sps == rx_sps";
+ return NULL;
+ }
+ if (lo_offset != 0.0) {
+ LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset";
+ return NULL;
+ }
+ return new LMSDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+}
diff --git a/Transceiver52M/device/lms/LMSDevice.h b/Transceiver52M/device/lms/LMSDevice.h
new file mode 100644
index 0000000..349efbb
--- /dev/null
+++ b/Transceiver52M/device/lms/LMSDevice.h
@@ -0,0 +1,202 @@
+/*
+* Copyright 2018 sysmocom - s.f.m.c. GmbH
+*
+* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+#ifndef _LMS_DEVICE_H_
+#define _LMS_DEVICE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "radioDevice.h"
+
+#include <sys/time.h>
+#include <math.h>
+#include <limits.h>
+#include <string>
+#include <iostream>
+#include <lime/LimeSuite.h>
+
+#define LIMESDR_TX_AMPL 0.3
+
+/** A class to handle a LimeSuite supported device */
+class LMSDevice:public RadioDevice {
+
+private:
+
+ static constexpr double masterClockRate = 52.0e6;
+
+ lms_device_t *m_lms_dev;
+ std::vector<lms_stream_t> m_lms_stream_rx;
+ std::vector<lms_stream_t> m_lms_stream_tx;
+
+ std::vector<uint32_t> m_last_rx_underruns;
+ std::vector<uint32_t> m_last_rx_overruns;
+ std::vector<uint32_t> m_last_tx_underruns;
+ std::vector<uint32_t> m_last_tx_overruns;
+
+ double actualSampleRate; ///< the actual USRP sampling rate
+
+ unsigned long long samplesRead; ///< number of samples read from LMS
+ unsigned long long samplesWritten; ///< number of samples sent to LMS
+
+ bool started; ///< flag indicates LMS has started
+ bool skipRx; ///< set if LMS is transmit-only.
+
+ TIMESTAMP ts_initial, ts_offset;
+
+ double rxGain;
+
+ int get_ant_idx(const std::string & name, bool dir_tx, size_t chan);
+ bool flush_recv(size_t num_pkts);
+
+public:
+
+ /** Object constructor */
+ LMSDevice(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);
+
+ /** Instantiate the LMS */
+ int open(const std::string &args, int ref, bool swap_channels);
+
+ /** Start the LMS */
+ bool start();
+
+ /** Stop the LMS */
+ bool stop();
+
+ /** Set priority not supported */
+ void setPriority(float prio = 0.5) {
+ }
+
+ enum TxWindowType getWindowType() {
+ return TX_WINDOW_LMS1;
+ }
+
+ /**
+ Read samples from the LMS.
+ @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 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);
+ /**
+ 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);
+
+ /** Update the alignment between the read and write timestamps */
+ bool updateAlignment(TIMESTAMP timestamp);
+
+ /** Set the transmitter frequency */
+ bool setTxFreq(double wFreq, size_t chan = 0);
+
+ /** Set the receiver frequency */
+ bool setRxFreq(double wFreq, size_t chan = 0);
+
+ /** Returns the starting write Timestamp*/
+ TIMESTAMP initialWriteTimestamp(void) {
+ return ts_initial;
+ }
+
+ /** Returns the starting read Timestamp*/
+ TIMESTAMP initialReadTimestamp(void) {
+ return ts_initial;
+ }
+
+ /** returns the full-scale transmit amplitude **/
+ double fullScaleInputValue() {
+ return(double) SHRT_MAX * LIMESDR_TX_AMPL;
+ }
+
+ /** returns the full-scale receive amplitude **/
+ double fullScaleOutputValue() {
+ return (double) SHRT_MAX;
+ }
+
+ /** sets the receive chan gain, returns the gain setting **/
+ double setRxGain(double dB, size_t chan = 0);
+
+ /** get the current receive gain */
+ double getRxGain(size_t chan = 0) {
+ return rxGain;
+ }
+
+ /** return maximum Rx Gain **/
+ double maxRxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minRxGain(void);
+
+ /** sets the transmit chan gain, returns the gain setting **/
+ double setTxGain(double dB, size_t chan = 0);
+
+ /** return maximum Tx Gain **/
+ double maxTxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minTxGain(void);
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ bool setRxAntenna(const std::string & ant, size_t chan = 0);
+
+ /* return the used RX path */
+ std::string getRxAntenna(size_t chan = 0);
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ bool setTxAntenna(const std::string & ant, size_t chan = 0);
+
+ /* return the used RX path */
+ std::string getTxAntenna(size_t chan = 0);
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ bool requiresRadioAlign();
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ virtual GSM::Time minLatency();
+
+ /** Return internal status values */
+ inline double getTxFreq(size_t chan = 0) {
+ return 0;
+ }
+ inline double getRxFreq(size_t chan = 0) {
+ return 0;
+ }
+ inline double getSampleRate() {
+ return actualSampleRate;
+ }
+ inline double numberRead() {
+ return samplesRead;
+ }
+ inline double numberWritten() {
+ return samplesWritten;
+ }
+};
+
+#endif // _LMS_DEVICE_H_
diff --git a/Transceiver52M/device/lms/Makefile.am b/Transceiver52M/device/lms/Makefile.am
new file mode 100644
index 0000000..8471074
--- /dev/null
+++ b/Transceiver52M/device/lms/Makefile.am
@@ -0,0 +1,10 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/..
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS)
+
+noinst_HEADERS = LMSDevice.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = LMSDevice.cpp
diff --git a/Transceiver52M/device/radioDevice.h b/Transceiver52M/device/radioDevice.h
new file mode 100644
index 0000000..5d001fb
--- /dev/null
+++ b/Transceiver52M/device/radioDevice.h
@@ -0,0 +1,210 @@
+/*
+* 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 use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+#ifndef __RADIO_DEVICE_H__
+#define __RADIO_DEVICE_H__
+
+#include <string>
+#include <vector>
+
+#include "GSMCommon.h"
+#include "Logger.h"
+
+extern "C" {
+#include "config_defs.h"
+}
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define GSMRATE (1625e3/6)
+#define MCBTS_SPACING 800000.0
+
+/** a 64-bit virtual timestamp for radio data */
+typedef unsigned long long TIMESTAMP;
+
+/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */
+class RadioDevice {
+
+ public:
+ /* Available transport bus types */
+ enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED, TX_WINDOW_LMS1 };
+
+ /* Radio interface types */
+ enum InterfaceType {
+ NORMAL,
+ RESAMP_64M,
+ RESAMP_100M,
+ MULTI_ARFCN,
+ };
+
+ static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type,
+ size_t chans = 1, double offset = 0.0,
+ const std::vector<std::string>& tx_paths = std::vector<std::string>(1, ""),
+ const std::vector<std::string>& rx_paths = std::vector<std::string>(1, ""));
+
+ /** Initialize the USRP */
+ virtual int open(const std::string &args, int ref, bool swap_channels)=0;
+
+ virtual ~RadioDevice() { }
+
+ /** Start the USRP */
+ virtual bool start()=0;
+
+ /** Stop the USRP */
+ virtual bool stop()=0;
+
+ /** 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
+ @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 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;
+ /**
+ 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;
+
+ /** Update the alignment between the read and write timestamps */
+ virtual bool updateAlignment(TIMESTAMP timestamp)=0;
+
+ /** Set the transmitter frequency */
+ virtual bool setTxFreq(double wFreq, size_t chan = 0) = 0;
+
+ /** Set the receiver frequency */
+ virtual bool setRxFreq(double wFreq, size_t chan = 0) = 0;
+
+ /** Returns the starting write Timestamp*/
+ virtual TIMESTAMP initialWriteTimestamp(void)=0;
+
+ /** Returns the starting read Timestamp*/
+ virtual TIMESTAMP initialReadTimestamp(void)=0;
+
+ /** returns the full-scale transmit amplitude **/
+ virtual double fullScaleInputValue()=0;
+
+ /** returns the full-scale receive amplitude **/
+ virtual double fullScaleOutputValue()=0;
+
+ /** sets the receive chan gain, returns the gain setting **/
+ virtual double setRxGain(double dB, size_t chan = 0) = 0;
+
+ /** gets the current receive gain **/
+ virtual double getRxGain(size_t chan = 0) = 0;
+
+ /** return maximum Rx Gain **/
+ virtual double maxRxGain(void) = 0;
+
+ /** 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 maximum Tx Gain **/
+ virtual double maxTxGain(void) = 0;
+
+ /** return minimum Tx Gain **/
+ virtual double minTxGain(void) = 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;
+
+ /** return the used RX path */
+ virtual std::string getRxAntenna(size_t chan = 0) = 0;
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ virtual bool setTxAntenna(const std::string &ant, size_t chan = 0) = 0;
+
+ /** return the used RX path */
+ virtual std::string getTxAntenna(size_t chan = 0) = 0;
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ virtual bool requiresRadioAlign() = 0;
+
+ /** Minimum latency that the device can achieve */
+ virtual GSM::Time minLatency() = 0;
+
+ /** Return internal status values */
+ virtual double getTxFreq(size_t chan = 0) = 0;
+ virtual double getRxFreq(size_t chan = 0) = 0;
+ virtual double getSampleRate()=0;
+ virtual double numberRead()=0;
+ virtual double numberWritten()=0;
+
+ protected:
+ size_t tx_sps, rx_sps;
+ InterfaceType iface;
+ size_t chans;
+ double lo_offset;
+ std::vector<std::string> tx_paths, rx_paths;
+
+ RadioDevice(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chans, 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_paths(tx_paths), rx_paths(rx_paths)
+ { }
+
+ bool set_antennas() {
+ unsigned int i;
+
+ for (i = 0; i < tx_paths.size(); i++) {
+ if (tx_paths[i] == "")
+ continue;
+ LOG(DEBUG) << "Configuring channel " << i << " with antenna " << tx_paths[i];
+ if (!setTxAntenna(tx_paths[i], i)) {
+ LOG(ALERT) << "Failed configuring channel " << i << " with antenna " << tx_paths[i];
+ return false;
+ }
+ }
+
+ for (i = 0; i < rx_paths.size(); i++) {
+ if (rx_paths[i] == "")
+ continue;
+ LOG(DEBUG) << "Configuring channel " << i << " with antenna " << rx_paths[i];
+ if (!setRxAntenna(rx_paths[i], i)) {
+ LOG(ALERT) << "Failed configuring channel " << i << " with antenna " << rx_paths[i];
+ return false;
+ }
+ }
+ LOG(INFO) << "Antennas configured successfully";
+ return true;
+ }
+
+
+};
+
+#endif
diff --git a/Transceiver52M/device/uhd/Makefile.am b/Transceiver52M/device/uhd/Makefile.am
new file mode 100644
index 0000000..bb34d2f
--- /dev/null
+++ b/Transceiver52M/device/uhd/Makefile.am
@@ -0,0 +1,8 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/..
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = UHDDevice.cpp
diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp
new file mode 100644
index 0000000..3db09a8
--- /dev/null
+++ b/Transceiver52M/device/uhd/UHDDevice.cpp
@@ -0,0 +1,1561 @@
+/*
+ * Device support for Ettus Research UHD driver
+ *
+ * Copyright 2010,2011 Free Software Foundation, Inc.
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * Author: Tom Tsou <tom.tsou@ettus.com>
+ *
+ * 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 "radioDevice.h"
+#include "Threads.h"
+#include "Logger.h"
+#include <uhd/version.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/usrp/multi_usrp.hpp>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef USE_UHD_3_11
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/thread_priority.hpp>
+#else
+#include <uhd/utils/thread.hpp>
+#endif
+
+#define USRP_TX_AMPL 0.3
+#define UMTRX_TX_AMPL 0.7
+#define LIMESDR_TX_AMPL 0.3
+#define SAMPLE_BUF_SZ (1 << 20)
+
+/*
+ * UHD timeout value on streaming (re)start
+ *
+ * Allow some time for streaming to commence after the start command is issued,
+ * but consider a wait beyond one second to be a definite error condition.
+ */
+#define UHD_RESTART_TIMEOUT 1.0
+
+/*
+ * UmTRX specific settings
+ */
+#define UMTRX_VGA1_DEF -18
+
+enum uhd_dev_type {
+ USRP1,
+ USRP2,
+ B100,
+ B200,
+ B210,
+ B2XX_MCBTS,
+ E1XX,
+ E3XX,
+ X3XX,
+ UMTRX,
+ LIMESDR,
+};
+
+/*
+ * USRP version dependent device timings
+ */
+#if defined(USE_UHD_3_9) || defined(USE_UHD_3_11)
+#define B2XX_TIMING_1SPS 1.7153e-4
+#define B2XX_TIMING_4SPS 1.1696e-4
+#define B2XX_TIMING_4_4SPS 6.18462e-5
+#define B2XX_TIMING_MCBTS 7e-5
+#else
+#define B2XX_TIMING_1SPS 9.9692e-5
+#define B2XX_TIMING_4SPS 6.9248e-5
+#define B2XX_TIMING_4_4SPS 4.52308e-5
+#define B2XX_TIMING_MCBTS 6.42452e-5
+#endif
+
+/*
+ * 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<uhd_dev_type, int, int> dev_key;
+
+/* Device parameter descriptor */
+struct dev_desc {
+ unsigned channels;
+ double mcr;
+ double rate;
+ double offset;
+ std::string str;
+};
+
+static const std::map<dev_key, dev_desc> dev_param_map {
+ { std::make_tuple(USRP2, 1, 1), { 1, 0.0, 390625, 1.2184e-4, "N2XX 1 SPS" } },
+ { std::make_tuple(USRP2, 4, 1), { 1, 0.0, 390625, 7.6547e-5, "N2XX 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(USRP2, 4, 4), { 1, 0.0, 390625, 4.6080e-5, "N2XX 4 SPS" } },
+ { std::make_tuple(B100, 1, 1), { 1, 0.0, 400000, 1.2104e-4, "B100 1 SPS" } },
+ { std::make_tuple(B100, 4, 1), { 1, 0.0, 400000, 7.9307e-5, "B100 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(B200, 1, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_1SPS, "B200 1 SPS" } },
+ { std::make_tuple(B200, 4, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(B200, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } },
+ { std::make_tuple(B210, 1, 1), { 2, 26e6, GSMRATE, B2XX_TIMING_1SPS, "B210 1 SPS" } },
+ { std::make_tuple(B210, 4, 1), { 2, 26e6, GSMRATE, B2XX_TIMING_4SPS, "B210 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(B210, 4, 4), { 2, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B210 4 SPS" } },
+ { std::make_tuple(E1XX, 1, 1), { 1, 52e6, GSMRATE, 9.5192e-5, "E1XX 1 SPS" } },
+ { std::make_tuple(E1XX, 4, 1), { 1, 52e6, GSMRATE, 6.5571e-5, "E1XX 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(E3XX, 1, 1), { 2, 26e6, GSMRATE, 1.8462e-4, "E3XX 1 SPS" } },
+ { std::make_tuple(E3XX, 4, 1), { 2, 26e6, GSMRATE, 1.2923e-4, "E3XX 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(X3XX, 1, 1), { 2, 0.0, 390625, 1.5360e-4, "X3XX 1 SPS" } },
+ { std::make_tuple(X3XX, 4, 1), { 2, 0.0, 390625, 1.1264e-4, "X3XX 4/1 Tx/Rx SPS" } },
+ { std::make_tuple(X3XX, 4, 4), { 2, 0.0, 390625, 5.6567e-5, "X3XX 4 SPS" } },
+ { std::make_tuple(UMTRX, 1, 1), { 2, 0.0, GSMRATE, 9.9692e-5, "UmTRX 1 SPS" } },
+ { std::make_tuple(UMTRX, 4, 1), { 2, 0.0, GSMRATE, 7.3846e-5, "UmTRX 4/1 Tx/Rx SPS"} },
+ { std::make_tuple(UMTRX, 4, 4), { 2, 0.0, GSMRATE, 5.1503e-5, "UmTRX 4 SPS" } },
+ { std::make_tuple(LIMESDR, 4, 4), { 1, GSMRATE*32, GSMRATE, 8.9e-5, "LimeSDR 4 SPS" } },
+ { std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } },
+};
+
+/*
+ Sample Buffer - Allows reading and writing of timed samples using osmo-trx
+ or UHD style timestamps. Time conversions are handled
+ internally or accessable through the static convert calls.
+*/
+class smpl_buf {
+public:
+ /** Sample buffer constructor
+ @param len number of 32-bit samples the buffer should hold
+ @param rate sample clockrate
+ @param timestamp
+ */
+ smpl_buf(size_t len, double rate);
+ ~smpl_buf();
+
+ /** Query number of samples available for reading
+ @param timestamp time of first sample
+ @return number of available samples or error
+ */
+ ssize_t avail_smpls(TIMESTAMP timestamp) const;
+ ssize_t avail_smpls(uhd::time_spec_t timestamp) const;
+
+ /** Read and write
+ @param buf pointer to buffer
+ @param len number of samples desired to read or write
+ @param timestamp time of first stample
+ @return number of actual samples read or written or error
+ */
+ ssize_t read(void *buf, size_t len, TIMESTAMP timestamp);
+ ssize_t read(void *buf, size_t len, uhd::time_spec_t timestamp);
+ ssize_t write(void *buf, size_t len, TIMESTAMP timestamp);
+ ssize_t write(void *buf, size_t len, uhd::time_spec_t timestamp);
+
+ /** Buffer status string
+ @return a formatted string describing internal buffer state
+ */
+ std::string str_status(size_t ts) const;
+
+ /** Formatted error string
+ @param code an error code
+ @return a formatted error string
+ */
+ static std::string str_code(ssize_t code);
+
+ enum err_code {
+ ERROR_TIMESTAMP = -1,
+ ERROR_READ = -2,
+ ERROR_WRITE = -3,
+ ERROR_OVERFLOW = -4
+ };
+
+private:
+ uint32_t *data;
+ size_t buf_len;
+
+ double clk_rt;
+
+ TIMESTAMP time_start;
+ TIMESTAMP time_end;
+
+ size_t data_start;
+ size_t data_end;
+};
+
+/*
+ uhd_device - UHD implementation of the Device interface. Timestamped samples
+ are sent to and received from the device. An intermediate buffer
+ on the receive side collects and aligns packets of samples.
+ Events and errors such as underruns are reported asynchronously
+ by the device and received in a separate thread.
+*/
+class uhd_device : public RadioDevice {
+public:
+ uhd_device(size_t tx_sps, size_t rx_sps, InterfaceType type,
+ size_t chans, double offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths);
+ ~uhd_device();
+
+ int open(const std::string &args, int ref, bool swap_channels);
+ 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);
+
+ int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
+ TIMESTAMP timestamp, bool isControl);
+
+ 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 setTxGain(double db, size_t chan);
+ double maxTxGain(void) { return tx_gain_max; }
+ double minTxGain(void) { return tx_gain_min; }
+
+ double getTxFreq(size_t chan);
+ double getRxFreq(size_t chan);
+ double getRxFreq();
+
+ bool setRxAntenna(const std::string &ant, size_t chan);
+ std::string getRxAntenna(size_t chan);
+ bool setTxAntenna(const std::string &ant, size_t chan);
+ std::string getTxAntenna(size_t chan);
+
+ bool requiresRadioAlign();
+
+ GSM::Time minLatency();
+
+ inline double getSampleRate() { return tx_rate; }
+ inline double numberRead() { return rx_pkt_cnt; }
+ inline double numberWritten() { return 0; }
+
+ /** 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,
+ };
+
+private:
+ uhd::usrp::multi_usrp::sptr usrp_dev;
+ uhd::tx_streamer::sptr tx_stream;
+ uhd::rx_streamer::sptr rx_stream;
+ enum TxWindowType tx_window;
+ enum uhd_dev_type dev_type;
+
+ 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;
+ size_t tx_spp, rx_spp;
+
+ bool started;
+ bool aligned;
+
+ size_t rx_pkt_cnt;
+ size_t drop_cnt;
+ uhd::time_spec_t prev_ts;
+
+ TIMESTAMP ts_initial, ts_offset;
+ std::vector<smpl_buf *> rx_buffers;
+
+ void init_gains();
+ void set_channels(bool swap);
+ void set_rates();
+ bool parse_dev_type();
+ bool flush_recv(size_t num_pkts);
+ int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls);
+
+ std::string str_code(uhd::rx_metadata_t metadata);
+ std::string str_code(uhd::async_metadata_t metadata);
+
+ uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
+ bool set_freq(double freq, size_t chan, bool tx);
+
+ Thread *async_event_thrd;
+ Mutex tune_lock;
+};
+
+void *async_event_loop(uhd_device *dev)
+{
+ set_selfthread_name("UHDAsyncEvent");
+ dev->setPriority(0.43);
+
+ while (1) {
+ dev->recv_async_msg();
+ pthread_testcancel();
+ }
+
+ return NULL;
+}
+
+#ifndef USE_UHD_3_11
+/*
+ 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)
+{
+ switch (type) {
+ case uhd::msg::status:
+ LOGC(DDEV, INFO) << msg;
+ break;
+ case uhd::msg::warning:
+ LOGC(DDEV, WARNING) << msg;
+ break;
+ case uhd::msg::error:
+ LOGC(DDEV, ERR) << msg;
+ break;
+ case uhd::msg::fastpath:
+ break;
+ }
+}
+#endif
+
+static void thread_enable_cancel(bool cancel)
+{
+ cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+}
+
+uhd_device::uhd_device(size_t tx_sps, size_t rx_sps,
+ InterfaceType iface, size_t chans, double lo_offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths)
+ : RadioDevice(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),
+ started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0),
+ prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
+{
+}
+
+uhd_device::~uhd_device()
+{
+ stop();
+
+ for (size_t i = 0; i < rx_buffers.size(); i++)
+ delete rx_buffers[i];
+}
+
+void uhd_device::init_gains()
+{
+ uhd::gain_range_t range;
+
+ if (dev_type == UMTRX) {
+ std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0);
+ if (gain_stages[0] == "VGA") {
+ LOGC(DDEV, WARNING) << "Update your UHD version for a proper Tx gain support";
+ }
+ if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") {
+ range = usrp_dev->get_tx_gain_range();
+ tx_gain_min = range.start();
+ tx_gain_max = range.stop();
+ } else {
+ range = usrp_dev->get_tx_gain_range("VGA2");
+ tx_gain_min = UMTRX_VGA1_DEF + range.start();
+ tx_gain_max = UMTRX_VGA1_DEF + range.stop();
+ }
+ } else {
+ range = usrp_dev->get_tx_gain_range();
+ tx_gain_min = range.start();
+ tx_gain_max = range.stop();
+ }
+ LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]";
+
+ range = usrp_dev->get_rx_gain_range();
+ rx_gain_min = range.start();
+ rx_gain_max = range.stop();
+ LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
+
+ for (size_t i = 0; i < tx_gains.size(); i++) {
+ double gain = (tx_gain_min + tx_gain_max) / 2;
+ LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain;
+ usrp_dev->set_tx_gain(gain, i);
+ tx_gains[i] = usrp_dev->get_tx_gain(i);
+ }
+
+ for (size_t i = 0; i < rx_gains.size(); i++) {
+ double gain = (rx_gain_min + rx_gain_max) / 2;
+ LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain;
+ usrp_dev->set_rx_gain(gain, i);
+ rx_gains[i] = usrp_dev->get_rx_gain(i);
+ }
+
+ return;
+
+}
+
+void uhd_device::set_rates()
+{
+ dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
+ if (desc.mcr != 0.0)
+ usrp_dev->set_master_clock_rate(desc.mcr);
+
+ tx_rate = (dev_type != B2XX_MCBTS) ? desc.rate * tx_sps : desc.rate;
+ rx_rate = (dev_type != B2XX_MCBTS) ? desc.rate * rx_sps : desc.rate;
+
+ usrp_dev->set_tx_rate(tx_rate);
+ usrp_dev->set_rx_rate(rx_rate);
+ tx_rate = usrp_dev->get_tx_rate();
+ rx_rate = usrp_dev->get_rx_rate();
+
+ ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate);
+ LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
+}
+
+double uhd_device::setTxGain(double db, size_t chan)
+{
+ if (iface == MULTI_ARFCN)
+ chan = 0;
+
+ if (chan >= tx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan;
+ return 0.0f;
+ }
+
+ 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") {
+ usrp_dev->set_tx_gain(db, chan);
+ } else {
+ // New UHD versions support split configuration of
+ // Tx gain stages. We utilize this to set the gain
+ // configuration, optimal for the Tx signal quality.
+ // From our measurements, VGA1 must be 18dB plus-minus
+ // one and VGA2 is the best when 23dB or lower.
+ usrp_dev->set_tx_gain(UMTRX_VGA1_DEF, "VGA1", chan);
+ usrp_dev->set_tx_gain(db-UMTRX_VGA1_DEF, "VGA2", chan);
+ }
+ } else {
+ usrp_dev->set_tx_gain(db, 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)";
+
+ return tx_gains[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 (iface == MULTI_ARFCN)
+ chan = 0;
+
+ if (chan >= rx_gains.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return 0.0f;
+ }
+
+ return rx_gains[chan];
+}
+
+/*
+ Parse the UHD device tree and mboard name to find out what device we're
+ dealing with. We need the window type so that the transceiver knows how to
+ deal with the transport latency. Reject the USRP1 because UHD doesn't
+ support timestamped samples with it.
+ */
+bool uhd_device::parse_dev_type()
+{
+ uhd::property_tree::sptr prop_tree = usrp_dev->get_device()->get_tree();
+ std::string devString = prop_tree->access<std::string>("/name").get();
+ std::string mboardString = usrp_dev->get_mboard_name();
+
+ const std::map<std::string, std::pair<uhd_dev_type, TxWindowType>> devStringMap {
+ { "B100", { B100, TX_WINDOW_USRP1 } },
+ { "B200", { B200, TX_WINDOW_USRP1 } },
+ { "B200mini", { B200, TX_WINDOW_USRP1 } },
+ { "B205mini", { B200, TX_WINDOW_USRP1 } },
+ { "B210", { B210, TX_WINDOW_USRP1 } },
+ { "E100", { E1XX, TX_WINDOW_FIXED } },
+ { "E110", { E1XX, TX_WINDOW_FIXED } },
+ { "E310", { E3XX, TX_WINDOW_FIXED } },
+ { "E3XX", { E3XX, TX_WINDOW_FIXED } },
+ { "X300", { X3XX, TX_WINDOW_FIXED } },
+ { "X310", { X3XX, TX_WINDOW_FIXED } },
+ { "USRP2", { USRP2, TX_WINDOW_FIXED } },
+ { "UmTRX", { UMTRX, TX_WINDOW_FIXED } },
+ { "LimeSDR", { LIMESDR, TX_WINDOW_FIXED } },
+ };
+
+ // Compare UHD motherboard and device strings */
+ auto mapIter = devStringMap.begin();
+ while (mapIter != devStringMap.end()) {
+ if (devString.find(mapIter->first) != std::string::npos ||
+ mboardString.find(mapIter->first) != std::string::npos) {
+ dev_type = std::get<0>(mapIter->second);
+ tx_window = std::get<1>(mapIter->second);
+ return true;
+ }
+ mapIter++;
+ }
+
+ LOGC(DDEV, ALERT) << "Unsupported device " << devString;
+ return false;
+}
+
+/*
+ * Check for UHD version > 3.9.0 for E3XX support
+ */
+static bool uhd_e3xx_version_chk()
+{
+ std::string ver = uhd::get_version_string();
+ std::string major_str(ver.begin(), ver.begin() + 3);
+ std::string minor_str(ver.begin() + 4, ver.begin() + 7);
+
+ int major_val = atoi(major_str.c_str());
+ int minor_val = atoi(minor_str.c_str());
+
+ if (major_val < 3)
+ return false;
+ if (minor_val < 9)
+ return false;
+
+ return true;
+}
+
+void uhd_device::set_channels(bool swap)
+{
+ if (iface == MULTI_ARFCN) {
+ 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)
+ throw std::invalid_argument("Device does not support number of requested channels");
+
+ std::string subdev_string;
+ switch (dev_type) {
+ case B210:
+ case E3XX:
+ if (chans == 1)
+ subdev_string = swap ? "A:B" : "A:A";
+ else if (chans == 2)
+ subdev_string = swap ? "A:B A:A" : "A:A A:B";
+ break;
+ case X3XX:
+ case UMTRX:
+ if (chans == 1)
+ subdev_string = swap ? "B:0" : "A:0";
+ else if (chans == 2)
+ subdev_string = swap ? "B:0 A:0" : "A:0 B:0";
+ break;
+ default:
+ break;
+ }
+
+ if (!subdev_string.empty()) {
+ uhd::usrp::subdev_spec_t spec(subdev_string);
+ usrp_dev->set_tx_subdev_spec(spec);
+ usrp_dev->set_rx_subdev_spec(spec);
+ }
+}
+
+int uhd_device::open(const std::string &args, int ref, bool swap_channels)
+{
+ const char *refstr;
+
+ // Find UHD devices
+ uhd::device_addr_t addr(args);
+ uhd::device_addrs_t dev_addrs = uhd::device::find(addr);
+ if (dev_addrs.size() == 0) {
+ LOGC(DDEV, ALERT) << "No UHD devices found with address '" << args << "'";
+ return -1;
+ }
+
+ // Use the first found device
+ LOGC(DDEV, INFO) << "Using discovered UHD device " << dev_addrs[0].to_string();
+ try {
+ usrp_dev = uhd::usrp::multi_usrp::make(addr);
+ } catch(...) {
+ LOGC(DDEV, ALERT) << "UHD make failed, device " << args;
+ return -1;
+ }
+
+ // Check for a valid device type and set bus type
+ if (!parse_dev_type())
+ return -1;
+
+ if ((dev_type == E3XX) && !uhd_e3xx_version_chk()) {
+ LOGC(DDEV, ALERT) << "E3XX requires UHD 003.009.000 or greater";
+ return -1;
+ }
+
+ try {
+ set_channels(swap_channels);
+ } catch (const std::exception &e) {
+ LOGC(DDEV, ALERT) << "Channel setting failed - " << e.what();
+ return -1;
+ }
+
+ if (!set_antennas()) {
+ LOGC(DDEV, ALERT) << "UHD antenna setting failed";
+ return -1;
+ }
+
+ 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:
+ refstr = "internal";
+ break;
+ case REF_EXTERNAL:
+ refstr = "external";
+ break;
+ case REF_GPS:
+ refstr = "gpsdo";
+ break;
+ default:
+ LOGC(DDEV, ALERT) << "Invalid reference type";
+ return -1;
+ }
+
+ usrp_dev->set_clock_source(refstr);
+
+ try {
+ set_rates();
+ } catch (const std::exception &e) {
+ LOGC(DDEV, ALERT) << "UHD rate setting failed - " << e.what();
+ return -1;
+ }
+
+ // Set RF frontend bandwidth
+ if (dev_type == UMTRX) {
+ // Setting LMS6002D LPF to 500kHz gives us the best signal quality
+ for (size_t i = 0; i < chans; i++) {
+ usrp_dev->set_tx_bandwidth(500*1000*2, i);
+ usrp_dev->set_rx_bandwidth(500*1000*2, i);
+ }
+ } else if (dev_type == LIMESDR) {
+ for (size_t i = 0; i < chans; i++) {
+ usrp_dev->set_tx_bandwidth(5.2e6, i);
+ usrp_dev->set_rx_bandwidth(1.4001e6, i);
+ }
+ }
+
+ /* Create TX and RX streamers */
+ uhd::stream_args_t stream_args("sc16");
+ for (size_t i = 0; i < chans; i++)
+ stream_args.channels.push_back(i);
+
+ tx_stream = usrp_dev->get_tx_stream(stream_args);
+ rx_stream = usrp_dev->get_rx_stream(stream_args);
+
+ /* Number of samples per over-the-wire packet */
+ tx_spp = tx_stream->get_max_num_samps();
+ rx_spp = rx_stream->get_max_num_samps();
+
+ // 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, rx_rate);
+
+ // Initialize and shadow gain values
+ init_gains();
+
+ // Print configuration
+ LOGC(DDEV, INFO) << "\n" << usrp_dev->get_pp_string();
+
+ if (iface == MULTI_ARFCN)
+ return MULTI_ARFCN;
+
+ switch (dev_type) {
+ case B100:
+ return RESAMP_64M;
+ case USRP2:
+ case X3XX:
+ return RESAMP_100M;
+ case B200:
+ case B210:
+ case E1XX:
+ case E3XX:
+ case LIMESDR:
+ default:
+ break;
+ }
+
+ return NORMAL;
+}
+
+bool uhd_device::flush_recv(size_t num_pkts)
+{
+ uhd::rx_metadata_t md;
+ size_t num_smpls;
+ float timeout = UHD_RESTART_TIMEOUT;
+
+ std::vector<std::vector<short> >
+ pkt_bufs(chans, std::vector<short>(2 * rx_spp));
+
+ std::vector<short *> pkt_ptrs;
+ for (size_t i = 0; i < pkt_bufs.size(); i++)
+ pkt_ptrs.push_back(&pkt_bufs[i].front());
+
+ ts_initial = 0;
+ while (!ts_initial || (num_pkts-- > 0)) {
+ num_smpls = rx_stream->recv(pkt_ptrs, rx_spp, md,
+ timeout, true);
+ if (!num_smpls) {
+ switch (md.error_code) {
+ case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
+ LOGC(DDEV, ALERT) << "Device timed out";
+ return false;
+ default:
+ continue;
+ }
+ }
+
+ ts_initial = md.time_spec.to_ticks(rx_rate);
+ }
+
+ LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
+
+ return true;
+}
+
+bool uhd_device::restart()
+{
+ /* Allow 100 ms delay to align multi-channel streams */
+ double delay = 0.1;
+
+ aligned = false;
+
+ uhd::time_spec_t current = usrp_dev->get_time_now();
+
+ uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
+ cmd.stream_now = false;
+ cmd.time_spec = uhd::time_spec_t(current.get_real_secs() + delay);
+
+ usrp_dev->issue_stream_cmd(cmd);
+
+ return flush_recv(10);
+}
+
+bool uhd_device::start()
+{
+ LOGC(DDEV, INFO) << "Starting USRP...";
+
+ if (started) {
+ LOGC(DDEV, ERR) << "Device already started";
+ 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);
+
+ // Start streaming
+ if (!restart())
+ return false;
+
+ // Display usrp time
+ double time_now = usrp_dev->get_time_now().get_real_secs();
+ LOGC(DDEV, INFO) << "The current time is " << time_now << " seconds";
+
+ started = true;
+ return true;
+}
+
+bool uhd_device::stop()
+{
+ if (!started)
+ return false;
+
+ uhd::stream_cmd_t stream_cmd =
+ uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
+
+ usrp_dev->issue_stream_cmd(stream_cmd);
+
+ async_event_thrd->cancel();
+ async_event_thrd->join();
+ delete async_event_thrd;
+
+ 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) {
+ LOGC(DDEV, ERR) << str_code(md);
+
+ switch (md.error_code) {
+ case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
+ LOGC(DDEV, ALERT) << "UHD: Receive timed out";
+ return ERROR_TIMEOUT;
+ case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
+ case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
+ case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN:
+ case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET:
+ default:
+ return ERROR_UNHANDLED;
+ }
+ }
+
+ // Missing timestamp
+ if (!md.has_time_spec) {
+ LOGC(DDEV, ALERT) << "UHD: Received packet missing timestamp";
+ return ERROR_UNRECOVERABLE;
+ }
+
+ // Monotonicity check
+ if (md.time_spec < prev_ts) {
+ LOGC(DDEV, ALERT) << "UHD: Loss of monotonic time";
+ LOGC(DDEV, ALERT) << "Current time: " << md.time_spec.get_real_secs() << ", "
+ << "Previous time: " << prev_ts.get_real_secs();
+ return ERROR_TIMING;
+ }
+
+ // Workaround for UHD tick rounding bug
+ TIMESTAMP ticks = md.time_spec.to_ticks(rx_rate);
+ if (ticks - prev_ts.to_ticks(rx_rate) == rx_spp - 1)
+ md.time_spec = uhd::time_spec_t::from_ticks(++ticks, rx_rate);
+
+ prev_ts = md.time_spec;
+
+ return 0;
+}
+
+int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
+ TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
+{
+ ssize_t rc;
+ uhd::time_spec_t ts;
+ uhd::rx_metadata_t metadata;
+
+ 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 = uhd::time_spec_t::from_ticks(timestamp, rx_rate);
+ LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts.get_real_secs();
+
+ // Check that timestamp is valid
+ rc = rx_buffers[0]->avail_smpls(timestamp);
+ if (rc < 0) {
+ LOGC(DDEV, ERR) << rx_buffers[0]->str_code(rc);
+ LOGC(DDEV, ERR) << rx_buffers[0]->str_status(timestamp);
+ return 0;
+ }
+
+ // Create vector buffer
+ std::vector<std::vector<short> >
+ pkt_bufs(chans, std::vector<short>(2 * rx_spp));
+
+ std::vector<short *> pkt_ptrs;
+ for (size_t i = 0; i < pkt_bufs.size(); i++)
+ pkt_ptrs.push_back(&pkt_bufs[i].front());
+
+ // Receive samples from the usrp until we have enough
+ while (rx_buffers[0]->avail_smpls(timestamp) < len) {
+ thread_enable_cancel(false);
+ size_t num_smpls = rx_stream->recv(pkt_ptrs, rx_spp,
+ metadata, 0.1, true);
+ thread_enable_cancel(true);
+
+ rx_pkt_cnt++;
+
+ // Check for errors
+ rc = check_rx_md_err(metadata, num_smpls);
+ switch (rc) {
+ case ERROR_UNRECOVERABLE:
+ LOGC(DDEV, ALERT) << "UHD: Version " << uhd::get_version_string();
+ LOGC(DDEV, ALERT) << "UHD: Unrecoverable error, exiting...";
+ exit(-1);
+ case ERROR_TIMEOUT:
+ // Assume stopping condition
+ return 0;
+ case ERROR_TIMING:
+ restart();
+ case ERROR_UNHANDLED:
+ continue;
+ }
+
+ ts = metadata.time_spec;
+ LOGC(DDEV, DEBUG) << "Received timestamp = " << ts.get_real_secs();
+
+ 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);
+
+ // Continue on local overrun, exit on other errors
+ if ((rc < 0)) {
+ LOGC(DDEV, ERR) << rx_buffers[i]->str_code(rc);
+ LOGC(DDEV, ERR) << rx_buffers[i]->str_status(timestamp);
+ if (rc != smpl_buf::ERROR_OVERFLOW)
+ return 0;
+ }
+ }
+ }
+
+ // 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, ERR) << rx_buffers[i]->str_code(rc);
+ LOGC(DDEV, ERR) << rx_buffers[i]->str_status(timestamp);
+ return 0;
+ }
+ }
+
+ return len;
+}
+
+int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
+ unsigned long long timestamp,bool isControl)
+{
+ uhd::tx_metadata_t metadata;
+ metadata.has_time_spec = true;
+ metadata.start_of_burst = false;
+ metadata.end_of_burst = false;
+ metadata.time_spec = uhd::time_spec_t::from_ticks(timestamp, tx_rate);
+
+ *underrun = false;
+
+ // No control packets
+ if (isControl) {
+ LOGC(DDEV, ERR) << "Control packets not supported";
+ return 0;
+ }
+
+ if (bufs.size() != chans) {
+ LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
+ return -1;
+ }
+
+ // Drop a fixed number of packets (magic value)
+ if (!aligned) {
+ drop_cnt++;
+
+ if (drop_cnt == 1) {
+ LOGC(DDEV, DEBUG) << "Aligning transmitter: stop burst";
+ *underrun = true;
+ metadata.end_of_burst = true;
+ } else if (drop_cnt < 30) {
+ LOGC(DDEV, DEBUG) << "Aligning transmitter: packet advance";
+ return len;
+ } else {
+ LOGC(DDEV, DEBUG) << "Aligning transmitter: start burst";
+ metadata.start_of_burst = true;
+ aligned = true;
+ drop_cnt = 0;
+ }
+ }
+
+ thread_enable_cancel(false);
+ size_t num_smpls = tx_stream->send(bufs, len, metadata);
+ thread_enable_cancel(true);
+
+ if (num_smpls != (unsigned) len) {
+ LOGC(DDEV, ALERT) << "UHD: Device send timed out";
+ }
+
+ return num_smpls;
+}
+
+bool uhd_device::updateAlignment(TIMESTAMP timestamp)
+{
+ return true;
+}
+
+uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx)
+{
+ double rf_spread, rf_freq;
+ std::vector<double> freqs;
+ uhd::tune_request_t treq(freq);
+
+ if (dev_type == UMTRX) {
+ if (lo_offset != 0.0)
+ return uhd::tune_request_t(freq, lo_offset);
+
+ // Don't use DSP tuning, because LMS6002D PLL steps are small enough.
+ // We end up with DSP tuning just for 2-3Hz, which is meaningless and
+ // only distort the signal (because cordic is not ideal).
+ treq.target_freq = freq;
+ treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
+ treq.rf_freq = freq;
+ treq.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
+ treq.dsp_freq = 0.0;
+ return treq;
+ } else if (chans == 1) {
+ if (lo_offset == 0.0)
+ return treq;
+
+ return uhd::tune_request_t(freq, lo_offset);
+ } else if ((dev_type != B210) || (chans > 2) || (chan > 1)) {
+ LOGC(DDEV, ALERT) << chans << " channels unsupported";
+ return treq;
+ }
+
+ if (tx)
+ freqs = tx_freqs;
+ else
+ freqs = rx_freqs;
+
+ /* Tune directly if other channel isn't tuned */
+ if (freqs[!chan] < 10.0)
+ return treq;
+
+ /* Find center frequency between channels */
+ rf_spread = fabs(freqs[!chan] - freq);
+ if (rf_spread > dev_param_map.at(dev_key(B210, tx_sps, rx_sps)).mcr) {
+ LOGC(DDEV, ALERT) << rf_spread << "Hz tuning spread not supported\n";
+ return treq;
+ }
+
+ rf_freq = (freqs[!chan] + freq) / 2.0f;
+
+ treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
+ treq.target_freq = freq;
+ treq.rf_freq = rf_freq;
+
+ return treq;
+}
+
+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);
+
+ if (tx) {
+ tres = usrp_dev->set_tx_freq(treq, chan);
+ tx_freqs[chan] = usrp_dev->get_tx_freq(chan);
+ } else {
+ tres = usrp_dev->set_rx_freq(treq, chan);
+ rx_freqs[chan] = usrp_dev->get_rx_freq(chan);
+ }
+ LOGC(DDEV, INFO) << "\n" << tres.to_pp_string() << std::endl;
+
+ if ((chans == 1) || ((chans == 2) && dev_type == UMTRX))
+ return true;
+
+ /* Manual RF policy means we intentionally tuned with a baseband
+ * offset for dual-channel purposes. Now retune the other channel
+ * with the opposite corresponding frequency offset
+ */
+ if (treq.rf_freq_policy == uhd::tune_request_t::POLICY_MANUAL) {
+ if (tx) {
+ treq = select_freq(tx_freqs[!chan], !chan, true);
+ tres = usrp_dev->set_tx_freq(treq, !chan);
+ tx_freqs[!chan] = usrp_dev->get_tx_freq(!chan);
+ } else {
+ treq = select_freq(rx_freqs[!chan], !chan, false);
+ tres = usrp_dev->set_rx_freq(treq, !chan);
+ rx_freqs[!chan] = usrp_dev->get_rx_freq(!chan);
+
+ }
+ LOGC(DDEV, INFO) << "\n" << tres.to_pp_string() << std::endl;
+ }
+
+ return true;
+}
+
+bool uhd_device::setTxFreq(double wFreq, size_t chan)
+{
+ 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);
+}
+
+bool uhd_device::setRxFreq(double wFreq, size_t chan)
+{
+ if (chan >= rx_freqs.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+ ScopedLock lock(tune_lock);
+
+ return set_freq(wFreq, chan, false);
+}
+
+double uhd_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 uhd_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 uhd_device::setRxAntenna(const std::string &ant, size_t chan)
+{
+ std::vector<std::string> avail;
+ if (chan >= rx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+
+ avail = usrp_dev->get_rx_antennas(chan);
+ 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: ";
+ for (std::vector<std::string>::const_iterator i = avail.begin(); i != avail.end(); ++i)
+ LOGC(DDEV, INFO) << "- '" << *i << "'";
+ return false;
+ }
+ usrp_dev->set_rx_antenna(ant, chan);
+ rx_paths[chan] = usrp_dev->get_rx_antenna(chan);
+
+ if (ant != rx_paths[chan]) {
+ LOGC(DDEV, ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << rx_paths[chan];
+ return false;
+ }
+
+ return true;
+}
+
+std::string uhd_device::getRxAntenna(size_t chan)
+{
+ if (chan >= rx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return "";
+ }
+ return usrp_dev->get_rx_antenna(chan);
+}
+
+bool uhd_device::setTxAntenna(const std::string &ant, size_t chan)
+{
+ std::vector<std::string> avail;
+ if (chan >= tx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+
+ avail = usrp_dev->get_tx_antennas(chan);
+ 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: ";
+ for (std::vector<std::string>::const_iterator i = avail.begin(); i != avail.end(); ++i)
+ LOGC(DDEV, INFO) << "- '" << *i << "'";
+ return false;
+ }
+ usrp_dev->set_tx_antenna(ant, chan);
+ tx_paths[chan] = usrp_dev->get_tx_antenna(chan);
+
+ if (ant != tx_paths[chan]) {
+ LOGC(DDEV, ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << tx_paths[chan];
+ return false;
+ }
+
+ return true;
+}
+
+std::string uhd_device::getTxAntenna(size_t chan)
+{
+ if (chan >= tx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return "";
+ }
+ return usrp_dev->get_tx_antenna(chan);
+}
+
+bool uhd_device::requiresRadioAlign()
+{
+ return false;
+}
+
+GSM::Time uhd_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);
+}
+
+/*
+ * Only allow sampling the Rx path lower than Tx and not vice-versa.
+ * Using Tx with 4 SPS and Rx at 1 SPS is the only allowed mixed
+ * combination.
+ */
+TIMESTAMP uhd_device::initialWriteTimestamp()
+{
+ if ((iface == MULTI_ARFCN) || (rx_sps == tx_sps))
+ return ts_initial;
+ else
+ return ts_initial * tx_sps;
+}
+
+TIMESTAMP uhd_device::initialReadTimestamp()
+{
+ return ts_initial;
+}
+
+double uhd_device::fullScaleInputValue()
+{
+ if (dev_type == LIMESDR)
+ return (double) SHRT_MAX * LIMESDR_TX_AMPL;
+ if (dev_type == UMTRX)
+ return (double) SHRT_MAX * UMTRX_TX_AMPL;
+ else
+ return (double) SHRT_MAX * USRP_TX_AMPL;
+}
+
+double uhd_device::fullScaleOutputValue()
+{
+ return (double) SHRT_MAX;
+}
+
+bool uhd_device::recv_async_msg()
+{
+ uhd::async_metadata_t md;
+
+ thread_enable_cancel(false);
+ bool rc = usrp_dev->get_device()->recv_async_msg(md);
+ thread_enable_cancel(true);
+ if (!rc)
+ return false;
+
+ // Assume that any error requires resynchronization
+ if (md.event_code != uhd::async_metadata_t::EVENT_CODE_BURST_ACK) {
+ aligned = false;
+
+ if ((md.event_code != uhd::async_metadata_t::EVENT_CODE_UNDERFLOW) &&
+ (md.event_code != uhd::async_metadata_t::EVENT_CODE_TIME_ERROR)) {
+ LOGC(DDEV, ERR) << str_code(md);
+ }
+ }
+
+ return true;
+}
+
+std::string uhd_device::str_code(uhd::rx_metadata_t metadata)
+{
+ std::ostringstream ost("UHD: ");
+
+ switch (metadata.error_code) {
+ case uhd::rx_metadata_t::ERROR_CODE_NONE:
+ ost << "No error";
+ break;
+ case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
+ ost << "No packet received, implementation timed-out";
+ break;
+ case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
+ ost << "A stream command was issued in the past";
+ break;
+ case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN:
+ ost << "Expected another stream command";
+ break;
+ case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
+ ost << "An internal receive buffer has filled";
+ break;
+ case uhd::rx_metadata_t::ERROR_CODE_ALIGNMENT:
+ ost << "Multi-channel alignment failed";
+ break;
+ case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET:
+ ost << "The packet could not be parsed";
+ break;
+ default:
+ ost << "Unknown error " << metadata.error_code;
+ }
+
+ if (metadata.has_time_spec)
+ ost << " at " << metadata.time_spec.get_real_secs() << " sec.";
+
+ return ost.str();
+}
+
+std::string uhd_device::str_code(uhd::async_metadata_t metadata)
+{
+ std::ostringstream ost("UHD: ");
+
+ switch (metadata.event_code) {
+ case uhd::async_metadata_t::EVENT_CODE_BURST_ACK:
+ ost << "A packet was successfully transmitted";
+ break;
+ case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW:
+ ost << "An internal send buffer has emptied";
+ break;
+ case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR:
+ ost << "Packet loss between host and device";
+ break;
+ case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR:
+ ost << "Packet time was too late or too early";
+ break;
+ case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET:
+ ost << "Underflow occurred inside a packet";
+ break;
+ case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST:
+ ost << "Packet loss within a burst";
+ break;
+ default:
+ ost << "Unknown error " << metadata.event_code;
+ }
+
+ if (metadata.has_time_spec)
+ ost << " at " << metadata.time_spec.get_real_secs() << " sec.";
+
+ return ost.str();
+}
+
+smpl_buf::smpl_buf(size_t len, double rate)
+ : buf_len(len), clk_rt(rate),
+ time_start(0), time_end(0), data_start(0), data_end(0)
+{
+ data = new uint32_t[len];
+}
+
+smpl_buf::~smpl_buf()
+{
+ delete[] data;
+}
+
+ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const
+{
+ if (timestamp < time_start)
+ return ERROR_TIMESTAMP;
+ else if (timestamp >= time_end)
+ return 0;
+ else
+ return time_end - timestamp;
+}
+
+ssize_t smpl_buf::avail_smpls(uhd::time_spec_t timespec) const
+{
+ return avail_smpls(timespec.to_ticks(clk_rt));
+}
+
+ssize_t smpl_buf::read(void *buf, size_t len, TIMESTAMP timestamp)
+{
+ int type_sz = 2 * sizeof(short);
+
+ // Check for valid read
+ if (timestamp < time_start)
+ return ERROR_TIMESTAMP;
+ if (timestamp >= time_end)
+ return 0;
+ if (len >= buf_len)
+ return ERROR_READ;
+
+ // How many samples should be copied
+ size_t num_smpls = time_end - timestamp;
+ if (num_smpls > len)
+ num_smpls = len;
+
+ // Starting index
+ size_t read_start = (data_start + (timestamp - time_start)) % buf_len;
+
+ // Read it
+ if (read_start + num_smpls < buf_len) {
+ size_t numBytes = len * type_sz;
+ memcpy(buf, data + read_start, numBytes);
+ } else {
+ size_t first_cp = (buf_len - read_start) * type_sz;
+ size_t second_cp = len * type_sz - first_cp;
+
+ memcpy(buf, data + read_start, first_cp);
+ memcpy((char*) buf + first_cp, data, second_cp);
+ }
+
+ data_start = (read_start + len) % buf_len;
+ time_start = timestamp + len;
+
+ if (time_start > time_end)
+ return ERROR_READ;
+ else
+ return num_smpls;
+}
+
+ssize_t smpl_buf::read(void *buf, size_t len, uhd::time_spec_t ts)
+{
+ return read(buf, len, ts.to_ticks(clk_rt));
+}
+
+ssize_t smpl_buf::write(void *buf, size_t len, TIMESTAMP timestamp)
+{
+ int type_sz = 2 * sizeof(short);
+
+ // Check for valid write
+ if ((len == 0) || (len >= buf_len))
+ return ERROR_WRITE;
+ if ((timestamp + len) <= time_end)
+ return ERROR_TIMESTAMP;
+
+ if (timestamp < time_end) {
+ LOGC(DDEV, ERR) << "Overwriting old buffer data: timestamp="<<timestamp<<" time_end="<<time_end;
+ uhd::time_spec_t ts = uhd::time_spec_t::from_ticks(timestamp, clk_rt);
+ LOGC(DDEV, DEBUG) << "Requested timestamp = " << timestamp << " (real_sec=" << std::fixed << ts.get_real_secs() << " = " << ts.to_ticks(clk_rt) << ") rate=" << clk_rt;
+ // Do not return error here, because it's a rounding error and is not fatal
+ }
+ if (timestamp > time_end && time_end != 0) {
+ LOGC(DDEV, ERR) << "Skipping buffer data: timestamp="<<timestamp<<" time_end="<<time_end;
+ uhd::time_spec_t ts = uhd::time_spec_t::from_ticks(timestamp, clk_rt);
+ LOGC(DDEV, DEBUG) << "Requested timestamp = " << timestamp << " (real_sec=" << std::fixed << ts.get_real_secs() << " = " << ts.to_ticks(clk_rt) << ") rate=" << clk_rt;
+ // Do not return error here, because it's a rounding error and is not fatal
+ }
+
+ // Starting index
+ size_t write_start = (data_start + (timestamp - time_start)) % buf_len;
+
+ // Write it
+ if ((write_start + len) < buf_len) {
+ size_t numBytes = len * type_sz;
+ memcpy(data + write_start, buf, numBytes);
+ } else {
+ size_t first_cp = (buf_len - write_start) * type_sz;
+ size_t second_cp = len * type_sz - first_cp;
+
+ memcpy(data + write_start, buf, first_cp);
+ memcpy(data, (char*) buf + first_cp, second_cp);
+ }
+
+ data_end = (write_start + len) % buf_len;
+ time_end = timestamp + len;
+
+ if (!data_start)
+ data_start = write_start;
+
+ if (((write_start + len) > buf_len) && (data_end > data_start))
+ return ERROR_OVERFLOW;
+ else if (time_end <= time_start)
+ return ERROR_WRITE;
+ else
+ return len;
+}
+
+ssize_t smpl_buf::write(void *buf, size_t len, uhd::time_spec_t ts)
+{
+ return write(buf, len, ts.to_ticks(clk_rt));
+}
+
+std::string smpl_buf::str_status(size_t ts) const
+{
+ std::ostringstream ost("Sample buffer: ");
+
+ ost << "timestamp = " << ts;
+ ost << ", length = " << buf_len;
+ ost << ", time_start = " << time_start;
+ ost << ", time_end = " << time_end;
+ ost << ", data_start = " << data_start;
+ ost << ", data_end = " << data_end;
+
+ return ost.str();
+}
+
+std::string smpl_buf::str_code(ssize_t code)
+{
+ switch (code) {
+ case ERROR_TIMESTAMP:
+ return "Sample buffer: Requested timestamp is not valid";
+ case ERROR_READ:
+ return "Sample buffer: Read error";
+ case ERROR_WRITE:
+ return "Sample buffer: Write error";
+ case ERROR_OVERFLOW:
+ return "Sample buffer: Overrun";
+ default:
+ return "Sample buffer: Unknown error";
+ }
+}
+
+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 uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+}
diff --git a/Transceiver52M/device/usrp1/Makefile.am b/Transceiver52M/device/usrp1/Makefile.am
new file mode 100644
index 0000000..d99874a
--- /dev/null
+++ b/Transceiver52M/device/usrp1/Makefile.am
@@ -0,0 +1,10 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/..
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS)
+
+noinst_HEADERS = USRPDevice.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = USRPDevice.cpp
diff --git a/Transceiver52M/device/usrp1/USRPDevice.cpp b/Transceiver52M/device/usrp1/USRPDevice.cpp
new file mode 100644
index 0000000..5d19514
--- /dev/null
+++ b/Transceiver52M/device/usrp1/USRPDevice.cpp
@@ -0,0 +1,671 @@
+/*
+* Copyright 2008, 2009 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+
+/*
+ Compilation Flags
+
+ SWLOOPBACK compile for software loopback testing
+*/
+
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include "Logger.h"
+#include "Threads.h"
+#include "USRPDevice.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+using namespace std;
+
+enum dboardConfigType {
+ TXA_RXB,
+ TXB_RXA,
+ TXA_RXA,
+ TXB_RXB
+};
+
+#ifdef SINGLEDB
+const dboardConfigType dboardConfig = TXA_RXA;
+#else
+const dboardConfigType dboardConfig = TXA_RXB;
+#endif
+
+const double USRPDevice::masterClockRate = 52.0e6;
+
+USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface,
+ size_t chans, double lo_offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths):
+ RadioDevice(tx_sps, rx_sps, iface, chans, 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;
+
+ /*
+ * Undetermined delay b/w ping response timestamp and true
+ * receive timestamp. Values are empirically measured. With
+ * split sample rate Tx/Rx - 4/1 sps we need to need to
+ * compensate for advance rather than delay.
+ */
+ if (tx_sps == 1)
+ pingOffset = 272;
+ else if (tx_sps == 4)
+ pingOffset = 269 - 7500;
+ else
+ pingOffset = 0;
+
+#ifdef SWLOOPBACK
+ samplePeriod = 1.0e6/actualSampleRate;
+ loopbackBufferSize = 0;
+ gettimeofday(&lastReadTime,NULL);
+ firstRead = false;
+#endif
+}
+
+int USRPDevice::open(const std::string &, int, bool)
+{
+ writeLock.unlock();
+
+ LOGC(DDEV, INFO) << "opening USRP device..";
+#ifndef SWLOOPBACK
+ string rbf = "std_inband.rbf";
+ //string rbf = "inband_1rxhb_1tx.rbf";
+ m_uRx.reset();
+ if (!skipRx) {
+ try {
+ m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(
+ 0, decimRate * tx_sps, 1, -1,
+ usrp_standard_rx::FPGA_MODE_NORMAL,
+ 1024, 16 * 8, rbf));
+ m_uRx->set_fpga_master_clock_freq(masterClockRate);
+ }
+
+ catch(...) {
+ LOGC(DDEV, ALERT) << "make failed on Rx";
+ m_uRx.reset();
+ return -1;
+ }
+
+ if (m_uRx->fpga_master_clock_freq() != masterClockRate)
+ {
+ LOGC(DDEV, ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq()
+ << ", desired clock freq = " << masterClockRate;
+ m_uRx.reset();
+ return -1;
+ }
+ }
+
+ try {
+ m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(
+ 0, decimRate * 2, 1, -1,
+ 1024, 16 * 8, rbf));
+ m_uTx->set_fpga_master_clock_freq(masterClockRate);
+ }
+
+ catch(...) {
+ LOGC(DDEV, ALERT) << "make failed on Tx";
+ m_uTx.reset();
+ return -1;
+ }
+
+ if (m_uTx->fpga_master_clock_freq() != masterClockRate)
+ {
+ LOGC(DDEV, ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq()
+ << ", desired clock freq = " << masterClockRate;
+ m_uTx.reset();
+ return -1;
+ }
+
+ if (!skipRx) m_uRx->stop();
+ m_uTx->stop();
+
+#endif
+
+ switch (dboardConfig) {
+ case TXA_RXB:
+ txSubdevSpec = usrp_subdev_spec(0,0);
+ rxSubdevSpec = usrp_subdev_spec(1,0);
+ break;
+ case TXB_RXA:
+ txSubdevSpec = usrp_subdev_spec(1,0);
+ rxSubdevSpec = usrp_subdev_spec(0,0);
+ break;
+ case TXA_RXA:
+ txSubdevSpec = usrp_subdev_spec(0,0);
+ rxSubdevSpec = usrp_subdev_spec(0,0);
+ break;
+ case TXB_RXB:
+ txSubdevSpec = usrp_subdev_spec(1,0);
+ rxSubdevSpec = usrp_subdev_spec(1,0);
+ break;
+ default:
+ txSubdevSpec = usrp_subdev_spec(0,0);
+ rxSubdevSpec = usrp_subdev_spec(1,0);
+ }
+
+ m_dbTx = m_uTx->selected_subdev(txSubdevSpec);
+ m_dbRx = m_uRx->selected_subdev(rxSubdevSpec);
+
+ samplesRead = 0;
+ samplesWritten = 0;
+ started = false;
+
+ return NORMAL;
+}
+
+
+
+bool USRPDevice::start()
+{
+ LOGC(DDEV, INFO) << "starting USRP...";
+#ifndef SWLOOPBACK
+ if (!m_uRx && !skipRx) return false;
+ if (!m_uTx) return false;
+
+ if (!skipRx) m_uRx->stop();
+ m_uTx->stop();
+
+ writeLock.lock();
+ // power up and configure daughterboards
+ m_dbTx->set_enable(true);
+ m_uTx->set_mux(m_uTx->determine_tx_mux_value(txSubdevSpec));
+ m_uRx->set_mux(m_uRx->determine_rx_mux_value(rxSubdevSpec));
+
+ if (!m_dbRx->select_rx_antenna(1))
+ m_dbRx->select_rx_antenna(0);
+
+ writeLock.unlock();
+
+ // Set gains to midpoint
+ setTxGain((minTxGain() + maxTxGain()) / 2);
+ setRxGain((minRxGain() + maxRxGain()) / 2);
+
+ data = new short[currDataSize];
+ dataStart = 0;
+ dataEnd = 0;
+ timeStart = 0;
+ timeEnd = 0;
+ timestampOffset = 0;
+ latestWriteTimestamp = 0;
+ lastPktTimestamp = 0;
+ hi32Timestamp = 0;
+ isAligned = false;
+
+
+ if (!skipRx)
+ started = (m_uRx->start() && m_uTx->start());
+ else
+ started = m_uTx->start();
+ return started;
+#else
+ gettimeofday(&lastReadTime,NULL);
+ return true;
+#endif
+}
+
+bool USRPDevice::stop()
+{
+#ifndef SWLOOPBACK
+ if (!m_uRx) return false;
+ if (!m_uTx) return false;
+
+ delete[] currData;
+
+ started = !(m_uRx->stop() && m_uTx->stop());
+ return !started;
+#else
+ return true;
+#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();
+}
+
+double USRPDevice::minRxGain()
+{
+ return m_dbRx->gain_min();
+}
+
+double USRPDevice::setTxGain(double dB, size_t chan)
+{
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return 0.0;
+ }
+
+ writeLock.lock();
+ if (dB > maxTxGain())
+ dB = maxTxGain();
+ if (dB < minTxGain())
+ dB = minTxGain();
+
+ LOGC(DDEV, NOTICE) << "Setting TX gain to " << dB << " dB.";
+
+ if (!m_dbTx->set_gain(dB))
+ LOGC(DDEV, ERR) << "Error setting TX gain";
+
+ writeLock.unlock();
+
+ return dB;
+}
+
+
+double USRPDevice::setRxGain(double dB, size_t chan)
+{
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return 0.0;
+ }
+
+ dB = 47.0;
+
+ writeLock.lock();
+ if (dB > maxRxGain())
+ dB = maxRxGain();
+ if (dB < minRxGain())
+ dB = minRxGain();
+
+ LOGC(DDEV, NOTICE) << "Setting RX gain to " << dB << " dB.";
+
+ if (!m_dbRx->set_gain(dB))
+ LOGC(DDEV, ERR) << "Error setting RX gain";
+
+ writeLock.unlock();
+
+ return dB;
+}
+
+bool USRPDevice::setRxAntenna(const std::string &ant, size_t chan)
+{
+ if (chan >= rx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+ LOGC(DDEV, ALERT) << "Not implemented";
+ return true;
+}
+
+std::string USRPDevice::getRxAntenna(size_t chan)
+{
+ if (chan >= rx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return "";
+ }
+ LOGC(DDEV, ALERT) << "Not implemented";
+ return "";
+}
+
+bool USRPDevice::setTxAntenna(const std::string &ant, size_t chan)
+{
+ if (chan >= tx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return false;
+ }
+ LOGC(DDEV, ALERT) << "Not implemented";
+ return true;
+}
+
+std::string USRPDevice::getTxAntenna(size_t chan)
+{
+ if (chan >= tx_paths.size()) {
+ LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+ return "";
+ }
+ LOGC(DDEV, ALERT) << "Not implemented";
+ return "";
+}
+
+bool USRPDevice::requiresRadioAlign()
+{
+ return true;
+}
+
+GSM::Time USRPDevice::minLatency() {
+ return GSM::Time(1,1);
+}
+
+// NOTE: Assumes sequential reads
+int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
+ TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
+{
+#ifndef SWLOOPBACK
+ if (!m_uRx)
+ return 0;
+
+ short *buf = bufs[0];
+
+ timestamp += timestampOffset;
+
+ if (timestamp + len < timeStart) {
+ memset(buf,0,len*2*sizeof(short));
+ return len;
+ }
+
+ if (underrun) *underrun = false;
+
+ uint32_t readBuf[2000];
+
+ while (1) {
+ //guestimate USB read size
+ int readLen=0;
+ {
+ int numSamplesNeeded = timestamp + len - timeEnd;
+ if (numSamplesNeeded <=0) break;
+ readLen = 512 * ((int) ceil((float) numSamplesNeeded/126.0));
+ if (readLen > 8000) readLen= (8000/512)*512;
+ }
+
+ // read USRP packets, parse and save A/D data as needed
+ readLen = m_uRx->read((void *)readBuf,readLen,overrun);
+ for (int pktNum = 0; pktNum < (readLen/512); pktNum++) {
+ // tmpBuf points to start of a USB packet
+ uint32_t* tmpBuf = (uint32_t *) (readBuf+pktNum*512/4);
+ TIMESTAMP pktTimestamp = usrp_to_host_u32(tmpBuf[1]);
+ uint32_t word0 = usrp_to_host_u32(tmpBuf[0]);
+ uint32_t chan = (word0 >> 16) & 0x1f;
+ unsigned payloadSz = word0 & 0x1ff;
+ LOGC(DDEV, DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp;
+
+ bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp);
+ if (incrementHi32 && (timeStart!=0)) {
+ LOGC(DDEV, DEBUG) << "high 32 increment!!!";
+ hi32Timestamp++;
+ }
+ pktTimestamp = (((TIMESTAMP) hi32Timestamp) << 32) | pktTimestamp;
+ lastPktTimestamp = pktTimestamp;
+
+ if (chan == 0x01f) {
+ // control reply, check to see if its ping reply
+ uint32_t word2 = usrp_to_host_u32(tmpBuf[2]);
+ if ((word2 >> 16) == ((0x01 << 8) | 0x02)) {
+ timestamp -= timestampOffset;
+ timestampOffset = pktTimestamp - pingTimestamp + pingOffset;
+ LOGC(DDEV, DEBUG) << "updating timestamp offset to: " << timestampOffset;
+ timestamp += timestampOffset;
+ isAligned = true;
+ }
+ continue;
+ }
+ if (chan != 0) {
+ LOGC(DDEV, DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz;
+ continue;
+ }
+ if ((word0 >> 28) & 0x04) {
+ if (underrun) *underrun = true;
+ LOGC(DDEV, DEBUG) << "UNDERRUN in TRX->USRP interface";
+ }
+ if (RSSI) *RSSI = (word0 >> 21) & 0x3f;
+
+ if (!isAligned) continue;
+
+ unsigned cursorStart = pktTimestamp - timeStart + dataStart;
+ while (cursorStart*2 > currDataSize) {
+ cursorStart -= currDataSize/2;
+ }
+ if (cursorStart*2 + payloadSz/2 > currDataSize) {
+ // need to circle around buffer
+ memcpy(data+cursorStart*2,tmpBuf+2,(currDataSize-cursorStart*2)*sizeof(short));
+ memcpy(data,tmpBuf+2+(currDataSize/2-cursorStart),payloadSz-(currDataSize-cursorStart*2)*sizeof(short));
+ }
+ else {
+ memcpy(data+cursorStart*2,tmpBuf+2,payloadSz);
+ }
+ if (pktTimestamp + payloadSz/2/sizeof(short) > timeEnd)
+ timeEnd = pktTimestamp+payloadSz/2/sizeof(short);
+
+ LOGC(DDEV, DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp;
+
+ }
+ }
+
+ // copy desired data to buf
+ unsigned bufStart = dataStart+(timestamp-timeStart);
+ if (bufStart + len < currDataSize/2) {
+ LOGC(DDEV, DEBUG) << "bufStart: " << bufStart;
+ memcpy(buf,data+bufStart*2,len*2*sizeof(short));
+ memset(data+bufStart*2,0,len*2*sizeof(short));
+ }
+ else {
+ LOGC(DDEV, DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart;
+ unsigned firstLength = (currDataSize/2-bufStart);
+ LOGC(DDEV, DEBUG) << "firstLength: " << firstLength;
+ memcpy(buf,data+bufStart*2,firstLength*2*sizeof(short));
+ memset(data+bufStart*2,0,firstLength*2*sizeof(short));
+ memcpy(buf+firstLength*2,data,(len-firstLength)*2*sizeof(short));
+ memset(data,0,(len-firstLength)*2*sizeof(short));
+ }
+ dataStart = (bufStart + len) % (currDataSize/2);
+ timeStart = timestamp + len;
+
+ return len;
+
+#else
+ if (loopbackBufferSize < 2) return 0;
+ int numSamples = 0;
+ struct timeval currTime;
+ gettimeofday(&currTime,NULL);
+ double timeElapsed = (currTime.tv_sec - lastReadTime.tv_sec)*1.0e6 +
+ (currTime.tv_usec - lastReadTime.tv_usec);
+ if (timeElapsed < samplePeriod) {return 0;}
+ int numSamplesToRead = (int) floor(timeElapsed/samplePeriod);
+ if (numSamplesToRead < len) return 0;
+
+ if (numSamplesToRead > len) numSamplesToRead = len;
+ if (numSamplesToRead > loopbackBufferSize/2) {
+ firstRead =false;
+ numSamplesToRead = loopbackBufferSize/2;
+ }
+ memcpy(buf,loopbackBuffer,sizeof(short)*2*numSamplesToRead);
+ loopbackBufferSize -= 2*numSamplesToRead;
+ memcpy(loopbackBuffer,loopbackBuffer+2*numSamplesToRead,
+ sizeof(short)*loopbackBufferSize);
+ numSamples = numSamplesToRead;
+ if (firstRead) {
+ int new_usec = lastReadTime.tv_usec + (int) round((double) numSamplesToRead * samplePeriod);
+ lastReadTime.tv_sec = lastReadTime.tv_sec + new_usec/1000000;
+ lastReadTime.tv_usec = new_usec % 1000000;
+ }
+ else {
+ gettimeofday(&lastReadTime,NULL);
+ firstRead = true;
+ }
+ samplesRead += numSamples;
+
+ return numSamples;
+#endif
+}
+
+int USRPDevice::writeSamples(std::vector<short *> &bufs, int len,
+ bool *underrun, unsigned long long timestamp,
+ bool isControl)
+{
+ writeLock.lock();
+
+#ifndef SWLOOPBACK
+ if (!m_uTx)
+ return 0;
+
+ short *buf = bufs[0];
+
+ static uint32_t outData[128*20];
+
+ for (int i = 0; i < len*2; i++) {
+ buf[i] = host_to_usrp_short(buf[i]);
+ }
+
+ int numWritten = 0;
+ unsigned isStart = 1;
+ unsigned RSSI = 0;
+ unsigned CHAN = (isControl) ? 0x01f : 0x00;
+ len = len*2*sizeof(short);
+ int numPkts = (int) ceil((float)len/(float)504);
+ unsigned isEnd = (numPkts < 2);
+ uint32_t *outPkt = outData;
+ int pktNum = 0;
+ while (numWritten < len) {
+ // pkt is pointer to start of a USB packet
+ uint32_t *pkt = outPkt + pktNum*128;
+ isEnd = (len - numWritten <= 504);
+ unsigned payloadLen = ((len - numWritten) < 504) ? (len-numWritten) : 504;
+ pkt[0] = (isStart << 12 | isEnd << 11 | (RSSI & 0x3f) << 5 | CHAN) << 16 | payloadLen;
+ pkt[1] = timestamp & 0x0ffffffffll;
+ memcpy(pkt+2,buf+(numWritten/sizeof(short)),payloadLen);
+ numWritten += payloadLen;
+ timestamp += payloadLen/2/sizeof(short);
+ isStart = 0;
+ pkt[0] = host_to_usrp_u32(pkt[0]);
+ pkt[1] = host_to_usrp_u32(pkt[1]);
+ pktNum++;
+ }
+ m_uTx->write((const void*) outPkt,sizeof(uint32_t)*128*numPkts,NULL);
+
+ samplesWritten += len/2/sizeof(short);
+ writeLock.unlock();
+
+ return len/2/sizeof(short);
+#else
+ int retVal = len;
+ memcpy(loopbackBuffer+loopbackBufferSize,buf,sizeof(short)*2*len);
+ samplesWritten += retVal;
+ loopbackBufferSize += retVal*2;
+
+ return retVal;
+#endif
+}
+
+bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
+{
+#ifndef SWLOOPBACK
+ short data[] = {0x00,0x02,0x00,0x00};
+ uint32_t *wordPtr = (uint32_t *) data;
+ *wordPtr = host_to_usrp_u32(*wordPtr);
+ bool tmpUnderrun;
+
+ std::vector<short *> buf(1, data);
+ if (writeSamples(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) {
+ pingTimestamp = timestamp;
+ return true;
+ }
+ return false;
+#else
+ return true;
+#endif
+}
+
+#ifndef SWLOOPBACK
+bool USRPDevice::setTxFreq(double wFreq, size_t chan)
+{
+ usrp_tune_result result;
+
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return false;
+ }
+
+ if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) {
+ LOGC(DDEV, INFO) << "set TX: " << wFreq << std::endl
+ << " baseband freq: " << result.baseband_freq << std::endl
+ << " DDC freq: " << result.dxc_freq << std::endl
+ << " residual freq: " << result.residual_freq;
+ return true;
+ }
+ else {
+ LOGC(DDEV, ALERT) << "set TX: " << wFreq << "failed" << std::endl
+ << " baseband freq: " << result.baseband_freq << std::endl
+ << " DDC freq: " << result.dxc_freq << std::endl
+ << " residual freq: " << result.residual_freq;
+ return false;
+ }
+}
+
+bool USRPDevice::setRxFreq(double wFreq, size_t chan)
+{
+ usrp_tune_result result;
+
+ if (chan) {
+ LOGC(DDEV, ALERT) << "Invalid channel " << chan;
+ return false;
+ }
+
+ if (m_uRx->tune(0, m_dbRx, wFreq, &result)) {
+ LOGC(DDEV, INFO) << "set RX: " << wFreq << std::endl
+ << " baseband freq: " << result.baseband_freq << std::endl
+ << " DDC freq: " << result.dxc_freq << std::endl
+ << " residual freq: " << result.residual_freq;
+ return true;
+ }
+ else {
+ LOGC(DDEV, ALERT) << "set RX: " << wFreq << "failed" << std::endl
+ << " baseband freq: " << result.baseband_freq << std::endl
+ << " DDC freq: " << result.dxc_freq << std::endl
+ << " residual freq: " << result.residual_freq;
+ return false;
+ }
+
+}
+
+#else
+bool USRPDevice::setTxFreq(double wFreq) { return true;};
+bool USRPDevice::setRxFreq(double wFreq) { return true;};
+#endif
+
+RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
+ InterfaceType iface, size_t chans, double lo_offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths)
+{
+ if (tx_sps != rx_sps) {
+ LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps";
+ return NULL;
+ }
+ if (chans != 1) {
+ LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel";
+ return NULL;
+ }
+ if (lo_offset != 0.0) {
+ LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset";
+ return NULL;
+ }
+ return new USRPDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+}
diff --git a/Transceiver52M/device/usrp1/USRPDevice.h b/Transceiver52M/device/usrp1/USRPDevice.h
new file mode 100644
index 0000000..451b5a9
--- /dev/null
+++ b/Transceiver52M/device/usrp1/USRPDevice.h
@@ -0,0 +1,209 @@
+/*
+* 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 use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+#ifndef _USRP_DEVICE_H_
+#define _USRP_DEVICE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "radioDevice.h"
+
+#include <usrp/usrp_standard.h>
+#include <usrp/usrp_bytesex.h>
+#include <usrp/usrp_prims.h>
+#include <sys/time.h>
+#include <math.h>
+#include <string>
+#include <iostream>
+
+#include <boost/shared_ptr.hpp>
+typedef boost::shared_ptr<usrp_standard_tx> usrp_standard_tx_sptr;
+typedef boost::shared_ptr<usrp_standard_rx> usrp_standard_rx_sptr;
+
+/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */
+class USRPDevice: public RadioDevice {
+
+private:
+
+ static const double masterClockRate; ///< the USRP clock rate
+ double desiredSampleRate; ///< the desired sampling rate
+ usrp_standard_rx_sptr m_uRx; ///< the USRP receiver
+ usrp_standard_tx_sptr m_uTx; ///< the USRP transmitter
+
+ db_base_sptr m_dbRx; ///< rx daughterboard
+ db_base_sptr m_dbTx; ///< tx daughterboard
+ usrp_subdev_spec rxSubdevSpec;
+ usrp_subdev_spec txSubdevSpec;
+
+ double actualSampleRate; ///< the actual USRP sampling rate
+ unsigned int decimRate; ///< the USRP decimation rate
+
+ unsigned long long samplesRead; ///< number of samples read from USRP
+ unsigned long long samplesWritten; ///< number of samples sent to USRP
+
+ bool started; ///< flag indicates USRP has started
+ bool skipRx; ///< set if USRP is transmit-only.
+
+ static const unsigned int currDataSize_log2 = 21;
+ static const unsigned long currDataSize = (1 << currDataSize_log2);
+ short *data;
+ unsigned long dataStart;
+ unsigned long dataEnd;
+ TIMESTAMP timeStart;
+ TIMESTAMP timeEnd;
+ bool isAligned;
+
+ Mutex writeLock;
+
+ short *currData; ///< internal data buffer when reading from USRP
+ TIMESTAMP currTimestamp; ///< timestamp of internal data buffer
+ unsigned currLen; ///< size of internal data buffer
+
+ TIMESTAMP timestampOffset; ///< timestamp offset b/w Tx and Rx blocks
+ TIMESTAMP latestWriteTimestamp; ///< timestamp of most recent ping command
+ TIMESTAMP pingTimestamp; ///< timestamp of most recent ping response
+
+ long long pingOffset;
+ unsigned long hi32Timestamp;
+ unsigned long lastPktTimestamp;
+
+ double rxGain;
+
+#ifdef SWLOOPBACK
+ short loopbackBuffer[1000000];
+ int loopbackBufferSize;
+ double samplePeriod;
+
+ struct timeval startTime;
+ struct timeval lastReadTime;
+ bool firstRead;
+#endif
+
+ public:
+
+ /** Object constructor */
+ USRPDevice(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);
+
+ /** Instantiate the USRP */
+ int open(const std::string &, int, bool);
+
+ /** Start the USRP */
+ bool start();
+
+ /** Stop the USRP */
+ bool stop();
+
+ /** Set priority not supported */
+ void setPriority(float prio = 0.5) { }
+
+ enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; }
+
+ /**
+ Read samples from the USRP.
+ @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 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);
+ /**
+ 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);
+
+ /** Update the alignment between the read and write timestamps */
+ bool updateAlignment(TIMESTAMP timestamp);
+
+ /** Set the transmitter frequency */
+ bool setTxFreq(double wFreq, size_t chan = 0);
+
+ /** Set the receiver frequency */
+ bool setRxFreq(double wFreq, size_t chan = 0);
+
+ /** Returns the starting write Timestamp*/
+ TIMESTAMP initialWriteTimestamp(void) { return 20000;}
+
+ /** Returns the starting read Timestamp*/
+ TIMESTAMP initialReadTimestamp(void) { return 20000;}
+
+ /** returns the full-scale transmit amplitude **/
+ double fullScaleInputValue() {return 13500.0;}
+
+ /** returns the full-scale receive amplitude **/
+ double fullScaleOutputValue() {return 9450.0;}
+
+ /** sets the receive chan gain, returns the gain setting **/
+ double setRxGain(double dB, size_t chan = 0);
+
+ /** get the current receive gain */
+ double getRxGain(size_t chan = 0) { return rxGain; }
+
+ /** return maximum Rx Gain **/
+ double maxRxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minRxGain(void);
+
+ /** sets the transmit chan gain, returns the gain setting **/
+ double setTxGain(double dB, size_t chan = 0);
+
+ /** return maximum Tx Gain **/
+ double maxTxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minTxGain(void);
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ bool setRxAntenna(const std::string &ant, size_t chan = 0);
+
+ /* return the used RX path */
+ std::string getRxAntenna(size_t chan = 0);
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ bool setTxAntenna(const std::string &ant, size_t chan = 0);
+
+ /* return the used RX path */
+ std::string getTxAntenna(size_t chan = 0);
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ bool requiresRadioAlign();
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ virtual GSM::Time minLatency();
+
+ /** Return internal status values */
+ inline double getTxFreq(size_t chan = 0) { return 0; }
+ inline double getRxFreq(size_t chan = 0) { return 0; }
+ inline double getSampleRate() { return actualSampleRate; }
+ inline double numberRead() { return samplesRead; }
+ inline double numberWritten() { return samplesWritten; }
+};
+
+#endif // _USRP_DEVICE_H_
diff --git a/Transceiver52M/inband-signaling-usb b/Transceiver52M/inband-signaling-usb
new file mode 100644
index 0000000..14f8347
--- /dev/null
+++ b/Transceiver52M/inband-signaling-usb
@@ -0,0 +1,314 @@
+This file specifies the format of USB packets used for in-band data
+transmission and signaling on the USRP. All packets are 512-byte long,
+and are transfered using USB "bulk" transfers.
+
+IN packets are sent towards the host.
+OUT packets are sent away from the host.
+
+The layout is 32-bits wide. All data is transmitted in little-endian
+format across the USB.
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |O|U|D|S|E| RSSI | Chan | mbz | Tag | Payload Len |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Timestamp |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | Payload |
+ . .
+ . .
+ . .
+ | |
+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ... | .
+ +-+-+-+-+-+-+-+ .
+ . .
+ . Padding .
+ . .
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ mbz Must be Zero: these bits must be zero in both IN and OUT packets.
+
+ O Overrun Flag: set in an IN packet if an overrun condition was
+ detected. Must be zero in OUT packets. Overrun occurs when
+ the FPGA has data to transmit to the host and there is no
+ buffer space available. This generally indicates a problem on
+ the host. Either it is not keeping up, or it has configured
+ the FPGA to transmit data at a higher rate than the transport
+ (USB) can support.
+
+ U Underrun Flag: set in an IN packet if an underrun condition
+ was detected. Must be zero in OUT packets. Underrun occurs
+ when the FPGA runs out of samples, and it's not between
+ bursts. See the "End of Burst flag" below.
+
+ D Dropped Packet Flag: Set in an IN packet if the FPGA
+ discarded an OUT packet because its timestamp had already
+ passed.
+
+ S Start of Burst Flag: Set in an OUT packet if the data is the
+ first segment of what is logically a continuous burst of data.
+ Must be zero in IN packets.
+
+ E End of Burst Flag: Set in an OUT packet if the data is the
+ last segment of what is logically a continuous burst of data.
+ Must be zero in IN packets. Underruns are not reported
+ when the FPGA runs out of samples between bursts.
+
+
+ RSSI 6-bit Received Strength Signal Indicator: Must be zero in OUT
+ packets. In IN packets, indicates RSSI as reported by front end.
+ FIXME The format and interpretation are to be determined.
+
+ Chan 5-bit logical channel number. Channel number 0x1f is reserved
+ for control information. See "Control Channel" below. Other
+ channels are "data channels." Each data channel is logically
+ independent of the others. A data channel payload field
+ contains a sequence of homogeneous samples. The format of the
+ samples is determined by the configuration associated with the
+ given channel. It is often the case that the payload field
+ contains 32-bit complex samples, each containing 16-bit real
+ and imaginary components.
+
+ Tag 4-bit tag for matching IN packets with OUT packets.
+ [FIXME, write more...]
+
+ Payload Len: 9-bit field that specifies the length of the payload
+ field in bytes. Must be in the range 0 to 504 inclusive.
+
+ Timestamp: 32-bit timestamp.
+ On IN packets, the timestamp indicates the time at which the
+ first sample of the packet was produced by the A/D converter(s)
+ for that channel. On OUT packets, the timestamp specifies the
+ time at which the first sample in the packet should go out the
+ D/A converter(s) for that channel. If a packet reaches the
+ head of the transmit queue, and the current time is later than
+ the timestamp, an error is assumed to have occurred and the
+ packet is discarded. As a special case, the timestamp
+ 0xffffffff is interpreted as "Now".
+
+ The time base is a free running 32-bit counter that is
+ incremented by the A/D sample-clock.
+
+ Payload: Variable length field. Length is specified by the
+ Payload Len field.
+
+ Padding: This field is 504 - Payload Len bytes long, and its content
+ is unspecified. This field pads the packet out to a constant
+ 512 bytes.
+
+
+
+"Data Channel" payload format:
+-------------------------------
+
+If Chan != 0x1f, the packet is a "data packet" and the payload is a
+sequence of homogeneous samples. The format of the samples is
+determined by the configuration associated with the given channel.
+It is often the case that the payload field contains 32-bit complex
+samples, each containing 16-bit real and imaginary components.
+
+
+"Control Channel" payload format:
+---------------------------------
+
+If Chan == 0x1f, the packet is a "control packet". The control channel
+payload consists of a sequence of 0 or more sub-packets.
+
+Each sub-packet starts on a 32-bit boundary, and consists of an 8-bit
+Opcode field, an 8-bit Length field, Length bytes of arguments, and 0,
+1, 2 or 3 bytes of padding to align the tail of the sub-packet to
+a 32-bit boundary.
+
+Control channel packets shall be processed at the head of the queue,
+and shall observe the timestamp semantics described above.
+
+
+General sub-packet format:
+--------------------------
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-//-+-+-+-+-+-+-+-+
+ | Opcode | Length | <length bytes> ... |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-//-+-+-+-+-+-+-+-+
+
+
+Specific sub-packet formats:
+----------------------------
+
+ RID: 6-bit Request-ID. Copied from request sub-packet into corresponding
+ reply sub-packet. RID allows the host to match requests and replies.
+
+ Reg Number: 10-bit Register Number.
+
+
+
+Ping Fixed Length:
+
+ Opcode: OP_PING_FIXED
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 2 | RID | Ping Value |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+Ping Fixed Length Reply:
+
+ Opcode: OP_PING_FIXED_REPLY
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 2 | RID | Ping Value |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+Write Register:
+
+ Opcode: OP_WRITE_REG
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 6 | mbz | Reg Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Register Value |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+Write Register Masked:
+
+ Opcode: OP_WRITE_REG_MASKED
+
+ REG[Num] = (REG[Num] & ~Mask) | (Value & Mask)
+
+ That is, only the register bits that correspond to 1's in the
+ mask are written with the new value.
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 10 | mbz | Reg Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Register Value |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Mask Value |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+Read Register:
+
+ Opcode: OP_READ_REG
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 2 | RID | Reg Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+Read Register Reply:
+
+ Opcode: OP_READ_REG_REPLY
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 6 | RID | Reg Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Register Value |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+I2C Write:
+
+ Opcode: OP_I2C_WRITE
+ I2C Addr: 7-bit I2C address
+ Data: The bytes to write to the I2C bus
+ Length: Length of Data + 2
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | Length | mbz | I2C Addr |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ... .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+I2C Read:
+
+ Opcode: OP_I2C_READ
+ I2C Addr: 7-bit I2C address
+ Nbytes: Number of bytes to read from I2C bus
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 3 | RID | mbz | I2C Addr |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Nbytes | unspecified padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+I2C Read Reply:
+
+ Opcode: OP_I2C_READ_REPLY
+ I2C Addr: 7-bit I2C address
+ Data: Length - 2 bytes of data read from I2C bus.
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | Length | RID | mbz | I2C Addr |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ... .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+SPI Write:
+
+ Opcode: OP_SPI_WRITE
+ Enables: Which SPI enables to assert (mask)
+ Format: Specifies format of SPI data and Opt Header Bytes
+ Opt Header Bytes: 2-byte field containing optional Tx bytes; see Format
+ Data: The bytes to write to the SPI bus
+ Length: Length of Data + 6
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | Length | mbz |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Enables | Format | Opt Header Bytes |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ... .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+SPI Read:
+
+ Opcode: OP_SPI_READ
+ Enables: Which SPI enables to assert (mask)
+ Format: Specifies format of SPI data and Opt Header Bytes
+ Opt Header Bytes: 2-byte field containing optional Tx bytes; see Format
+ Nbytes: Number of bytes to read from SPI bus.
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 7 | RID | mbz |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Enables | Format | Opt Header Bytes |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Nbytes | unspecified padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+SPI Read Reply:
+
+ Opcode: OP_SPI_READ_REPLY
+ Data: Length - 2 bytes of data read from SPI bus.
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | Length | RID | mbz |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ... .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+Delay:
+
+ Opcode: OP_DELAY
+ Ticks: 16-bit unsigned delay count
+
+ Delay Ticks clock ticks before executing next operation.
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Opcode | 2 | Ticks |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
diff --git a/Transceiver52M/laurent.m b/Transceiver52M/laurent.m
new file mode 100644
index 0000000..ef15428
--- /dev/null
+++ b/Transceiver52M/laurent.m
@@ -0,0 +1,83 @@
+%
+% Laurent decomposition of GMSK signals
+% Generates C0, C1, and C2 pulse shapes
+%
+% Pierre Laurent, "Exact and Approximate Construction of Digital Phase
+% Modulations by Superposition of Amplitude Modulated Pulses", IEEE
+% Transactions of Communications, Vol. 34, No. 2, Feb 1986.
+%
+% Author: Thomas Tsou <tom@tsou.cc>
+%
+
+% Modulation parameters
+oversamp = 16;
+L = 3;
+f = 270.83333e3;
+T = 1/f;
+h = 0.5;
+BT = 0.30;
+B = BT / T;
+
+% Generate sampling points for L symbol periods
+t = -(L*T/2):T/oversamp:(L*T/2);
+t = t(1:end-1) + (T/oversamp/2);
+
+% Generate Gaussian pulse
+g = qfunc(2*pi*B*(t - T/2)/(log(2)^.5)) - qfunc(2*pi*B*(t + T/2)/(log(2)^.5));
+g = g / sum(g) * pi/2;
+g = [0 g];
+
+% Integrate phase
+q = 0;
+for i = 1:size(g,2);
+ q(i) = sum(g(1:i));
+end
+
+% Compute two sided "generalized phase pulse" function
+s = 0;
+for i = 1:size(g,2);
+ s(i) = sin(q(i)) / sin(pi*h);
+end
+for i = (size(g,2) + 1):(2 * size(g,2) - 1);
+ s(i) = sin(pi*h - q(i - (size(g,2) - 1))) / sin(pi*h);
+end
+
+% Compute C0 pulse: valid for all L values
+c0 = s(1:end-(oversamp*(L-1)));
+for i = 1:L-1;
+ c0 = c0 .* s((1 + i*oversamp):end-(oversamp*(L - 1 - i)));
+end
+
+% Compute C1 pulse: valid for L = 3 only!
+% C1 = S0 * S4 * S2
+c1 = s(1:end-(oversamp*(4)));
+c1 = c1 .* s((1 + 4*oversamp):end-(oversamp*(4 - 1 - 3)));
+c1 = c1 .* s((1 + 2*oversamp):end-(oversamp*(4 - 1 - 1)));
+
+% Compute C2 pulse: valid for L = 3 only!
+% C2 = S0 * S1 * S5
+c2 = s(1:end-(oversamp*(5)));
+c2 = c2 .* s((1 + 1*oversamp):end-(oversamp*(5 - 1 - 0)));
+c2 = c2 .* s((1 + 5*oversamp):end-(oversamp*(5 - 1 - 4)));
+
+% Plot C0, C1, C2 Laurent pulse series
+figure(1);
+hold off;
+plot((0:size(c0,2)-1)/oversamp - 2,c0, 'b');
+hold on;
+plot((0:size(c1,2)-1)/oversamp - 2,c1, 'r');
+plot((0:size(c2,2)-1)/oversamp - 2,c2, 'g');
+
+% Generate OpenBTS pulse
+numSamples = size(c0,2);
+centerPoint = (numSamples - 1)/2;
+i = ((0:numSamples) - centerPoint) / oversamp;
+xP = .96*exp(-1.1380*i.^2 - 0.527*i.^4);
+xP = xP / max(xP) * max(c0);
+
+% Plot C0 pulse compared to OpenBTS pulse
+figure(2);
+hold off;
+plot((0:size(c0,2)-1)/oversamp, c0, 'b');
+hold on;
+plot((0:size(xP,2)-1)/oversamp, xP, 'r');
diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp
new file mode 100644
index 0000000..b6b676e
--- /dev/null
+++ b/Transceiver52M/osmo-trx.cpp
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * 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
+#include "config.h"
+#endif
+
+#include "Transceiver.h"
+#include "radioDevice.h"
+
+#include <time.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sched.h>
+#include <vector>
+#include <string>
+#include <sstream>
+#include <iostream>
+
+#include <GSMCommon.h>
+#include <Logger.h>
+
+extern "C" {
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/command.h>
+
+#include "convolve.h"
+#include "convert.h"
+#include "trx_vty.h"
+#include "debug.h"
+#include "osmo_signal.h"
+}
+
+#define DEFAULT_CONFIG_FILE "osmo-trx.cfg"
+
+#define charp2str(a) ((a) ? std::string(a) : std::string(""))
+
+static char* config_file = (char*)DEFAULT_CONFIG_FILE;
+
+volatile bool gshutdown = false;
+
+static void *tall_trx_ctx;
+static struct trx_ctx *g_trx_ctx;
+static struct ctrl_handle *g_ctrlh;
+
+static RadioDevice *usrp;
+static RadioInterface *radio;
+static Transceiver *transceiver;
+
+/* Create radio interface
+ * The interface consists of sample rate changes, frequency shifts,
+ * channel multiplexing, and other conversions. The transceiver core
+ * accepts input vectors sampled at multiples of the GSM symbol rate.
+ * The radio interface connects the main transceiver with the device
+ * object, which may be operating some other rate.
+ */
+RadioInterface *makeRadioInterface(struct trx_ctx *trx,
+ RadioDevice *usrp, int type)
+{
+ RadioInterface *radio = NULL;
+
+ switch (type) {
+ case RadioDevice::NORMAL:
+ radio = new RadioInterface(usrp, trx->cfg.tx_sps,
+ trx->cfg.rx_sps, trx->cfg.num_chans);
+ break;
+ case RadioDevice::RESAMP_64M:
+ case RadioDevice::RESAMP_100M:
+ radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps,
+ trx->cfg.rx_sps);
+ break;
+ case RadioDevice::MULTI_ARFCN:
+ radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps,
+ trx->cfg.rx_sps, trx->cfg.num_chans);
+ break;
+ default:
+ LOG(ALERT) << "Unsupported radio interface configuration";
+ return NULL;
+ }
+
+ if (!radio->init(type)) {
+ LOG(ALERT) << "Failed to initialize radio interface";
+ return NULL;
+ }
+
+ return radio;
+}
+
+/* Callback function to be called every time we receive a signal from TRANSC */
+static int transc_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ switch (signal) {
+ case S_TRANSC_STOP_REQUIRED:
+ gshutdown = true;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Create transceiver core
+ * The multi-threaded modem core operates at multiples of the GSM rate of
+ * 270.8333 ksps and consists of GSM specific modulation, demodulation,
+ * and decoding schemes. Also included are the socket interfaces for
+ * connecting to the upper layer stack.
+ */
+int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
+{
+ VectorFIFO *fifo;
+
+ transceiver = new Transceiver(trx->cfg.base_port, trx->cfg.bind_addr,
+ trx->cfg.remote_addr, trx->cfg.tx_sps,
+ trx->cfg.rx_sps, trx->cfg.num_chans, GSM::Time(3,0),
+ radio, trx->cfg.rssi_offset);
+ if (!transceiver->init(trx->cfg.filler, trx->cfg.rtsc,
+ trx->cfg.rach_delay, trx->cfg.egprs)) {
+ LOG(ALERT) << "Failed to initialize transceiver";
+ return -1;
+ }
+
+ transceiver->setSignalHandler(transc_sig_cb);
+
+ for (size_t i = 0; i < trx->cfg.num_chans; i++) {
+ fifo = radio->receiveFIFO(i);
+ if (fifo && transceiver->receiveFIFO(fifo, i))
+ continue;
+
+ LOG(ALERT) << "Could not attach FIFO to channel " << i;
+ return -1;
+ }
+ return 0;
+}
+
+static void sig_handler(int signo)
+{
+ fprintf(stdout, "signal %d received\n", signo);
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ fprintf(stdout, "shutting down\n");
+ gshutdown = true;
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ talloc_report(tall_trx_ctx, stderr);
+ talloc_report_full(tall_trx_ctx, stderr);
+ break;
+ case SIGUSR2:
+ talloc_report_full(tall_trx_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static void setup_signal_handlers()
+{
+ /* Handle keyboard interrupt SIGINT */
+ signal(SIGINT, &sig_handler);
+ signal(SIGTERM, &sig_handler);
+ signal(SIGABRT, &sig_handler);
+ signal(SIGUSR1, &sig_handler);
+ signal(SIGUSR2, &sig_handler);
+ osmo_init_ignore_signals();
+}
+
+static std::vector<std::string> comma_delimited_to_vector(char* opt)
+{
+ std::string str = std::string(opt);
+ std::vector<std::string> result;
+ std::stringstream ss(str);
+
+ while( ss.good() )
+ {
+ std::string substr;
+ getline(ss, substr, ',');
+ result.push_back(substr);
+ }
+ return result;
+}
+
+static void print_help()
+{
+ fprintf(stdout, "Options:\n"
+ " -h, --help This text\n"
+ " -C, --config Filename The config file to use\n"
+ " -V, --version Print the version of OsmoTRX\n"
+ );
+}
+
+static void print_deprecated(char opt)
+{
+ LOG(WARNING) << "Cmd line option '" << opt << "' is deprecated and will be soon removed."
+ << " Please use VTY cfg option instead."
+ << " All cmd line options are already being overriden by VTY options if set.";
+}
+
+static void handle_options(int argc, char **argv, struct trx_ctx* trx)
+{
+ int option;
+ unsigned int i;
+ std::vector<std::string> rx_paths, tx_paths;
+ bool rx_paths_set = false, tx_paths_set = false;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"config", 1, 0, 'C'},
+ {"version", 0, 0, 'V'},
+ {NULL, 0, 0, 0}
+ };
+
+ while ((option = getopt_long(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:V", long_options,
+ NULL)) != -1) {
+ switch (option) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 'a':
+ print_deprecated(option);
+ osmo_talloc_replace_string(trx, &trx->cfg.dev_args, optarg);
+ break;
+ case 'l':
+ print_deprecated(option);
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'i':
+ print_deprecated(option);
+ osmo_talloc_replace_string(trx, &trx->cfg.remote_addr, optarg);
+ break;
+ case 'j':
+ print_deprecated(option);
+ osmo_talloc_replace_string(trx, &trx->cfg.bind_addr, optarg);
+ break;
+ case 'p':
+ print_deprecated(option);
+ trx->cfg.base_port = atoi(optarg);
+ break;
+ case 'c':
+ print_deprecated(option);
+ trx->cfg.num_chans = atoi(optarg);
+ break;
+ case 'm':
+ print_deprecated(option);
+ trx->cfg.multi_arfcn = true;
+ break;
+ case 'x':
+ print_deprecated(option);
+ trx->cfg.clock_ref = REF_EXTERNAL;
+ break;
+ case 'g':
+ print_deprecated(option);
+ trx->cfg.clock_ref = REF_GPS;
+ break;
+ case 'f':
+ print_deprecated(option);
+ trx->cfg.filler = FILLER_DUMMY;
+ break;
+ case 'o':
+ print_deprecated(option);
+ trx->cfg.offset = atof(optarg);
+ break;
+ case 's':
+ print_deprecated(option);
+ trx->cfg.tx_sps = atoi(optarg);
+ break;
+ case 'b':
+ print_deprecated(option);
+ trx->cfg.rx_sps = atoi(optarg);
+ break;
+ case 'r':
+ print_deprecated(option);
+ trx->cfg.rtsc_set = true;
+ trx->cfg.rtsc = atoi(optarg);
+ if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */
+ trx->cfg.filler = FILLER_NORM_RAND;
+ break;
+ case 'A':
+ print_deprecated(option);
+ trx->cfg.rach_delay_set = true;
+ trx->cfg.rach_delay = atoi(optarg);
+ trx->cfg.filler = FILLER_ACCESS_RAND;
+ break;
+ case 'R':
+ print_deprecated(option);
+ trx->cfg.rssi_offset = atof(optarg);
+ break;
+ case 'S':
+ print_deprecated(option);
+ trx->cfg.swap_channels = true;
+ break;
+ case 'e':
+ print_deprecated(option);
+ trx->cfg.egprs = true;
+ break;
+ case 't':
+ print_deprecated(option);
+ trx->cfg.sched_rr = atoi(optarg);
+ break;
+ case 'y':
+ print_deprecated(option);
+ tx_paths = comma_delimited_to_vector(optarg);
+ tx_paths_set = true;
+ break;
+ case 'z':
+ print_deprecated(option);
+ rx_paths = comma_delimited_to_vector(optarg);
+ rx_paths_set = true;
+ break;
+ case 'C':
+ config_file = optarg;
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ goto bad_config;
+ }
+ }
+
+ /* Cmd line option specific validation & setup */
+
+ if (trx->cfg.num_chans > TRX_CHAN_MAX) {
+ LOG(ERROR) << "Too many channels requested, maximum is " << TRX_CHAN_MAX;
+ goto bad_config;
+ }
+ if ((tx_paths_set && tx_paths.size() != trx->cfg.num_chans) ||
+ (rx_paths_set && rx_paths.size() != trx->cfg.num_chans)) {
+ LOG(ERROR) << "Num of channels and num of Rx/Tx Antennas doesn't match";
+ goto bad_config;
+ }
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ trx->cfg.chans[i].trx = trx;
+ trx->cfg.chans[i].idx = i;
+ if (tx_paths_set)
+ osmo_talloc_replace_string(trx, &trx->cfg.chans[i].tx_path, tx_paths[i].c_str());
+ if (rx_paths_set)
+ osmo_talloc_replace_string(trx, &trx->cfg.chans[i].rx_path, rx_paths[i].c_str());
+ }
+
+ return;
+
+bad_config:
+ print_help();
+ exit(0);
+}
+
+int trx_validate_config(struct trx_ctx *trx)
+{
+ if (trx->cfg.multi_arfcn && trx->cfg.num_chans > 5) {
+ LOG(ERROR) << "Unsupported number of channels";
+ return -1;
+ }
+
+ /* Force 4 SPS for EDGE or multi-ARFCN configurations */
+ if ((trx->cfg.egprs || trx->cfg.multi_arfcn) &&
+ (trx->cfg.tx_sps!=4 || trx->cfg.rx_sps!=4)) {
+ LOG(ERROR) << "EDGE and Multi-Carrier options require 4 tx and rx sps. Check you config.";
+ return -1;
+ }
+
+ return 0;
+}
+
+static int set_sched_rr(unsigned int prio)
+{
+ struct sched_param param;
+ int rc;
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = prio;
+ printf("Setting SCHED_RR priority(%d)\n", param.sched_priority);
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc != 0) {
+ LOG(ERROR) << "Config: Setting SCHED_RR failed";
+ return -1;
+ }
+ return 0;
+}
+
+static void print_config(struct trx_ctx *trx)
+{
+ unsigned int i;
+ std::ostringstream ost("");
+
+ ost << "Config Settings" << std::endl;
+ ost << " Log Level............... " << (unsigned int) osmo_stderr_target->loglevel << std::endl;
+ ost << " Device args............. " << charp2str(trx->cfg.dev_args) << std::endl;
+ ost << " TRX Base Port........... " << trx->cfg.base_port << std::endl;
+ ost << " TRX Address............. " << charp2str(trx->cfg.bind_addr) << std::endl;
+ ost << " GSM BTS Address......... " << charp2str(trx->cfg.remote_addr) << std::endl;
+ ost << " Channels................ " << trx->cfg.num_chans << std::endl;
+ ost << " Tx Samples-per-Symbol... " << trx->cfg.tx_sps << std::endl;
+ ost << " Rx Samples-per-Symbol... " << trx->cfg.rx_sps << std::endl;
+ ost << " EDGE support............ " << trx->cfg.egprs << std::endl;
+ ost << " Reference............... " << trx->cfg.clock_ref << std::endl;
+ ost << " C0 Filler Table......... " << trx->cfg.filler << std::endl;
+ ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl;
+ ost << " Tuning offset........... " << trx->cfg.offset << std::endl;
+ ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << std::endl;
+ ost << " Swap channels........... " << trx->cfg.swap_channels << std::endl;
+ ost << " Tx Antennas.............";
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ std::string p = charp2str(trx->cfg.chans[i].tx_path);
+ ost << " '" << ((p != "") ? p : "<default>") << "'";
+ }
+ ost << std::endl;
+ ost << " Rx Antennas.............";
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ std::string p = charp2str(trx->cfg.chans[i].rx_path);
+ ost << " '" << ((p != "") ? p : "<default>") << "'";
+ }
+ ost << std::endl;
+
+ std::cout << ost << std::endl;
+}
+
+static void trx_stop()
+{
+ std::cout << "Shutting down transceiver..." << std::endl;
+
+ delete transceiver;
+ delete radio;
+ delete usrp;
+}
+
+static int trx_start(struct trx_ctx *trx)
+{
+ int type, chans;
+ unsigned int i;
+ std::vector<std::string> rx_paths, tx_paths;
+ RadioDevice::InterfaceType iface = RadioDevice::NORMAL;
+
+ /* Create the low level device object */
+ if (trx->cfg.multi_arfcn)
+ iface = RadioDevice::MULTI_ARFCN;
+
+ /* Generate vector of rx/tx_path: */
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path));
+ tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path));
+ }
+
+ usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface,
+ trx->cfg.num_chans, trx->cfg.offset,
+ tx_paths, rx_paths);
+ type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels);
+ if (type < 0) {
+ LOG(ALERT) << "Failed to create radio device" << std::endl;
+ goto shutdown;
+ }
+
+ /* Setup the appropriate device interface */
+ radio = makeRadioInterface(trx, usrp, type);
+ if (!radio)
+ goto shutdown;
+
+ /* Create the transceiver core */
+ if (makeTransceiver(trx, radio) < 0)
+ goto shutdown;
+
+ chans = transceiver->numChans();
+ std::cout << "-- Transceiver active with "
+ << chans << " channel(s)" << std::endl;
+
+ return 0;
+
+shutdown:
+ trx_stop();
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+
+ tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
+ msgb_talloc_ctx_init(tall_trx_ctx, 0);
+ g_vty_info.tall_ctx = tall_trx_ctx;
+
+ setup_signal_handlers();
+
+ g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx);
+
+#ifdef HAVE_SSE3
+ printf("Info: SSE3 support compiled in");
+#ifdef HAVE___BUILTIN_CPU_SUPPORTS
+ if (__builtin_cpu_supports("sse3"))
+ printf(" and supported by CPU\n");
+ else
+ printf(", but not supported by CPU\n");
+#else
+ printf(", but runtime SIMD detection disabled\n");
+#endif
+#endif
+
+#ifdef HAVE_SSE4_1
+ printf("Info: SSE4.1 support compiled in");
+#ifdef HAVE___BUILTIN_CPU_SUPPORTS
+ if (__builtin_cpu_supports("sse4.1"))
+ printf(" and supported by CPU\n");
+ else
+ printf(", but not supported by CPU\n");
+#else
+ printf(", but runtime SIMD detection disabled\n");
+#endif
+#endif
+
+ convolve_init();
+ convert_init();
+
+ osmo_init_logging2(tall_trx_ctx, &log_info);
+ osmo_stats_init(tall_trx_ctx);
+ vty_init(&g_vty_info);
+ ctrl_vty_init(tall_trx_ctx);
+ trx_vty_init(g_trx_ctx);
+
+ logging_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+
+ handle_options(argc, argv, g_trx_ctx);
+
+ rate_ctr_init(tall_trx_ctx);
+
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to open config file: '%s'\n", config_file);
+ exit(2);
+ }
+
+ rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX);
+ if (rc < 0)
+ exit(1);
+
+ g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_TRX, NULL);
+ if (!g_ctrlh) {
+ fprintf(stderr, "Failed to create CTRL interface.\n");
+ exit(1);
+ }
+
+ /* Backward compatibility: Hack to have 1 channel allocated by default.
+ * Can be Dropped once we * get rid of "-c" cmdline param */
+ if (g_trx_ctx->cfg.num_chans == 0) {
+ g_trx_ctx->cfg.num_chans = 1;
+ g_trx_ctx->cfg.chans[0].trx = g_trx_ctx;
+ g_trx_ctx->cfg.chans[0].idx = 0;
+ LOG(ERROR) << "No explicit channel config found. Make sure you" \
+ " configure channels in VTY config. Using 1 channel as default," \
+ " but expect your config to break in the future.";
+ }
+
+ print_config(g_trx_ctx);
+
+ if (trx_validate_config(g_trx_ctx) < 0) {
+ LOG(ERROR) << "Config failure - exiting";
+ return EXIT_FAILURE;
+ }
+
+ if (g_trx_ctx->cfg.sched_rr) {
+ if (set_sched_rr(g_trx_ctx->cfg.sched_rr) < 0)
+ return EXIT_FAILURE;
+ }
+
+ srandom(time(NULL));
+
+ if(trx_start(g_trx_ctx) < 0)
+ return EXIT_FAILURE;
+
+ while (!gshutdown)
+ osmo_select_main(0);
+
+ trx_stop();
+
+ return 0;
+}
diff --git a/Transceiver52M/pulseApproximate.m b/Transceiver52M/pulseApproximate.m
new file mode 100644
index 0000000..2ff9234
--- /dev/null
+++ b/Transceiver52M/pulseApproximate.m
@@ -0,0 +1,15 @@
+pp = [0 0 0.015 0.18 0.7 0.96 0.7 0.18 0.015 0 0];
+t = -2.5:0.5:2.5;
+
+v = -0.000:-0.001:-1.999;
+
+
+for ix1 = 1:length(v),
+ disp(ix1);
+ for ix2 = 1:length(v),
+ p = exp(v(ix1)*t.^2+v(ix2)*t.^4);
+ r(ix1,ix2) = norm(p./max(abs(p)) - pp./max(abs(pp)));
+ end;
+end;
+
+
diff --git a/Transceiver52M/radioBuffer.cpp b/Transceiver52M/radioBuffer.cpp
new file mode 100644
index 0000000..a2b42c4
--- /dev/null
+++ b/Transceiver52M/radioBuffer.cpp
@@ -0,0 +1,228 @@
+/*
+ * Segmented Ring Buffer
+ *
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * Author: Tom Tsou <tom@tsou.cc>
+ *
+ * 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 <string.h>
+#include <iostream>
+#include "radioBuffer.h"
+
+RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen,
+ size_t hLen, bool outDirection)
+ : writeIndex(0), readIndex(0), availSamples(0)
+{
+ if (!outDirection)
+ hLen = 0;
+
+ buffer = new float[2 * (hLen + numSegments * segmentLen)];
+ bufferLen = numSegments * segmentLen;
+
+ segments.resize(numSegments);
+
+ for (size_t i = 0; i < numSegments; i++)
+ segments[i] = &buffer[2 * (hLen + i * segmentLen)];
+
+ this->outDirection = outDirection;
+ this->numSegments = numSegments;
+ this->segmentLen = segmentLen;
+ this->hLen = hLen;
+}
+
+RadioBuffer::~RadioBuffer()
+{
+ delete[] buffer;
+}
+
+void RadioBuffer::reset()
+{
+ writeIndex = 0;
+ readIndex = 0;
+ availSamples = 0;
+}
+
+/*
+ * Output direction
+ *
+ * Return a pointer to the oldest segment or NULL if a complete segment is not
+ * available.
+ */
+const float *RadioBuffer::getReadSegment()
+{
+ if (!outDirection) {
+ std::cout << "Invalid direction" << std::endl;
+ return NULL;
+ }
+ if (availSamples < segmentLen) {
+ std::cout << "Not enough samples " << std::endl;
+ std::cout << availSamples << " available per segment "
+ << segmentLen << std::endl;
+ return NULL;
+ }
+
+ size_t num = readIndex / segmentLen;
+
+ if (num >= numSegments) {
+ std::cout << "Invalid segment" << std::endl;
+ return NULL;
+ } else if (!num) {
+ memcpy(buffer,
+ &buffer[2 * bufferLen],
+ hLen * 2 * sizeof(float));
+ }
+
+ availSamples -= segmentLen;
+ readIndex = (readIndex + segmentLen) % bufferLen;
+
+ return segments[num];
+}
+
+/*
+ * Output direction
+ *
+ * Write a non-segment length of samples to the buffer.
+ */
+bool RadioBuffer::write(const float *wr, size_t len)
+{
+ if (!outDirection) {
+ std::cout << "Invalid direction" << std::endl;
+ return false;
+ }
+ if (availSamples + len > bufferLen) {
+ std::cout << "Insufficient space" << std::endl;
+ std::cout << bufferLen - availSamples << " available per write "
+ << len << std::endl;
+ return false;
+ }
+
+ if (writeIndex + len <= bufferLen) {
+ memcpy(&buffer[2 * (writeIndex + hLen)],
+ wr, len * 2 * sizeof(float));
+ } else {
+ size_t len0 = bufferLen - writeIndex;
+ size_t len1 = len - len0;
+ memcpy(&buffer[2 * (writeIndex + hLen)], wr, len0 * 2 * sizeof(float));
+ memcpy(&buffer[2 * hLen], &wr[2 * len0], len1 * 2 * sizeof(float));
+ }
+
+ availSamples += len;
+ writeIndex = (writeIndex + len) % bufferLen;
+
+ return true;
+}
+
+bool RadioBuffer::zero(size_t len)
+{
+ if (!outDirection) {
+ std::cout << "Invalid direction" << std::endl;
+ return false;
+ }
+ if (availSamples + len > bufferLen) {
+ std::cout << "Insufficient space" << std::endl;
+ std::cout << bufferLen - availSamples << " available per zero "
+ << len << std::endl;
+ return false;
+ }
+
+ if (writeIndex + len <= bufferLen) {
+ memset(&buffer[2 * (writeIndex + hLen)],
+ 0, len * 2 * sizeof(float));
+ } else {
+ size_t len0 = bufferLen - writeIndex;
+ size_t len1 = len - len0;
+ memset(&buffer[2 * (writeIndex + hLen)], 0, len0 * 2 * sizeof(float));
+ memset(&buffer[2 * hLen], 0, len1 * 2 * sizeof(float));
+ }
+
+ availSamples += len;
+ writeIndex = (writeIndex + len) % bufferLen;
+
+ return true;
+}
+
+/*
+ * Input direction
+ */
+float *RadioBuffer::getWriteSegment()
+{
+ if (outDirection) {
+ std::cout << "Invalid direction" << std::endl;
+ return NULL;
+ }
+ if (bufferLen - availSamples < segmentLen) {
+ std::cout << "Insufficient samples" << std::endl;
+ std::cout << bufferLen - availSamples
+ << " available for segment " << segmentLen
+ << std::endl;
+ return NULL;
+ }
+ if (writeIndex % segmentLen) {
+ std::cout << "Internal segment error" << std::endl;
+ return NULL;
+ }
+
+ size_t num = writeIndex / segmentLen;
+
+ if (num >= numSegments)
+ return NULL;
+
+ availSamples += segmentLen;
+ writeIndex = (writeIndex + segmentLen) % bufferLen;
+
+ return segments[num];
+}
+
+bool RadioBuffer::zeroWriteSegment()
+{
+ float *segment = getWriteSegment();
+ if (!segment)
+ return false;
+
+ memset(segment, 0, segmentLen * 2 * sizeof(float));
+
+ return true;
+}
+
+bool RadioBuffer::read(float *rd, size_t len)
+{
+ if (outDirection) {
+ std::cout << "Invalid direction" << std::endl;
+ return false;
+ }
+ if (availSamples < len) {
+ std::cout << "Insufficient samples" << std::endl;
+ std::cout << availSamples << " available for "
+ << len << std::endl;
+ return false;
+ }
+
+ if (readIndex + len <= bufferLen) {
+ memcpy(rd, &buffer[2 * readIndex], len * 2 * sizeof(float));
+ } else {
+ size_t len0 = bufferLen - readIndex;
+ size_t len1 = len - len0;
+ memcpy(rd, &buffer[2 * readIndex], len0 * 2 * sizeof(float));
+ memcpy(&rd[2 * len0], buffer, len1 * 2 * sizeof(float));
+ }
+
+ availSamples -= len;
+ readIndex = (readIndex + len) % bufferLen;
+
+ return true;
+}
diff --git a/Transceiver52M/radioBuffer.h b/Transceiver52M/radioBuffer.h
new file mode 100644
index 0000000..e5aa315
--- /dev/null
+++ b/Transceiver52M/radioBuffer.h
@@ -0,0 +1,45 @@
+#include <stdlib.h>
+#include <stddef.h>
+#include <vector>
+
+class RadioBuffer {
+public:
+ RadioBuffer(size_t numSegments, size_t segmentLen,
+ size_t hLen, bool outDirection);
+
+ ~RadioBuffer();
+
+ const size_t getSegmentLen() { return segmentLen; }
+ const size_t getNumSegments() { return numSegments; }
+ const size_t getAvailSamples() { return availSamples; }
+ const size_t getAvailSegments() { return availSamples / segmentLen; }
+
+ const size_t getFreeSamples()
+ {
+ return bufferLen - availSamples;
+ }
+
+ const size_t getFreeSegments()
+ {
+ return getFreeSamples() / segmentLen;
+ }
+
+ void reset();
+
+ /* Output direction */
+ const float *getReadSegment();
+ bool write(const float *wr, size_t len);
+ bool zero(size_t len);
+
+ /* Input direction */
+ float *getWriteSegment();
+ bool zeroWriteSegment();
+ bool read(float *rd, size_t len);
+
+private:
+ size_t writeIndex, readIndex, availSamples;
+ size_t bufferLen, numSegments, segmentLen, hLen;
+ float *buffer;
+ std::vector<float *> segments;
+ bool outDirection;
+};
diff --git a/Transceiver52M/radioClock.cpp b/Transceiver52M/radioClock.cpp
new file mode 100644
index 0000000..505bb01
--- /dev/null
+++ b/Transceiver52M/radioClock.cpp
@@ -0,0 +1,49 @@
+/*
+ * Written by Thomas Tsou <ttsou@vt.edu>
+ * Based on code by Harvind S Samra <hssamra@kestrelsp.com>
+ *
+ * Copyright 2011 Free Software Foundation, Inc.
+ *
+ * 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 "radioClock.h"
+
+void RadioClock::set(const GSM::Time& wTime)
+{
+ ScopedLock lock(mLock);
+ mClock = wTime;
+ updateSignal.signal();
+}
+
+void RadioClock::incTN()
+{
+ ScopedLock lock(mLock);
+ mClock.incTN();
+ updateSignal.signal();
+}
+
+GSM::Time RadioClock::get()
+{
+ ScopedLock lock(mLock);
+ GSM::Time retVal = mClock;
+ return retVal;
+}
+
+void RadioClock::wait()
+{
+ ScopedLock lock(mLock);
+ updateSignal.wait(mLock,1);
+}
diff --git a/Transceiver52M/radioClock.h b/Transceiver52M/radioClock.h
new file mode 100644
index 0000000..9c35c44
--- /dev/null
+++ b/Transceiver52M/radioClock.h
@@ -0,0 +1,40 @@
+/*
+ * Written by Thomas Tsou <ttsou@vt.edu>
+ * Based on code by Harvind S Samra <hssamra@kestrelsp.com>
+ *
+ * Copyright 2011 Free Software Foundation, Inc.
+ *
+ * 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 RADIOCLOCK_H
+#define RADIOCLOCK_H
+
+#include "GSMCommon.h"
+
+class RadioClock {
+public:
+ void set(const GSM::Time& wTime);
+ void incTN();
+ GSM::Time get();
+ void wait();
+
+private:
+ GSM::Time mClock;
+ Mutex mLock;
+ Signal updateSignal;
+};
+
+#endif /* RADIOCLOCK_H */
diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp
new file mode 100644
index 0000000..6245cfc
--- /dev/null
+++ b/Transceiver52M/radioInterface.cpp
@@ -0,0 +1,360 @@
+/*
+ * Radio device interface
+ *
+ * Copyright (C) 2008-2014 Free Software Foundation, Inc.
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * 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 "radioInterface.h"
+#include "Resampler.h"
+#include <Logger.h>
+
+extern "C" {
+#include "convert.h"
+}
+
+#define CHUNK 625
+#define NUMCHUNKS 4
+
+RadioInterface::RadioInterface(RadioDevice *wRadio, size_t tx_sps,
+ size_t rx_sps, size_t chans,
+ int wReceiveOffset, GSM::Time wStartTime)
+ : mRadio(wRadio), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans),
+ underrun(false), overrun(false), receiveOffset(wReceiveOffset), mOn(false)
+{
+ mClock.set(wStartTime);
+}
+
+RadioInterface::~RadioInterface(void)
+{
+ close();
+}
+
+bool RadioInterface::init(int type)
+{
+ if ((type != RadioDevice::NORMAL) || !mChans) {
+ LOG(ALERT) << "Invalid configuration";
+ return false;
+ }
+
+ close();
+
+ sendBuffer.resize(mChans);
+ recvBuffer.resize(mChans);
+ convertSendBuffer.resize(mChans);
+ convertRecvBuffer.resize(mChans);
+ mReceiveFIFO.resize(mChans);
+ powerScaling.resize(mChans);
+
+ for (size_t i = 0; i < mChans; i++) {
+ sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true);
+ recvBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSRx, 0, false);
+
+ convertSendBuffer[i] = new short[CHUNK * mSPSTx * 2];
+ convertRecvBuffer[i] = new short[CHUNK * mSPSRx * 2];
+
+ powerScaling[i] = 1.0;
+ }
+
+ return true;
+}
+
+void RadioInterface::close()
+{
+ sendBuffer.resize(0);
+ recvBuffer.resize(0);
+ convertSendBuffer.resize(0);
+ convertRecvBuffer.resize(0);
+}
+
+double RadioInterface::fullScaleInputValue(void) {
+ return mRadio->fullScaleInputValue();
+}
+
+double RadioInterface::fullScaleOutputValue(void) {
+ return mRadio->fullScaleOutputValue();
+}
+
+int RadioInterface::setPowerAttenuation(int atten, size_t chan)
+{
+ double rfGain, digAtten;
+
+ if (chan >= mChans) {
+ LOG(ALERT) << "Invalid channel requested";
+ return -1;
+ }
+
+ if (atten < 0.0)
+ atten = 0.0;
+
+ rfGain = mRadio->setTxGain(mRadio->maxTxGain() - (double) atten, chan);
+ digAtten = (double) atten - mRadio->maxTxGain() + rfGain;
+
+ if (digAtten < 1.0)
+ powerScaling[chan] = 1.0;
+ else
+ powerScaling[chan] = 1.0 / sqrt(pow(10, digAtten / 10.0));
+
+ return atten;
+}
+
+int RadioInterface::radioifyVector(signalVector &wVector,
+ size_t chan, bool zero)
+{
+ if (zero)
+ sendBuffer[chan]->zero(wVector.size());
+ else
+ sendBuffer[chan]->write((float *) wVector.begin(), wVector.size());
+
+ return wVector.size();
+}
+
+int RadioInterface::unRadioifyVector(signalVector *newVector, size_t chan)
+{
+ if (newVector->size() > recvBuffer[chan]->getAvailSamples()) {
+ LOG(ALERT) << "Insufficient number of samples in receive buffer";
+ return -1;
+ }
+
+ recvBuffer[chan]->read((float *) newVector->begin(), newVector->size());
+
+ return newVector->size();
+}
+
+bool RadioInterface::tuneTx(double freq, size_t chan)
+{
+ return mRadio->setTxFreq(freq, chan);
+}
+
+bool RadioInterface::tuneRx(double freq, size_t chan)
+{
+ return mRadio->setRxFreq(freq, chan);
+}
+
+/** synchronization thread loop */
+void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
+{
+ set_selfthread_name("AlignRadio");
+ while (1) {
+ sleep(60);
+ radioInterface->alignRadio();
+ pthread_testcancel();
+ }
+ return NULL;
+}
+
+void RadioInterface::alignRadio() {
+ mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000);
+}
+
+bool RadioInterface::start()
+{
+ if (mOn)
+ return true;
+
+ LOG(INFO) << "Starting radio device";
+ if (mRadio->requiresRadioAlign())
+ mAlignRadioServiceLoopThread.start(
+ (void * (*)(void*))AlignRadioServiceLoopAdapter,
+ (void*)this);
+
+ if (!mRadio->start())
+ return false;
+
+ for (size_t i = 0; i < mChans; i++) {
+ sendBuffer[i]->reset();
+ recvBuffer[i]->reset();
+ }
+
+ writeTimestamp = mRadio->initialWriteTimestamp();
+ readTimestamp = mRadio->initialReadTimestamp();
+
+ mRadio->updateAlignment(writeTimestamp-10000);
+ mRadio->updateAlignment(writeTimestamp-10000);
+
+ mOn = true;
+ LOG(INFO) << "Radio started";
+ return true;
+}
+
+/*
+ * Stop the radio device
+ *
+ * This is a pass-through call to the device interface. Because the underlying
+ * stop command issuance generally doesn't return confirmation on device status,
+ * this call will only return false if the device is already stopped.
+ */
+bool RadioInterface::stop()
+{
+ if (!mOn || !mRadio->stop())
+ return false;
+
+ mOn = false;
+ return true;
+}
+
+void RadioInterface::driveTransmitRadio(std::vector<signalVector *> &bursts,
+ std::vector<bool> &zeros)
+{
+ if (!mOn)
+ return;
+
+ for (size_t i = 0; i < mChans; i++)
+ radioifyVector(*bursts[i], i, zeros[i]);
+
+ while (pushBuffer());
+}
+
+int RadioInterface::driveReceiveRadio()
+{
+ radioVector *burst = NULL;
+
+ if (!mOn)
+ return 0;
+
+ if (pullBuffer() < 0)
+ return -1;
+
+ GSM::Time rcvClock = mClock.get();
+ rcvClock.decTN(receiveOffset);
+ unsigned tN = rcvClock.TN();
+ int recvSz = recvBuffer[0]->getAvailSamples();
+ const int symbolsPerSlot = gSlotLen + 8;
+ int burstSize;
+
+ if (mSPSRx == 4)
+ burstSize = 625;
+ else
+ burstSize = symbolsPerSlot + (tN % 4 == 0);
+
+ /*
+ * Pre-allocate head room for the largest correlation size
+ * so we can later avoid a re-allocation and copy
+ * */
+ size_t head = GSM::gRACHSynchSequenceTS0.size();
+
+ /*
+ * Form receive bursts and pass up to transceiver. Use repeating
+ * pattern of 157-156-156-156 symbols per timeslot
+ */
+ while (recvSz > burstSize) {
+ for (size_t i = 0; i < mChans; i++) {
+ burst = new radioVector(rcvClock, burstSize, head);
+ unRadioifyVector(burst->getVector(), i);
+
+ if (mReceiveFIFO[i].size() < 32)
+ mReceiveFIFO[i].write(burst);
+ else
+ delete burst;
+ }
+
+ mClock.incTN();
+ rcvClock.incTN();
+ recvSz -= burstSize;
+
+ tN = rcvClock.TN();
+
+ if (mSPSRx != 4)
+ burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
+ }
+
+ return 1;
+}
+
+bool RadioInterface::isUnderrun()
+{
+ bool retVal = underrun;
+ underrun = false;
+
+ return retVal;
+}
+
+VectorFIFO* RadioInterface::receiveFIFO(size_t chan)
+{
+ if (chan >= mReceiveFIFO.size())
+ return NULL;
+
+ return &mReceiveFIFO[chan];
+}
+
+double RadioInterface::setRxGain(double dB, size_t chan)
+{
+ return mRadio->setRxGain(dB, chan);
+}
+
+double RadioInterface::getRxGain(size_t chan)
+{
+ return mRadio->getRxGain(chan);
+}
+
+/* Receive a timestamped chunk from the device */
+int RadioInterface::pullBuffer()
+{
+ bool local_underrun;
+ int numRecv;
+ size_t segmentLen = recvBuffer[0]->getSegmentLen();
+
+ if (recvBuffer[0]->getFreeSegments() <= 0)
+ return -1;
+
+ /* Outer buffer access size is fixed */
+ numRecv = mRadio->readSamples(convertRecvBuffer,
+ segmentLen,
+ &overrun,
+ readTimestamp,
+ &local_underrun);
+
+ if ((size_t) numRecv != segmentLen) {
+ LOG(ALERT) << "Receive error " << numRecv;
+ return -1;
+ }
+
+ for (size_t i = 0; i < mChans; i++) {
+ convert_short_float(recvBuffer[i]->getWriteSegment(),
+ convertRecvBuffer[i],
+ segmentLen * 2);
+ }
+
+ underrun |= local_underrun;
+ readTimestamp += numRecv;
+ return 0;
+}
+
+/* Send timestamped chunk to the device with arbitrary size */
+bool RadioInterface::pushBuffer()
+{
+ size_t numSent, segmentLen = sendBuffer[0]->getSegmentLen();
+
+ if (sendBuffer[0]->getAvailSegments() < 1)
+ return false;
+
+ for (size_t i = 0; i < mChans; i++) {
+ convert_float_short(convertSendBuffer[i],
+ (float *) sendBuffer[i]->getReadSegment(),
+ powerScaling[i],
+ segmentLen * 2);
+ }
+
+ /* Send the all samples in the send buffer */
+ numSent = mRadio->writeSamples(convertSendBuffer,
+ segmentLen,
+ &underrun,
+ writeTimestamp);
+ writeTimestamp += numSent;
+
+ return true;
+}
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
new file mode 100644
index 0000000..f19a8dc
--- /dev/null
+++ b/Transceiver52M/radioInterface.h
@@ -0,0 +1,188 @@
+/*
+* 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 use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+
+
+#include "sigProcLib.h"
+#include "GSMCommon.h"
+#include "LinkedLists.h"
+#include "radioDevice.h"
+#include "radioVector.h"
+#include "radioClock.h"
+#include "radioBuffer.h"
+#include "Resampler.h"
+#include "Channelizer.h"
+#include "Synthesis.h"
+
+static const unsigned gSlotLen = 148; ///< number of symbols per slot, not counting guard periods
+
+/** class to interface the transceiver with the USRP */
+class RadioInterface {
+
+protected:
+
+ Thread mAlignRadioServiceLoopThread; ///< thread that synchronizes transmit and receive sections
+
+ std::vector<VectorFIFO> mReceiveFIFO; ///< FIFO that holds receive bursts
+
+ RadioDevice *mRadio; ///< the USRP object
+
+ size_t mSPSTx;
+ size_t mSPSRx;
+ size_t mChans;
+
+ std::vector<RadioBuffer *> sendBuffer;
+ std::vector<RadioBuffer *> recvBuffer;
+
+ std::vector<short *> convertRecvBuffer;
+ std::vector<short *> convertSendBuffer;
+ std::vector<float> powerScaling;
+ bool underrun; ///< indicates writes to USRP are too slow
+ bool overrun; ///< indicates reads from USRP are too slow
+ TIMESTAMP writeTimestamp; ///< sample timestamp of next packet written to USRP
+ TIMESTAMP readTimestamp; ///< sample timestamp of next packet read from USRP
+
+ RadioClock mClock; ///< the basestation clock!
+
+ int receiveOffset; ///< offset b/w transmit and receive GSM timestamps, in timeslots
+
+ bool mOn; ///< indicates radio is on
+
+private:
+
+ /** format samples to USRP */
+ int radioifyVector(signalVector &wVector, size_t chan, bool zero);
+
+ /** format samples from USRP */
+ int unRadioifyVector(signalVector *wVector, size_t chan);
+
+ /** push GSM bursts into the transmit buffer */
+ virtual bool pushBuffer(void);
+
+ /** pull GSM bursts from the receive buffer */
+ virtual int pullBuffer(void);
+
+public:
+
+ /** start the interface */
+ bool start();
+ bool stop();
+
+ /** intialization */
+ virtual bool init(int type);
+ virtual void close();
+
+ /** constructor */
+ RadioInterface(RadioDevice* wRadio, size_t tx_sps, size_t rx_sps,
+ size_t chans = 1, int receiveOffset = 3,
+ GSM::Time wStartTime = GSM::Time(0));
+
+ /** destructor */
+ virtual ~RadioInterface();
+
+ /** check for underrun, resets underrun value */
+ bool isUnderrun();
+
+ /** return the receive FIFO */
+ VectorFIFO* receiveFIFO(size_t chan = 0);
+
+ /** return the basestation clock */
+ RadioClock* getClock(void) { return &mClock;};
+
+ /** set transmit frequency */
+ virtual bool tuneTx(double freq, size_t chan = 0);
+
+ /** set receive frequency */
+ virtual bool tuneRx(double freq, size_t chan = 0);
+
+ /** set receive gain */
+ double setRxGain(double dB, size_t chan = 0);
+
+ /** get receive gain */
+ double getRxGain(size_t chan = 0);
+
+ /** drive transmission of GSM bursts */
+ void driveTransmitRadio(std::vector<signalVector *> &bursts,
+ std::vector<bool> &zeros);
+
+ /** drive reception of GSM bursts. -1: Error. 0: Radio off. 1: Received something. */
+ int driveReceiveRadio();
+
+ int setPowerAttenuation(int atten, size_t chan = 0);
+
+ /** returns the full-scale transmit amplitude **/
+ double fullScaleInputValue();
+
+ /** returns the full-scale receive amplitude **/
+ double fullScaleOutputValue();
+
+ /** set thread priority on current thread */
+ void setPriority(float prio = 0.5) { mRadio->setPriority(prio); }
+
+ /** get transport window type of attached device */
+ enum RadioDevice::TxWindowType getWindowType() { return mRadio->getWindowType(); }
+
+ /** Minimum latency that the device can achieve */
+ GSM::Time minLatency() { return mRadio->minLatency(); }
+
+protected:
+ /** drive synchronization of Tx/Rx of USRP */
+ void alignRadio();
+
+ friend void *AlignRadioServiceLoopAdapter(RadioInterface*);
+};
+
+class RadioInterfaceResamp : public RadioInterface {
+private:
+ signalVector *outerSendBuffer;
+ signalVector *outerRecvBuffer;
+
+ bool pushBuffer();
+ int pullBuffer();
+
+public:
+ RadioInterfaceResamp(RadioDevice* wRadio, size_t tx_sps, size_t rx_sps);
+ ~RadioInterfaceResamp();
+
+ bool init(int type);
+ void close();
+};
+
+class RadioInterfaceMulti : public RadioInterface {
+private:
+ bool pushBuffer();
+ int pullBuffer();
+
+ signalVector *outerSendBuffer;
+ signalVector *outerRecvBuffer;
+ std::vector<signalVector *> history;
+ std::vector<bool> active;
+
+ Resampler *dnsampler;
+ Resampler *upsampler;
+ Channelizer *channelizer;
+ Synthesis *synthesis;
+
+public:
+ RadioInterfaceMulti(RadioDevice* radio, size_t tx_sps,
+ size_t rx_sps, size_t chans = 1);
+ ~RadioInterfaceMulti();
+
+ bool init(int type);
+ void close();
+
+ bool tuneTx(double freq, size_t chan);
+ bool tuneRx(double freq, size_t chan);
+ double setRxGain(double dB, size_t chan);
+};
diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp
new file mode 100644
index 0000000..17a015b
--- /dev/null
+++ b/Transceiver52M/radioInterfaceMulti.cpp
@@ -0,0 +1,405 @@
+/*
+ * Multi-carrier radio interface
+ *
+ * Copyright (C) 2016 Ettus Research LLC
+ *
+ * Author: Tom Tsou <tom.tsou@ettus.com>
+ *
+ * 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 <radioInterface.h>
+#include <Logger.h>
+
+#include "Resampler.h"
+
+extern "C" {
+#include "convert.h"
+}
+
+/* Resampling parameters for 64 MHz clocking */
+#define RESAMP_INRATE 65
+#define RESAMP_OUTRATE (96 / 2)
+
+/* Universal resampling parameters */
+#define NUMCHUNKS 24
+
+#define MCHANS 4
+
+RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *radio, size_t tx_sps,
+ size_t rx_sps, size_t chans)
+ : RadioInterface(radio, tx_sps, rx_sps, chans),
+ outerSendBuffer(NULL), outerRecvBuffer(NULL),
+ dnsampler(NULL), upsampler(NULL), channelizer(NULL), synthesis(NULL)
+{
+}
+
+RadioInterfaceMulti::~RadioInterfaceMulti()
+{
+ close();
+}
+
+void RadioInterfaceMulti::close()
+{
+ delete outerSendBuffer;
+ delete outerRecvBuffer;
+ delete dnsampler;
+ delete upsampler;
+ delete channelizer;
+ delete synthesis;
+
+ outerSendBuffer = NULL;
+ outerRecvBuffer = NULL;
+ dnsampler = NULL;
+ upsampler = NULL;
+ channelizer = NULL;
+ synthesis = NULL;
+
+ mReceiveFIFO.resize(0);
+ powerScaling.resize(0);
+ history.resize(0);
+ active.resize(0);
+
+ RadioInterface::close();
+}
+
+static int getLogicalChan(size_t pchan, size_t chans)
+{
+ switch (chans) {
+ case 1:
+ if (pchan == 0)
+ return 0;
+ else
+ return -1;
+ break;
+ case 2:
+ if (pchan == 0)
+ return 0;
+ if (pchan == 3)
+ return 1;
+ else
+ return -1;
+ break;
+ case 3:
+ if (pchan == 1)
+ return 0;
+ if (pchan == 0)
+ return 1;
+ if (pchan == 3)
+ return 2;
+ else
+ return -1;
+ break;
+ default:
+ break;
+ };
+
+ return -1;
+}
+
+static int getFreqShift(size_t chans)
+{
+ switch (chans) {
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 3:
+ return 1;
+ default:
+ break;
+ };
+
+ return -1;
+}
+
+/* Initialize I/O specific objects */
+bool RadioInterfaceMulti::init(int type)
+{
+ float cutoff = 1.0f;
+ size_t inchunk = 0, outchunk = 0;
+
+ if (mChans > MCHANS - 1) {
+ LOG(ALERT) << "Invalid channel configuration " << mChans;
+ return false;
+ }
+
+ close();
+
+ sendBuffer.resize(mChans);
+ recvBuffer.resize(mChans);
+ convertSendBuffer.resize(1);
+ convertRecvBuffer.resize(1);
+
+ mReceiveFIFO.resize(mChans);
+ powerScaling.resize(mChans);
+ history.resize(mChans);
+ active.resize(MCHANS, false);
+
+ inchunk = RESAMP_INRATE * 4;
+ outchunk = RESAMP_OUTRATE * 4;
+
+ if (inchunk * NUMCHUNKS < 625 * 2) {
+ LOG(ALERT) << "Invalid inner chunk size " << inchunk;
+ return false;
+ }
+
+ dnsampler = new Resampler(RESAMP_INRATE, RESAMP_OUTRATE);
+ if (!dnsampler->init(1.0)) {
+ LOG(ALERT) << "Rx resampler failed to initialize";
+ return false;
+ }
+
+ upsampler = new Resampler(RESAMP_OUTRATE, RESAMP_INRATE);
+ if (!upsampler->init(cutoff)) {
+ LOG(ALERT) << "Tx resampler failed to initialize";
+ return false;
+ }
+
+ channelizer = new Channelizer(MCHANS, outchunk);
+ if (!channelizer->init()) {
+ LOG(ALERT) << "Rx channelizer failed to initialize";
+ return false;
+ }
+
+ synthesis = new Synthesis(MCHANS, outchunk);
+ if (!synthesis->init()) {
+ LOG(ALERT) << "Tx synthesis filter failed to initialize";
+ return false;
+ }
+
+ /*
+ * Allocate high and low rate buffers. The high rate receive
+ * buffer and low rate transmit vectors feed into the resampler
+ * and requires headroom equivalent to the filter length. Low
+ * rate buffers are allocated in the main radio interface code.
+ */
+ for (size_t i = 0; i < mChans; i++) {
+ sendBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk,
+ upsampler->len(), true);
+ recvBuffer[i] = new RadioBuffer(NUMCHUNKS, inchunk,
+ 0, false);
+ history[i] = new signalVector(dnsampler->len());
+
+ synthesis->resetBuffer(i);
+ }
+
+ outerSendBuffer = new signalVector(synthesis->outputLen());
+ outerRecvBuffer = new signalVector(channelizer->inputLen());
+
+ convertSendBuffer[0] = new short[2 * synthesis->outputLen()];
+ convertRecvBuffer[0] = new short[2 * channelizer->inputLen()];
+
+ /* Configure channels */
+ switch (mChans) {
+ case 1:
+ active[0] = true;
+ break;
+ case 2:
+ active[0] = true;
+ active[3] = true;
+ break;
+ case 3:
+ active[0] = true;
+ active[1] = true;
+ active[3] = true;
+ break;
+ default:
+ LOG(ALERT) << "Unsupported channel combination";
+ return false;
+ }
+
+ return true;
+}
+
+/* Receive a timestamped chunk from the device */
+int RadioInterfaceMulti::pullBuffer()
+{
+ bool local_underrun;
+ size_t num;
+ float *buf;
+ unsigned int i;
+
+ if (recvBuffer[0]->getFreeSegments() <= 0)
+ return -1;
+
+ /* Outer buffer access size is fixed */
+ num = mRadio->readSamples(convertRecvBuffer,
+ outerRecvBuffer->size(),
+ &overrun,
+ readTimestamp,
+ &local_underrun);
+ if (num != channelizer->inputLen()) {
+ LOG(ALERT) << "Receive error " << num << ", " << channelizer->inputLen();
+ return -1;
+ }
+
+ convert_short_float((float *) outerRecvBuffer->begin(),
+ convertRecvBuffer[0], 2 * outerRecvBuffer->size());
+
+ underrun |= local_underrun;
+ readTimestamp += num;
+
+ channelizer->rotate((float *) outerRecvBuffer->begin(),
+ outerRecvBuffer->size());
+
+ for (size_t pchan = 0; pchan < MCHANS; pchan++) {
+ if (!active[pchan])
+ continue;
+
+ int lchan = getLogicalChan(pchan, mChans);
+ if (lchan < 0) {
+ LOG(ALERT) << "Invalid logical channel " << pchan;
+ continue;
+ }
+
+ /*
+ * Update history by writing into the head portion of the
+ * channelizer output buffer. For this to work, filter length of
+ * the polyphase channelizer partition filter should be equal to
+ * or larger than the resampling filter.
+ */
+ buf = channelizer->outputBuffer(pchan);
+ size_t cLen = channelizer->outputLen();
+ size_t hLen = dnsampler->len();
+
+ float *fdst = &buf[2 * -hLen];
+ complex *src = history[lchan]->begin();
+ for (i = 0; i < hLen; i++) {
+ fdst[0] = src->real();
+ fdst[1] = src->imag();
+ src++;
+ fdst += 2;
+ }
+ complex *dst = history[lchan]->begin();
+ float *fsrc = &buf[2 * (cLen - hLen)];
+ for (i = 0; i < hLen; i++) {
+ *dst = complex(fdst[0], fdst[1]);
+ fsrc += 2;
+ dst++;
+ }
+
+ float *wr_segment = recvBuffer[lchan]->getWriteSegment();
+
+ /* Write to the end of the inner receive buffer */
+ if (!dnsampler->rotate(channelizer->outputBuffer(pchan),
+ channelizer->outputLen(),
+ wr_segment,
+ recvBuffer[lchan]->getSegmentLen())) {
+ LOG(ALERT) << "Sample rate upsampling error";
+ }
+ }
+ return 0;
+}
+
+/* Send a timestamped chunk to the device */
+bool RadioInterfaceMulti::pushBuffer()
+{
+ if (sendBuffer[0]->getAvailSegments() <= 0)
+ return false;
+
+ for (size_t pchan = 0; pchan < MCHANS; pchan++) {
+ if (!active[pchan]) {
+ synthesis->resetBuffer(pchan);
+ continue;
+ }
+
+ int lchan = getLogicalChan(pchan, mChans);
+ if (lchan < 0) {
+ LOG(ALERT) << "Invalid logical channel " << pchan;
+ continue;
+ }
+
+ if (!upsampler->rotate(sendBuffer[lchan]->getReadSegment(),
+ sendBuffer[lchan]->getSegmentLen(),
+ synthesis->inputBuffer(pchan),
+ synthesis->inputLen())) {
+ LOG(ALERT) << "Sample rate downsampling error";
+ }
+ }
+
+ synthesis->rotate((float *) outerSendBuffer->begin(),
+ outerSendBuffer->size());
+
+ convert_float_short(convertSendBuffer[0],
+ (float *) outerSendBuffer->begin(),
+ 1.0 / (float) mChans, 2 * outerSendBuffer->size());
+
+ size_t num = mRadio->writeSamples(convertSendBuffer,
+ outerSendBuffer->size(),
+ &underrun,
+ writeTimestamp);
+ if (num != outerSendBuffer->size()) {
+ LOG(ALERT) << "Transmit error " << num;
+ }
+
+ writeTimestamp += num;
+
+ return true;
+}
+
+/* Frequency comparison limit */
+#define FREQ_DELTA_LIMIT 10.0
+
+static bool fltcmp(double a, double b)
+{
+ return fabs(a - b) < FREQ_DELTA_LIMIT ? true : false;
+}
+
+bool RadioInterfaceMulti::tuneTx(double freq, size_t chan)
+{
+ if (chan >= mChans)
+ return false;
+
+ double shift = (double) getFreqShift(mChans);
+
+ if (!chan)
+ return mRadio->setTxFreq(freq + shift * MCBTS_SPACING);
+
+ double center = mRadio->getTxFreq();
+ if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING)) {
+ LOG(NOTICE) << "Channel " << chan << " RF frequency offset is "
+ << freq / 1e6 << " MHz";
+ }
+
+ return true;
+}
+
+bool RadioInterfaceMulti::tuneRx(double freq, size_t chan)
+{
+ if (chan >= mChans)
+ return false;
+
+ double shift = (double) getFreqShift(mChans);
+
+ if (!chan)
+ return mRadio->setRxFreq(freq + shift * MCBTS_SPACING);
+
+ double center = mRadio->getRxFreq();
+ if (!fltcmp(freq, center + (double) (chan - shift) * MCBTS_SPACING)) {
+ LOG(NOTICE) << "Channel " << chan << " RF frequency offset is "
+ << freq / 1e6 << " MHz";
+ }
+
+ return true;
+}
+
+double RadioInterfaceMulti::setRxGain(double db, size_t chan)
+{
+ if (!chan)
+ return mRadio->setRxGain(db);
+ else
+ return mRadio->getRxGain();
+}
diff --git a/Transceiver52M/radioInterfaceResamp.cpp b/Transceiver52M/radioInterfaceResamp.cpp
new file mode 100644
index 0000000..8ae4aa1
--- /dev/null
+++ b/Transceiver52M/radioInterfaceResamp.cpp
@@ -0,0 +1,235 @@
+/*
+ * Radio device interface with sample rate conversion
+ *
+ * Copyright (C) 2011-2014 Free Software Foundation, Inc.
+ * Copyright (C) 2015 Ettus Research LLC
+ *
+ * Author: Tom Tsou <tom@tsou.cc>
+ *
+ * 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 <radioInterface.h>
+#include <Logger.h>
+
+#include "Resampler.h"
+
+extern "C" {
+#include "convert.h"
+}
+
+/* Resampling parameters for 64 MHz clocking */
+#define RESAMP_64M_INRATE 65
+#define RESAMP_64M_OUTRATE 96
+
+/* Resampling parameters for 100 MHz clocking */
+#define RESAMP_100M_INRATE 52
+#define RESAMP_100M_OUTRATE 75
+
+/* Universal resampling parameters */
+#define NUMCHUNKS 24
+
+/*
+ * Resampling filter bandwidth scaling factor
+ * This narrows the filter cutoff relative to the output bandwidth
+ * of the polyphase resampler. At 4 samples-per-symbol using the
+ * 2 pulse Laurent GMSK approximation gives us below 0.5 degrees
+ * RMS phase error at the resampler output.
+ */
+#define RESAMP_TX4_FILTER 0.45
+
+static Resampler *upsampler = NULL;
+static Resampler *dnsampler = NULL;
+static size_t resamp_inrate = 0;
+static size_t resamp_inchunk = 0;
+static size_t resamp_outrate = 0;
+static size_t resamp_outchunk = 0;
+
+RadioInterfaceResamp::RadioInterfaceResamp(RadioDevice *wRadio,
+ size_t tx_sps, size_t rx_sps)
+ : RadioInterface(wRadio, tx_sps, rx_sps, 1),
+ outerSendBuffer(NULL), outerRecvBuffer(NULL)
+{
+}
+
+RadioInterfaceResamp::~RadioInterfaceResamp()
+{
+ close();
+}
+
+void RadioInterfaceResamp::close()
+{
+ delete outerSendBuffer;
+ delete outerRecvBuffer;
+
+ delete upsampler;
+ delete dnsampler;
+
+ outerSendBuffer = NULL;
+ outerRecvBuffer = NULL;
+
+ upsampler = NULL;
+ dnsampler = NULL;
+
+ if (sendBuffer.size())
+ sendBuffer[0] = NULL;
+ if (recvBuffer.size())
+ recvBuffer[0] = NULL;
+
+ RadioInterface::close();
+}
+
+/* Initialize I/O specific objects */
+bool RadioInterfaceResamp::init(int type)
+{
+ float cutoff = 1.0f;
+
+ close();
+
+ sendBuffer.resize(1);
+ recvBuffer.resize(1);
+ convertSendBuffer.resize(1);
+ convertRecvBuffer.resize(1);
+ mReceiveFIFO.resize(1);
+ powerScaling.resize(1);
+
+ switch (type) {
+ case RadioDevice::RESAMP_64M:
+ resamp_inrate = RESAMP_64M_INRATE;
+ resamp_outrate = RESAMP_64M_OUTRATE;
+ break;
+ case RadioDevice::RESAMP_100M:
+ resamp_inrate = RESAMP_100M_INRATE;
+ resamp_outrate = RESAMP_100M_OUTRATE;
+ break;
+ case RadioDevice::NORMAL:
+ default:
+ LOG(ALERT) << "Invalid device configuration";
+ return false;
+ }
+
+ resamp_inchunk = resamp_inrate * 4 * mSPSRx;
+ resamp_outchunk = resamp_outrate * 4 * mSPSRx;
+
+ if (mSPSTx == 4)
+ cutoff = RESAMP_TX4_FILTER;
+
+ dnsampler = new Resampler(resamp_inrate, resamp_outrate);
+ if (!dnsampler->init()) {
+ LOG(ALERT) << "Rx resampler failed to initialize";
+ return false;
+ }
+
+ upsampler = new Resampler(resamp_outrate, resamp_inrate);
+ if (!upsampler->init(cutoff)) {
+ LOG(ALERT) << "Tx resampler failed to initialize";
+ return false;
+ }
+
+ /*
+ * Allocate high and low rate buffers. The high rate receive
+ * buffer and low rate transmit vectors feed into the resampler
+ * and requires headroom equivalent to the filter length. Low
+ * rate buffers are allocated in the main radio interface code.
+ */
+ sendBuffer[0] = new RadioBuffer(NUMCHUNKS, resamp_inchunk,
+ upsampler->len(), true);
+ recvBuffer[0] = new RadioBuffer(NUMCHUNKS * 20, resamp_inchunk, 0, false);
+
+ outerSendBuffer =
+ new signalVector(NUMCHUNKS * resamp_outchunk);
+ outerRecvBuffer =
+ new signalVector(resamp_outchunk, dnsampler->len());
+
+ convertSendBuffer[0] = new short[outerSendBuffer->size() * 2];
+ convertRecvBuffer[0] = new short[outerRecvBuffer->size() * 2];
+
+ return true;
+}
+
+/* Receive a timestamped chunk from the device */
+int RadioInterfaceResamp::pullBuffer()
+{
+ bool local_underrun;
+ int rc, num_recv;
+
+ if (recvBuffer[0]->getFreeSegments() <= 0)
+ return -1;
+
+ /* Outer buffer access size is fixed */
+ num_recv = mRadio->readSamples(convertRecvBuffer,
+ resamp_outchunk,
+ &overrun,
+ readTimestamp,
+ &local_underrun);
+ if (num_recv != (int) resamp_outchunk) {
+ LOG(ALERT) << "Receive error " << num_recv;
+ return -1;
+ }
+
+ convert_short_float((float *) outerRecvBuffer->begin(),
+ convertRecvBuffer[0], 2 * resamp_outchunk);
+
+ underrun |= local_underrun;
+ readTimestamp += (TIMESTAMP) resamp_outchunk;
+
+ /* Write to the end of the inner receive buffer */
+ rc = dnsampler->rotate((float *) outerRecvBuffer->begin(),
+ resamp_outchunk,
+ recvBuffer[0]->getWriteSegment(),
+ resamp_inchunk);
+ if (rc < 0) {
+ LOG(ALERT) << "Sample rate upsampling error";
+ }
+
+ /* Set history for the next chunk */
+ outerRecvBuffer->updateHistory();
+ return 0;
+}
+
+/* Send a timestamped chunk to the device */
+bool RadioInterfaceResamp::pushBuffer()
+{
+ int rc;
+ size_t numSent;
+
+ if (sendBuffer[0]->getAvailSegments() <= 0)
+ return false;
+
+ /* Always send from the beginning of the buffer */
+ rc = upsampler->rotate(sendBuffer[0]->getReadSegment(),
+ resamp_inchunk,
+ (float *) outerSendBuffer->begin(),
+ resamp_outchunk);
+ if (rc < 0) {
+ LOG(ALERT) << "Sample rate downsampling error";
+ }
+
+ convert_float_short(convertSendBuffer[0],
+ (float *) outerSendBuffer->begin(),
+ powerScaling[0], 2 * resamp_outchunk);
+
+ numSent = mRadio->writeSamples(convertSendBuffer,
+ resamp_outchunk,
+ &underrun,
+ writeTimestamp);
+ if (numSent != resamp_outchunk) {
+ LOG(ALERT) << "Transmit error " << numSent;
+ }
+
+ writeTimestamp += resamp_outchunk;
+
+ return true;
+}
diff --git a/Transceiver52M/radioVector.cpp b/Transceiver52M/radioVector.cpp
new file mode 100644
index 0000000..2e3af9d
--- /dev/null
+++ b/Transceiver52M/radioVector.cpp
@@ -0,0 +1,155 @@
+/*
+ * Written by Thomas Tsou <ttsou@vt.edu>
+ * Based on code by Harvind S Samra <hssamra@kestrelsp.com>
+ *
+ * Copyright 2011 Free Software Foundation, Inc.
+ *
+ * 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 "radioVector.h"
+
+radioVector::radioVector(GSM::Time &time, size_t size,
+ size_t start, size_t chans)
+ : vectors(chans), mTime(time)
+{
+ for (size_t i = 0; i < vectors.size(); i++)
+ vectors[i] = new signalVector(size, start);
+}
+
+radioVector::radioVector(GSM::Time& wTime, signalVector *vector)
+ : vectors(1), mTime(wTime)
+{
+ vectors[0] = vector;
+}
+
+radioVector::~radioVector()
+{
+ for (size_t i = 0; i < vectors.size(); i++)
+ delete vectors[i];
+}
+
+GSM::Time radioVector::getTime() const
+{
+ return mTime;
+}
+
+void radioVector::setTime(const GSM::Time& wTime)
+{
+ mTime = wTime;
+}
+
+bool radioVector::operator>(const radioVector& other) const
+{
+ return mTime > other.mTime;
+}
+
+signalVector *radioVector::getVector(size_t chan) const
+{
+ if (chan >= vectors.size())
+ return NULL;
+
+ return vectors[chan];
+}
+
+bool radioVector::setVector(signalVector *vector, size_t chan)
+{
+ if (chan >= vectors.size())
+ return false;
+
+ vectors[chan] = vector;
+
+ return true;
+}
+
+noiseVector::noiseVector(size_t size)
+ : std::vector<float>(size), itr(0)
+{
+}
+
+float noiseVector::avg() const
+{
+ float val = 0.0;
+
+ for (size_t i = 0; i < size(); i++)
+ val += (*this)[i];
+
+ return val / (float) size();
+}
+
+bool noiseVector::insert(float val)
+{
+ if (!size())
+ return false;
+
+ if (itr >= this->size())
+ itr = 0;
+
+ (*this)[itr++] = val;
+
+ return true;
+}
+
+GSM::Time VectorQueue::nextTime() const
+{
+ GSM::Time retVal;
+ mLock.lock();
+
+ while (mQ.size()==0)
+ mWriteSignal.wait(mLock);
+
+ retVal = mQ.top()->getTime();
+ mLock.unlock();
+
+ return retVal;
+}
+
+radioVector* VectorQueue::getStaleBurst(const GSM::Time& targTime)
+{
+ mLock.lock();
+ if ((mQ.size()==0)) {
+ mLock.unlock();
+ return NULL;
+ }
+
+ if (mQ.top()->getTime() < targTime) {
+ radioVector* retVal = mQ.top();
+ mQ.pop();
+ mLock.unlock();
+ return retVal;
+ }
+ mLock.unlock();
+
+ return NULL;
+}
+
+radioVector* VectorQueue::getCurrentBurst(const GSM::Time& targTime)
+{
+ mLock.lock();
+ if ((mQ.size()==0)) {
+ mLock.unlock();
+ return NULL;
+ }
+
+ if (mQ.top()->getTime() == targTime) {
+ radioVector* retVal = mQ.top();
+ mQ.pop();
+ mLock.unlock();
+ return retVal;
+ }
+ mLock.unlock();
+
+ return NULL;
+}
diff --git a/Transceiver52M/radioVector.h b/Transceiver52M/radioVector.h
new file mode 100644
index 0000000..0566123
--- /dev/null
+++ b/Transceiver52M/radioVector.h
@@ -0,0 +1,68 @@
+/*
+ * Written by Thomas Tsou <ttsou@vt.edu>
+ * Based on code by Harvind S Samra <hssamra@kestrelsp.com>
+ *
+ * Copyright 2011 Free Software Foundation, Inc.
+ *
+ * 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 RADIOVECTOR_H
+#define RADIOVECTOR_H
+
+#include "sigProcLib.h"
+#include "GSMCommon.h"
+#include "Interthread.h"
+
+class radioVector {
+public:
+ radioVector(GSM::Time& wTime, size_t size = 0,
+ size_t start = 0, size_t chans = 1);
+
+ radioVector(GSM::Time& wTime, signalVector *vector);
+ ~radioVector();
+
+ GSM::Time getTime() const;
+ void setTime(const GSM::Time& wTime);
+ bool operator>(const radioVector& other) const;
+
+ signalVector *getVector(size_t chan = 0) const;
+ bool setVector(signalVector *vector, size_t chan = 0);
+ size_t chans() const { return vectors.size(); }
+private:
+ std::vector<signalVector *> vectors;
+ GSM::Time mTime;
+};
+
+class noiseVector : std::vector<float> {
+public:
+ noiseVector(size_t size = 0);
+ bool insert(float val);
+ float avg() const;
+
+private:
+ size_t itr;
+};
+
+class VectorFIFO : public InterthreadQueue<radioVector> { };
+
+class VectorQueue : public InterthreadPriorityQueue<radioVector> {
+public:
+ GSM::Time nextTime() const;
+ radioVector* getStaleBurst(const GSM::Time& targTime);
+ radioVector* getCurrentBurst(const GSM::Time& targTime);
+};
+
+#endif /* RADIOVECTOR_H */
diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp
new file mode 100644
index 0000000..28c4ded
--- /dev/null
+++ b/Transceiver52M/sigProcLib.cpp
@@ -0,0 +1,1892 @@
+/*
+* Copyright 2008, 2011 Free Software Foundation, Inc.
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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/>.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sigProcLib.h"
+#include "GSMCommon.h"
+#include "Logger.h"
+#include "Resampler.h"
+
+extern "C" {
+#include "convolve.h"
+#include "scale.h"
+#include "mult.h"
+}
+
+using namespace GSM;
+
+#define TABLESIZE 1024
+#define DELAYFILTS 64
+
+/* Clipping detection threshold */
+#define CLIP_THRESH 30000.0f
+
+/** Lookup tables for trigonometric approximation */
+static float sincTable[TABLESIZE+1]; // add 1 element for wrap around
+
+/** Constants */
+static const float M_PI_F = (float)M_PI;
+
+/* Precomputed rotation vectors */
+static signalVector *GMSKRotation4 = NULL;
+static signalVector *GMSKReverseRotation4 = NULL;
+static signalVector *GMSKRotation1 = NULL;
+static signalVector *GMSKReverseRotation1 = NULL;
+
+/* Precomputed fractional delay filters */
+static signalVector *delayFilters[DELAYFILTS];
+
+static const Complex<float> psk8_table[8] = {
+ Complex<float>(-0.70710678, 0.70710678),
+ Complex<float>( 0.0, -1.0),
+ Complex<float>( 0.0, 1.0),
+ Complex<float>( 0.70710678, -0.70710678),
+ Complex<float>(-1.0, 0.0),
+ Complex<float>(-0.70710678, -0.70710678),
+ Complex<float>( 0.70710678, 0.70710678),
+ Complex<float>( 1.0, 0.0),
+};
+
+/* Downsampling filterbank - 4 SPS to 1 SPS */
+#define DOWNSAMPLE_IN_LEN 624
+#define DOWNSAMPLE_OUT_LEN 156
+
+static Resampler *dnsampler = NULL;
+
+/*
+ * RACH and midamble correlation waveforms. Store the buffer separately
+ * because we need to allocate it explicitly outside of the signal vector
+ * constructor. This is because C++ (prior to C++11) is unable to natively
+ * perform 16-byte memory alignment required by many SSE instructions.
+ */
+struct CorrelationSequence {
+ CorrelationSequence() : sequence(NULL), buffer(NULL)
+ {
+ }
+
+ ~CorrelationSequence()
+ {
+ delete sequence;
+ free(buffer);
+ }
+
+ signalVector *sequence;
+ void *buffer;
+ float toa;
+ complex gain;
+};
+
+/*
+ * Gaussian and empty modulation pulses. Like the correlation sequences,
+ * store the runtime (Gaussian) buffer separately because of needed alignment
+ * for SSE instructions.
+ */
+struct PulseSequence {
+ PulseSequence() : c0(NULL), c1(NULL), c0_inv(NULL), empty(NULL),
+ c0_buffer(NULL), c1_buffer(NULL), c0_inv_buffer(NULL)
+ {
+ }
+
+ ~PulseSequence()
+ {
+ delete c0;
+ delete c1;
+ delete c0_inv;
+ delete empty;
+ free(c0_buffer);
+ free(c1_buffer);
+ }
+
+ signalVector *c0;
+ signalVector *c1;
+ signalVector *c0_inv;
+ signalVector *empty;
+ void *c0_buffer;
+ void *c1_buffer;
+ void *c0_inv_buffer;
+};
+
+static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
+static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
+static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL};
+static PulseSequence *GSMPulse1 = NULL;
+static PulseSequence *GSMPulse4 = NULL;
+
+void sigProcLibDestroy()
+{
+ for (int i = 0; i < 8; i++) {
+ delete gMidambles[i];
+ delete gEdgeMidambles[i];
+ gMidambles[i] = NULL;
+ gEdgeMidambles[i] = NULL;
+ }
+
+ for (int i = 0; i < DELAYFILTS; i++) {
+ delete delayFilters[i];
+ delayFilters[i] = NULL;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ delete gRACHSequences[i];
+ gRACHSequences[i] = NULL;
+ }
+
+ delete GMSKRotation1;
+ delete GMSKReverseRotation1;
+ delete GMSKRotation4;
+ delete GMSKReverseRotation4;
+ delete GSMPulse1;
+ delete GSMPulse4;
+ delete dnsampler;
+
+ GMSKRotation1 = NULL;
+ GMSKRotation4 = NULL;
+ GMSKReverseRotation4 = NULL;
+ GMSKReverseRotation1 = NULL;
+ GSMPulse1 = NULL;
+ GSMPulse4 = NULL;
+}
+
+static float vectorNorm2(const signalVector &x)
+{
+ signalVector::const_iterator xPtr = x.begin();
+ float Energy = 0.0;
+ for (;xPtr != x.end();xPtr++) {
+ Energy += xPtr->norm2();
+ }
+ return Energy;
+}
+
+/*
+ * Initialize 4 sps and 1 sps rotation tables
+ */
+static void initGMSKRotationTables()
+{
+ size_t len1 = 157, len4 = 625;
+
+ GMSKRotation4 = new signalVector(len4);
+ GMSKReverseRotation4 = new signalVector(len4);
+ signalVector::iterator rotPtr = GMSKRotation4->begin();
+ signalVector::iterator revPtr = GMSKReverseRotation4->begin();
+ auto phase = 0.0;
+ while (rotPtr != GMSKRotation4->end()) {
+ *rotPtr++ = complex(cos(phase), sin(phase));
+ *revPtr++ = complex(cos(-phase), sin(-phase));
+ phase += M_PI / 2.0 / 4.0;
+ }
+
+ GMSKRotation1 = new signalVector(len1);
+ GMSKReverseRotation1 = new signalVector(len1);
+ rotPtr = GMSKRotation1->begin();
+ revPtr = GMSKReverseRotation1->begin();
+ phase = 0.0;
+ while (rotPtr != GMSKRotation1->end()) {
+ *rotPtr++ = complex(cos(phase), sin(phase));
+ *revPtr++ = complex(cos(-phase), sin(-phase));
+ phase += M_PI / 2.0;
+ }
+}
+
+static void GMSKRotate(signalVector &x, int sps)
+{
+#if HAVE_NEON
+ size_t len;
+ signalVector *a, *b, *out;
+
+ a = &x;
+ out = &x;
+ len = out->size();
+
+ if (len == 157)
+ len--;
+
+ if (sps == 1)
+ b = GMSKRotation1;
+ else
+ b = GMSKRotation4;
+
+ mul_complex((float *) out->begin(),
+ (float *) a->begin(),
+ (float *) b->begin(), len);
+#else
+ signalVector::iterator rotPtr, xPtr = x.begin();
+
+ if (sps == 1)
+ rotPtr = GMSKRotation1->begin();
+ else
+ rotPtr = GMSKRotation4->begin();
+
+ if (x.isReal()) {
+ while (xPtr < x.end()) {
+ *xPtr = *rotPtr++ * (xPtr->real());
+ xPtr++;
+ }
+ }
+ else {
+ while (xPtr < x.end()) {
+ *xPtr = *rotPtr++ * (*xPtr);
+ xPtr++;
+ }
+ }
+#endif
+}
+
+static bool GMSKReverseRotate(signalVector &x, int sps)
+{
+ signalVector::iterator rotPtr, xPtr= x.begin();
+
+ if (sps == 1)
+ rotPtr = GMSKReverseRotation1->begin();
+ else if (sps == 4)
+ rotPtr = GMSKReverseRotation4->begin();
+ else
+ return false;
+
+ if (x.isReal()) {
+ while (xPtr < x.end()) {
+ *xPtr = *rotPtr++ * (xPtr->real());
+ xPtr++;
+ }
+ }
+ else {
+ while (xPtr < x.end()) {
+ *xPtr = *rotPtr++ * (*xPtr);
+ xPtr++;
+ }
+ }
+
+ return true;
+}
+
+/** Convolution type indicator */
+enum ConvType {
+ START_ONLY,
+ NO_DELAY,
+ CUSTOM,
+ UNDEFINED,
+};
+
+static signalVector *convolve(const signalVector *x, const signalVector *h,
+ signalVector *y, ConvType spanType,
+ size_t start = 0, size_t len = 0,
+ size_t step = 1, int offset = 0)
+{
+ int rc;
+ size_t head = 0, tail = 0;
+ bool alloc = false, append = false;
+ const signalVector *_x = NULL;
+
+ if (!x || !h)
+ return NULL;
+
+ switch (spanType) {
+ case START_ONLY:
+ start = 0;
+ head = h->size() - 1;
+ len = x->size();
+
+ if (x->getStart() < head)
+ append = true;
+ break;
+ case NO_DELAY:
+ start = h->size() / 2;
+ head = start;
+ tail = start;
+ len = x->size();
+ append = true;
+ break;
+ case CUSTOM:
+ if (start < h->size() - 1) {
+ head = h->size() - start;
+ append = true;
+ }
+ if (start + len > x->size()) {
+ tail = start + len - x->size();
+ append = true;
+ }
+ break;
+ default:
+ return NULL;
+ }
+
+ /*
+ * Error if the output vector is too small. Create the output vector
+ * if the pointer is NULL.
+ */
+ if (y && (len > y->size()))
+ return NULL;
+ if (!y) {
+ y = new signalVector(len);
+ alloc = true;
+ }
+
+ /* Prepend or post-pend the input vector if the parameters require it */
+ if (append)
+ _x = new signalVector(*x, head, tail);
+ else
+ _x = x;
+
+ /*
+ * Four convovle types:
+ * 1. Complex-Real (aligned)
+ * 2. Complex-Complex (aligned)
+ * 3. Complex-Real (!aligned)
+ * 4. Complex-Complex (!aligned)
+ */
+ if (h->isReal() && h->isAligned()) {
+ rc = convolve_real((float *) _x->begin(), _x->size(),
+ (float *) h->begin(), h->size(),
+ (float *) y->begin(), y->size(),
+ start, len, step, offset);
+ } else if (!h->isReal() && h->isAligned()) {
+ rc = convolve_complex((float *) _x->begin(), _x->size(),
+ (float *) h->begin(), h->size(),
+ (float *) y->begin(), y->size(),
+ start, len, step, offset);
+ } else if (h->isReal() && !h->isAligned()) {
+ rc = base_convolve_real((float *) _x->begin(), _x->size(),
+ (float *) h->begin(), h->size(),
+ (float *) y->begin(), y->size(),
+ start, len, step, offset);
+ } else if (!h->isReal() && !h->isAligned()) {
+ rc = base_convolve_complex((float *) _x->begin(), _x->size(),
+ (float *) h->begin(), h->size(),
+ (float *) y->begin(), y->size(),
+ start, len, step, offset);
+ } else {
+ rc = -1;
+ }
+
+ if (append)
+ delete _x;
+
+ if (rc < 0) {
+ if (alloc)
+ delete y;
+ return NULL;
+ }
+
+ return y;
+}
+
+/*
+ * Generate static EDGE linear equalizer. This equalizer is not adaptive.
+ * Filter taps are generated from the inverted 1 SPS impulse response of
+ * the EDGE pulse shape captured after the downsampling filter.
+ */
+static bool generateInvertC0Pulse(PulseSequence *pulse)
+{
+ if (!pulse)
+ return false;
+
+ pulse->c0_inv_buffer = convolve_h_alloc(5);
+ pulse->c0_inv = new signalVector((complex *) pulse->c0_inv_buffer, 0, 5);
+ pulse->c0_inv->isReal(true);
+ pulse->c0_inv->setAligned(false);
+
+ signalVector::iterator xP = pulse->c0_inv->begin();
+ *xP++ = 0.15884;
+ *xP++ = -0.43176;
+ *xP++ = 1.00000;
+ *xP++ = -0.42608;
+ *xP++ = 0.14882;
+
+ return true;
+}
+
+static bool generateC1Pulse(int sps, PulseSequence *pulse)
+{
+ int len;
+
+ if (!pulse)
+ return false;
+
+ switch (sps) {
+ case 4:
+ len = 8;
+ break;
+ default:
+ return false;
+ }
+
+ pulse->c1_buffer = convolve_h_alloc(len);
+ pulse->c1 = new signalVector((complex *)
+ pulse->c1_buffer, 0, len);
+ pulse->c1->isReal(true);
+
+ /* Enable alignment for SSE usage */
+ pulse->c1->setAligned(true);
+
+ signalVector::iterator xP = pulse->c1->begin();
+
+ switch (sps) {
+ case 4:
+ /* BT = 0.30 */
+ *xP++ = 0.0;
+ *xP++ = 8.16373112e-03;
+ *xP++ = 2.84385729e-02;
+ *xP++ = 5.64158904e-02;
+ *xP++ = 7.05463553e-02;
+ *xP++ = 5.64158904e-02;
+ *xP++ = 2.84385729e-02;
+ *xP++ = 8.16373112e-03;
+ }
+
+ return true;
+}
+
+static PulseSequence *generateGSMPulse(int sps)
+{
+ int len;
+ float arg, avg, center;
+ PulseSequence *pulse;
+
+ if ((sps != 1) && (sps != 4))
+ return NULL;
+
+ /* Store a single tap filter used for correlation sequence generation */
+ pulse = new PulseSequence();
+ pulse->empty = new signalVector(1);
+ pulse->empty->isReal(true);
+ *(pulse->empty->begin()) = 1.0f;
+
+ /*
+ * For 4 samples-per-symbol use a precomputed single pulse Laurent
+ * approximation. This should yields below 2 degrees of phase error at
+ * the modulator output. Use the existing pulse approximation for all
+ * other oversampling factors.
+ */
+ switch (sps) {
+ case 4:
+ len = 16;
+ break;
+ case 1:
+ default:
+ len = 4;
+ }
+
+ pulse->c0_buffer = convolve_h_alloc(len);
+ pulse->c0 = new signalVector((complex *) pulse->c0_buffer, 0, len);
+ pulse->c0->isReal(true);
+
+ /* Enable alingnment for SSE usage */
+ pulse->c0->setAligned(true);
+
+ signalVector::iterator xP = pulse->c0->begin();
+
+ if (sps == 4) {
+ *xP++ = 0.0;
+ *xP++ = 4.46348606e-03;
+ *xP++ = 2.84385729e-02;
+ *xP++ = 1.03184855e-01;
+ *xP++ = 2.56065552e-01;
+ *xP++ = 4.76375085e-01;
+ *xP++ = 7.05961177e-01;
+ *xP++ = 8.71291644e-01;
+ *xP++ = 9.29453645e-01;
+ *xP++ = 8.71291644e-01;
+ *xP++ = 7.05961177e-01;
+ *xP++ = 4.76375085e-01;
+ *xP++ = 2.56065552e-01;
+ *xP++ = 1.03184855e-01;
+ *xP++ = 2.84385729e-02;
+ *xP++ = 4.46348606e-03;
+ generateC1Pulse(sps, pulse);
+ } else {
+ center = (float) (len - 1.0) / 2.0;
+
+ /* GSM pulse approximation */
+ for (int i = 0; i < len; i++) {
+ arg = ((float) i - center) / (float) sps;
+ *xP++ = 0.96 * exp(-1.1380 * arg * arg -
+ 0.527 * arg * arg * arg * arg);
+ }
+
+ avg = sqrtf(vectorNorm2(*pulse->c0) / sps);
+ xP = pulse->c0->begin();
+ for (int i = 0; i < len; i++)
+ *xP++ /= avg;
+ }
+
+ /*
+ * Current form of the EDGE equalization filter non-realizable at 4 SPS.
+ * Load the onto both 1 SPS and 4 SPS objects for convenience. Note that
+ * the EDGE demodulator downsamples to 1 SPS prior to equalization.
+ */
+ generateInvertC0Pulse(pulse);
+
+ return pulse;
+}
+
+bool vectorSlicer(SoftVector *x)
+{
+ SoftVector::iterator xP = x->begin();
+ SoftVector::iterator xPEnd = x->end();
+ while (xP < xPEnd) {
+ *xP = 0.5 * (*xP + 1.0f);
+ if (*xP > 1.0)
+ *xP = 1.0;
+ if (*xP < 0.0)
+ *xP = 0.0;
+ xP++;
+ }
+ return true;
+}
+
+static signalVector *rotateBurst(const BitVector &wBurst,
+ int guardPeriodLength, int sps)
+{
+ int burst_len;
+ signalVector *pulse, rotated;
+ signalVector::iterator itr;
+
+ pulse = GSMPulse1->empty;
+ burst_len = sps * (wBurst.size() + guardPeriodLength);
+ rotated = signalVector(burst_len);
+ itr = rotated.begin();
+
+ for (unsigned i = 0; i < wBurst.size(); i++) {
+ *itr = 2.0 * (wBurst[i] & 0x01) - 1.0;
+ itr += sps;
+ }
+
+ GMSKRotate(rotated, sps);
+ rotated.isReal(false);
+
+ /* Dummy filter operation */
+ return convolve(&rotated, pulse, NULL, START_ONLY);
+}
+
+static void rotateBurst2(signalVector &burst, double phase)
+{
+ Complex<float> rot = Complex<float>(cos(phase), sin(phase));
+
+ for (size_t i = 0; i < burst.size(); i++)
+ burst[i] = burst[i] * rot;
+}
+
+/*
+ * Ignore the guard length argument in the GMSK modulator interface
+ * because it results in 624/628 sized bursts instead of the preferred
+ * burst length of 625. Only 4 SPS is supported.
+ */
+static signalVector *modulateBurstLaurent(const BitVector &bits)
+{
+ int burst_len, sps = 4;
+ float phase;
+ signalVector *c0_pulse, *c1_pulse, *c0_shaped, *c1_shaped;
+ signalVector::iterator c0_itr, c1_itr;
+
+ c0_pulse = GSMPulse4->c0;
+ c1_pulse = GSMPulse4->c1;
+
+ if (bits.size() > 156)
+ return NULL;
+
+ burst_len = 625;
+
+ signalVector c0_burst(burst_len, c0_pulse->size());
+ c0_burst.isReal(true);
+ c0_itr = c0_burst.begin();
+
+ signalVector c1_burst(burst_len, c1_pulse->size());
+ c1_itr = c1_burst.begin();
+
+ /* Padded differential tail bits */
+ *c0_itr = 2.0 * (0x00 & 0x01) - 1.0;
+ c0_itr += sps;
+
+ /* Main burst bits */
+ for (unsigned i = 0; i < bits.size(); i++) {
+ *c0_itr = 2.0 * (bits[i] & 0x01) - 1.0;
+ c0_itr += sps;
+ }
+
+ /* Padded differential tail bits */
+ *c0_itr = 2.0 * (0x00 & 0x01) - 1.0;
+
+ /* Generate C0 phase coefficients */
+ GMSKRotate(c0_burst, sps);
+ c0_burst.isReal(false);
+
+ c0_itr = c0_burst.begin();
+ c0_itr += sps * 2;
+ c1_itr += sps * 2;
+
+ /* Start magic */
+ phase = 2.0 * ((0x01 & 0x01) ^ (0x01 & 0x01)) - 1.0;
+ *c1_itr = *c0_itr * Complex<float>(0, phase);
+ c0_itr += sps;
+ c1_itr += sps;
+
+ /* Generate C1 phase coefficients */
+ for (unsigned i = 2; i < bits.size(); i++) {
+ phase = 2.0 * ((bits[i - 1] & 0x01) ^ (bits[i - 2] & 0x01)) - 1.0;
+ *c1_itr = *c0_itr * Complex<float>(0, phase);
+
+ c0_itr += sps;
+ c1_itr += sps;
+ }
+
+ /* End magic */
+ int i = bits.size();
+ phase = 2.0 * ((bits[i-1] & 0x01) ^ (bits[i-2] & 0x01)) - 1.0;
+ *c1_itr = *c0_itr * Complex<float>(0, phase);
+
+ /* Primary (C0) and secondary (C1) pulse shaping */
+ c0_shaped = convolve(&c0_burst, c0_pulse, NULL, START_ONLY);
+ c1_shaped = convolve(&c1_burst, c1_pulse, NULL, START_ONLY);
+
+ /* Sum shaped outputs into C0 */
+ c0_itr = c0_shaped->begin();
+ c1_itr = c1_shaped->begin();
+ for (unsigned i = 0; i < c0_shaped->size(); i++ )
+ *c0_itr++ += *c1_itr++;
+
+ delete c1_shaped;
+ return c0_shaped;
+}
+
+static signalVector *rotateEdgeBurst(const signalVector &symbols, int sps)
+{
+ signalVector *burst;
+ signalVector::iterator burst_itr;
+
+ burst = new signalVector(symbols.size() * sps);
+ burst_itr = burst->begin();
+
+ for (size_t i = 0; i < symbols.size(); i++) {
+ float phase = i * 3.0f * M_PI / 8.0f;
+ Complex<float> rot = Complex<float>(cos(phase), sin(phase));
+
+ *burst_itr = symbols[i] * rot;
+ burst_itr += sps;
+ }
+
+ return burst;
+}
+
+static signalVector *derotateEdgeBurst(const signalVector &symbols, int sps)
+{
+ signalVector *burst;
+ signalVector::iterator burst_itr;
+
+ if (symbols.size() % sps)
+ return NULL;
+
+ burst = new signalVector(symbols.size() / sps);
+ burst_itr = burst->begin();
+
+ for (size_t i = 0; i < burst->size(); i++) {
+ float phase = (float) (i % 16) * 3.0f * M_PI / 8.0f;
+ Complex<float> rot = Complex<float>(cosf(phase), -sinf(phase));
+
+ *burst_itr = symbols[sps * i] * rot;
+ burst_itr++;
+ }
+
+ return burst;
+}
+
+static signalVector *mapEdgeSymbols(const BitVector &bits)
+{
+ if (bits.size() % 3)
+ return NULL;
+
+ signalVector *symbols = new signalVector(bits.size() / 3);
+
+ for (size_t i = 0; i < symbols->size(); i++) {
+ unsigned index = (((unsigned) bits[3 * i + 0] & 0x01) << 0) |
+ (((unsigned) bits[3 * i + 1] & 0x01) << 1) |
+ (((unsigned) bits[3 * i + 2] & 0x01) << 2);
+
+ (*symbols)[i] = psk8_table[index];
+ }
+
+ return symbols;
+}
+
+/*
+ * EDGE 8-PSK rotate and pulse shape
+ *
+ * Delay the EDGE downlink bursts by one symbol in order to match GMSK pulse
+ * shaping group delay. The difference in group delay arises from the dual
+ * pulse filter combination of the GMSK Laurent represenation whereas 8-PSK
+ * uses a single pulse linear filter.
+ */
+static signalVector *shapeEdgeBurst(const signalVector &symbols)
+{
+ size_t nsyms, nsamps = 625, sps = 4;
+ signalVector::iterator burst_itr;
+
+ nsyms = symbols.size();
+
+ if (nsyms * sps > nsamps)
+ nsyms = 156;
+
+ signalVector burst(nsamps, GSMPulse4->c0->size());
+
+ /* Delay burst by 1 symbol */
+ burst_itr = burst.begin() + sps;
+ for (size_t i = 0; i < nsyms; i++) {
+ float phase = i * 3.0f * M_PI / 8.0f;
+ Complex<float> rot = Complex<float>(cos(phase), sin(phase));
+
+ *burst_itr = symbols[i] * rot;
+ burst_itr += sps;
+ }
+
+ /* Single Gaussian pulse approximation shaping */
+ return convolve(&burst, GSMPulse4->c0, NULL, START_ONLY);
+}
+
+/*
+ * Generate a random GSM normal burst.
+ */
+signalVector *genRandNormalBurst(int tsc, int sps, int tn)
+{
+ if ((tsc < 0) || (tsc > 7) || (tn < 0) || (tn > 7))
+ return NULL;
+ if ((sps != 1) && (sps != 4))
+ return NULL;
+
+ int i = 0;
+ BitVector bits(148);
+
+ /* Tail bits */
+ for (; i < 3; i++)
+ bits[i] = 0;
+
+ /* Random bits */
+ for (; i < 60; i++)
+ bits[i] = rand() % 2;
+
+ /* Stealing bit */
+ bits[i++] = 0;
+
+ /* Training sequence */
+ for (int n = 0; i < 87; i++, n++)
+ bits[i] = gTrainingSequence[tsc][n];
+
+ /* Stealing bit */
+ bits[i++] = 0;
+
+ /* Random bits */
+ for (; i < 145; i++)
+ bits[i] = rand() % 2;
+
+ /* Tail bits */
+ for (; i < 148; i++)
+ bits[i] = 0;
+
+ int guard = 8 + !(tn % 4);
+ return modulateBurst(bits, guard, sps);
+}
+
+/*
+ * Generate a random GSM access burst.
+ */
+signalVector *genRandAccessBurst(int delay, int sps, int tn)
+{
+ if ((tn < 0) || (tn > 7))
+ return NULL;
+ if ((sps != 1) && (sps != 4))
+ return NULL;
+ if (delay > 68)
+ return NULL;
+
+ int i = 0;
+ BitVector bits(88 + delay);
+
+ /* delay */
+ for (; i < delay; i++)
+ bits[i] = 0;
+
+ /* head and synch bits */
+ for (int n = 0; i < 49+delay; i++, n++)
+ bits[i] = gRACHBurst[n];
+
+ /* Random bits */
+ for (; i < 85+delay; i++)
+ bits[i] = rand() % 2;
+
+ /* Tail bits */
+ for (; i < 88+delay; i++)
+ bits[i] = 0;
+
+ int guard = 68-delay + !(tn % 4);
+ return modulateBurst(bits, guard, sps);
+}
+
+signalVector *generateEmptyBurst(int sps, int tn)
+{
+ if ((tn < 0) || (tn > 7))
+ return NULL;
+
+ if (sps == 4)
+ return new signalVector(625);
+ else if (sps == 1)
+ return new signalVector(148 + 8 + !(tn % 4));
+ else
+ return NULL;
+}
+
+signalVector *generateDummyBurst(int sps, int tn)
+{
+ if (((sps != 1) && (sps != 4)) || (tn < 0) || (tn > 7))
+ return NULL;
+
+ return modulateBurst(gDummyBurst, 8 + !(tn % 4), sps);
+}
+
+/*
+ * Generate a random 8-PSK EDGE burst. Only 4 SPS is supported with
+ * the returned burst being 625 samples in length.
+ */
+signalVector *generateEdgeBurst(int tsc)
+{
+ int tail = 9 / 3;
+ int data = 174 / 3;
+ int train = 78 / 3;
+
+ if ((tsc < 0) || (tsc > 7))
+ return NULL;
+
+ signalVector burst(148);
+ const BitVector *midamble = &gEdgeTrainingSequence[tsc];
+
+ /* Tail */
+ int n, i = 0;
+ for (; i < tail; i++)
+ burst[i] = psk8_table[7];
+
+ /* Body */
+ for (; i < tail + data; i++)
+ burst[i] = psk8_table[rand() % 8];
+
+ /* TSC */
+ for (n = 0; i < tail + data + train; i++, n++) {
+ unsigned index = (((unsigned) (*midamble)[3 * n + 0] & 0x01) << 0) |
+ (((unsigned) (*midamble)[3 * n + 1] & 0x01) << 1) |
+ (((unsigned) (*midamble)[3 * n + 2] & 0x01) << 2);
+
+ burst[i] = psk8_table[index];
+ }
+
+ /* Body */
+ for (; i < tail + data + train + data; i++)
+ burst[i] = psk8_table[rand() % 8];
+
+ /* Tail */
+ for (; i < tail + data + train + data + tail; i++)
+ burst[i] = psk8_table[7];
+
+ return shapeEdgeBurst(burst);
+}
+
+/*
+ * Modulate 8-PSK burst. When empty pulse shaping (rotation only)
+ * is enabled, the output vector length will be bit sequence length
+ * times the SPS value. When pulse shaping is enabled, the output
+ * vector length is fixed at 625 samples (156.25 symbols at 4 SPS).
+ * Pulse shaped bit sequences that go beyond one burst are truncated.
+ * Pulse shaping at anything but 4 SPS is not supported.
+ */
+signalVector *modulateEdgeBurst(const BitVector &bits,
+ int sps, bool empty)
+{
+ signalVector *shape, *burst;
+
+ if ((sps != 4) && !empty)
+ return NULL;
+
+ burst = mapEdgeSymbols(bits);
+ if (!burst)
+ return NULL;
+
+ if (empty)
+ shape = rotateEdgeBurst(*burst, sps);
+ else
+ shape = shapeEdgeBurst(*burst);
+
+ delete burst;
+ return shape;
+}
+
+static signalVector *modulateBurstBasic(const BitVector &bits,
+ int guard_len, int sps)
+{
+ int burst_len;
+ signalVector *pulse;
+ signalVector::iterator burst_itr;
+
+ if (sps == 1)
+ pulse = GSMPulse1->c0;
+ else
+ pulse = GSMPulse4->c0;
+
+ burst_len = sps * (bits.size() + guard_len);
+
+ signalVector burst(burst_len, pulse->size());
+ burst.isReal(true);
+ burst_itr = burst.begin();
+
+ /* Raw bits are not differentially encoded */
+ for (unsigned i = 0; i < bits.size(); i++) {
+ *burst_itr = 2.0 * (bits[i] & 0x01) - 1.0;
+ burst_itr += sps;
+ }
+
+ GMSKRotate(burst, sps);
+ burst.isReal(false);
+
+ /* Single Gaussian pulse approximation shaping */
+ return convolve(&burst, pulse, NULL, START_ONLY);
+}
+
+/* Assume input bits are not differentially encoded */
+signalVector *modulateBurst(const BitVector &wBurst, int guardPeriodLength,
+ int sps, bool emptyPulse)
+{
+ if (emptyPulse)
+ return rotateBurst(wBurst, guardPeriodLength, sps);
+ else if (sps == 4)
+ return modulateBurstLaurent(wBurst);
+ else
+ return modulateBurstBasic(wBurst, guardPeriodLength, sps);
+}
+
+static void generateSincTable()
+{
+ for (int i = 0; i < TABLESIZE; i++) {
+ auto x = (double) i / TABLESIZE * 8 * M_PI;
+ auto y = sin(x) / x;
+ sincTable[i] = std::isnan(y) ? 1.0 : y;
+ }
+}
+
+static float sinc(float x)
+{
+ if (fabs(x) >= 8 * M_PI)
+ return 0.0;
+
+ int index = (int) floorf(fabs(x) / (8 * M_PI) * TABLESIZE);
+
+ return sincTable[index];
+}
+
+/*
+ * Create fractional delay filterbank with Blackman-harris windowed
+ * sinc function generator. The number of filters generated is specified
+ * by the DELAYFILTS value.
+ */
+static void generateDelayFilters()
+{
+ int h_len = 20;
+ complex *data;
+ signalVector *h;
+ signalVector::iterator itr;
+
+ float k, sum;
+ float a0 = 0.35875;
+ float a1 = 0.48829;
+ float a2 = 0.14128;
+ float a3 = 0.01168;
+
+ for (int i = 0; i < DELAYFILTS; i++) {
+ data = (complex *) convolve_h_alloc(h_len);
+ h = new signalVector(data, 0, h_len);
+ h->setAligned(true);
+ h->isReal(true);
+
+ sum = 0.0;
+ itr = h->end();
+ for (int n = 0; n < h_len; n++) {
+ k = (float) n;
+ *--itr = (complex) sinc(M_PI_F *
+ (k - (float) h_len / 2.0 - (float) i / DELAYFILTS));
+ *itr *= a0 -
+ a1 * cos(2 * M_PI * n / (h_len - 1)) +
+ a2 * cos(4 * M_PI * n / (h_len - 1)) -
+ a3 * cos(6 * M_PI * n / (h_len - 1));
+
+ sum += itr->real();
+ }
+
+ itr = h->begin();
+ for (int n = 0; n < h_len; n++)
+ *itr++ /= sum;
+
+ delayFilters[i] = h;
+ }
+}
+
+signalVector *delayVector(const signalVector *in, signalVector *out, float delay)
+{
+ int whole, index;
+ float frac;
+ signalVector *h, *shift, *fshift = NULL;
+
+ whole = floor(delay);
+ frac = delay - whole;
+
+ /* Sinc interpolated fractional shift (if allowable) */
+ if (fabs(frac) > 1e-2) {
+ index = floorf(frac * (float) DELAYFILTS);
+ h = delayFilters[index];
+
+ fshift = convolve(in, h, NULL, NO_DELAY);
+ if (!fshift)
+ return NULL;
+ }
+
+ if (!fshift)
+ shift = new signalVector(*in);
+ else
+ shift = fshift;
+
+ /* Integer sample shift */
+ if (whole < 0) {
+ whole = -whole;
+ signalVector::iterator wBurstItr = shift->begin();
+ signalVector::iterator shiftedItr = shift->begin() + whole;
+
+ while (shiftedItr < shift->end())
+ *wBurstItr++ = *shiftedItr++;
+
+ while (wBurstItr < shift->end())
+ *wBurstItr++ = 0.0;
+ } else if (whole >= 0) {
+ signalVector::iterator wBurstItr = shift->end() - 1;
+ signalVector::iterator shiftedItr = shift->end() - 1 - whole;
+
+ while (shiftedItr >= shift->begin())
+ *wBurstItr-- = *shiftedItr--;
+
+ while (wBurstItr >= shift->begin())
+ *wBurstItr-- = 0.0;
+ }
+
+ if (!out)
+ return shift;
+
+ out->clone(*shift);
+ delete shift;
+ return out;
+}
+
+static complex interpolatePoint(const signalVector &inSig, float ix)
+{
+ int start = (int) (floor(ix) - 10);
+ if (start < 0) start = 0;
+ int end = (int) (floor(ix) + 11);
+ if ((unsigned) end > inSig.size()-1) end = inSig.size()-1;
+
+ complex pVal = 0.0;
+ if (!inSig.isReal()) {
+ for (int i = start; i < end; i++)
+ pVal += inSig[i] * sinc(M_PI_F*(i-ix));
+ }
+ else {
+ for (int i = start; i < end; i++)
+ pVal += inSig[i].real() * sinc(M_PI_F*(i-ix));
+ }
+
+ return pVal;
+}
+
+static complex fastPeakDetect(const signalVector &rxBurst, float *index)
+{
+ float val, max = 0.0f;
+ complex amp;
+ int _index = -1;
+
+ for (size_t i = 0; i < rxBurst.size(); i++) {
+ val = rxBurst[i].norm2();
+ if (val > max) {
+ max = val;
+ _index = i;
+ amp = rxBurst[i];
+ }
+ }
+
+ if (index)
+ *index = (float) _index;
+
+ return amp;
+}
+
+static complex peakDetect(const signalVector &rxBurst,
+ float *peakIndex, float *avgPwr)
+{
+ complex maxVal = 0.0;
+ float maxIndex = -1;
+ float sumPower = 0.0;
+
<