diff options
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 @@ -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 @@ -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. @@ -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 = *~ @@ -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", ¶ms)) { + 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", ¶ms)) { + 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", ¶ms)) { + //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", ¶ms)) { + //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", ¶ms)) { + //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", ¶ms)) { + 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", ¶ms)) { + 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", ¶ms)) { + // 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", ¶ms)) { + // 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", ¶ms)) { + // 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", ¶ms)) { + // set slot type + int corrCode; + int timeslot; + sscanf(params, "%d %d", ×lot, &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", ¶ms)) { + // 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(¶m, 0, sizeof(param)); + param.sched_priority = prio; + printf("Setting SCHED_RR priority(%d)\n", param.sched_priority); + rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); + 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; + < |