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; + + for (unsigned int i = 0; i < rxBurst.size(); i++) { + float samplePower = rxBurst[i].norm2(); + if (samplePower > maxVal.real()) { + maxVal = samplePower; + maxIndex = i; + } + sumPower += samplePower; + } + + // interpolate around the peak + // to save computation, we'll use early-late balancing + float earlyIndex = maxIndex-1; + float lateIndex = maxIndex+1; + + float incr = 0.5; + while (incr > 1.0/1024.0) { + complex earlyP = interpolatePoint(rxBurst,earlyIndex); + complex lateP = interpolatePoint(rxBurst,lateIndex); + if (earlyP < lateP) + earlyIndex += incr; + else if (earlyP > lateP) + earlyIndex -= incr; + else break; + incr /= 2.0; + lateIndex = earlyIndex + 2.0; + } + + maxIndex = earlyIndex + 1.0; + maxVal = interpolatePoint(rxBurst,maxIndex); + + if (peakIndex!=NULL) + *peakIndex = maxIndex; + + if (avgPwr!=NULL) + *avgPwr = (sumPower-maxVal.norm2()) / (rxBurst.size()-1); + + return maxVal; + +} + +void scaleVector(signalVector &x, + complex scale) +{ +#ifdef HAVE_NEON + int len = x.size(); + + scale_complex((float *) x.begin(), + (float *) x.begin(), + (float *) &scale, len); +#else + signalVector::iterator xP = x.begin(); + signalVector::iterator xPEnd = x.end(); + if (!x.isReal()) { + while (xP < xPEnd) { + *xP = *xP * scale; + xP++; + } + } + else { + while (xP < xPEnd) { + *xP = xP->real() * scale; + xP++; + } + } +#endif +} + +/** in-place conjugation */ +static void conjugateVector(signalVector &x) +{ + if (x.isReal()) return; + signalVector::iterator xP = x.begin(); + signalVector::iterator xPEnd = x.end(); + while (xP < xPEnd) { + *xP = xP->conj(); + xP++; + } +} + +static bool generateMidamble(int sps, int tsc) +{ + bool status = true; + float toa; + complex *data = NULL; + signalVector *autocorr = NULL, *midamble = NULL; + signalVector *midMidamble = NULL, *_midMidamble = NULL; + + if ((tsc < 0) || (tsc > 7)) + return false; + + delete gMidambles[tsc]; + + /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */ + midMidamble = modulateBurst(gTrainingSequence[tsc].segment(5,16), 0, sps, true); + if (!midMidamble) + return false; + + /* Simulated receive sequence is pulse shaped */ + midamble = modulateBurst(gTrainingSequence[tsc], 0, sps, false); + if (!midamble) { + status = false; + goto release; + } + + // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst, + // the ideal TSC has an + 180 degree phase shift, + // due to the pi/2 frequency shift, that + // needs to be accounted for. + // 26-midamble is 61 symbols into burst, has +90 degree phase shift. + scaleVector(*midMidamble, complex(-1.0, 0.0)); + scaleVector(*midamble, complex(0.0, 1.0)); + + conjugateVector(*midMidamble); + + /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */ + data = (complex *) convolve_h_alloc(midMidamble->size()); + _midMidamble = new signalVector(data, 0, midMidamble->size()); + _midMidamble->setAligned(true); + midMidamble->copyTo(*_midMidamble); + + autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY); + if (!autocorr) { + status = false; + goto release; + } + + gMidambles[tsc] = new CorrelationSequence; + gMidambles[tsc]->buffer = data; + gMidambles[tsc]->sequence = _midMidamble; + gMidambles[tsc]->gain = peakDetect(*autocorr, &toa, NULL); + + /* For 1 sps only + * (Half of correlation length - 1) + midpoint of pulse shape + remainder + * 13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2 + */ + if (sps == 1) + gMidambles[tsc]->toa = toa - 13.5; + else + gMidambles[tsc]->toa = 0; + +release: + delete autocorr; + delete midamble; + delete midMidamble; + + if (!status) { + delete _midMidamble; + free(data); + gMidambles[tsc] = NULL; + } + + return status; +} + +static CorrelationSequence *generateEdgeMidamble(int tsc) +{ + complex *data = NULL; + signalVector *midamble = NULL, *_midamble = NULL; + CorrelationSequence *seq; + + if ((tsc < 0) || (tsc > 7)) + return NULL; + + /* Use middle 48 bits of each TSC. Correlation sequence is not pulse shaped */ + const BitVector *bits = &gEdgeTrainingSequence[tsc]; + midamble = modulateEdgeBurst(bits->segment(15, 48), 1, true); + if (!midamble) + return NULL; + + conjugateVector(*midamble); + + data = (complex *) convolve_h_alloc(midamble->size()); + _midamble = new signalVector(data, 0, midamble->size()); + _midamble->setAligned(true); + midamble->copyTo(*_midamble); + + /* Channel gain is an empirically measured value */ + seq = new CorrelationSequence; + seq->buffer = data; + seq->sequence = _midamble; + seq->gain = Complex<float>(-19.6432, 19.5006) / 1.18; + seq->toa = 0; + + delete midamble; + + return seq; +} + +static bool generateRACHSequence(CorrelationSequence **seq, const BitVector &bv, int sps) +{ + bool status = true; + float toa; + complex *data = NULL; + signalVector *autocorr = NULL; + signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL; + + if (*seq != NULL) + delete *seq; + + seq0 = modulateBurst(bv, 0, sps, false); + if (!seq0) + return false; + + seq1 = modulateBurst(bv.segment(0, 40), 0, sps, true); + if (!seq1) { + status = false; + goto release; + } + + conjugateVector(*seq1); + + /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */ + data = (complex *) convolve_h_alloc(seq1->size()); + _seq1 = new signalVector(data, 0, seq1->size()); + _seq1->setAligned(true); + seq1->copyTo(*_seq1); + + autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY); + if (!autocorr) { + status = false; + goto release; + } + + *seq = new CorrelationSequence; + (*seq)->sequence = _seq1; + (*seq)->buffer = data; + (*seq)->gain = peakDetect(*autocorr, &toa, NULL); + + /* For 1 sps only + * (Half of correlation length - 1) + midpoint of pulse shaping filer + * 20.5 = (40 / 2 - 1) + 1.5 + */ + if (sps == 1) + (*seq)->toa = toa - 20.5; + else + (*seq)->toa = 0.0; + +release: + delete autocorr; + delete seq0; + delete seq1; + + if (!status) { + delete _seq1; + free(data); + *seq = NULL; + } + + return status; +} + +/* + * Peak-to-average computation +/- range from peak in symbols + */ +#define COMPUTE_PEAK_MIN 2 +#define COMPUTE_PEAK_MAX 5 + +/* + * Minimum number of values needed to compute peak-to-average + */ +#define COMPUTE_PEAK_CNT 5 + +static float computePeakRatio(signalVector *corr, + int sps, float toa, complex amp) +{ + int num = 0; + complex *peak; + float rms, avg = 0.0; + + /* Check for bogus results */ + if ((toa < 0.0) || (toa > corr->size())) + return 0.0; + + peak = corr->begin() + (int) rint(toa); + + for (int i = COMPUTE_PEAK_MIN * sps; i <= COMPUTE_PEAK_MAX * sps; i++) { + if (peak - i >= corr->begin()) { + avg += (peak - i)->norm2(); + num++; + } + if (peak + i < corr->end()) { + avg += (peak + i)->norm2(); + num++; + } + } + + if (num < COMPUTE_PEAK_CNT) + return 0.0; + + rms = sqrtf(avg / (float) num) + 0.00001; + + return (amp.abs()) / rms; +} + +float energyDetect(const signalVector &rxBurst, unsigned windowLength) +{ + + signalVector::const_iterator windowItr = rxBurst.begin(); //+rxBurst.size()/2 - 5*windowLength/2; + float energy = 0.0; + if (windowLength == 0) return 0.0; + if (windowLength > rxBurst.size()) windowLength = rxBurst.size(); + for (unsigned i = 0; i < windowLength; i++) { + energy += windowItr->norm2(); + windowItr+=4; + } + return energy/windowLength; +} + +static signalVector *downsampleBurst(const signalVector &burst) +{ + signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len()); + signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN); + burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN); + + if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN, + (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) { + delete out; + out = NULL; + } + + return out; +}; + +/* + * Detect a burst based on correlation and peak-to-average ratio + * + * For one sampler-per-symbol, perform fast peak detection (no interpolation) + * for initial gating. We do this because energy detection should be disabled. + * For higher oversampling values, we assume the energy detector is in place + * and we run full interpolating peak detection. + */ +static int detectBurst(const signalVector &burst, + signalVector &corr, CorrelationSequence *sync, + float thresh, int sps, complex *amp, float *toa, + int start, int len) +{ + const signalVector *corr_in; + signalVector *dec = NULL; + + if (sps == 4) { + dec = downsampleBurst(burst); + corr_in = dec; + sps = 1; + } else { + corr_in = &burst; + } + + /* Correlate */ + if (!convolve(corr_in, sync->sequence, &corr, + CUSTOM, start, len, 1, 0)) { + delete dec; + return -1; + } + + delete dec; + + /* Running at the downsampled rate at this point */ + sps = 1; + + /* Peak detection - place restrictions at correlation edges */ + *amp = fastPeakDetect(corr, toa); + + if ((*toa < 3 * sps) || (*toa > len - 3 * sps)) + return 0; + + /* Peak -to-average ratio */ + if (computePeakRatio(&corr, sps, *toa, *amp) < thresh) + return 0; + + /* Compute peak-to-average ratio. Reject if we don't have enough values */ + *amp = peakDetect(corr, toa, NULL); + + /* Normalize our channel gain */ + *amp = *amp / sync->gain; + + /* Compensate for residuate time lag */ + *toa = *toa - sync->toa; + + return 1; +} + +static float maxAmplitude(const signalVector &burst) +{ + float max = 0.0; + for (size_t i = 0; i < burst.size(); i++) { + if (fabs(burst[i].real()) > max) + max = fabs(burst[i].real()); + if (fabs(burst[i].imag()) > max) + max = fabs(burst[i].imag()); + } + + return max; +} + +/* + * RACH/Normal burst detection with clipping detection + * + * Correlation window parameters: + * target: Tail bits + burst length + * head: Search symbols before target + * tail: Search symbols after target + */ +static int detectGeneralBurst(const signalVector &rxBurst, + float thresh, + int sps, + complex &, + float &toa, + int target, int head, int tail, + CorrelationSequence *sync) +{ + int rc, start, len; + bool clipping = false; + + if ((sps != 1) && (sps != 4)) + return -SIGERR_UNSUPPORTED; + + // Detect potential clipping + // We still may be able to demod the burst, so we'll give it a try + // and only report clipping if we can't demod. + float maxAmpl = maxAmplitude(rxBurst); + if (maxAmpl > CLIP_THRESH) { + LOG(DEBUG) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; + clipping = true; + } + + start = target - head - 1; + len = head + tail; + signalVector corr(len); + + rc = detectBurst(rxBurst, corr, sync, + thresh, sps, &, &toa, start, len); + if (rc < 0) { + return -SIGERR_INTERNAL; + } else if (!rc) { + amp = 0.0f; + toa = 0.0f; + return clipping?-SIGERR_CLIP:SIGERR_NONE; + } + + /* Subtract forward search bits from delay */ + toa -= head; + + return 1; +} + + +/* + * RACH burst detection + * + * Correlation window parameters: + * target: Tail bits + RACH length (reduced from 41 to a multiple of 4) + * head: Search 8 symbols before target + * tail: Search 8 symbols + maximum expected delay + */ +static int detectRACHBurst(const signalVector &burst, float threshold, int sps, + complex &litude, float &toa, unsigned max_toa, bool ext) +{ + int rc, target, head, tail; + int i, num_seq; + + target = 8 + 40; + head = 8; + tail = 8 + max_toa; + num_seq = ext ? 3 : 1; + + for (i = 0; i < num_seq; i++) { + rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa, + target, head, tail, gRACHSequences[i]); + if (rc > 0) + break; + } + + return rc; +} + +/* + * Normal burst detection + * + * Correlation window parameters: + * target: Tail + data + mid-midamble + 1/2 remaining midamblebits + * head: Search 6 symbols before target + * tail: Search 6 symbols + maximum expected delay + */ +static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float threshold, + int sps, complex &litude, float &toa, unsigned max_toa) +{ + int rc, target, head, tail; + CorrelationSequence *sync; + + if (tsc > 7) + return -SIGERR_UNSUPPORTED; + + target = 3 + 58 + 16 + 5; + head = 6; + tail = 6 + max_toa; + sync = gMidambles[tsc]; + + rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa, + target, head, tail, sync); + return rc; +} + +static int detectEdgeBurst(const signalVector &burst, unsigned tsc, float threshold, + int sps, complex &litude, float &toa, unsigned max_toa) +{ + int rc, target, head, tail; + CorrelationSequence *sync; + + if (tsc > 7) + return -SIGERR_UNSUPPORTED; + + target = 3 + 58 + 16 + 5; + head = 6; + tail = 6 + max_toa; + sync = gEdgeMidambles[tsc]; + + rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa, + target, head, tail, sync); + return rc; +} + +int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold, + int sps, CorrType type, complex &, float &toa, + unsigned max_toa) +{ + int rc = 0; + + switch (type) { + case EDGE: + rc = detectEdgeBurst(burst, tsc, threshold, sps, + amp, toa, max_toa); + if (rc > 0) + break; + else + type = TSC; + case TSC: + rc = analyzeTrafficBurst(burst, tsc, threshold, sps, + amp, toa, max_toa); + break; + case EXT_RACH: + case RACH: + rc = detectRACHBurst(burst, threshold, sps, amp, toa, + max_toa, type == EXT_RACH); + break; + default: + LOG(ERR) << "Invalid correlation type"; + } + + if (rc > 0) + return type; + + return rc; +} + +/* + * Soft 8-PSK decoding using Manhattan distance metric + */ +static SoftVector *softSliceEdgeBurst(signalVector &burst) +{ + size_t nsyms = 148; + + if (burst.size() < nsyms) + return NULL; + + signalVector::iterator itr; + SoftVector *bits = new SoftVector(nsyms * 3); + + /* + * Bits 0 and 1 - First and second bits of the symbol respectively + */ + rotateBurst2(burst, -M_PI / 8.0); + itr = burst.begin(); + for (size_t i = 0; i < nsyms; i++) { + (*bits)[3 * i + 0] = -itr->imag(); + (*bits)[3 * i + 1] = itr->real(); + itr++; + } + + /* + * Bit 2 - Collapse symbols into quadrant 0 (positive X and Y). + * Decision area is then simplified to X=Y axis. Rotate again to + * place decision boundary on X-axis. + */ + itr = burst.begin(); + for (size_t i = 0; i < burst.size(); i++) { + burst[i] = Complex<float>(fabs(itr->real()), fabs(itr->imag())); + itr++; + } + + rotateBurst2(burst, -M_PI / 4.0); + itr = burst.begin(); + for (size_t i = 0; i < nsyms; i++) { + (*bits)[3 * i + 2] = -itr->imag(); + itr++; + } + + signalVector soft(bits->size()); + for (size_t i = 0; i < bits->size(); i++) + soft[i] = (*bits)[i]; + + return bits; +} + +/* + * Convert signalVector to SoftVector by taking real part of the signal. + */ +static SoftVector *signalToSoftVector(signalVector *dec) +{ + SoftVector *bits = new SoftVector(dec->size()); + + SoftVector::iterator bit_itr = bits->begin(); + signalVector::iterator burst_itr = dec->begin(); + + for (; burst_itr < dec->end(); burst_itr++) + *bit_itr++ = burst_itr->real(); + + return bits; +} + +/* + * Shared portion of GMSK and EDGE demodulators consisting of timing + * recovery and single tap channel correction. For 4 SPS (if activated), + * the output is downsampled prior to the 1 SPS modulation specific + * stages. + */ +static signalVector *demodCommon(const signalVector &burst, int sps, + complex chan, float toa) +{ + signalVector *delay, *dec; + + if ((sps != 1) && (sps != 4)) + return NULL; + + delay = delayVector(&burst, NULL, -toa * (float) sps); + scaleVector(*delay, (complex) 1.0 / chan); + + if (sps == 1) + return delay; + + dec = downsampleBurst(*delay); + + delete delay; + return dec; +} + +/* + * Demodulate GSMK burst. Prior to symbol rotation, operate at + * 4 SPS (if activated) to minimize distortion through the fractional + * delay filters. Symbol rotation and after always operates at 1 SPS. + */ +static SoftVector *demodGmskBurst(const signalVector &rxBurst, + int sps, complex channel, float TOA) +{ + SoftVector *bits; + signalVector *dec; + + dec = demodCommon(rxBurst, sps, channel, TOA); + if (!dec) + return NULL; + + /* Shift up by a quarter of a frequency */ + GMSKReverseRotate(*dec, 1); + /* Take real part of the signal */ + bits = signalToSoftVector(dec); + delete dec; + + return bits; +} + +/* + * Demodulate an 8-PSK burst. Prior to symbol rotation, operate at + * 4 SPS (if activated) to minimize distortion through the fractional + * delay filters. Symbol rotation and after always operates at 1 SPS. + * + * Allow 1 SPS demodulation here, but note that other parts of the + * transceiver restrict EDGE operatoin to 4 SPS - 8-PSK distortion + * through the fractional delay filters at 1 SPS renders signal + * nearly unrecoverable. + */ +static SoftVector *demodEdgeBurst(const signalVector &burst, + int sps, complex chan, float toa) +{ + SoftVector *bits; + signalVector *dec, *rot, *eq; + + dec = demodCommon(burst, sps, chan, toa); + if (!dec) + return NULL; + + /* Equalize and derotate */ + eq = convolve(dec, GSMPulse4->c0_inv, NULL, NO_DELAY); + rot = derotateEdgeBurst(*eq, 1); + + /* Soft slice and normalize */ + bits = softSliceEdgeBurst(*rot); + + delete dec; + delete eq; + delete rot; + + return bits; +} + +SoftVector *demodAnyBurst(const signalVector &burst, int sps, complex amp, + float toa, CorrType type) +{ + if (type == EDGE) + return demodEdgeBurst(burst, sps, amp, toa); + else + return demodGmskBurst(burst, sps, amp, toa); +} + +bool sigProcLibSetup() +{ + generateSincTable(); + initGMSKRotationTables(); + + GSMPulse1 = generateGSMPulse(1); + GSMPulse4 = generateGSMPulse(4); + + generateRACHSequence(&gRACHSequences[0], gRACHSynchSequenceTS0, 1); + generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1); + generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1); + + for (int tsc = 0; tsc < 8; tsc++) { + generateMidamble(1, tsc); + gEdgeMidambles[tsc] = generateEdgeMidamble(tsc); + } + + generateDelayFilters(); + + dnsampler = new Resampler(1, 4); + if (!dnsampler->init()) { + LOG(ALERT) << "Rx resampler failed to initialize"; + goto fail; + } + + return true; + +fail: + sigProcLibDestroy(); + return false; +} diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h new file mode 100644 index 0000000..79a5c3f --- /dev/null +++ b/Transceiver52M/sigProcLib.h @@ -0,0 +1,130 @@ +/* +* 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 SIGPROCLIB_H +#define SIGPROCLIB_H + +#include "Vector.h" +#include "Complex.h" +#include "BitVector.h" +#include "signalVector.h" + +/* Burst lengths */ +#define NORMAL_BURST_NBITS 148 +#define EDGE_BURST_NBITS 444 +#define EDGE_BURST_NSYMS (EDGE_BURST_NBITS / 3) + +/** Codes for burst types of received bursts*/ +enum CorrType{ + OFF, ///< timeslot is off + TSC, ///< timeslot should contain a normal burst + EXT_RACH, ///< timeslot should contain an extended access burst + RACH, ///< timeslot should contain an access burst + EDGE, ///< timeslot should contain an EDGE burst + IDLE ///< timeslot is an idle (or dummy) burst +}; + +enum SignalError { + SIGERR_NONE, + SIGERR_BOUNDS, + SIGERR_CLIP, + SIGERR_UNSUPPORTED, + SIGERR_INTERNAL, +}; + +/* + * Burst detection threshold + * + * Decision threshold value for burst gating on peak-to-average value of + * correlated synchronization sequences. Lower values pass more bursts up + * to upper layers but will increase the false detection rate. + */ +#define BURST_THRESH 4.0 + +/** Setup the signal processing library */ +bool sigProcLibSetup(); + +/** Destroy the signal processing library */ +void sigProcLibDestroy(void); + +/** Operate soft slicer on a soft-bit vector */ +bool vectorSlicer(SoftVector *x); + +/** GMSK modulate a GSM burst of bits */ +signalVector *modulateBurst(const BitVector &wBurst, + int guardPeriodLength, + int sps, bool emptyPulse = false); + +/** 8-PSK modulate a burst of bits */ +signalVector *modulateEdgeBurst(const BitVector &bits, + int sps, bool emptyPulse = false); + +/** Generate a EDGE burst with random payload - 4 SPS (625 samples) only */ +signalVector *generateEdgeBurst(int tsc); + +/** Generate an empty burst - 4 or 1 SPS */ +signalVector *generateEmptyBurst(int sps, int tn); + +/** Generate a normal GSM burst with random payload - 4 or 1 SPS */ +signalVector *genRandNormalBurst(int tsc, int sps, int tn); + +/** Generate an access GSM burst with random payload - 4 or 1 SPS */ +signalVector *genRandAccessBurst(int delay, int sps, int tn); + +/** Generate a dummy GSM burst - 4 or 1 SPS */ +signalVector *generateDummyBurst(int sps, int tn); + +/** + Apply a scalar to a vector. + @param x The vector of interest. + @param scale The scalar. +*/ +void scaleVector(signalVector &x, + complex scale); + +/** + Rough energy estimator. + @param rxBurst A GSM burst. + @param windowLength The number of burst samples used to compute burst energy + @return The average power of the received burst. +*/ +float energyDetect(const signalVector &rxBurst, + unsigned windowLength); +/** + 8-PSK/GMSK/RACH burst detector + @param burst The received GSM burst of interest + @param tsc Midamble type (0..7) also known as TSC + @param threshold The threshold that the received burst's post-correlator SNR is compared against to determine validity. + @param sps The number of samples per GSM symbol. + @param amplitude The estimated amplitude of received TSC burst. + @param toa The estimate time-of-arrival of received TSC burst (in symbols). + @param max_toa The maximum expected time-of-arrival (in symbols). + @return positive value (CorrType) if threshold value is reached, + negative value (-SignalError) on error, + zero (SIGERR_NONE) if no burst is detected +*/ +int detectAnyBurst(const signalVector &burst, + unsigned tsc, + float threshold, + int sps, + CorrType type, + complex &, + float &toa, + unsigned max_toa); + +/** Demodulate burst basde on type and output soft bits */ +SoftVector *demodAnyBurst(const signalVector &burst, int sps, + complex amp, float toa, CorrType type); + +#endif /* SIGPROCLIB_H */ diff --git a/Transceiver52M/signalVector.cpp b/Transceiver52M/signalVector.cpp new file mode 100644 index 0000000..fc8157e --- /dev/null +++ b/Transceiver52M/signalVector.cpp @@ -0,0 +1,107 @@ +#include "signalVector.h" + +signalVector::signalVector(size_t size) + : Vector<complex>(size), + real(false), aligned(false), symmetry(NONE) +{ +} + +signalVector::signalVector(size_t size, size_t start) + : Vector<complex>(size + start), + real(false), aligned(false), symmetry(NONE) +{ + mStart = mData + start; +} + +signalVector::signalVector(complex *data, size_t start, size_t span) + : Vector<complex>(NULL, data + start, data + start + span), + real(false), aligned(false), symmetry(NONE) +{ +} + +signalVector::signalVector(const signalVector &vector) + : Vector<complex>(vector.size() + vector.getStart()), aligned(false) +{ + mStart = mData + vector.getStart(); + vector.copyTo(*this); + symmetry = vector.getSymmetry(); + real = vector.isReal(); +}; + +signalVector::signalVector(const signalVector &vector, + size_t start, size_t tail) + : Vector<complex>(start + vector.size() + tail), aligned(false) +{ + mStart = mData + start; + vector.copyTo(*this); + symmetry = vector.getSymmetry(); + real = vector.isReal(); +}; + +void signalVector::operator=(const signalVector& vector) +{ + resize(vector.size() + vector.getStart()); + + unsigned int i; + complex *dst = mData; + complex *src = vector.mData; + for (i = 0; i < size(); i++, src++, dst++) + *dst = *src; + /* TODO: optimize for non non-trivially copyable types: */ + /*memcpy(mData, vector.mData, bytes()); */ + mStart = mData + vector.getStart(); +} + +signalVector signalVector::segment(size_t start, size_t span) +{ + return signalVector(mData, start, span); +} + +size_t signalVector::getStart() const +{ + return mStart - mData; +} + +size_t signalVector::updateHistory() +{ + size_t num = getStart(); + unsigned int i; + complex *dst = mData; + complex *src = mStart + this->size() - num; + for (i = 0; i < num; i++, src++, dst++) + *dst = *src; + /* TODO: optimize for non non-trivially copyable types: */ + /*memmove(mData, mStart + this->size() - num, num * sizeof(complex)); */ + + return num; +} + +Symmetry signalVector::getSymmetry() const +{ + return symmetry; +} + +void signalVector::setSymmetry(Symmetry symmetry) +{ + this->symmetry = symmetry; +} + +bool signalVector::isReal() const +{ + return real; +} + +void signalVector::isReal(bool wOnly) +{ + real = wOnly; +} + +bool signalVector::isAligned() const +{ + return aligned; +} + +void signalVector::setAligned(bool aligned) +{ + this->aligned = aligned; +} diff --git a/Transceiver52M/signalVector.h b/Transceiver52M/signalVector.h new file mode 100644 index 0000000..83f141e --- /dev/null +++ b/Transceiver52M/signalVector.h @@ -0,0 +1,55 @@ +#ifndef _SIGNALVECTOR_H_ +#define _SIGNALVECTOR_H_ + +#include <Vector.h> +#include <Complex.h> + +/** Vector symmetry */ +enum Symmetry { + NONE = 0, + ABSSYM = 1 +}; + +class signalVector: public Vector<complex> { +public: + /** Default constructor */ + signalVector(size_t size = 0); + + /** Construct with head room */ + signalVector(size_t size, size_t start); + + /** Construct from existing buffer data (buffer not managed) */ + signalVector(complex *data, size_t start, size_t span); + + /** Construct by from existing vector */ + signalVector(const signalVector &vector); + + /** Construct by from existing vector and append head-tail room */ + signalVector(const signalVector &vector, size_t start, size_t tail = 0); + + /** Override base assignment operator to include start offsets */ + void operator=(const signalVector& vector); + + /** Return an alias to a segment of this signalVector. */ + signalVector segment(size_t start, size_t span); + + /** Return head room */ + size_t getStart() const; + size_t updateHistory(); + + Symmetry getSymmetry() const; + void setSymmetry(Symmetry symmetry); + + bool isReal() const; + void isReal(bool real); + + bool isAligned() const; + void setAligned(bool aligned); + +private: + bool real; + bool aligned; + Symmetry symmetry; +}; + +#endif /* _SIGNALVECTOR_H_ */ diff --git a/Transceiver52M/std_inband.rbf b/Transceiver52M/std_inband.rbf Binary files differnew file mode 100755 index 0000000..63842b7 --- /dev/null +++ b/Transceiver52M/std_inband.rbf diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..b2ed160 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,1578 @@ +#!/bin/sh +# a u t o g e n . s h +# +# Copyright (c) 2005-2009 United States Government as represented by +# the U.S. Army Research Laboratory. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of the author may not be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +### +# +# Script for automatically preparing the sources for compilation by +# performing the myrid of necessary steps. The script attempts to +# detect proper version support, and outputs warnings about particular +# systems that have autotool peculiarities. +# +# Basically, if everything is set up and installed correctly, the +# script will validate that minimum versions of the GNU Build System +# tools are installed, account for several common configuration +# issues, and then simply run autoreconf for you. +# +# If autoreconf fails, which can happen for many valid configurations, +# this script proceeds to run manual preparation steps effectively +# providing a POSIX shell script (mostly complete) reimplementation of +# autoreconf. +# +# The AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER +# environment variables and corresponding _OPTIONS variables (e.g. +# AUTORECONF_OPTIONS) may be used to override the default automatic +# detection behaviors. Similarly the _VERSION variables will override +# the minimum required version numbers. +# +# Examples: +# +# To obtain help on usage: +# ./autogen.sh --help +# +# To obtain verbose output: +# ./autogen.sh --verbose +# +# To skip autoreconf and prepare manually: +# AUTORECONF=false ./autogen.sh +# +# To verbosely try running with an older (unsupported) autoconf: +# AUTOCONF_VERSION=2.50 ./autogen.sh --verbose +# +# Author: +# Christopher Sean Morrison <morrison@brlcad.org> +# +# Patches: +# Sebastian Pipping <sebastian@pipping.org> +# +###################################################################### + +# set to minimum acceptible version of autoconf +if [ "x$AUTOCONF_VERSION" = "x" ] ; then + AUTOCONF_VERSION=2.52 +fi +# set to minimum acceptible version of automake +if [ "x$AUTOMAKE_VERSION" = "x" ] ; then + AUTOMAKE_VERSION=1.6.0 +fi +# set to minimum acceptible version of libtool +if [ "x$LIBTOOL_VERSION" = "x" ] ; then + LIBTOOL_VERSION=1.4.2 +fi + + +################## +# ident function # +################## +ident ( ) { + # extract copyright from header + __copyright="`grep Copyright $AUTOGEN_SH | head -${HEAD_N}1 | awk '{print $4}'`" + if [ "x$__copyright" = "x" ] ; then + __copyright="`date +%Y`" + fi + + # extract version from CVS Id string + __id="$Id: autogen.sh 33925 2009-03-01 23:27:06Z brlcad $" + __version="`echo $__id | sed 's/.*\([0-9][0-9][0-9][0-9]\)[-\/]\([0-9][0-9]\)[-\/]\([0-9][0-9]\).*/\1\2\3/'`" + if [ "x$__version" = "x" ] ; then + __version="" + fi + + echo "autogen.sh build preparation script by Christopher Sean Morrison" + echo " + config.guess download patch by Sebastian Pipping (2008-12-03)" + echo "revised 3-clause BSD-style license, copyright (c) $__copyright" + echo "script version $__version, ISO/IEC 9945 POSIX shell script" +} + + +################## +# USAGE FUNCTION # +################## +usage ( ) { + echo "Usage: $AUTOGEN_SH [-h|--help] [-v|--verbose] [-q|--quiet] [-d|--download] [--version]" + echo " --help Help on $NAME_OF_AUTOGEN usage" + echo " --verbose Verbose progress output" + echo " --quiet Quiet suppressed progress output" + echo " --download Download the latest config.guess from gnulib" + echo " --version Only perform GNU Build System version checks" + echo + echo "Description: This script will validate that minimum versions of the" + echo "GNU Build System tools are installed and then run autoreconf for you." + echo "Should autoreconf fail, manual preparation steps will be run" + echo "potentially accounting for several common preparation issues. The" + + echo "AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER," + echo "PROJECT, & CONFIGURE environment variables and corresponding _OPTIONS" + echo "variables (e.g. AUTORECONF_OPTIONS) may be used to override the" + echo "default automatic detection behavior." + echo + + ident + + return 0 +} + + +########################## +# VERSION_ERROR FUNCTION # +########################## +version_error ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided a version" + exit 1 + fi + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided an application name" + exit 1 + fi + $ECHO + $ECHO "ERROR: To prepare the ${PROJECT} build system from scratch," + $ECHO " at least version $1 of $2 must be installed." + $ECHO + $ECHO "$NAME_OF_AUTOGEN does not need to be run on the same machine that will" + $ECHO "run configure or make. Either the GNU Autotools will need to be installed" + $ECHO "or upgraded on this system, or $NAME_OF_AUTOGEN must be run on the source" + $ECHO "code on another system and then transferred to here. -- Cheers!" + $ECHO +} + +########################## +# VERSION_CHECK FUNCTION # +########################## +version_check ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_check was not provided a minimum version" + exit 1 + fi + _min="$1" + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version check was not provided a comparison version" + exit 1 + fi + _cur="$2" + + # needed to handle versions like 1.10 and 1.4-p6 + _min="`echo ${_min}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + _cur="`echo ${_cur}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + + _min_major="`echo $_min | cut -d. -f1`" + _min_minor="`echo $_min | cut -d. -f2`" + _min_patch="`echo $_min | cut -d. -f3`" + + _cur_major="`echo $_cur | cut -d. -f1`" + _cur_minor="`echo $_cur | cut -d. -f2`" + _cur_patch="`echo $_cur | cut -d. -f3`" + + if [ "x$_min_major" = "x" ] ; then + _min_major=0 + fi + if [ "x$_min_minor" = "x" ] ; then + _min_minor=0 + fi + if [ "x$_min_patch" = "x" ] ; then + _min_patch=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_major=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_minor=0 + fi + if [ "x$_cur_patch" = "x" ] ; then + _cur_patch=0 + fi + + $VERBOSE_ECHO "Checking if ${_cur_major}.${_cur_minor}.${_cur_patch} is greater than ${_min_major}.${_min_minor}.${_min_patch}" + + if [ $_min_major -lt $_cur_major ] ; then + return 0 + elif [ $_min_major -eq $_cur_major ] ; then + if [ $_min_minor -lt $_cur_minor ] ; then + return 0 + elif [ $_min_minor -eq $_cur_minor ] ; then + if [ $_min_patch -lt $_cur_patch ] ; then + return 0 + elif [ $_min_patch -eq $_cur_patch ] ; then + return 0 + fi + fi + fi + return 1 +} + + +###################################### +# LOCATE_CONFIGURE_TEMPLATE FUNCTION # +###################################### +locate_configure_template ( ) { + _pwd="`pwd`" + if test -f "./configure.ac" ; then + echo "./configure.ac" + elif test -f "./configure.in" ; then + echo "./configure.in" + elif test -f "$_pwd/configure.ac" ; then + echo "$_pwd/configure.ac" + elif test -f "$_pwd/configure.in" ; then + echo "$_pwd/configure.in" + elif test -f "$PATH_TO_AUTOGEN/configure.ac" ; then + echo "$PATH_TO_AUTOGEN/configure.ac" + elif test -f "$PATH_TO_AUTOGEN/configure.in" ; then + echo "$PATH_TO_AUTOGEN/configure.in" + fi +} + + +################## +# argument check # +################## +ARGS="$*" +PATH_TO_AUTOGEN="`dirname $0`" +NAME_OF_AUTOGEN="`basename $0`" +AUTOGEN_SH="$PATH_TO_AUTOGEN/$NAME_OF_AUTOGEN" + +LIBTOOL_M4="${PATH_TO_AUTOGEN}/misc/libtool.m4" + +if [ "x$HELP" = "x" ] ; then + HELP=no +fi +if [ "x$QUIET" = "x" ] ; then + QUIET=no +fi +if [ "x$VERBOSE" = "x" ] ; then + VERBOSE=no +fi +if [ "x$VERSION_ONLY" = "x" ] ; then + VERSION_ONLY=no +fi +if [ "x$DOWNLOAD" = "x" ] ; then + DOWNLOAD=no +fi +if [ "x$AUTORECONF_OPTIONS" = "x" ] ; then + AUTORECONF_OPTIONS="-i -f" +fi +if [ "x$AUTOCONF_OPTIONS" = "x" ] ; then + AUTOCONF_OPTIONS="-f" +fi +if [ "x$AUTOMAKE_OPTIONS" = "x" ] ; then + AUTOMAKE_OPTIONS="-a -c -f" +fi +ALT_AUTOMAKE_OPTIONS="-a -c" +if [ "x$LIBTOOLIZE_OPTIONS" = "x" ] ; then + LIBTOOLIZE_OPTIONS="--automake -c -f" +fi +ALT_LIBTOOLIZE_OPTIONS="--automake --copy --force" +if [ "x$ACLOCAL_OPTIONS" = "x" ] ; then + ACLOCAL_OPTIONS="" +fi +if [ "x$AUTOHEADER_OPTIONS" = "x" ] ; then + AUTOHEADER_OPTIONS="" +fi +if [ "x$CONFIG_GUESS_URL" = "x" ] ; then + CONFIG_GUESS_URL="http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=build-aux/config.guess;hb=HEAD" +fi +for arg in $ARGS ; do + case "x$arg" in + x--help) HELP=yes ;; + x-[hH]) HELP=yes ;; + x--quiet) QUIET=yes ;; + x-[qQ]) QUIET=yes ;; + x--verbose) VERBOSE=yes ;; + x-[dD]) DOWNLOAD=yes ;; + x--download) DOWNLOAD=yes ;; + x-[vV]) VERBOSE=yes ;; + x--version) VERSION_ONLY=yes ;; + *) + echo "Unknown option: $arg" + echo + usage + exit 1 + ;; + esac +done + + +##################### +# environment check # +##################### + +# sanity check before recursions potentially begin +if [ ! -f "$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: $AUTOGEN_SH does not exist" + if [ ! "x$0" = "x$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $AUTOGEN_SH" + fi + exit 1 +fi + +# force locale setting to C so things like date output as expected +LC_ALL=C + +# commands that this script expects +for __cmd in echo head tail pwd ; do + echo "test" | $__cmd > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "INTERNAL ERROR: '${__cmd}' command is required" + exit 2 + fi +done +echo "test" | grep "test" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: grep command is required" + exit 1 +fi +echo "test" | sed "s/test/test/" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: sed command is required" + exit 1 +fi + + +# determine the behavior of echo +case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in + *c*,-n*) ECHO_N= ECHO_C=' +' ECHO_T=' ' ;; + *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; + *) ECHO_N= ECHO_C='\c' ECHO_T= ;; +esac + +# determine the behavior of head +case "x`echo 'head' | head -n 1 2>&1`" in + *xhead*) HEAD_N="n " ;; + *) HEAD_N="" ;; +esac + +# determine the behavior of tail +case "x`echo 'tail' | tail -n 1 2>&1`" in + *xtail*) TAIL_N="n " ;; + *) TAIL_N="" ;; +esac + +VERBOSE_ECHO=: +ECHO=: +if [ "x$QUIET" = "xyes" ] ; then + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output quelled by quiet option. Further output disabled." + fi +else + ECHO=echo + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output enabled" + VERBOSE_ECHO=echo + fi +fi + + +# allow a recursive run to disable further recursions +if [ "x$RUN_RECURSIVE" = "x" ] ; then + RUN_RECURSIVE=yes +fi + + +################################################ +# check for help arg and bypass version checks # +################################################ +if [ "x`echo $ARGS | sed 's/.*[hH][eE][lL][pP].*/help/'`" = "xhelp" ] ; then + HELP=yes +fi +if [ "x$HELP" = "xyes" ] ; then + usage + $ECHO "---" + $ECHO "Help was requested. No preparation or configuration will be performed." + exit 0 +fi + + +####################### +# set up signal traps # +####################### +untrap_abnormal ( ) { + for sig in 1 2 13 15; do + trap - $sig + done +} + +# do this cleanup whenever we exit. +trap ' + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # restore/delete backup files + if test "x$PFC_INIT" = "x1" ; then + recursive_restore + fi +' 0 + +# trap SIGHUP (1), SIGINT (2), SIGPIPE (13), SIGTERM (15) +for sig in 1 2 13 15; do + trap ' + $ECHO "" + $ECHO "Aborting $NAME_OF_AUTOGEN: caught signal '$sig'" + + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # clean up on abnormal exit + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + + if test -f "acinclude.m4.$$.backup" ; then + $VERBOSE_ECHO "cat acinclude.m4.$$.backup > acinclude.m4" + chmod u+w acinclude.m4 + cat acinclude.m4.$$.backup > acinclude.m4 + + $VERBOSE_ECHO "rm -f acinclude.m4.$$.backup" + rm -f acinclude.m4.$$.backup + fi + + { (exit 1); exit 1; } +' $sig +done + + +############################# +# look for a configure file # +############################# +if [ "x$CONFIGURE" = "x" ] ; then + CONFIGURE="`locate_configure_template`" + if [ ! "x$CONFIGURE" = "x" ] ; then + $VERBOSE_ECHO "Found a configure template: $CONFIGURE" + fi +else + $ECHO "Using CONFIGURE environment variable override: $CONFIGURE" +fi +if [ "x$CONFIGURE" = "x" ] ; then + if [ "x$VERSION_ONLY" = "xyes" ] ; then + CONFIGURE=/dev/null + else + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi +fi + +#################### +# get project name # +#################### +if [ "x$PROJECT" = "x" ] ; then + PROJECT="`grep AC_INIT $CONFIGURE | grep -v '.*#.*AC_INIT' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_INIT(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if [ "x$PROJECT" = "xAC_INIT" ] ; then + # projects might be using the older/deprecated arg-less AC_INIT .. look for AM_INIT_AUTOMAKE instead + PROJECT="`grep AM_INIT_AUTOMAKE $CONFIGURE | grep -v '.*#.*AM_INIT_AUTOMAKE' | tail -${TAIL_N}1 | sed 's/^[ ]*AM_INIT_AUTOMAKE(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + fi + if [ "x$PROJECT" = "xAM_INIT_AUTOMAKE" ] ; then + PROJECT="project" + fi + if [ "x$PROJECT" = "x" ] ; then + PROJECT="project" + fi +else + $ECHO "Using PROJECT environment variable override: $PROJECT" +fi +$ECHO "Preparing the $PROJECT build system...please wait" +$ECHO + + +######################## +# check for autoreconf # +######################## +HAVE_AUTORECONF=no +if [ "x$AUTORECONF" = "x" ] ; then + for AUTORECONF in autoreconf ; do + $VERBOSE_ECHO "Checking autoreconf version: $AUTORECONF --version" + $AUTORECONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + HAVE_AUTORECONF=yes + break + fi + done +else + HAVE_AUTORECONF=yes + $ECHO "Using AUTORECONF environment variable override: $AUTORECONF" +fi + + +########################## +# autoconf version check # +########################## +_acfound=no +if [ "x$AUTOCONF" = "x" ] ; then + for AUTOCONF in autoconf ; do + $VERBOSE_ECHO "Checking autoconf version: $AUTOCONF --version" + $AUTOCONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _acfound=yes + break + fi + done +else + _acfound=yes + $ECHO "Using AUTOCONF environment variable override: $AUTOCONF" +fi + +_report_error=no +if [ ! "x$_acfound" = "xyes" ] ; then + $ECHO "ERROR: Unable to locate GNU Autoconf." + _report_error=yes +else + _version="`$AUTOCONF --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Autoconf version $_version" + version_check "$AUTOCONF_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOCONF_VERSION" "GNU Autoconf" + exit 1 +fi + + +########################## +# automake version check # +########################## +_amfound=no +if [ "x$AUTOMAKE" = "x" ] ; then + for AUTOMAKE in automake ; do + $VERBOSE_ECHO "Checking automake version: $AUTOMAKE --version" + $AUTOMAKE --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _amfound=yes + break + fi + done +else + _amfound=yes + $ECHO "Using AUTOMAKE environment variable override: $AUTOMAKE" +fi + + +_report_error=no +if [ ! "x$_amfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Automake." + _report_error=yes +else + _version="`$AUTOMAKE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Automake version $_version" + version_check "$AUTOMAKE_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOMAKE_VERSION" "GNU Automake" + exit 1 +fi + + +######################## +# check for libtoolize # +######################## +HAVE_LIBTOOLIZE=yes +HAVE_ALT_LIBTOOLIZE=no +_ltfound=no +if [ "x$LIBTOOLIZE" = "x" ] ; then + LIBTOOLIZE=libtoolize + $VERBOSE_ECHO "Checking libtoolize version: $LIBTOOLIZE --version" + $LIBTOOLIZE --version > /dev/null 2>&1 + if [ ! $? = 0 ] ; then + HAVE_LIBTOOLIZE=no + $ECHO + if [ "x$HAVE_AUTORECONF" = "xno" ] ; then + $ECHO "Warning: libtoolize does not appear to be available." + else + $ECHO "Warning: libtoolize does not appear to be available. This means that" + $ECHO "the automatic build preparation via autoreconf will probably not work." + $ECHO "Preparing the build by running each step individually, however, should" + $ECHO "work and will be done automatically for you if autoreconf fails." + fi + + # look for some alternates + for tool in glibtoolize libtoolize15 libtoolize14 libtoolize13 ; do + $VERBOSE_ECHO "Checking libtoolize alternate: $tool --version" + _glibtoolize="`$tool --version > /dev/null 2>&1`" + if [ $? = 0 ] ; then + $VERBOSE_ECHO "Found $tool --version" + _glti="`which $tool`" + if [ "x$_glti" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool with which" + continue; + fi + if test ! -f "$_glti" ; then + $VERBOSE_ECHO "Cannot use $tool, $_glti is not a file" + continue; + fi + _gltidir="`dirname $_glti`" + if [ "x$_gltidir" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool path with dirname of $_glti" + continue; + fi + if test ! -d "$_gltidir" ; then + $VERBOSE_ECHO "Cannot use $tool, $_gltidir is not a directory" + continue; + fi + HAVE_ALT_LIBTOOLIZE=yes + LIBTOOLIZE="$tool" + $ECHO + $ECHO "Fortunately, $tool was found which means that your system may simply" + $ECHO "have a non-standard or incomplete GNU Autotools install. If you have" + $ECHO "sufficient system access, it may be possible to quell this warning by" + $ECHO "running:" + $ECHO + sudo -V > /dev/null 2>&1 + if [ $? = 0 ] ; then + $ECHO " sudo ln -s $_glti $_gltidir/libtoolize" + $ECHO + else + $ECHO " ln -s $_glti $_gltidir/libtoolize" + $ECHO + $ECHO "Run that as root or with proper permissions to the $_gltidir directory" + $ECHO + fi + _ltfound=yes + break + fi + done + else + _ltfound=yes + fi +else + _ltfound=yes + $ECHO "Using LIBTOOLIZE environment variable override: $LIBTOOLIZE" +fi + + +############################ +# libtoolize version check # +############################ +_report_error=no +if [ ! "x$_ltfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Libtool." + _report_error=yes +else + _version="`$LIBTOOLIZE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Libtool version $_version" + version_check "$LIBTOOL_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$LIBTOOL_VERSION" "GNU Libtool" + exit 1 +fi + + +##################### +# check for aclocal # +##################### +if [ "x$ACLOCAL" = "x" ] ; then + for ACLOCAL in aclocal ; do + $VERBOSE_ECHO "Checking aclocal version: $ACLOCAL --version" + $ACLOCAL --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using ACLOCAL environment variable override: $ACLOCAL" +fi + + +######################## +# check for autoheader # +######################## +if [ "x$AUTOHEADER" = "x" ] ; then + for AUTOHEADER in autoheader ; do + $VERBOSE_ECHO "Checking autoheader version: $AUTOHEADER --version" + $AUTOHEADER --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using AUTOHEADER environment variable override: $AUTOHEADER" +fi + + +######################### +# check if version only # +######################### +$VERBOSE_ECHO "Checking whether to only output version information" +if [ "x$VERSION_ONLY" = "xyes" ] ; then + $ECHO + ident + $ECHO "---" + $ECHO "Version requested. No preparation or configuration will be performed." + exit 0 +fi + + +################################# +# PROTECT_FROM_CLOBBER FUNCTION # +################################# +protect_from_clobber ( ) { + PFC_INIT=1 + + # protect COPYING & INSTALL from overwrite by automake. the + # automake force option will (inappropriately) ignore the existing + # contents of a COPYING and/or INSTALL files (depending on the + # version) instead of just forcing *missing* files like it does + # for AUTHORS, NEWS, and README. this is broken but extremely + # prevalent behavior, so we protect against it by keeping a backup + # of the file that can later be restored. + + for file in COPYING INSTALL ; do + if test -f ${file} ; then + if test -f ${file}.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "Already backed up ${file} in `pwd`" + else + $VERBOSE_ECHO "Backing up ${file} in `pwd`" + $VERBOSE_ECHO "cp -p ${file} ${file}.$$.protect_from_automake.backup" + cp -p ${file} ${file}.$$.protect_from_automake.backup + fi + fi + done +} + + +############################## +# RECURSIVE_PROTECT FUNCTION # +############################## +recursive_protect ( ) { + + # for projects using recursive configure, run the build + # preparation steps for the subdirectories. this function assumes + # START_PATH was set to pwd before recursion begins so that + # relative paths work. + + # git 'r done, protect COPYING and INSTALL from being clobbered + protect_from_clobber + + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + # $VERBOSE_ECHO "Looking for configure template found `pwd`/$_configure" + + # look for subdirs + # $VERBOSE_ECHO "Looking for subdirs in `pwd`" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Protecting files from automake in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r done + recursive_protect + done + fi +} # end of recursive_protect + + +############################# +# RESTORE_CLOBBERED FUNCION # +############################# +restore_clobbered ( ) { + + # The automake (and autoreconf by extension) -f/--force-missing + # option may overwrite COPYING and INSTALL even if they do exist. + # Here we restore the files if necessary. + + spacer=no + + for file in COPYING INSTALL ; do + if test -f ${file}.$$.protect_from_automake.backup ; then + if test -f ${file} ; then + # compare entire content, restore if needed + if test "x`cat ${file}`" != "x`cat ${file}.$$.protect_from_automake.backup`" ; then + if test "x$spacer" = "xno" ; then + $VERBOSE_ECHO + spacer=yes + fi + # restore the backup + $VERBOSE_ECHO "Restoring ${file} from backup (automake -f likely clobbered it)" + $VERBOSE_ECHO "rm -f ${file}" + rm -f ${file} + $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" + mv ${file}.$$.protect_from_automake.backup ${file} + fi # check contents + elif test -f ${file}.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" + mv ${file}.$$.protect_from_automake.backup ${file} + fi # -f ${file} + + # just in case + $VERBOSE_ECHO "rm -f ${file}.$$.protect_from_automake.backup" + rm -f ${file}.$$.protect_from_automake.backup + fi # -f ${file}.$$.protect_from_automake.backup + done + + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + return + fi + + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + fi + + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "rm -f \"${_aux_dir}/${file}.backup\"" + rm -f "${_aux_dir}/${file}.backup" + fi + done +} # end of restore_clobbered + + +############################## +# RECURSIVE_RESTORE FUNCTION # +############################## +recursive_restore ( ) { + + # restore COPYING and INSTALL from backup if they were clobbered + # for each directory recursively. + + # git 'r undone + restore_clobbered + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + + # look for subdirs + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Checking files for automake damage in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r undone + recursive_restore + done + fi +} # end of recursive_restore + + +####################### +# INITIALIZE FUNCTION # +####################### +initialize ( ) { + + # this routine performs a variety of directory-specific + # initializations. some are sanity checks, some are preventive, + # and some are necessary setup detection. + # + # this function sets: + # CONFIGURE + # SEARCH_DIRS + # CONFIG_SUBDIRS + + ################################## + # check for a configure template # + ################################## + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi + + ##################### + # detect an aux dir # + ##################### + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + else + $VERBOSE_ECHO "Detected auxillary directory: $_aux_dir" + fi + + ################################ + # detect a recursive configure # + ################################ + CONFIG_SUBDIRS="" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $CONFIGURE | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + $VERBOSE_ECHO "Detected recursive configure directory: `pwd`/$dir" + CONFIG_SUBDIRS="$CONFIG_SUBDIRS `pwd`/$dir" + fi + done + + ########################################################### + # make sure certain required files exist for GNU projects # + ########################################################### + _marker_found="" + _marker_found_message_intro='Detected non-GNU marker "' + _marker_found_message_mid='" in ' + for marker in foreign cygnus ; do + _marker_found_message=${_marker_found_message_intro}${marker}${_marker_found_message_mid} + _marker_found="`grep 'AM_INIT_AUTOMAKE.*'${marker} $CONFIGURE`" + if [ ! "x$_marker_found" = "x" ] ; then + $VERBOSE_ECHO "${_marker_found_message}`basename \"$CONFIGURE\"`" + break + fi + if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then + _marker_found="`grep 'AUTOMAKE_OPTIONS.*'${marker} Makefile.am`" + if [ ! "x$_marker_found" = "x" ] ; then + $VERBOSE_ECHO "${_marker_found_message}Makefile.am" + break + fi + fi + done + if [ "x${_marker_found}" = "x" ] ; then + _suggest_foreign=no + for file in AUTHORS COPYING ChangeLog INSTALL NEWS README ; do + if [ ! -f $file ] ; then + $VERBOSE_ECHO "Touching ${file} since it does not exist" + _suggest_foreign=yes + touch $file + fi + done + + if [ "x${_suggest_foreign}" = "xyes" ] ; then + $ECHO + $ECHO "Warning: Several files expected of projects that conform to the GNU" + $ECHO "coding standards were not found. The files were automatically added" + $ECHO "for you since you do not have a 'foreign' declaration specified." + $ECHO + $ECHO "Considered adding 'foreign' to AM_INIT_AUTOMAKE in `basename \"$CONFIGURE\"`" + if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then + $ECHO "or to AUTOMAKE_OPTIONS in your top-level Makefile.am file." + fi + $ECHO + fi + fi + + ################################################## + # make sure certain generated files do not exist # + ################################################## + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "mv -f \"${_aux_dir}/${file}\" \"${_aux_dir}/${file}.backup\"" + mv -f "${_aux_dir}/${file}" "${_aux_dir}/${file}.backup" + fi + done + + ############################ + # search alternate m4 dirs # + ############################ + SEARCH_DIRS="" + for dir in m4 ; do + if [ -d $dir ] ; then + $VERBOSE_ECHO "Found extra aclocal search directory: $dir" + SEARCH_DIRS="$SEARCH_DIRS -I $dir" + fi + done + + ###################################### + # remove any previous build products # + ###################################### + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi +# tcl/tk (and probably others) have a customized aclocal.m4, so can't delete it +# if test -f aclocal.m4 ; then +# $VERBOSE_ECHO "Found an aclocal.m4 file, deleting it" +# $VERBOSE_ECHO "rm -f aclocal.m4" +# rm -f aclocal.m4 +# fi + +} # end of initialize() + + +############## +# initialize # +############## + +# stash path +START_PATH="`pwd`" + +# Before running autoreconf or manual steps, some prep detection work +# is necessary or useful. Only needs to occur once per directory, but +# does need to traverse the entire subconfigure hierarchy to protect +# files from being clobbered even by autoreconf. +recursive_protect + +# start from where we started +cd "$START_PATH" + +# get ready to process +initialize + + +######################################### +# DOWNLOAD_GNULIB_CONFIG_GUESS FUNCTION # +######################################### + +# TODO - should make sure wget/curl exist and/or work before trying to +# use them. + +download_gnulib_config_guess () { + # abuse gitweb to download gnulib's latest config.guess via HTTP + config_guess_temp="config.guess.$$.download" + ret=1 + for __cmd in wget curl fetch ; do + $VERBOSE_ECHO "Checking for command ${__cmd}" + ${__cmd} --version > /dev/null 2>&1 + ret=$? + if [ ! $ret = 0 ] ; then + continue + fi + + __cmd_version=`${__cmd} --version | head -n 1 | sed -e 's/^[^0-9]\+//' -e 's/ .*//'` + $VERBOSE_ECHO "Found ${__cmd} ${__cmd_version}" + + opts="" + case ${__cmd} in + wget) + opts="-O" + ;; + curl) + opts="-o" + ;; + fetch) + opts="-t 5 -f" + ;; + esac + + $VERBOSE_ECHO "Running $__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" + eval "$__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" > /dev/null 2>&1 + if [ $? = 0 ] ; then + mv -f "${config_guess_temp}" ${_aux_dir}/config.guess + ret=0 + break + fi + done + + if [ ! $ret = 0 ] ; then + $ECHO "Warning: config.guess download failed from: $CONFIG_GUESS_URL" + rm -f "${config_guess_temp}" + fi +} + + +############################## +# LIBTOOLIZE_NEEDED FUNCTION # +############################## +libtoolize_needed () { + ret=1 # means no, don't need libtoolize + for feature in AC_PROG_LIBTOOL AM_PROG_LIBTOOL LT_INIT ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + ret=0 # means yes, need to run libtoolize + break + fi + done + return ${ret} +} + + + +############################################ +# prepare build via autoreconf or manually # +############################################ +reconfigure_manually=no +if [ "x$HAVE_AUTORECONF" = "xyes" ] ; then + $ECHO + $ECHO $ECHO_N "Automatically preparing build ... $ECHO_C" + + $VERBOSE_ECHO "$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS" + autoreconf_output="`$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoreconf_output" + + if [ ! $ret = 0 ] ; then + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + if [ ! "x`echo \"$autoreconf_output\" | grep libtoolize | grep \"No such file or directory\"`" = "x" ] ; then + $ECHO + $ECHO "Warning: autoreconf failed but due to what is usually a common libtool" + $ECHO "misconfiguration issue. This problem is encountered on systems that" + $ECHO "have installed libtoolize under a different name without providing a" + $ECHO "symbolic link or without setting the LIBTOOLIZE environment variable." + $ECHO + $ECHO "Restarting the preparation steps with LIBTOOLIZE set to $LIBTOOLIZE" + + export LIBTOOLIZE + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi + + $ECHO "Warning: $AUTORECONF failed" + + if test -f ltmain.sh ; then + $ECHO "libtoolize being run by autoreconf is not creating ltmain.sh in the auxillary directory like it should" + fi + + $ECHO "Attempting to run the preparation steps individually" + reconfigure_manually=yes + else + if [ "x$DOWNLOAD" = "xyes" ] ; then + if libtoolize_needed ; then + download_gnulib_config_guess + fi + fi + fi +else + reconfigure_manually=yes +fi + + +############################ +# LIBTOOL_FAILURE FUNCTION # +############################ +libtool_failure ( ) { + + # libtool is rather error-prone in comparison to the other + # autotools and this routine attempts to compensate for some + # common failures. the output after a libtoolize failure is + # parsed for an error related to AC_PROG_LIBTOOL and if found, we + # attempt to inject a project-provided libtool.m4 file. + + _autoconf_output="$1" + + if [ "x$RUN_RECURSIVE" = "xno" ] ; then + # we already tried the libtool.m4, don't try again + return 1 + fi + + if test -f "$LIBTOOL_M4" ; then + found_libtool="`$ECHO $_autoconf_output | grep AC_PROG_LIBTOOL`" + if test ! "x$found_libtool" = "x" ; then + if test -f acinclude.m4 ; then + rm -f acinclude.m4.$$.backup + $VERBOSE_ECHO "cat acinclude.m4 > acinclude.m4.$$.backup" + cat acinclude.m4 > acinclude.m4.$$.backup + fi + $VERBOSE_ECHO "cat \"$LIBTOOL_M4\" >> acinclude.m4" + chmod u+w acinclude.m4 + cat "$LIBTOOL_M4" >> acinclude.m4 + + # don't keep doing this + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $ECHO + $ECHO "Restarting the preparation steps with libtool macros in acinclude.m4" + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi +} + + +########################### +# MANUAL_AUTOGEN FUNCTION # +########################### +manual_autogen ( ) { + + ################################################## + # Manual preparation steps taken are as follows: # + # aclocal [-I m4] # + # libtoolize --automake -c -f # + # aclocal [-I m4] # + # autoconf -f # + # autoheader # + # automake -a -c -f # + ################################################## + + ########### + # aclocal # + ########### + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $ACLOCAL failed" && exit 2 ; fi + + ############## + # libtoolize # + ############## + if libtoolize_needed ; then + if [ "x$HAVE_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + else + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + fi + fi + + ########### + # aclocal # + ########### + # re-run again as instructed by libtoolize + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + + # libtoolize might put ltmain.sh in the wrong place + if test -f ltmain.sh ; then + if test ! -f "${_aux_dir}/ltmain.sh" ; then + $ECHO + $ECHO "Warning: $LIBTOOLIZE is creating ltmain.sh in the wrong directory" + $ECHO + $ECHO "Fortunately, the problem can be worked around by simply copying the" + $ECHO "file to the appropriate location (${_aux_dir}/). This has been done for you." + $ECHO + $VERBOSE_ECHO "cp -p ltmain.sh \"${_aux_dir}/ltmain.sh\"" + cp -p ltmain.sh "${_aux_dir}/ltmain.sh" + $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" + fi + fi # ltmain.sh + + if [ "x$DOWNLOAD" = "xyes" ] ; then + download_gnulib_config_guess + fi + fi # libtoolize_needed + + ############ + # autoconf # + ############ + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF $AUTOCONF_OPTIONS" + autoconf_output="`$AUTOCONF $AUTOCONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # retry without the -f and check for usage of macros that are too new + ac2_59_macros="AC_C_RESTRICT AC_INCLUDES_DEFAULT AC_LANG_ASSERT AC_LANG_WERROR AS_SET_CATFILE" + ac2_55_macros="AC_COMPILER_IFELSE AC_FUNC_MBRTOWC AC_HEADER_STDBOOL AC_LANG_CONFTEST AC_LANG_SOURCE AC_LANG_PROGRAM AC_LANG_CALL AC_LANG_FUNC_TRY_LINK AC_MSG_FAILURE AC_PREPROC_IFELSE" + ac2_54_macros="AC_C_BACKSLASH_A AC_CONFIG_LIBOBJ_DIR AC_GNU_SOURCE AC_PROG_EGREP AC_PROG_FGREP AC_REPLACE_FNMATCH AC_FUNC_FNMATCH_GNU AC_FUNC_REALLOC AC_TYPE_MBSTATE_T" + + macros_to_search="" + ac_major="`echo ${AUTOCONF_VERSION}. | cut -d. -f1 | sed 's/[^0-9]//g'`" + ac_minor="`echo ${AUTOCONF_VERSION}. | cut -d. -f2 | sed 's/[^0-9]//g'`" + + if [ $ac_major -lt 2 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + else + if [ $ac_minor -lt 54 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + elif [ $ac_minor -lt 55 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros" + elif [ $ac_minor -lt 59 ] ; then + macros_to_search="$ac2_59_macros" + fi + fi + + configure_ac_macros=__none__ + for feature in $macros_to_search ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + if [ "x$configure_ac_macros" = "x__none__" ] ; then + configure_ac_macros="$feature" + else + configure_ac_macros="$feature $configure_ac_macros" + fi + fi + done + if [ ! "x$configure_ac_macros" = "x__none__" ] ; then + $ECHO + $ECHO "Warning: Unsupported macros were found in $CONFIGURE" + $ECHO + $ECHO "The `basename \"$CONFIGURE\"` file was scanned in order to determine if any" + $ECHO "unsupported macros are used that exceed the minimum version" + $ECHO "settings specified within this file. As such, the following macros" + $ECHO "should be removed from configure.ac or the version numbers in this" + $ECHO "file should be increased:" + $ECHO + $ECHO "$configure_ac_macros" + $ECHO + $ECHO $ECHO_N "Ignorantly continuing build preparation ... $ECHO_C" + fi + + ################### + # autoconf, retry # + ################### + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF" + autoconf_output="`$AUTOCONF 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # test if libtool is busted + libtool_failure "$autoconf_output" + + # let the user know what went wrong + cat <<EOF +$autoconf_output +EOF + $ECHO "ERROR: $AUTOCONF failed" + exit 2 + else + # autoconf sans -f and possibly sans unsupported options succeed so warn verbosely + $ECHO + $ECHO "Warning: autoconf seems to have succeeded by removing the following options:" + $ECHO " AUTOCONF_OPTIONS=\"$AUTOCONF_OPTIONS\"" + $ECHO + $ECHO "Removing those options should not be necessary and indicate some other" + $ECHO "problem with the build system. The build preparation is highly suspect" + $ECHO "and may result in configuration or compilation errors. Consider" + if [ "x$VERBOSE_ECHO" = "x:" ] ; then + $ECHO "rerunning the build preparation with verbose output enabled." + $ECHO " $AUTOGEN_SH --verbose" + else + $ECHO "reviewing the minimum GNU Autotools version settings contained in" + $ECHO "this script along with the macros being used in your `basename \"$CONFIGURE\"` file." + fi + $ECHO + $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" + fi # autoconf ret = 0 + fi # autoconf ret = 0 + + ############## + # autoheader # + ############## + need_autoheader=no + for feature in AM_CONFIG_HEADER AC_CONFIG_HEADER ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + need_autoheader=yes + break + fi + done + if [ "x$need_autoheader" = "xyes" ] ; then + $VERBOSE_ECHO "$AUTOHEADER $AUTOHEADER_OPTIONS" + autoheader_output="`$AUTOHEADER $AUTOHEADER_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoheader_output" + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $AUTOHEADER failed" && exit 2 ; fi + fi # need_autoheader + + ############ + # automake # + ############ + need_automake=no + for feature in AM_INIT_AUTOMAKE ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + need_automake=yes + break + fi + done + + if [ "x$need_automake" = "xyes" ] ; then + $VERBOSE_ECHO "$AUTOMAKE $AUTOMAKE_OPTIONS" + automake_output="`$AUTOMAKE $AUTOMAKE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$automake_output" + + if [ ! $ret = 0 ] ; then + + ################### + # automake, retry # + ################### + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOMAKE $ALT_AUTOMAKE_OPTIONS" + # retry without the -f + automake_output="`$AUTOMAKE $ALT_AUTOMAKE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$automake_output" + + if [ ! $ret = 0 ] ; then + # test if libtool is busted + libtool_failure "$automake_output" + + # let the user know what went wrong + cat <<EOF +$automake_output +EOF + $ECHO "ERROR: $AUTOMAKE failed" + exit 2 + fi # automake retry + fi # automake ret = 0 + fi # need_automake +} # end of manual_autogen + + +##################################### +# RECURSIVE_MANUAL_AUTOGEN FUNCTION # +##################################### +recursive_manual_autogen ( ) { + + # run the build preparation steps manually for this directory + manual_autogen + + # for projects using recursive configure, run the build + # preparation steps for the subdirectories. + if [ ! "x$CONFIG_SUBDIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively configuring the following directories:" + $VERBOSE_ECHO " $CONFIG_SUBDIRS" + for dir in $CONFIG_SUBDIRS ; do + $VERBOSE_ECHO "Processing recursive configure in $dir" + cd "$START_PATH" + cd "$dir" + + # new directory, prepare + initialize + + # run manual steps for the subdir and any others below + recursive_manual_autogen + done + fi +} + + +################################ +# run manual preparation steps # +################################ +if [ "x$reconfigure_manually" = "xyes" ] ; then + $ECHO + $ECHO $ECHO_N "Preparing build ... $ECHO_C" + + recursive_manual_autogen +fi + + +######################### +# restore and summarize # +######################### +cd "$START_PATH" + +# restore COPYING and INSTALL from backup if necessary +recursive_restore + +# make sure we end up with a configure script +config_ac="`locate_configure_template`" +config="`echo $config_ac | sed 's/\.ac$//' | sed 's/\.in$//'`" +if [ "x$config" = "x" ] ; then + $VERBOSE_ECHO "Could not locate the configure template (from `pwd`)" +fi + +# summarize +$ECHO "done" +$ECHO +if test "x$config" = "x" -o ! -f "$config" ; then + $ECHO "WARNING: The $PROJECT build system should now be prepared but there" + $ECHO "does not seem to be a resulting configure file. This is unexpected" + $ECHO "and likely the result of an error. You should run $NAME_OF_AUTOGEN" + $ECHO "with the --verbose option to get more details on a potential" + $ECHO "misconfiguration." +else + $ECHO "The $PROJECT build system is now prepared. To build here, run:" + $ECHO " $config" + $ECHO " make" +fi + + +# Local Variables: +# mode: sh +# tab-width: 8 +# sh-basic-offset: 4 +# sh-indentation: 4 +# indent-tabs-mode: t +# End: +# ex: shiftwidth=4 tabstop=8 diff --git a/config/ax_check_compile_flag.m4 b/config/ax_check_compile_flag.m4 new file mode 100644 index 0000000..c3a8d69 --- /dev/null +++ b/config/ax_check_compile_flag.m4 @@ -0,0 +1,72 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# 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/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/config/ax_cxx_compile_stdcxx.m4 b/config/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..5032bba --- /dev/null +++ b/config/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,982 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> +# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com> +# Copyright (c) 2015 Paul Norman <penorman@mac.com> +# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu> +# Copyright (c) 2016 Krzesimir Nowak <qdlacz@gmail.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 7 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AX_REQUIRE_DEFINED([AC_MSG_WARN]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) + m4_if([$1], [17], [AC_MSG_WARN([C++17 is not yet standardized, so the checks may change in incompatible ways anytime])]) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual void f() {} + }; + + struct Derived : public Base + { + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check<void> single_type; + typedef check<check<void>> double_type; + typedef check<check<check<void>>> triple_type; + typedef check<check<check<check<void>>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same<T, T> + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same<int, decltype(0)>::value == true, ""); + static_assert(is_same<int, decltype(c)>::value == false, ""); + static_assert(is_same<int, decltype(v)>::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same<int, decltype(ac)>::value == true, ""); + static_assert(is_same<int, decltype(av)>::value == true, ""); + static_assert(is_same<int, decltype(sumi)>::value == true, ""); + static_assert(is_same<int, decltype(sumf)>::value == false, ""); + static_assert(is_same<int, decltype(add(c, v))>::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template <int...> + struct sum; + + template <int N0, int... N1toN> + struct sum<N0, N1toN...> + { + static constexpr auto value = N0 + sum<N1toN...>::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template<typename T> + using member = typename T::member_type; + + template<typename T> + void func(...) {} + + template<typename T> + void func(member<T>*) {} + + void test(); + + void test() { func<foo>(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same<T, T> + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same<int, decltype(f(x))>::value, ""); + static_assert(is_same<int&, decltype(g(x))>::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus <= 201402L + +#error "This is not a C++17 compiler" + +#else + +#if defined(__clang__) + #define REALLY_CLANG +#else + #if defined(__GNUC__) + #define REALLY_GCC + #endif +#endif + +#include <initializer_list> +#include <utility> +#include <type_traits> + +namespace cxx17 +{ + +#if !defined(REALLY_CLANG) + namespace test_constexpr_lambdas + { + + // TODO: test it with clang++ from git + + constexpr int foo = [](){return 42;}(); + + } +#endif // !defined(REALLY_CLANG) + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template<typename... Args> + int multiply(Args... args) + { + return (args * ... * 1); + } + + template<typename... Args> + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same<std::initializer_list<int>, decltype(foo)>::value); + static_assert(std::is_same<int, decltype(bar)>::value); + } + + namespace test_typename_in_template_template_parameter + { + + template<template<typename> typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template <bool cond> + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + +#if !defined(REALLY_CLANG) + namespace test_template_argument_deduction_for_class_templates + { + + // TODO: test it with clang++ from git + + template <typename T1, typename T2> + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } +#endif // !defined(REALLY_CLANG) + + namespace test_non_type_auto_template_parameters + { + + template <auto n> + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + +#if !defined(REALLY_CLANG) + namespace test_structured_bindings + { + + // TODO: test it with clang++ from git + + int arr[2] = { 1, 2 }; + std::pair<int, int> pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair<int, int>& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } +#endif // !defined(REALLY_CLANG) + +#if !defined(REALLY_CLANG) + namespace test_exception_spec_type_system + { + + // TODO: test it with clang++ from git + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template<typename T> + Bad + f(T*, T*); + + template<typename T1, typename T2> + Good + f(T1*, T2*); + + static_assert (std::is_same_v<Good, decltype(f(g1, g2))>); + + } +#endif // !defined(REALLY_CLANG) + + namespace test_inline_variables + { + + template<class T> void f(T) + {} + + template<class T> inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus <= 201402L + +]]) diff --git a/config/ax_cxx_compile_stdcxx_11.m4 b/config/ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 0000000..1733fd8 --- /dev/null +++ b/config/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,39 @@ +# ============================================================================= +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================= +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXX and CXXCPP to enable +# support. +# +# This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX +# macro with the version set to C++11. The two optional arguments are +# forwarded literally as the second and third argument respectively. +# Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for +# more information. If you want to use this macro, you also need to +# download the ax_cxx_compile_stdcxx.m4 file. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> +# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com> +# Copyright (c) 2015 Paul Norman <penorman@mac.com> +# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 18 + +AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [AX_CXX_COMPILE_STDCXX([11], [$1], [$2])]) diff --git a/config/ax_sse.m4 b/config/ax_sse.m4 new file mode 100644 index 0000000..de97124 --- /dev/null +++ b/config/ax_sse.m4 @@ -0,0 +1,71 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_ext.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_SSE +# +# DESCRIPTION +# +# Find SIMD extensions supported by compiler. The -m"simdextensionname" is +# added to SIMD_FLAGS if compiler supports it. For example, if "sse2" is +# available, then "-msse2" is added to SIMD_FLAGS. +# +# This macro calls: +# +# AC_SUBST(SIMD_FLAGS) +# +# And defines: +# +# HAVE_SSE3 / HAVE_SSE4.1 +# +# LICENSE +# +# Copyright (c) 2007 Christophe Tournayre <turn3r@users.sourceforge.net> +# Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. +# +# NOTE: The functionality that requests the cpuid has been stripped because +# this project detects the CPU capabilities during runtime. However, we +# still need to check if the compiler supports the requested SIMD flag + +#serial 12 + +AC_DEFUN([AX_SSE], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) + + AM_CONDITIONAL(HAVE_SSE3, false) + AM_CONDITIONAL(HAVE_SSE4_1, false) + + case $host_cpu in + i[[3456]]86*|x86_64*|amd64*) + AX_CHECK_COMPILE_FLAG(-msse3, ax_cv_support_sse3_ext=yes, []) + if test x"$ax_cv_support_sse3_ext" = x"yes"; then + SIMD_FLAGS="$SIMD_FLAGS -msse3" + AC_DEFINE(HAVE_SSE3,, + [Support SSE3 (Streaming SIMD Extensions 3) instructions]) + AM_CONDITIONAL(HAVE_SSE3, true) + else + AC_MSG_WARN([Your compiler does not support SSE3 instructions]) + fi + + AX_CHECK_COMPILE_FLAG(-msse4.1, ax_cv_support_sse41_ext=yes, []) + if test x"$ax_cv_support_sse41_ext" = x"yes"; then + SIMD_FLAGS="$SIMD_FLAGS -msse4.1" + AC_DEFINE(HAVE_SSE4_1,, + [Support SSE4.1 (Streaming SIMD Extensions 4.1) instructions]) + AM_CONDITIONAL(HAVE_SSE4_1, true) + else + AC_MSG_WARN([Your compiler does not support SSE4.1]) + fi + ;; + esac + + AC_SUBST(SIMD_FLAGS) +]) diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..64e3b35 --- /dev/null +++ b/configure.ac @@ -0,0 +1,270 @@ +dnl +dnl Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +dnl +dnl This software is distributed under the terms of the GNU Public License. +dnl See the COPYING file in the main directory for details. +dnl +dnl This program is free software: you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation, either version 3 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program. If not, see <http://www.gnu.org/licenses/>. +dnl + +AC_INIT([osmo-trx], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc@lists.osmocom.org]) +AC_PREREQ(2.57) +AC_CONFIG_SRCDIR([Transceiver52M/Makefile.am]) +AC_CONFIG_AUX_DIR([.]) +AC_CONFIG_MACRO_DIR([config]) +AM_CONFIG_HEADER(config.h) +AC_CONFIG_TESTDIR(tests) + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE([subdir-objects]) + +dnl Linux kernel KBuild style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +AM_PROG_AS +AC_PROG_CC +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX_11 +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_INSTALL +AC_PATH_PROG([RM_PROG], [rm]) +AC_LANG([C++]) + +dnl check for pkg-config (explained in detail in libosmocore/configure.ac) +AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no) +if test "x$PKG_CONFIG_INSTALLED" = "xno"; then + AC_MSG_WARN([You need to install pkg-config]) +fi +PKG_PROG_PKG_CONFIG([0.20]) + +AC_LIBTOOL_WIN32_DLL +AC_ENABLE_SHARED dnl do build shared libraries +AC_DISABLE_STATIC dnl don't build static libraries +AC_PROG_LIBTOOL + +dnl Checks for header files. +AC_HEADER_STDC +dnl This is required for GnuRadio includes to understand endianess correctly: +AC_CHECK_HEADERS([byteswap.h]) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_TYPE_SIZE_T +AC_HEADER_TIME +AC_C_BIGENDIAN + +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0) + +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING( + [--enable-sanitize], + [Compile with address sanitizer enabled], + )], + [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" + LDFLAGS="$LDFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + + +AC_ARG_WITH(uhd, [ + AS_HELP_STRING([--with-uhd], + [enable UHD based transceiver]) +]) + +AC_ARG_WITH(usrp1, [ + AS_HELP_STRING([--with-usrp1], + [enable USRP1 gnuradio based transceiver]) +]) + +AC_ARG_WITH(lms, [ + AS_HELP_STRING([--with-lms], + [enable LimeSuite based transceiver]) +]) + +AC_ARG_WITH(singledb, [ + AS_HELP_STRING([--with-singledb], + [enable single daughterboard use on USRP1]) +]) + +AC_ARG_WITH(neon, [ + AS_HELP_STRING([--with-neon], + [enable ARM NEON support]) +]) + +AC_ARG_WITH(neon-vfpv4, [ + AS_HELP_STRING([--with-neon-vfpv4], + [enable ARM NEON FMA support]) +]) + +AC_ARG_WITH(sse, [ + AS_HELP_STRING([--with-sse], + [enable x86 SSE support (default)]) +]) + +AS_IF([test "x$with_neon" = "xyes"], [ + AC_DEFINE(HAVE_NEON, 1, Support ARM NEON) +]) + +AS_IF([test "x$with_neon_vfpv4" = "xyes"], [ + AC_DEFINE(HAVE_NEON, 1, Support ARM NEON) + AC_DEFINE(HAVE_NEON_FMA, 1, Support ARM NEON with FMA) +]) + +AS_IF([test "x$with_usrp1" = "xyes"], [ + PKG_CHECK_MODULES(USRP, usrp >= 3.3) +]) + +AS_IF([test "x$with_lms" = "xyes"], [ + PKG_CHECK_MODULES(LMS, LimeSuite) +]) + +AS_IF([test "x$with_uhd" != "xno"],[ + PKG_CHECK_MODULES(UHD, uhd >= 003.011, + [AC_DEFINE(USE_UHD_3_11, 1, UHD version 3.11.0 or higher)], + [PKG_CHECK_MODULES(UHD, uhd >= 003.009, + [AC_DEFINE(USE_UHD_3_9, 1, UHD version 3.9.0 or higher)], + [PKG_CHECK_MODULES(UHD, uhd >= 003.005)] + )] + ) +]) + +AS_IF([test "x$with_singledb" = "xyes"], [ + AC_DEFINE(SINGLEDB, 1, Define to 1 for single daughterboard) +]) + +# Find and define supported SIMD extensions +AS_IF([test "x$with_sse" != "xno"], [ + AX_SSE +], [ + AM_CONDITIONAL(HAVE_SSE3, false) + AM_CONDITIONAL(HAVE_SSE4_1, false) +]) + +dnl Check if the compiler supports specified GCC's built-in function +AC_DEFUN([CHECK_BUILTIN_SUPPORT], [ + AC_CACHE_CHECK( + [whether ${CC} has $1 built-in], + [osmo_cv_cc_has_builtin], [ + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([], [ + __builtin_cpu_supports("sse"); + ]) + ], + [AS_VAR_SET([osmo_cv_cc_has_builtin], [yes])], + [AS_VAR_SET([osmo_cv_cc_has_builtin], [no])]) + ] + ) + + AS_IF([test yes = AS_VAR_GET([osmo_cv_cc_has_builtin])], [ + AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_$1), 1, + [Define to 1 if compiler has the '$1' built-in function]) + ], [ + AC_MSG_WARN($2) + ]) +]) + +dnl Check if the compiler supports runtime SIMD detection +CHECK_BUILTIN_SUPPORT([__builtin_cpu_supports], + [Runtime SIMD detection will be disabled]) + +AM_CONDITIONAL(DEVICE_UHD, [test "x$with_uhd" != "xno"]) +AM_CONDITIONAL(DEVICE_USRP1, [test "x$with_usrp1" = "xyes"]) +AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"]) +AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"]) +AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"]) + +PKG_CHECK_MODULES(LIBUSB, libusb-1.0) +PKG_CHECK_MODULES(FFTWF, fftw3f) + +AC_CHECK_HEADER([boost/config.hpp],[], + [AC_MSG_ERROR([boost/config.hpp not found, install e.g. libboost-dev])]) + + # https://www.freedesktop.org/software/systemd/man/daemon.html + AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) + AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) + AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) + AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CXXFLAGS="$CXXFLAGS"]) +AC_MSG_RESULT([LDFLAGS="$LDFLAGS"]) + +dnl Output files +AC_CONFIG_FILES([\ + Makefile \ + CommonLibs/Makefile \ + GSM/Makefile \ + Transceiver52M/Makefile \ + Transceiver52M/arch/Makefile \ + Transceiver52M/arch/common/Makefile \ + Transceiver52M/arch/arm/Makefile \ + Transceiver52M/arch/x86/Makefile \ + Transceiver52M/device/Makefile \ + Transceiver52M/device/uhd/Makefile \ + Transceiver52M/device/usrp1/Makefile \ + Transceiver52M/device/lms/Makefile \ + tests/Makefile \ + tests/CommonLibs/Makefile \ + tests/Transceiver52M/Makefile \ + doc/Makefile \ + doc/examples/Makefile \ + contrib/Makefile \ + contrib/systemd/Makefile \ +]) + +AC_OUTPUT diff --git a/contrib/Makefile.am b/contrib/Makefile.am new file mode 100644 index 0000000..3439c97 --- /dev/null +++ b/contrib/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = systemd diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh new file mode 100755 index 0000000..029f0e1 --- /dev/null +++ b/contrib/jenkins.sh @@ -0,0 +1,87 @@ +#!/bin/sh +set -ex + +substr() { [ -z "${2##*$1*}" ]; } + +#apt-get install qemu qemu-user-static qemu-system-arm debootstrap fakeroot proot +mychroot_nocwd() { + # LC_ALL + LANGUAGE set to avoid lots of print errors due to locale not being set inside container + # PATH is needed to be able to reach binaries like ldconfig without logging in to root, which adds the paths to PATH. + # PROOT_NO_SECCOMP is requried due to proot bug #106 + LC_ALL=C LANGUAGE=C PATH="$PATH:/usr/sbin:/sbin" PROOT_NO_SECCOMP=1 proot -r "$ROOTFS" -w / -b /proc --root-id -q qemu-arm-static "$@" +} + +mychroot() { + mychroot_nocwd -w / "$@" +} + +base="$PWD" +deps="$base/deps" +inst="$deps/install" +export deps inst + +if [ -z "${INSIDE_CHROOT}" ]; then + + osmo-clean-workspace.sh + + # Only use ARM chroot if host is not ARM and the target is ARM: + if ! $(substr "arm" "$(uname -m)") && [ "x${INSTR}" = "x--with-neon" -o "x${INSTR}" = "x--with-neon-vfpv4" ]; then + + OSMOTRX_DIR="$PWD" # we assume we are called as contrib/jenkins.sh + ROOTFS_PREFIX="${ROOTFS_PREFIX:-$HOME}" + ROOTFS="${ROOTFS_PREFIX}/qemu-img" + mkdir -p "${ROOTFS_PREFIX}" + + # Prepare chroot: + if [ ! -d "$ROOTFS" ]; then + mkdir -p "$ROOTFS" + if [ "x${USE_DEBOOTSTRAP}" = "x1" ]; then + fakeroot qemu-debootstrap --foreign --include="linux-image-armmp-lpae" --arch=armhf stretch "$ROOTFS" http://ftp.de.debian.org/debian/ + # Hack to avoid debootstrap trying to mount /proc, as it will fail with "no permissions" and anyway proot takes care of it: + sed -i "s/setup_proc//g" "$ROOTFS/debootstrap/suite-script" + mychroot /debootstrap/debootstrap --second-stage --verbose http://ftp.de.debian.org/debian/ + else + YESTERDAY=$(python -c 'import datetime ; print((datetime.datetime.now() - datetime.timedelta(days=1)).strftime("%Y%m%d"))') + wget -nc -q "https://uk.images.linuxcontainers.org/images/debian/stretch/armhf/default/${YESTERDAY}_22:42/rootfs.tar.xz" + tar -xf rootfs.tar.xz -C "$ROOTFS/" || true + echo "nameserver 8.8.8.8" > "$ROOTFS/etc/resolv.conf" + fi + mychroot -b /dev apt-get update + mychroot apt-get -y install build-essential dh-autoreconf pkg-config libuhd-dev libusb-1.0-0-dev libusb-dev git libtalloc-dev libgnutls28-dev stow + fi + # Run jenkins.sh inside the chroot: + INSIDE_CHROOT=1 mychroot_nocwd \ + -w /osmo-trx \ + -b "$OSMOTRX_DIR:/osmo-trx" \ + -b "$(which osmo-clean-workspace.sh):/usr/bin/osmo-clean-workspace.sh" \ + -b "$(which osmo-build-dep.sh):/usr/bin/osmo-build-dep.sh" \ + -b "$(which osmo-deps.sh):/usr/bin/osmo-deps.sh" \ + ./contrib/jenkins.sh + exit 0 + fi +fi + +mkdir "$deps" || true + +osmo-build-dep.sh libosmocore "" "--enable-sanitize --disable-doxygen --disable-pcsc" +osmo-build-dep.sh libusrp + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +set +x +echo +echo +echo +echo " =============================== osmo-trx ===============================" +echo +set -x + +cd "$base" +autoreconf --install --force +./configure --enable-sanitize --enable-werror --with-uhd --with-usrp1 --with-lms $INSTR +$MAKE $PARALLEL_MAKE +$MAKE check \ + || cat-testlogs.sh + +osmo-clean-workspace.sh diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am new file mode 100644 index 0000000..21fbb53 --- /dev/null +++ b/contrib/systemd/Makefile.am @@ -0,0 +1,18 @@ +if HAVE_SYSTEMD +SYSTEMD_SERVICES = + +if DEVICE_UHD +SYSTEMD_SERVICES += osmo-trx-uhd.service +endif + +if DEVICE_USRP1 +SYSTEMD_SERVICES += osmo-trx-usrp1.service +endif + +if DEVICE_LMS +SYSTEMD_SERVICES += osmo-trx-lms.service +endif + +EXTRA_DIST = $(SYSTEMD_SERVICES) +systemdsystemunit_DATA = $(SYSTEMD_SERVICES) +endif # HAVE_SYSTEMD diff --git a/contrib/systemd/osmo-trx-lms.service b/contrib/systemd/osmo-trx-lms.service new file mode 100644 index 0000000..df63e21 --- /dev/null +++ b/contrib/systemd/osmo-trx-lms.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom SDR BTS L1 Transceiver (LimeSuite backend) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-trx-lms -C /etc/osmocom/osmo-trx-lms.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/contrib/systemd/osmo-trx-uhd.service b/contrib/systemd/osmo-trx-uhd.service new file mode 100644 index 0000000..ba27f37 --- /dev/null +++ b/contrib/systemd/osmo-trx-uhd.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom SDR BTS L1 Transceiver (UHD Backend) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-trx-uhd -C /etc/osmocom/osmo-trx-uhd.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/contrib/systemd/osmo-trx-usrp1.service b/contrib/systemd/osmo-trx-usrp1.service new file mode 100644 index 0000000..fbff631 --- /dev/null +++ b/contrib/systemd/osmo-trx-usrp1.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom SDR BTS L1 Transceiver (libusrp backend) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-trx-usrp1 -C /etc/osmocom/osmo-trx-usrp1.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..a9724d8 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,300 @@ +osmo-trx (0.4.0) unstable; urgency=medium + + [ Neels Hofmeyr ] + * jenkins: use osmo-clean-workspace.sh before and after build + + [ Harald Welte ] + * SocketsTest: Fix printing of non-nul-terminated string + * Revert "debian: Remove osmo-trx-usrp1 until we can build libusrp1.deb" + * debian/control: Remove "Maintainer" from binary package section + * debian/rules: Make sure we always require libusrp + * debian: Ensure USRP1 firmware is part of osmo-trx-usrp1 + * debian/control: Add build dependency to libusrp-dev + * update .gitignore for new executable names + * osmo-trx: s/GSM Core Address/GSM BTS Address/ + + [ Piotr Krysik ] + * UHDDevice.cpp: add USRP B205mini support + + [ Max ] + * Mark release target as virtual + * Remove outdated references to OpenBTS + * Remove unused headers + * Update installation instructions + * Update legal disclaimer + * Update license notes + * tests: null-terminate buffer + + [ Pau Espin Pedrol ] + * cosmetic: Remove trailing whitespace + * Logger: Stop using Log.Alarms.Max from config + * Logger: Stop using Log.File and Log.Level from config + * Drop use of ConfigurationTable gConfig + * Remove Configuration module and libsqlite dependency + * cosmetic: AUTHORS: fix trailing whitespace + * Set up GNU Autotest infrastructure + * tests: InterThread: adapt to have reproducible output and enable autotest + * tests: Timeval: adapt to have reproducible output and enable autotest + * tests: Log: adapt to have reproducible output and enable autotest + * Sockets.cpp: Fix initialization of UDD socket + * tests: Sockets: adapt to have reproducible output and enable autotest + * utils/convolvtest: Remove uneeded libosmocore dependency + * Move ARCH_LA to Makefile.common + * tests: Migrate convtest util to autotest infrastructure + * arm/convert.c: Fix compilation error + * arm/convert.c: Add missing convert_init implementation + * .gitignore: Add missing test related files + * Remove UDDSocket class + * tests: SocketTests: Pick OS-assigned instead of setting one manually + * tests: SocketsTest: Avoid hang forever if test fails + * tests: SocketsTest: Fail test on write fail + * tests: TimevalTest: refactor and avoid double comparison + * contrib/jenkins.sh: Use qemu+proot+debootstrap to run tests with ARM instruction set + * tests: convolve: Disable due to difference in output in different archs + * Remove unneeded libdl dependency + * Fix whitespace + * Add support to set Rx/TxAntenna + * UHDDevice: Initialize async_event_thrd in constructor + * Logger: Drop unused gLogEarly + * Logger: Remove unused logging macros + * Logger: get rid of alarm APIs + * Logger: Drop syslog support + * Logger: Drop support to log into file + * Logger: Remove unused includes + * Logger: Remove gLogToConsole flag + * configure.ac: Check for pkg-config + * Depend on libosmocore + * osmo-trx: set up signals using libosmocore helpers + * osmo-trx: Set up talloc ctx + * debian: Depend on libtalloc and libosmocore + * Add initial support for logging, vty, ctrl + * Logger: Use libosmocore logging system + * osmo-trx.cpp: Move trx start and stop to helper functions + * Move enums required by VTY to a separate header + * vty: Implement VTY cfg parsing for current parameters + * doc: Add sample cfg file for LimeSDR + * osmo-trx: Use VTY cfg structures while still allowing cmd line options + * osmo-trx: Re-introduce -l cmd line parameter + * Makefile.am: Avoid using subdir if arch is not required + * Build Transceiver52M/common as an .la lib + * use osmo_init_logging2() + * tests: Makefile.am: Fix typo in include path + * configure.ac: Add --enable-sanitize option + * Move arch specific fiels to arch subdir + * Move device specific files to device subdir + * Change configure define USRP1 to DEVICE_USRP1 + * Move device specific code out of radioInterface + * Transceiver: Move device specific code to radioDevice class + * Build one osmo-trx binary for each device support enabled + * Logger: Print correct source file and line number + * Transceiver: log timing info of stale bursts + * build: Fix make distcheck + * build: More OBS build failure fixes + * jenkins.sh: Enable build of osmo-trx-usrp1 + * debian: Remove osmo-trx-usrp1 until we can build libusrp1.deb + * debian: Fix OBS build + * build: Fix OBS build for ARM + * git-version-gen: Take into account tags not in master + + [ Alexander Huemer ] + * Unbreak `./configure --with-usrp1` build + * Fix USRP1 build with support for setting Rx/TxAntenna + + [ Alexander Couzens ] + * jenkins.sh: fix the download url if the qemu image wasn't setup + * jenkins.sh: cleanup always the workspace + * jenkins.sh: change qemu-img default location to $HOME/qemu-img instead of /opt/qemu-img + + [ Vadim Yanitskiy ] + * Transceiver.cpp: use a define for the MAX_PACKET_LENGTH + * Transceiver.cpp: properly zero-terminate received commands + * Transceiver.cpp: use pointer arithmetics for CMD parsing + * Transceiver.cpp: fix incorrect format string for SETTSC + * Transceiver.cpp: prevent out-of-range array access + + [ Martin Hauke ] + * configure.ac: Fix typo + + [ Philipp Maier ] + * doc: add example config for usrp B200 series + + -- Pau Espin Pedrol <pespin@sysmocom.de> Thu, 03 May 2018 16:23:29 +0200 + +osmo-trx (0.2.0) unstable; urgency=medium + + [ Alexander Chemeris ] + * EDGE: Add support for UmTRX. + * Common: Get rid of a compilation warning. + * Common: Make sure gLogEarly() log to the same facilities as the normal log. + * transceiver: Properly handle MAXDLY. + * transceiver: Add an option to generate random Access Bursts. + * osmo-trx: Output Rx SPS as a part of configuration output. + * transceiver: Do not pass transceiver state struct to function where it's not used. + * makefile: Fix build from an external path. + * radioDevice: GSMRATE macro must have parentheses around its definition. + * uhd: Fix comment. + * radioInterface: Initialize power scale with a meaningful default. + * transceiver: Log channel number in DEBUG output of demoded bursts. + * transceiver: Add an option to emulate a RACH delay in random filler mode. + * UHD: Initial LimeSDR support. + * CommonLibs: Remove unused files. + * sigProcLib: Typo sybols -> symbols + * radioBuffer: Remove extra ; at the end of inline function definitions. + * sigProcLib: Fix documentation, sync argument names in .cpp and .h files. + * sigProcLib: make energyDetect() simpler by returning actual energy. + * sigProcLib: Rename demodulateBurst() to demodGmskBurst() for clarity. + * sigProcLib: Slice SoftVector instead of signalVector for GMSK demod. + * Call vectorSlicer() right before packing bits for transmission to osmo-bts. + * CommonLibs: Print soft bits with less confidence to console when printing a soft vector. + * BitVector: Remove convolutional codec - we don't use it in osmo-trx. + * BitVector: Convert SoftVector from 0..1 to -1..+1 soft bits. + * signalVector: Implement segment(). + * vector: Introduce segmentMove() method to move data inside of a vector. + * vector: Introduce shrink() function to shrink vector size without loosing data. + * Move CorrType type from Transceiver to sigProcLib. + * sigProcLib: rename signalError type to SignalError. + * Move Transceiver::detectBurst() to sigProcLib to make it reusable. + * Move BURST_THRESH from Transceiver.cpp to sigProcLib.h to make it reusable. + * sigProcLib: Add operator<< to print CorrType to a string. + * sigProcLib.h: Fix whitespaces. No non-whitespace changes. + * Move Transceiver::demodulate() to sigProcLib to make it reusable. + * sigProcLib: constify signalVector arguments for detectBurst() functions. + * sigProcLib: Constify demodulation functions burst argument. + * sigProcLib: Fix number of tail bits in random Normal Bursts and zero Stealing Bits. + * Configuration: Variables allocated with 'new' must be freed with 'delete'. + * BitVector: Remove Generator class. + * PRBS: a Pseudo-random binary sequence (PRBS) generator class. + + [ Tom Tsou ] + * EDGE: Fix USRP B210 device support + * uhd: Correct timing alignment in 8-PSK and GMSK downlink bursts + * EDGE: Fix demodulation slicer input + * common: Restrict UDP binding to localhost only + * common: Add mandatory length field to UDP receive calls + * uhd: Update default E3XX settings + * uhd: Set default Tx sampling to 4 sps + * uhd: Make device offset check a private method + * uhd: Set minimum UHD version requirement for E3XX + * sigproc: Expand RACH, TSC, and EDGE correlation windows + * transceiver: Do not report error on SETTSC when radio is on + * transceiver: Add Rx samples-per-symbol option + * radioInterface: Convert diversity argument to general type + * iface: Add inner ring-buffer implementation + * mcbts: Add multi-ARFCN channelizing filters + * mcbts: Add multi-ARFCN radio support + * sigproc: Adjust burst detection threshold criteria + * egprs: Enable 8-PSK length vectors on the Tx interface + * egprs: Enable 8-PSK burst detection when EDGE is enabled + * transceiver: Remove HANDOVER warnings + * mcbts: Allow out of order channel setup + * radioInterface: Fix multi-channel buffer index bug + * uhd: Add command line option for GPS reference + * transceiver: Fix mixed GSMK / 8-PSK transmission + * transceiver: Fix 4 SPS receive TOA value + * sigproc: Fix missing 8-PSK tail symbols + * uhd: Update USRP2/N200/N210 for 4 SPS Rx + * sigproc: Match differential GMSK start/end bits to tail bits + * uhd: Add missing B200 sample timing for 4 SPS receive + * transceiver: Fix command build warning + * uhd: Set minimum supported version to 3.9.0 + * uhd: Add X300 sample timing for 4 SPS + * Revert "uhd: Set minimum supported version to 3.9.0" + * uhd: Add support for UHD-3.11 logging control + * uhd: Increase MC-BTS FPGA clock rate to 51.2 MHz + * Resampler: Fix initialization return checking + * sigProcLib: Remove unreachable code and no-effect checks + * sigProcLib: Check return status on downsampling + * sigProcLib: Fix negative value check on unsigned value + * Resampler: Fix non-array delete for filter taps + * Transceiver: Remove unsigned negative compares + * Configuration: Fix const and signedness compile warnings + * config: Remove OpenBTS style sqlite configuration + * radioInterface: Remove UmTRX 'diversity' option + * build: Require and check for gcc C++11 support + * uhd: Use map container for for device parameter access + * sigProcLib: Remove unused functions from public interface + * uhd: Add non-UmTRX channel swap support + * uhd: Fix Tx-RX timing offset setting + * uhd: Fix USRP2/N200/N210 device detection + * transceiver: Fix POWEROFF crash on USRP2/N200/X300 devices + * sigProcLib: Fix complex/real vector flag in Laurent modulator + * sigProcLib: Remove heap based signal vector allocations + * common: Declare explicit Vector move constructor + * sigProcLib: Remove trigonometric tables + * sigProcLib: Use explicit NaN check in sinc table generation + * sigProcLib: Replace dynamically allocated resampling buffers + * sigProcLib: Specify standard namespace for isnan() + * uhd: Always specify samples-per-symbol for device lookup + * LimeSDR: set approximate tx offset value to make GSM work + + [ Neels Hofmeyr ] + * add basic .gitignore + * configure.ac: check for boost/config.hpp header + * The INSTALL file is being overwritten by autoreconf, but it is committed as empty file. As a result, the INSTALL file always shows as modified. Instead, remove INSTALL from git and ignore it. + * add contrib/jenkins.sh, for gerrit build bot + + [ pierre.baudry ] + * transceiver: Fix mismatched allocations and deallocations + + [ Holger Hans Peter Freyther ] + * debian: Require fftw3 header files for osmo-trx + + [ Max ] + * Add gerrit settings + * Integrate Debian packaging changes + * Remove embedded sqlite3 + * Fix building against sqlite3 + * Add autoconf-archive to dependencies + * debian: remove obsolete dependency + * deb: remove unused dependency + * Remove redundant explicit dependency + * Use release helper from libosmocore + + [ Ruben Undheim ] + * Do not embed sqlite3 when building + + [ Philipp Maier ] + * buildenv: Turn off native architecture builds + * cosmetic: Make parameter lists uniform + * Add test program to verify convolution implementation + * ssedetect: Add runtime CPU detection + * cosmetic: remove code duplication + * buildenv: Make build CPU invariant + * buildenv: Split up SSE3 and SSE4.1 code + * cosmetic: Add info about SSE support + + [ Vadim Yanitskiy ] + * buildenv: correct the ax_sse macro description + * buildenv: actually strip unused cpuid functionality + * buildenv: fix build on systems without SIMD support + * buildenv: cosmetic changes + * buildenv: check for __builtin_cpu_supports call support + * ssedetect: call __builtin_cpu_supports() only if supported + + [ Pau Espin Pedrol ] + * cosmetic: transciever: Remove trailing whitespaces + * transceiver: Avoid sending clock indications when trx is not powered on + * Add -j option to bind to specific address + + [ ignasj ] + * LimeSDR: Change device detection to work with USB and PCIe versions + * LimeSDR: change tx window type to TX_WINDOW_FIXED + * LimeSDR: Fix sample value range + + [ Harald Welte ] + * Add '-t' command line option to enable SCHED_RR + * Import git-version-gen and update AC_INIT() + + -- Harald Welte <laforge@gnumonks.org> Sat, 28 Oct 2017 17:52:32 +0200 + +osmo-trx (0.1.9) trusty; urgency=medium + + * Ask Ivan, really + + -- Kirill Zakharenko <earwin@gmail.com> Thu, 16 Jul 2015 12:13:46 +0000 + +osmo-trx (0.1.8) precise; urgency=low + + * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP + + -- Ivan Klyuchnikov <Ivan.Kluchnikov@fairwaves.ru> Sun, 9 Mar 2014 14:10:10 +0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..ffb9ac1 --- /dev/null +++ b/debian/control @@ -0,0 +1,91 @@ +Source: osmo-trx +Section: net +Priority: optional +Maintainer: Ivan Klyuchnikov <ivan.kluchnikov@fairwaves.ru> +Build-Depends: debhelper (>= 9), + autotools-dev, + autoconf-archive, + pkg-config, + dh-autoreconf, + libuhd-dev, + libusb-1.0-0-dev, + libboost-all-dev, + libfftw3-dev, + libtalloc-dev, + libusrp-dev, + liblimesuite-dev, + libosmocore-dev (>= 0.10.0) +Standards-Version: 3.9.6 +Vcs-Browser: http://cgit.osmocom.org/osmo-trx +Vcs-Git: git://git.osmocom.org/osmo-trx +Homepage: https://projects.osmocom.org/projects/osmotrx + +Package: osmo-trx +Depends: osmo-trx-uhd +Architecture: all +Description: Metapackage for osmo-trx-uhd + +Package: osmo-trx-dbg +Architecture: any +Section: debug +Priority: extra +Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), osmo-trx-lms (= ${binary:Version}), ${misc:Depends} +Description: Debug symbols for the osmo-trx-* + Make debugging possible + +Package: osmo-trx-uhd +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SDR transceiver that implements Layer 1 of a GSM BTS (UHD) + OsmoTRX is a software-defined radio transceiver that implements the Layer 1 + physical layer of a BTS comprising the following 3GPP specifications: + . + TS 05.01 "Physical layer on the radio path" + TS 05.02 "Multiplexing and Multiple Access on the Radio Path" + TS 05.04 "Modulation" + TS 05.10 "Radio subsystem synchronization" + . + In this context, BTS is "Base transceiver station". It's the stations that + connect mobile phones to the mobile network. + . + 3GPP is the "3rd Generation Partnership Project" which is the collaboration + between different telecommunication associations for developing new + generations of mobile phone networks. (post-2G/GSM) + +Package: osmo-trx-usrp1 +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SDR transceiver that implements Layer 1 of a GSM BTS (USRP1) + OsmoTRX is a software-defined radio transceiver that implements the Layer 1 + physical layer of a BTS comprising the following 3GPP specifications: + . + TS 05.01 "Physical layer on the radio path" + TS 05.02 "Multiplexing and Multiple Access on the Radio Path" + TS 05.04 "Modulation" + TS 05.10 "Radio subsystem synchronization" + . + In this context, BTS is "Base transceiver station". It's the stations that + connect mobile phones to the mobile network. + . + 3GPP is the "3rd Generation Partnership Project" which is the collaboration + between different telecommunication associations for developing new + generations of mobile phone networks. (post-2G/GSM) + +Package: osmo-trx-lms +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SDR transceiver that implements Layer 1 of a GSM BTS (LimeSuite) + OsmoTRX is a software-defined radio transceiver that implements the Layer 1 + physical layer of a BTS comprising the following 3GPP specifications: + . + TS 05.01 "Physical layer on the radio path" + TS 05.02 "Multiplexing and Multiple Access on the Radio Path" + TS 05.04 "Modulation" + TS 05.10 "Radio subsystem synchronization" + . + In this context, BTS is "Base transceiver station". It's the stations that + connect mobile phones to the mobile network. + . + 3GPP is the "3rd Generation Partnership Project" which is the collaboration + between different telecommunication associations for developing new + generations of mobile phone networks. (post-2G/GSM) diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..b7790f2 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,147 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: OsmoTRX +Source: http://cgit.osmocom.org/osmo-trx/ +Files-Excluded: Transceiver52M/std_inband.rbf + +Files: * +Copyright: 2008-2013 Free Software Foundation + 2010 Kestrel Signal Processing, Inc. + 2010-2012 Range Networks, Inc. +License: AGPL-3+ + +Files: Transceiver52M/arm/* + Transceiver52M/x86/* + Transceiver52M/common/* + Transceiver52M/Resampler.cpp + Transceiver52M/Resampler.h + Transceiver52M/osmo-trx.cpp + Transceiver52M/radioInterfaceDiversity.cpp +Copyright: 2012-2013 Thomas Tsou <tom@tsou.cc> +License: LGPL-2.1+ + +Files: config/ax_check_compile_flag.m4 +Copyright: 2008 Guido U. Draheim <guidod@gmx.de> + 2011 Maarten Bosmans <mkbosmans@gmail.com> +License: GPL-3+ + +Files: config/ax_gcc_x86_cpuid.m4 +Copyright: 2008 Steven G. Johnson <stevenj@alum.mit.edu> + 2008 Matteo Frigo +License: GPL-3+ + +Files: config/ax_ext.m4 +Copyright: 2007 Christophe Tournayre <turn3r@users.sourceforge.net> + 2013 Michael Petch <mpetch@capp-sysware.com> +License: license_for_ax_ext_m4 + +Files: config/ax_gcc_x86_avx_xgetbv.m4 +Copyright: 2013 Michael Petch <mpetch@capp-sysware.com> +License: GPL-3+ + +Files: CommonLibs/Makefile.am + GSM/Makefile.am + Transceiver52M/Makefile.am + Transceiver52M/Transceiver.h + Transceiver52M/Transceiver.cpp +Copyright: 2008-2010 Free Software Foundation + 2010-2012 Range Networks, Inc. +License: GPL-3+ + +Files: autogen.sh +Copyright: 2005-2009 United States Government as represented by + the U.S. Army Research Laboratory. +License: BSD-3-clause + +Files: debian/* +Copyright: 2015 Ruben Undheim <ruben.undheim@gmail.com> +License: GPL-3+ + + +License: AGPL-3+ + 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/>. + + +License: GPL-3+ + This package 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 package 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/> + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". + + +License: LGPL-2.1+ + 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 program. If not, see + <http://www.gnu.org/licenses/> + . + On Debian systems, the complete text of the GNU Lesser General + Public License version 2.1 can be found in + "/usr/share/common-licenses/LGPL-2.1". + + +License: license_for_ax_ext_m4 + Copying and distribution of this file, with or without modification, are + permitted in any medium without royalty provided the copyright notice + and this notice are preserved. This file is offered as-is, without any + warranty. + + +License: BSD-3-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + . + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + . + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/debian/osmo-trx-lms.install b/debian/osmo-trx-lms.install new file mode 100644 index 0000000..b89506a --- /dev/null +++ b/debian/osmo-trx-lms.install @@ -0,0 +1,4 @@ +etc/osmocom/osmo-trx-lms.cfg +lib/systemd/system/osmo-trx-lms.service +/usr/bin/osmo-trx-lms +/usr/share/doc/osmo-trx/examples/osmo-trx-lms/osmo-trx-limesdr.cfg /usr/share/doc/osmo-trx/examples/osmo-trx-lms/ diff --git a/debian/osmo-trx-uhd.install b/debian/osmo-trx-uhd.install new file mode 100644 index 0000000..77236ca --- /dev/null +++ b/debian/osmo-trx-uhd.install @@ -0,0 +1,6 @@ +etc/osmocom/osmo-trx-uhd.cfg +lib/systemd/system/osmo-trx-uhd.service +/usr/bin/osmo-trx-uhd +/usr/share/doc/osmo-trx/examples/osmo-trx-uhd/osmo-trx-usrp_b200.cfg /usr/share/doc/osmo-trx/examples/osmo-trx-uhd/ +/usr/share/doc/osmo-trx/examples/osmo-trx-uhd/osmo-trx-limesdr.cfg /usr/share/doc/osmo-trx/examples/osmo-trx-uhd/ +/usr/share/doc/osmo-trx/examples/osmo-trx-uhd/osmo-trx-umtrx.cfg /usr/share/doc/osmo-trx/examples/osmo-trx-uhd/ diff --git a/debian/osmo-trx-usrp1.install b/debian/osmo-trx-usrp1.install new file mode 100644 index 0000000..112a149 --- /dev/null +++ b/debian/osmo-trx-usrp1.install @@ -0,0 +1,4 @@ +lib/systemd/system/osmo-trx-usrp1.service +/usr/bin/osmo-trx-usrp1 +/usr/share/usrp/rev2/std_inband.rbf +/usr/share/usrp/rev4/std_inband.rbf diff --git a/debian/osmo-trx.install b/debian/osmo-trx.install new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/debian/osmo-trx.install diff --git a/debian/patches/build-for-debian8.patch b/debian/patches/build-for-debian8.patch new file mode 100644 index 0000000..cd1f356 --- /dev/null +++ b/debian/patches/build-for-debian8.patch @@ -0,0 +1,57 @@ +Index: osmo-trx/debian/control +=================================================================== +--- osmo-trx.orig/debian/control ++++ osmo-trx/debian/control +@@ -13,7 +13,6 @@ Build-Depends: debhelper (>= 9), + libfftw3-dev, + libtalloc-dev, + libusrp-dev, +- liblimesuite-dev, + libosmocore-dev (>= 0.10.0) + Standards-Version: 3.9.6 + Vcs-Browser: http://cgit.osmocom.org/osmo-trx +@@ -29,7 +28,7 @@ Package: osmo-trx-dbg + Architecture: any + Section: debug + Priority: extra +-Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), osmo-trx-lms (= ${binary:Version}), ${misc:Depends} ++Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), ${misc:Depends} + Description: Debug symbols for the osmo-trx-* + Make debugging possible + +@@ -70,22 +70,3 @@ Description: SDR transceiver that implem + 3GPP is the "3rd Generation Partnership Project" which is the collaboration + between different telecommunication associations for developing new + generations of mobile phone networks. (post-2G/GSM) +- +-Package: osmo-trx-lms +-Architecture: any +-Depends: ${shlibs:Depends}, ${misc:Depends} +-Description: SDR transceiver that implements Layer 1 of a GSM BTS (LimeSuite) +- OsmoTRX is a software-defined radio transceiver that implements the Layer 1 +- physical layer of a BTS comprising the following 3GPP specifications: +- . +- TS 05.01 "Physical layer on the radio path" +- TS 05.02 "Multiplexing and Multiple Access on the Radio Path" +- TS 05.04 "Modulation" +- TS 05.10 "Radio subsystem synchronization" +- . +- In this context, BTS is "Base transceiver station". It's the stations that +- connect mobile phones to the mobile network. +- . +- 3GPP is the "3rd Generation Partnership Project" which is the collaboration +- between different telecommunication associations for developing new +- generations of mobile phone networks. (post-2G/GSM) +Index: osmo-trx/debian/rules +=================================================================== +--- osmo-trx.orig/debian/rules ++++ osmo-trx/debian/rules +@@ -9,7 +9,7 @@ override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info + + override_dh_auto_configure: +- dh_auto_configure -- --with-uhd --with-usrp1 --with-lms --with-systemdsystemunitdir=/lib/systemd/system ++ dh_auto_configure -- --with-uhd --with-usrp1 --with-systemdsystemunitdir=/lib/systemd/system + + override_dh_strip: + dh_strip --dbg-package=osmo-trx-dbg diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..82063b7 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +# build-for-debian8.patch diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..68de706 --- /dev/null +++ b/debian/rules @@ -0,0 +1,15 @@ +#!/usr/bin/make -f + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +%: + dh $@ --with autoreconf + +override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info + +override_dh_auto_configure: + dh_auto_configure -- --with-uhd --with-usrp1 --with-lms --with-systemdsystemunitdir=/lib/systemd/system + +override_dh_strip: + dh_strip --dbg-package=osmo-trx-dbg diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native)
\ No newline at end of file diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..1d42b0a --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + examples \ + $(NULL) diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am new file mode 100644 index 0000000..88d9142 --- /dev/null +++ b/doc/examples/Makefile.am @@ -0,0 +1,41 @@ +OSMOCONF_FILES = +osmoconfdir = $(sysconfdir)/osmocom + +if DEVICE_UHD +OSMOCONF_FILES += osmo-trx-uhd/osmo-trx-uhd.cfg +endif + +# if DEVICE_USRP1 +# TODO: no usrp1 sample file yet +# OSMOCONF_FILES += osmo-trx-usrp1/osmo-trx-usrp1.cfg +# endif + +if DEVICE_LMS +OSMOCONF_FILES += osmo-trx-lms/osmo-trx-lms.cfg +endif + +osmoconf_DATA = $(OSMOCONF_FILES) +EXTRA_DIST = $(OSMOCONF_FILES) + +CFG_FILES = find $(srcdir) -type f -name '*.cfg*' | sed -e 's,^$(srcdir),,' + +dist-hook: + for f in $$($(CFG_FILES)); do \ + j="$(distdir)/$$f" && \ + mkdir -p "$$(dirname $$j)" && \ + $(INSTALL_DATA) $(srcdir)/$$f $$j; \ + done + +install-data-hook: + for f in $$($(CFG_FILES)); do \ + j="$(DESTDIR)$(docdir)/examples/$$f" && \ + mkdir -p "$$(dirname $$j)" && \ + $(INSTALL_DATA) $(srcdir)/$$f $$j; \ + done + +uninstall-hook: + @$(PRE_UNINSTALL) + for f in $$($(CFG_FILES)); do \ + j="$(DESTDIR)$(docdir)/examples/$$f" && \ + $(RM) $$j; \ + done diff --git a/doc/examples/osmo-trx-lms/osmo-trx-limesdr.cfg b/doc/examples/osmo-trx-lms/osmo-trx-limesdr.cfg new file mode 100644 index 0000000..e2c67d6 --- /dev/null +++ b/doc/examples/osmo-trx-lms/osmo-trx-limesdr.cfg @@ -0,0 +1,22 @@ +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print file basename + logging level set-all info +! +line vty + no login +! +trx + bind-ip 127.0.0.1 + remote-ip 127.0.0.1 + base-port 5700 + egprs disable + tx-sps 4 + rx-sps 4 + rt-prio 18 + chan 0 + tx-path BAND1 + rx-path LNAW diff --git a/doc/examples/osmo-trx-lms/osmo-trx-lms.cfg b/doc/examples/osmo-trx-lms/osmo-trx-lms.cfg new file mode 120000 index 0000000..13ae04a --- /dev/null +++ b/doc/examples/osmo-trx-lms/osmo-trx-lms.cfg @@ -0,0 +1 @@ +osmo-trx-limesdr.cfg
\ No newline at end of file diff --git a/doc/examples/osmo-trx-uhd/osmo-trx-limesdr.cfg b/doc/examples/osmo-trx-uhd/osmo-trx-limesdr.cfg new file mode 100644 index 0000000..e2c67d6 --- /dev/null +++ b/doc/examples/osmo-trx-uhd/osmo-trx-limesdr.cfg @@ -0,0 +1,22 @@ +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print file basename + logging level set-all info +! +line vty + no login +! +trx + bind-ip 127.0.0.1 + remote-ip 127.0.0.1 + base-port 5700 + egprs disable + tx-sps 4 + rx-sps 4 + rt-prio 18 + chan 0 + tx-path BAND1 + rx-path LNAW diff --git a/doc/examples/osmo-trx-uhd/osmo-trx-uhd.cfg b/doc/examples/osmo-trx-uhd/osmo-trx-uhd.cfg new file mode 120000 index 0000000..11f2320 --- /dev/null +++ b/doc/examples/osmo-trx-uhd/osmo-trx-uhd.cfg @@ -0,0 +1 @@ +osmo-trx-usrp_b200.cfg
\ No newline at end of file diff --git a/doc/examples/osmo-trx-uhd/osmo-trx-umtrx.cfg b/doc/examples/osmo-trx-uhd/osmo-trx-umtrx.cfg new file mode 100644 index 0000000..1b15b47 --- /dev/null +++ b/doc/examples/osmo-trx-uhd/osmo-trx-umtrx.cfg @@ -0,0 +1,23 @@ +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print file basename + logging level set-all info +! +line vty + no login +! +trx + bind-ip 127.0.0.1 + remote-ip 127.0.0.1 + base-port 5700 + dev-args addr=192.168.10.2,pa=NONE,pa_power_max_dbm=23,fifo_ctrl_window=0,status_port=12345 + egprs disable + tx-sps 4 + rx-sps 4 + rssi-offset 38 + rt-prio 18 + chan 0 + chan 1 diff --git a/doc/examples/osmo-trx-uhd/osmo-trx-usrp_b200.cfg b/doc/examples/osmo-trx-uhd/osmo-trx-usrp_b200.cfg new file mode 100644 index 0000000..7d02506 --- /dev/null +++ b/doc/examples/osmo-trx-uhd/osmo-trx-usrp_b200.cfg @@ -0,0 +1,22 @@ +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print file basename + logging level set-all info +! +line vty + no login +! +trx + bind-ip 127.0.0.1 + remote-ip 127.0.0.1 + base-port 5700 + egprs disable + tx-sps 4 + rx-sps 4 + clock-ref external + rt-prio 18 + chan 0 + diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# 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/>. + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/tests/CommonLibs/BitVectorTest.cpp b/tests/CommonLibs/BitVectorTest.cpp new file mode 100644 index 0000000..063138f --- /dev/null +++ b/tests/CommonLibs/BitVectorTest.cpp @@ -0,0 +1,60 @@ +/* +* 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 "BitVector.h" +#include <iostream> +#include <cstdlib> + +using namespace std; + + +int main(int argc, char *argv[]) +{ + BitVector v5("000011110000"); + int r1 = v5.peekField(0,8); + int r2 = v5.peekField(4,4); + int r3 = v5.peekField(4,8); + cout << r1 << ' ' << r2 << ' ' << r3 << endl; + cout << v5 << endl; + v5.fillField(0,0xa,4); + int r4 = v5.peekField(0,8); + cout << v5 << endl; + cout << r4 << endl; + + v5.reverse8(); + cout << v5 << endl; + + + unsigned char ts[9] = "abcdefgh"; + BitVector tp(70); + cout << "ts=" << ts << endl; + tp.unpack(ts); + cout << "tp=" << tp << endl; + tp.pack(ts); + cout << "ts=" << ts << endl; +} diff --git a/tests/CommonLibs/BitVectorTest.ok b/tests/CommonLibs/BitVectorTest.ok new file mode 100644 index 0000000..f959a08 --- /dev/null +++ b/tests/CommonLibs/BitVectorTest.ok @@ -0,0 +1,8 @@ +15 15 240 +000011110000 +101011110000 +175 +111101010000 +ts=abcdefgh +tp=0110000101100010011000110110010001100101011001100110011101101000000000 +ts=abcdefgh diff --git a/tests/CommonLibs/InterthreadTest.cpp b/tests/CommonLibs/InterthreadTest.cpp new file mode 100644 index 0000000..c131c2a --- /dev/null +++ b/tests/CommonLibs/InterthreadTest.cpp @@ -0,0 +1,137 @@ +/* +* 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 "Threads.h" +#include "Interthread.h" +#include <iostream> + +using namespace std; + + +InterthreadQueue<int> gQ; +InterthreadMap<int,int> gMap; + +int q_last_read_val = -1; +int q_last_write_val; +int m_last_read_val; +int m_last_write_val; + +void* qWriter(void*) +{ + int *p; + for (int i=0; i<20; i++) { + p = new int; + *p = i; + CERR("queue write " << *p); + gQ.write(p); + q_last_write_val = i; + if (random()%2) sleep(1); + } + p = new int; + *p = -1; + gQ.write(p); + return NULL; +} + +void* qReader(void*) +{ + bool done = false; + while (!done) { + int *p = gQ.read(); + CERR("queue read " << *p); + if (*p<0) { + assert(q_last_read_val == 19 && *p == -1); + done = true; + } else { + assert(q_last_read_val == *p - 1); + q_last_read_val = *p; + } + delete p; + } + return NULL; +} + + +void* mapWriter(void*) +{ + int *p; + for (int i=0; i<20; i++) { + p = new int; + *p = i; + CERR("map write " << *p); + gMap.write(i,p); + m_last_write_val = i; + if (random()%2) sleep(1); + } + return NULL; +} + +void* mapReader(void*) +{ + for (int i=0; i<20; i++) { + int *p = gMap.read(i); + CERR("map read " << *p); + assert(*p == i); + m_last_read_val = *p; + // InterthreadMap will delete the pointers + // delete p; + } + return NULL; +} + + + + + + +int main(int argc, char *argv[]) +{ + Thread qReaderThread; + qReaderThread.start(qReader,NULL); + Thread mapReaderThread; + mapReaderThread.start(mapReader,NULL); + + Thread qWriterThread; + qWriterThread.start(qWriter,NULL); + Thread mapWriterThread; + mapWriterThread.start(mapWriter,NULL); + + qReaderThread.join(); + qWriterThread.join(); + mapReaderThread.join(); + mapWriterThread.join(); + + assert(q_last_write_val == 19); + assert(q_last_read_val == 19); + assert(m_last_write_val == 19); + assert(m_last_read_val == 19); + + printf("Done\n"); +} + + +// vim: ts=4 sw=4 diff --git a/tests/CommonLibs/InterthreadTest.ok b/tests/CommonLibs/InterthreadTest.ok new file mode 100644 index 0000000..a965a70 --- /dev/null +++ b/tests/CommonLibs/InterthreadTest.ok @@ -0,0 +1 @@ +Done diff --git a/tests/CommonLibs/LogTest.cpp b/tests/CommonLibs/LogTest.cpp new file mode 100644 index 0000000..5e51ce7 --- /dev/null +++ b/tests/CommonLibs/LogTest.cpp @@ -0,0 +1,69 @@ +/* +* Copyright 2009 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/>. + +*/ + +#include <iostream> +#include <iterator> + +#include "Logger.h" +extern "C" { +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/core/utils.h> +#include "debug.h" +} + +#define MYCAT 0 + +int main(int argc, char *argv[]) +{ + struct log_info_cat categories[1]; + struct log_info linfo; + categories[MYCAT] = { + "MYCAT", + NULL, + "Whatever", + LOGL_NOTICE, + 1, + }; + linfo.cat = categories; + linfo.num_cat = ARRAY_SIZE(categories); + + void *tall_ctx = talloc_named_const(NULL, 1, "OsmoTRX context"); + msgb_talloc_ctx_init(tall_ctx, 0); + + osmo_init_logging2(tall_ctx, &linfo); + + log_set_use_color(osmo_stderr_target, 0); + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 1); + + Log(MYCAT, LOGL_FATAL, __BASE_FILE__, __LINE__).get() << "testing the logger."; + Log(MYCAT, LOGL_ERROR, __BASE_FILE__, __LINE__).get() << "testing the logger."; + Log(MYCAT, LOGL_NOTICE, __BASE_FILE__, __LINE__).get() << "testing the logger."; + Log(MYCAT, LOGL_INFO, __BASE_FILE__, __LINE__).get() << "testing the logger."; + Log(MYCAT, LOGL_DEBUG, __BASE_FILE__, __LINE__).get() << "testing the logger."; +} diff --git a/tests/CommonLibs/LogTest.err b/tests/CommonLibs/LogTest.err new file mode 100644 index 0000000..153e850 --- /dev/null +++ b/tests/CommonLibs/LogTest.err @@ -0,0 +1,3 @@ +FATAL testing the logger. +ERROR testing the logger. +NOTICE testing the logger. diff --git a/tests/CommonLibs/LogTest.ok b/tests/CommonLibs/LogTest.ok new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/CommonLibs/LogTest.ok diff --git a/tests/CommonLibs/Makefile.am b/tests/CommonLibs/Makefile.am new file mode 100644 index 0000000..2a9a021 --- /dev/null +++ b/tests/CommonLibs/Makefile.am @@ -0,0 +1,46 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall -I$(top_srcdir)/CommonLibs $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -g +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS) + +EXTRA_DIST = BitVectorTest.ok \ + PRBSTest.ok \ + InterthreadTest.ok \ + SocketsTest.ok \ + TimevalTest.ok \ + VectorTest.ok \ + LogTest.ok \ + LogTest.err + +noinst_PROGRAMS = \ + BitVectorTest \ + PRBSTest \ + InterthreadTest \ + SocketsTest \ + TimevalTest \ + VectorTest \ + LogTest + +BitVectorTest_SOURCES = BitVectorTest.cpp +BitVectorTest_LDADD = $(COMMON_LA) + +PRBSTest_SOURCES = PRBSTest.cpp + +InterthreadTest_SOURCES = InterthreadTest.cpp +InterthreadTest_LDADD = $(COMMON_LA) +InterthreadTest_LDFLAGS = -lpthread $(AM_LDFLAGS) + +SocketsTest_SOURCES = SocketsTest.cpp +SocketsTest_LDADD = $(COMMON_LA) +SocketsTest_LDFLAGS = -lpthread $(AM_LDFLAGS) + +TimevalTest_SOURCES = TimevalTest.cpp +TimevalTest_LDADD = $(COMMON_LA) + +VectorTest_SOURCES = VectorTest.cpp +VectorTest_LDADD = $(COMMON_LA) + +LogTest_SOURCES = LogTest.cpp +LogTest_LDADD = $(COMMON_LA) + +MOSTLYCLEANFILES += testSource testDestination diff --git a/tests/CommonLibs/PRBSTest.cpp b/tests/CommonLibs/PRBSTest.cpp new file mode 100644 index 0000000..b83e93d --- /dev/null +++ b/tests/CommonLibs/PRBSTest.cpp @@ -0,0 +1,42 @@ +/* + * 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 + */ + +#include "PRBS.h" +#include <iostream> +#include <cstdlib> +#include <assert.h> + +void testPrbs(PRBS &prbs, uint64_t expectedPeriod) +{ + uint64_t period = 0; + do { + std::cout << prbs.generateBit(); + period++; + } while (!prbs.isFinished()); + std::cout << std::endl; + std::cout << "Period: " << period << std::endl; + assert(period == expectedPeriod); +} + +int main(int argc, char *argv[]) +{ + PRBS9 prbs9(0x01); + testPrbs(prbs9, (1<<9)-1); + PRBS15 prbs15(0x01); + testPrbs(prbs15, (1<<15)-1); +} diff --git a/tests/CommonLibs/PRBSTest.ok b/tests/CommonLibs/PRBSTest.ok new file mode 100644 index 0000000..d3fe378 --- /dev/null +++ b/tests/CommonLibs/PRBSTest.ok @@ -0,0 +1,4 @@ +1000010001100001001110010101011000011011110100110111001000101000010101101001111110110010010010110111111001001101010011001100000001100011001010001101001011111110100010110001110101100101100111100011111011101000001101011011011101100000101101011111010101010000001010010101111001011101110000001110011101001001111010111010100010010000110011100001011110110110011010000111011110000111111111000001111011111000101110011001000001001010011101101000111100111110011011000101010010001110001101101010111000100110001000100000000 +Period: 511 +1000000000000011000000000000101000000000001111000000000010001000000000110011000000001010101000000011111111000000100000001000001100000011000010100000101000111100001111001000100010001011001100110011101010101010100111111111111101000000000000111000000000001001000000000011011000000000101101000000001110111000000010011001000000110101011000001011111101000011100000111000100100001001001101100011011010110100101101111011101110110001100110011010010101010101110111111111110011000000000010101000000000111111000000001000001000000011000011000000101000101000001111001111000010001010001000110011110011001010100010101011111100111111100000101000000100001111000001100010001000010100110011000111101010101001000111111111011001000000001101011000000010111101000000111000111000001001001001000011011011011000101101101101001110110110111010011011011001110101101101010011110110111110100011011000011100101101000100101110111001101110011001010110010101011111010111111100001111000000100010001000001100110011000010101010101000111111111111001000000000001011000000000011101000000000100111000000001101001000000010111011000000111001101000001001010111000011011111001000101100001011001110100011101010011100100111110100101101000011101110111000100110011001001101010101011010111111111101111000000000110001000000001010011000000011110101000000100011111000001100100001000010101100011000111110100101001000011101111011000100110001101001101010010111010111110111001111000011001010001000101011110011001111100010101010000100111111110001101000000010010111000000110111001000001011001011000011101011101000100111100111001101000101001010111001111011111001010001100001011110010100011100010111100100100111000101101101001001110110111011010011011001101110101101010110011110111111010100011000001111100101000010000101111000110001110001001010010010011011110110110101100011011011110100101101100011101110110100100110011011101101010101100110111111110101011000000011111101000000100000111000001100001001000010100011011000111100101101001000101110111011001110011001101010010101010111110111111111000011000000001000101000000011001111000000101010001000001111110011000010000010101000110000111111001010001000001011110011000011100010101000100100111111001101101000001010110111000011111011001000100001101011001100010111101010100111000111111101001001000000111011011000001001101101000011010110111000101111011001001110001101011010010010111101110110111000110011011001001010101101011011111110111101100000011000110100000101001011100001111011100100010001100101100110010101110101010111110011111111000010100000001000111100000011001000100000101011001100001111101010100010000111111100110001000000101010011000001111110101000010000011111000110000100001001010001100011011110010100101100010111101110100111000110011101001001010100111011011111101001101100000111010110100001001111011100011010001100100101110010101101110010111110110010111000011010111001000101111001011001110001011101010010011100111110110100101000011011101111000101100110001001110101010011010011111110101110100000011110011100000100010100100001100111101100010101000110100111111001011101000001011100111000011100101001000100101111011001101110001101010110010010111111010110111000001111011001000010001101011000110010111101001010111000111011111001001001100001011011010100011101101111100100110110000101101011010001110111101110010011000110010110101001010111011111011111001100001100001010100010100011111100111100100000101000101100001111001110100010001010011100110011110100101010100011101111111100100110000000101101010000001110111110000010011000010000110101000110001011111001010011100001011110100100011100011101100100100100110101101101101011110110110111100011011011000100101101101001101110110111010110011011001111010101101010001111110111110010000011000010110000101000111010001111001001110010001011010010110011101110111010100110011001111101010101010000111111111110001000000000010011000000000110101000000001011111000000011100001000000100100011000001101100101000010110101111000111011110001001001100010011011010100110101101111101011110110000111100011010001000100101110011001101110010101010110010111111111010111000000001111001000000010001011000000110011101000001010100111000011111101001000100000111011001100001001101010100011010111111100101111000000101110001000001110010011000010010110101000110111011111001011001100001011101010100011100111111100100101000000101101111000001110110001000010011010011000110101110101001011110011111011100010100001100100111100010101101000100111110111001101000011001010111000101011111001001111100001011010000100011101110001100100110010010101101010110111110111111011000011000001101000101000010111001111000111001010001001001011110011011011100010101101100100111110110101101000011011110111000101100011001001110100101011010011101111101110100110000110011101010001010100111110011111101000010100000111000111100001001001000100011011011001100101101101010101110110111111110011011000000010101101000000111110111000001000011001000011000101011000101001111101001111010000111010001110001001110010010011010010110110101110111011011110011001101100010101010110100111111111011101000000001100111000000010101001000000111111011000001000001101000011000010111000101000111001001111001001011010001011011101110011101100110010100110101010111101011111111000111100000001001000100000011011001100000101101010100001110111111100010011000000100110101000001101011111000010111100001000111000100011001001001100101011011010101111101101111110000110110000010001011010000110011101110001010100110010011111101010110100000111111011100001000001100100011000010101100101000111110101111001000011110001011000100010011101001100110100111010101011101001111111100111010000000101001110000001111010010000010001110110000110010011010001010110101110011111011110010100001100010111100010100111000100111101001001101000111011010111001001101111001011010110001011101111010011100110001110100101010010011101111110110100110000011011101010000101100111110001110101000010010011111000110110100001001011011100011011101100100101100110101101110101011110110011111100011010100000100101111100001101110000100010110010001100111010110010101001111010111111010001111000001110010001000010010110011000110111010101001011001111111011101010000001100111110000010101000010000111111000110001000001001010011000011011110101000101100011111001110100100001010011101100011110100110100100011101011101100100111100110101101000101011110111001111100011001010000100101011110001101111100010010110000100110111010001101011001110010111101010010111000111110111001001000011001011011000101011101101001111100110111010000101011001110001111101010010010000111110110110001000011011010011000101101110101001110110011111010011010100001110101111100010011110000100110100010001101011100110010111100101010111000101111111001001110000001011010010000011101110110000100110011010001101010101110010111111110010111000000010111001000000111001011000001001011101000011011100111000101100101001001110101111011010011110001101110100010010110011100110111010100101011001111101111101010000110000111110001010001000010011110011000110100010101001011100111111011100101000001100101111000010101110001000111110010011001000010110101011000111011111101001001100000111011010100001001101111100011010110000100101111010001101110001110010110010010010111010110110111001111011011001010001101101011110010110111100010111011000100111001101001101001010111010111011111001111001100001010001010100011110011111100100010100000101100111100001110101000100010011111001100110100001010101011100011111111100100100000000101101100000001110110100000010011011100000110101100100001011110101100011100011110100100100100011101101101100100110110110101101011011011110111101101100011000110110100101001011011101111011101100110001100110101010010101011111110111111100000011000000100000101000001100001111000010100010001000111100110011001000101010101011001111111111101010000000000111110000000001000010000000011000110000000101001010000001111011110000010001100010000110010100110001010111101010011111000111110100001001000011100011011000100100101101001101101110111010110110011001111011010101010001101111111110010110000000010111010000000111001110000001001010010000011011110110000101100011010001110100101110010011101110010110100110010111011101010111001100111111001010101000001011111111000011100000001000100100000011001101100000101010110100001111111011100010000001100100110000010101101010000111110111110001000011000010011000101000110101001111001011111010001011100001110011100100010010100101100110111101110101011000110011111101001010100000111011111100001001100000100011010100001100101111100010101110000100111110010001101000010110010111000111010111001001001111001011011010001011101101110011100110110010100101011010111101111101111000110000110001001010001010011011110011110101100010100011110100111100100011101000101100100111001110101101001010011110111011110100011001100011100101010100100101111111101101110000000110110010000001011010110000011101111010000100110001110001101010010010010111110110110111000011011011001000101101101011001110110111101010011011000111110101101001000011110111011000100011001101001100101010111010101111111001111110000001010000010000011110000110000100010001010001100110011110010101010100010111111111100111000000000101001000000001111011000000010001101000000110010111000001010111001000011111001011000100001011101001100011100111010100100101001111101101111010000110110001110001011010010010011101110110110100110011011011101010101101100111111110110101000000011011111000000101100001000001110100011000010011100101000110100101111001011101110001011100110010011100101010110100101111111011101110000001100110010000010101010110000111111111010001000000001110011000000010010101000000110111111000001011000001000011101000011000100111000101001101001001111010111011010001111001101110010001010110010110011111010111010100001111001111100010001010000100110011110001101010100010010111111100110111000000101011001000001111101011000010000111101000110001000111001010011001001011110101011011100011111101100100100000110101101100001011110110100011100011011100100100101100101101101110101110110110011110011011010100010101101111100111110110000101000011010001111000101110010001001110010110011010010111010101110111001111110011001010000010101011110000111111100010001000000100110011000001101010101000010111111111000111000000001001001000000011011011000000101101101000001110110111000010011011001000110101101011001011110111101011100011000111100100101001000101101111011001110110001101010011010010111110101110111000011110011001000100010101011001100111111101010101000000111111111000001000000001000011000000011000101000000101001111000001111010001000010001110011000110010010101001010110111111011111011000001100001101000010100010111000111100111001001000101001011011001111011101101010001100110111110010101011000010111111101000111000000111001001000001001011011000011011101101000101100110111001110101011001010011111101011110100000111100011100001000100100100011001101101100101010110110101111111011011110000001101100010000010110100110000111011101010001001100111110011010101000010101111111000111110000001001000010000011011000110000101101001010001110111011110010011001100010110101010100111011111111101001100000000111010100000001001111100000011010000100000101110001100001110010010100010010110111100110111011000101011001101001111101010111010000111111001110001000001010010011000011110110101000100011011111001100101100001010101110100011111110011100100000010100101100000111101110100001000110011100011001010100100101011111101101111100000110110000100001011010001100011101110010100100110010111101101010111000110111111001001011000001011011101000011101100111000100110101001001101011111011010111100001101111000100010110001001100111010011010101001110101111111010011110000001110100010000010011100110000110100101010001011101111110011100110000010100101010000111101111110001000110000010011001010000110101011110001011111100010011100000100110100100001101011101100010111100110100111000101011101001001111100111011010000101001101110001111010110010010001111010110110010001111011010110010001101111010110010110001111010111010010001111001110110010001010011010110011110101111010100011110001111100100010010000101100110110001110101011010010011111101110110100000110011011100001010101100100011111110101100100000011110101100000100011110100001100100011100010101100100100111110101101101000011110110111000100011011001001100101101011010101110111101111110011000110000010101001010000111111011110001000001100010011000010100110101000111101011111001000111100001011001000100011101011001100100111101010101101000111111110111001000000011001011000000101011101000001111100111000010000101001000110001111011001010010001101011110110010111100011010111000100101111001001101110001011010110010011101111010110100110001111011101010010001100111110110010101000011010111111000101111000001001110001000011010010011000101110110101001110011011111010010101100001110111110100010011000011100110101000100101011111001101111100001010110000100011111010001100100001110010101100010010111110100110111000011101011001000100111101011001101000111101010111001000111111001011001000001011101011000011100111101000100101000111001101111001001010110001011011111010011101100001110100110100010011101011100110100111100101011101000101111100111001110000101001010010001111011110110010001100011010110010100101111010111101110001111000110010010001001010110110011011111011010101100001101111110100010110000011100111010000100101001110001101111010010010110001110110111010010011011001110110101101010011011110111110101100011000011110100101000100011101111001100100110001010101101010011111110111110100000011000011100000101000100100001111001101100010001010110100110011111011101010100001100111111100010101000000100111111000001101000001000010111000011000111001000101001001011001111011011101010001101100111110010110101000010111011111000111001100001001001010100011011011111100101101100000101110110100001110011011100010010101100100110111110101101011000011110111101000100011000111001100101001001010101111011011111110001101100000010010110100000110111011100001011001100100011101010101100100111111110101101000000011110111000000100011001000001100101011000010101111101000111110000111001000010001001011000110011011101001010101100111011111110101001100000011111010100000100001111100001100010000100010100110001100111101010010101000111110111111001000011000001011000101000011101001111000100111010001001101001110011010111010010101111001110111110001010011000010011110101000110100011111001011100100001011100101100011100101110100100101110011101101110010100110110010111101011010111000111101111001001000110001011011001010011101101011110100110111100011101011000100100111101001101101000111010110111001001111011001011010001101011101110010111100110010111000101010111001001111111001011010000001011101110000011100110010000100101010110001101111111010010110000001110111010000010011001110000110101010010001011111110110011100000011010100100000101111101100001110000110100010010001011100110110011100101011010100101111101111101110000110000110010001010001010110011110011111010100010100001111100111100010000101000100110001111001101010010001010111110110011111000011010100001000101111100011001110000100101010010001101111110110010110000011010111010000101111001110001110001010010010010011110110110110100011011011011100101101101100101110110110101110011011011110010101101100010111110110100111000011011101001000101100111011001110101001101010011111010111110100001111000011100010001000100100110011001101101010101010110111111111111011000000000001101000000000010111000000000111001000000001001011000000011011101000000101100111000001110101001000010011111011000110100001101001011100010111011100100111001100101101001010101110111011111110011001100000010101010100000111111111100001000000000100011000000001100101000000010101111000000111110001000001000010011000011000110101000101001011111001111011100001010001100100011110010101100100010111110101100111000011110101001000100011111011001100100001101010101100010111111110100111000000011101001000000100111011000001101001101000010111010111000111001111001001001010001011011011110011101101100010100110110100111101011011101000111101100111001000110101001011001011111011101011100001100111100100010101000101100111111001110101000001010011111000011110100001000100011100011001100100100101010101101101111111110110110000000011011010000000101101110000001110110010000010011010110000110101111010001011110001110011100010010010100100110110111101101011011000110111101101001011000110111011101001011001100111011101010101001100111111111010101000000001111111000000010000001000000110000011000001010000101000011110001111000100010010001001100110110011010101011010101111111101111110000000110000010000001010000110000011110001010000100010011110001100110100010010101011100110111111100101011000000101111101000001110000111000010010001001000110110011011001011010101101011101111110111100110000011000101010000101001111110001111010000010010001110000110110010010001011010110110011101111011010100110001101111101010010110000111110111010001000011001110011000101010010101001111110111111010000011000001110000101000010010001111000110110010001001011010110011011101111010101100110001111110101010010000011111110110000100000011010001100000101110010100001110010111100010010111000100110111001001101011001011010111101011101111000111100110001001000101010011011001111110101101010000011110111110000100011000010001100101000110010101111001010111110001011111000010011100001000110100100011001011101100101011100110101111100101011110000101111100010001110000100110010010001101010110110010111111011010111000001101111001000010110001011000111010011101001001110100111011010011101001101110100111010110011101001111010100111010001111101001110010000111010010110001001110111010011010011001110101110101010011110011111110100010100000011100111100000100101000100001101111001100010110001010100111010011111101001110100000111010011100001001110100100011010011101100101110100110101110011101011110010100111100010111101000100111000111001101001001001010111011011011111001101101100001010110110100011111011011100100001101100101100010110101110100111011110011101001100010100111010100111101001111101000111010000111001001110001001011010010011011101110110101100110011011110101010101100011111111110100100000000011101100000000100110100000001101011100000010111100100000111000101100001001001110100011011010011100101101110100101110110011101110011010100110010101111101010111110000111111000010001000001000110011000011001010101000101011111111001111100000001010000100000011110001100000100010010100001100110111100010101011000100111111101001101000000111010111000001001111001000011010001011000101110011101001110010100111010010111101001110111000111010011001001001110101011011010011111101101110100000110110011100001011010100100011101111101100100110000110101101010001011110111110011100011000010100100101000111101101111001000110110001011001011010011101011101110100111100110011101000101010100111001111111101001010000000111011110000001001100010000011010100110000101111101010001110000111110010010001000010110110011000111011010101001001101111111011010110000001101111010000010110001110000111010010010001001110110110011010011011010101110101101111110011110110000010100011010000111100101110001000101110010011001110010110101010010111011111110111001100000011001010100000101011111100001111100000100010000100001100110001100010101010010100111111110111101000000011000111000000101001001000001111011011000010001101101000110010110111001010111011001011111001101011100001010111100100011111000101100100001001110101100011010011110100101110100011101110011100100110010100101101010111101110111111000110011000001001010101000011011111111000101100000001001110100000011010011100000101110100100001110011101100010010100110100110111101011101011000111100111101001000101000111011001111001001101010001011010111110011101111000010100110001000111101010011001000111110101011001000011111101011000100000111101001100001000111010100011001001111100101011010000101111101110001110000110010010010001010110110110011111011011010100001101101111100010110110000100111011010001101001101110010111010110010111001111010111001010001111001011110010001011100010110011100100111010100101101001111101110111010000110011001110001010101010010011111111110110100000000011011100000000101100100000001110101100000010011110100000110100011100001011100100100011100101101100100101110110101101110011011110110010101100011010111110100101111000011101110001000100110010011001101010110101010111111011111111000001100000001000010100000011000111100000101001000100001111011001100010001101010100110010111111101010111000000111111001000001000001011000011000011101000101000100111001111001101001010001010111011110011111001100010100001010100111100011111101000100100000111001101100001001010110100011011111011100101100001100101110100010101110011100111110010100101000010111101111000111000110001001001001010011011011011110101101101100011110110110100100011011011101100101101100110101110110101011110011011111100010101100000100111110100001101000011100010111000100100111001001101101001011010110111011101111011001100110001101010101010010111111111110111000000000011001000000000101011000000001111101000000010000111000000110001001000001010011011000011110101101000100011110111001100100011001010101100101011111110101111100000011110000100000100010001100001100110010100010101010111100111111111000101000000001001111000000011010001000000101110011000001110010101000010010111111000110111000001001011001000011011101011000101100111101001110101000111010011111001001110100001011010011100011101110100100100110011101101101010100110110111111101011011000000111101101000001000110111000011001011001000101011101011001111100111101010000101000111110001111001000010010001011000110110011101001011010100111011101111101001100110000111010101010001001111111110011010000000010101110000000111110010000001000010110000011000111010000101001001110001111011010010010001101110110110010110011011010111010101101111001111110110001010000011010011110000101110100010001110011100110010010100101010110111101111111011000110000001101001010000010111011110000111001100010001001010100110011011111101010101100000111111110100001000000011100011000000100100101000001101101111000010110110001000111011010011001001101110101011010110011111101111010100000110001111100001010010000100011110110001100100011010010101100101110111110101110011000011110010101000100010111111001100111000001010101001000011111111011000100000001101001100000010111010100000111001111100001001010000100011011110001100101100010010101110100110111110011101011000010100111101000111101000111001000111001001011001001011011101011011101100111101100110101000110101011111001011111100001011100000100011100100001100100101100010101101110100111110110011101000011010100111000101111101001001110000111011010010001001101110110011010110011010101111010101111110001111110000010010000010000110110000110001011010001010011101110011110100110010100011101010111100100111111000101101000001001110111000011010011001000101110101011001110011111101010010100000111110111100001000011000100011000101001100101001111010101111010001111110001110010000010010010110000110110111010001011011001110011101101010010100110111110111101011000011000111101000101001000111001111011001001010001101011011110010111101100010111000110100111001001011101001011011100111011101100101001100110101111010101011110001111111100010010000000100110110000001101011010000010111101110000111000110010001001001010110011011011111010101101100001111110110100010000011011100110000101100101010001110101111110010011110000010110100010000111011100110001001100101010011010101111110101111110000011110000010000100010000110001100110001010010101010011110111111110100011000000011100101000000100101111000001101110001000010110010011000111010110101001001111011111011010001100001101110010100010110010111100111010111000101001111001001111010001011010001110011101110010010100110010110111101010111011000111111001101001000001010111011000011111001101000100001010111001100011111001010100100001011111101100011100000110100100100001011101101100011100110110100100101011011101101111101100110110000110101011010001011111101110011100000110010100100001010111101100011111000110100100001001011101100011011100110100101100101011101110101111100110011110000101010100010001111111100110010000000101010110000001111111010000010000001110000110000010010001010000110110011110001011010100010011101111100110100110000101011101010001111100111110010000101000010110001111000111010010001001001110110011011010011010101101110101111110110011110000011010100010000101111100110001110000101010010010001111110110110010000011011010110000101101111010001110110001110010011010010010110101110110111011110011011001100010101101010100111110111111101000011000000111000101000001001001111000011011010001000101101110011001110110010101010011010111111110101111000000011110001000000100010011000001100110101000010101011111000111111100001001000000100011011000001100101101000010101110111000111110011001001000010101011011000111111101101001000000110111011000001011001101000011101010111000100111111001001101000001011010111000011101111001000100110001011001101010011101010111110100111111000011101000001000100111000011001101001000101010111011001111111001101010000001010111110000011111000010000100001000110001100011001010010100101011110111101111100011000110000100101001010001101111011110010110001100010111010010100111001110111101001010011000111011110101001001100011111011010100100001101111101100010110000110100111010001011101001110011100111010010100101001110111101111010011000110001110101001010010011111011110110100001100011011100010100101100100111101110101101000110011110111001010100011001011111100101011100000101111100100001110000101100010010001110100110110010011101011010110100111101111011101000110001100111001010010101001011110111111011100011000001100100101000010101101111000111110110001001000011010011011000101110101101001110011110111010010100011001110111100101010011000101111110101001110000011111010010000100001110110001100010011010010100110101110111101011110011000111100010101001000100111111011001101000001101010111000010111111001000111000001011001001000011101011011000100111101101001101000110111010111001011001111001011101010001011100111110011100101000010100101111000111101110001001000110010011011001010110101101011111011110111100001100011000100010100101001100111101111010101000110001111111001010010000001011110110000011100011010000100100101110001101101110010010110110010110111011010111011001101111001101010110001010111111010011111000001110100001000010011100011000110100100101001011101101111011100110110001100101011010010101111101110111110000110011000010001010101000110011111111001010100000001011111100000011100000100000100100001100001101100010100010110100111100111011101000101001100111001111010101001010001111111011110010000001100010110000010100111010000111101001110001000111010010011001001110110101011010011011111101110101100000110011110100001010100011100011111100100100100000101101101100001110110110100010011011011100110101101100101011110110101111100011011110000100101100010001101110100110010110011101010111010100111111001111101000001010000111000011110001001000100010011011001100110101101010101011110111111111100011000000000100101000000001101111000000010110001000000111010011000001001110101000011010011111000101110100001001110011100011010010100100101110111101101110011000110110010101001011010111111011101111000001100110001000010101010011000111111110101001000000011111011000000100001101000001100010111000010100111001000111101001011001000111011101011001001100111101011010101000111101111111001000110000001011001010000011101011110000100111100010001101000100110010111001101010111001010111111001011111000001011100001000011100100011000100101100101001101110101111010110011110001111010100010010001111100110110010000101011010110001111101111010010000110001110110001010010011010011110110101110100011011110011100101100010100101110100111101110011101000110010100111001010111101001011111000111011100001001001100100011011010101100101101111110101110110000011110011010000100010101110001100111110010010101000010110111111000111011000001001001101000011011010111000101101111001001110110001011010011010011101110101110100110011110011101010100010100111111100111101000000101000111000001111001001000010001011011000110011101101001010100110111011111101011001100000111101010100001000111111100011001000000100101011000001101111101000010110000111000111010001001001001110011011011010010101101101110111110110110011000011011010101000101101111111001110110000001010011010000011110101110000100011110010001100100010110010101100111010111110101001111000011111010001000100001110011001100010010101010100110111111111101011000000000111101000000001000111000000011001001000000101011011000001111101101000010000110111000110001011001001010011101011011110100111101100011101000110100100111001011101101001011100110111011100101011001100101111101010101110000111111110010001000000010110011000000111010101000001001111111000011010000001000101110000011001110010000101010010110001111110111010010000011001110110000101010011010001111110101110010000011110010110000100010111010001100111001110010101001010010111111011110111000001100011001000010100101011000111101111101001000110000111011001010001001101011110011010111100010101111000100111110001001101000010011010111000110101111001001011110001011011100010011101100100110100110101101011101011110111100111100011000101000100101001111001101111010001010110001110011111010010010100001110110111100010011011000100110101101001101011110111010111100011001111000100101010001001101111110011010110000010101111010000111110001110001000010010010011000110110110101001011011011111011101101100001100110110100010101011011100111111101100101000000110101111000001011110001000011100010011000100100110101001101101011111010110111100001111011000100010001101001100110010111010101010111001111111111001010000000001011110000000011100010000000100100110000001101101010000010110111110000111011000010001001101000110011010111001010101111001011111110001011100000010011100100000110100101100001011101110100011100110011100100101010100101101111111101110110000000110011010000001010101110000011111110010000100000010110001100000111010010100001001110111100011010011000100101110101001101110011111010110010100001111010111100010001111000100110010001001101010110011010111111010101111000001111110001000010000010011000110000110101001010001011111011110011100001100010100100010100111101100111101000110101000111001011111001001011100001011011100100011101100101100100110101110101101011110011110111100010100011000100111100101001101000101111010111001110001111001010010010001011110110110011100011011010100100101101111101101110110000110110011010001011010101110011101111110010100110000010111101010000111000111110001001001000010011011011000110101101101001011110110111011100011011001100100101101010101101110111111110110011000000011010101000000101111111000001110000001000010010000011000110110000101001011010001111011101110010001100110010110010101010111010111111111001111000000001010001000000011110011000000100010101000001100111111000010101000001000111111000011001000001000101011000011001111101000101010000111001111110001001010000010011011110000110101100010001011110100110011100011101010100100100111111101101101000000110110111000001011011001000011101101011000100110111101001101011000111010111101001001111000111011010001001001101110011011010110010101101111010111110110001111000011010010001000101110110011001110011010101010010101111111110111110000000011000010000000101000110000001111001010000010001011110000110011100010001010100100110011111101101010100000110111111100001011000000100011101000001100100111000010101101001000111110111011001000011001101011000101010111101001111111000111010000001001001110000011011010010000101101110110001110110011010010011010101110110101111110011011110000010101100010000111110100110001000011101010011000100111110101001101000011111010111000100001111001001100010001011010100110011101111101010100110000111111101010001000000111110011000001000010101000011000111111000101001000001001111011000011010001101000101110010111001110010111001010010111001011110111001011100011001011100100101011100101101111100101110110000101110011010001110010101110010010111110010110111000010111011001000111001101011001001010111101011011111000111101100001001000110100011011001011100101101011100101110111100101110011000101110010101001110010111111010010111000001110111001000010011001011000110101011101001011111100111011100000101001100100001111010101100010001111110100110010000011101010110000100111111010001101000001110010111000010010111001000110111001011001011001011101011101011100111100111100101000101000101111001111001110001010001010010011110011110110100010100011011100111100101100101000101110101111001110011110001010010100010011110111100110100011000101011100101001111100101111010000101110001110001110010010010010010110110110110111011011011011001101101101101010110110110111111011011011000001101101101000010110110111000111011011001001001101101011011010110111101101111011000110110001101001011010010111011101110111001100110011001010101010101011111111111111100000000000000 +Period: 32767 diff --git a/tests/CommonLibs/SocketsTest.cpp b/tests/CommonLibs/SocketsTest.cpp new file mode 100644 index 0000000..e4eef54 --- /dev/null +++ b/tests/CommonLibs/SocketsTest.cpp @@ -0,0 +1,101 @@ +/* +* 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 "Sockets.h" +#include "Threads.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> + +static const int gNumToSend = 10; + +static void sigalarm_handler(int foo) +{ + printf("FAIL: test did not run successfully\n"); + exit(EXIT_FAILURE); +} + +void *testReaderIP(void *param) +{ + UDPSocket *readSocket = (UDPSocket *)param; + readSocket->nonblocking(); + int rc = 0; + while (rc<gNumToSend) { + char buf[MAX_UDP_LENGTH+1] = { 0 }; + int count = readSocket->read(buf, MAX_UDP_LENGTH); + if (count>0) { + buf[count] = 0; + CERR("read: " << buf); + rc++; + } else { + sleep(2); + } + } + return NULL; +} + +int main(int argc, char * argv[] ) +{ + int count; + + if (signal(SIGALRM, sigalarm_handler) == SIG_ERR) { + perror("signal"); + exit(EXIT_FAILURE); + } + + /* If the test takes longer than 2*gNumToSend seconds, abort it */ + alarm(2* gNumToSend); + + UDPSocket readSocket("127.0.0.1", 0); + UDPSocket socket1("127.0.0.1", 0, "localhost", readSocket.port()); + + CERR("socket1: " << socket1.port() << ", readSocket: " << readSocket.port()); + + Thread readerThreadIP; + readerThreadIP.start(testReaderIP, &readSocket); + + // give the readers time to open + sleep(1); + + for (int i=0; i<gNumToSend; i++) { + CERR("write"); + count = socket1.write("Hello IP land"); + if (count < 0) { + COUT("FAIL: write"); + exit(EXIT_FAILURE); + } + sleep(1); + } + + readerThreadIP.join(); + + printf("Done\n"); +} + +// vim: ts=4 sw=4 diff --git a/tests/CommonLibs/SocketsTest.ok b/tests/CommonLibs/SocketsTest.ok new file mode 100644 index 0000000..a965a70 --- /dev/null +++ b/tests/CommonLibs/SocketsTest.ok @@ -0,0 +1 @@ +Done diff --git a/tests/CommonLibs/TimevalTest.cpp b/tests/CommonLibs/TimevalTest.cpp new file mode 100644 index 0000000..61a92c9 --- /dev/null +++ b/tests/CommonLibs/TimevalTest.cpp @@ -0,0 +1,64 @@ +/* +* 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" +#include <iostream> +#include <assert.h> +#include <sys/time.h> + +using namespace std; + +int main(int argc, char *argv[]) +{ + long last_remaining = 10000; + Timeval then(last_remaining); + assert(then.elapsed() == -last_remaining); + cerr << then << " elapsed: " << then.elapsed() << endl; + + /* Check that last_remaining parameter affects setting time in the future */ + usleep(10000); + double increased_time_secs = Timeval().seconds(); + assert(increased_time_secs <= then.seconds()); + + struct timespec invariant_time = then.timespec(); + int loops = 0; + + while (!then.passed()) { + struct timespec tspecnow = then.timespec(); + cerr << "now: " << Timeval().seconds() << " then: " << then << " remaining: " << then.remaining() << endl; + assert(last_remaining >= then.remaining()); + assert(tspecnow.tv_sec == invariant_time.tv_sec && tspecnow.tv_nsec == invariant_time.tv_nsec); + usleep(500000); + loops++; + } + cerr << "now: " << Timeval() << " then: " << then << " remaining: " << then.remaining() << endl; + assert(then.remaining() <= 0); + assert(loops >= 18); + + printf("Done\n"); +} diff --git a/tests/CommonLibs/TimevalTest.ok b/tests/CommonLibs/TimevalTest.ok new file mode 100644 index 0000000..a965a70 --- /dev/null +++ b/tests/CommonLibs/TimevalTest.ok @@ -0,0 +1 @@ +Done diff --git a/tests/CommonLibs/VectorTest.cpp b/tests/CommonLibs/VectorTest.cpp new file mode 100644 index 0000000..6958889 --- /dev/null +++ b/tests/CommonLibs/VectorTest.cpp @@ -0,0 +1,63 @@ +/* +* 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 "Vector.h" +#include <iostream> + +using namespace std; + +typedef Vector<int> TestVector; + +int main(int argc, char *argv[]) +{ + TestVector test1(5); + for (int i=0; i<5; i++) test1[i]=i; + TestVector test2(5); + for (int i=0; i<5; i++) test2[i]=10+i; + + cout << test1 << endl; + cout << test2 << endl; + + { + TestVector testC(test1,test2); + cout << testC << endl; + cout << testC.head(3) << endl; + cout << testC.tail(3) << endl; + testC.fill(8); + cout << testC << endl; + test1.copyToSegment(testC,3); + cout << testC << endl; + + TestVector testD(testC.segment(4,3)); + cout << testD << endl; + testD.fill(9); + cout << testC << endl; + cout << testD << endl; + } + + return 0; +} diff --git a/tests/CommonLibs/VectorTest.ok b/tests/CommonLibs/VectorTest.ok new file mode 100644 index 0000000..afe5162 --- /dev/null +++ b/tests/CommonLibs/VectorTest.ok @@ -0,0 +1,10 @@ +0 1 2 3 4 +10 11 12 13 14 +0 1 2 3 4 10 11 12 13 14 +0 1 2 +3 4 10 11 12 13 14 +8 8 8 8 8 8 8 8 8 8 +8 8 8 0 1 2 3 4 8 8 +1 2 3 +8 8 8 0 9 9 9 4 8 8 +9 9 9 diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..d4589a4 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,41 @@ +SUBDIRS = \ + CommonLibs \ + Transceiver52M \ + $(NULL) + +# The `:;' works around a Bash 3.2 bug when the output is not writeable. +$(srcdir)/package.m4: $(top_srcdir)/configure.ac + :;{ \ + echo '# Signature of the current package.' && \ + echo 'm4_define([AT_PACKAGE_NAME],' && \ + echo ' [$(PACKAGE_NAME)])' && \ + echo 'm4_define([AT_PACKAGE_TARNAME],' && \ + echo ' [$(PACKAGE_TARNAME)])' && \ + echo 'm4_define([AT_PACKAGE_VERSION],' && \ + echo ' [$(PACKAGE_VERSION)])' && \ + echo 'm4_define([AT_PACKAGE_STRING],' && \ + echo ' [$(PACKAGE_STRING)])' && \ + echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \ + echo ' [$(PACKAGE_BUGREPORT)])'; \ + echo 'm4_define([AT_PACKAGE_URL],' && \ + echo ' [$(PACKAGE_URL)])'; \ + } >'$(srcdir)/package.m4' + +EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) +TESTSUITE = $(srcdir)/testsuite +DISTCLEANFILES = atconfig $(NULL) + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + +installcheck-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' $(TESTSUITEFLAGS) + +clean-local: + test ! -f '$(TESTSUITE)' || $(SHELL) '$(TESTSUITE)' --clean + +AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te +AUTOTEST = $(AUTOM4TE) --language=autotest +$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4 + $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at + mv $@.tmp $@ diff --git a/tests/Transceiver52M/Makefile.am b/tests/Transceiver52M/Makefile.am new file mode 100644 index 0000000..06db5b0 --- /dev/null +++ b/tests/Transceiver52M/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/Makefile.common + +AM_CFLAGS = -Wall -I$(top_srcdir)/Transceiver52M -I$(top_srcdir)/Transceiver52M/arch/common $(STD_DEFINES_AND_INCLUDES) -g + +EXTRA_DIST = convolve_test.ok + +noinst_PROGRAMS = \ + convolve_test + +convolve_test_SOURCES = convolve_test.c +convolve_test_CFLAGS = $(AM_CFLAGS) +convolve_test_LDADD = $(COMMON_LA) $(ARCH_LA) +if HAVE_SSE3 +convolve_test_CFLAGS += $(SIMD_FLAGS) +endif +if HAVE_SSE4_1 +convolve_test_CFLAGS += $(SIMD_FLAGS) +endif diff --git a/tests/Transceiver52M/convolve_test.c b/tests/Transceiver52M/convolve_test.c new file mode 100644 index 0000000..54bc7a1 --- /dev/null +++ b/tests/Transceiver52M/convolve_test.c @@ -0,0 +1,150 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "convolve.h" + +#define TESTVEC_LEN 1000 +#define DO_INIT 1 + +float x_vect[TESTVEC_LEN]; +float y_vect[TESTVEC_LEN]; +float h_vect[TESTVEC_LEN]; + +float *x; +float *h; +float *y; + +/* Generate some random values for testing */ +void gen_floats(float *vect, int len) +{ + int i; + for (i = 0; i < len; i++) { + vect[i] = (float)rand()/(float)(RAND_MAX); + } +} + +/* Reset testvectors */ +static void reset_testvec(int seed) +{ + srand(seed); + memset(x_vect,0,sizeof(x_vect)); + memset(y_vect,0,sizeof(y_vect)); + memset(h_vect,0,sizeof(h_vect)); + + x=x_vect + TESTVEC_LEN/2; + y=y_vect + TESTVEC_LEN/2; + h=h_vect + TESTVEC_LEN/2; + + gen_floats(x_vect,TESTVEC_LEN); + gen_floats(h_vect,TESTVEC_LEN); +} + +/* Show float vector data cut and paste friendly */ +static void dump_floats(float *vect, int len, char *name) +{ + int i; + + printf("float %s[] = {", name); + for(i = 0; i < len; i++) { + + printf("%f",vect[i]); + + if(i<len-1) + printf(","); + } + printf("}\n"); +} + +/* Test complex convolution */ +static void test_convolve_complex(int h_len) +{ + int x_len; + int y_len; + int start; + int len; + int step; + int offset; + + x_len=34; + y_len=26; + start=8; + len=26; + step=1; + offset=1; + reset_testvec(0); + dump_floats(x,x_len,"x"); + printf("\n"); + dump_floats(h,h_len,"h"); + printf("\n"); + convolve_complex(x, x_len, h, h_len, y, y_len, start, len, step, offset); + dump_floats(y,y_len,"y"); + printf("\n"); +} + +/* Test real convolution */ +static void test_convolve_real(int h_len) +{ + int x_len; + int y_len; + int start; + int len; + int step; + int offset; + + x_len=34; + y_len=26; + start=8; + len=26; + step=1; + offset=1; + reset_testvec(0); + dump_floats(x,x_len,"x"); + printf("\n"); + dump_floats(h,h_len,"h"); + printf("\n"); + convolve_real(x, x_len, h, h_len, y, y_len, start, len, step, offset); + dump_floats(y,y_len,"y"); + printf("\n"); +} + +int main(void) +{ +#if DO_INIT == 1 + convolve_init(); +#endif + + printf("==== TEST COMPLEX BASE IMPLEMENTATION ====\n"); + test_convolve_complex(17); + + printf("==== TEST COMPLEX SSE3 IMPLEMENTATION: (h_len%%4=0) ====\n"); + test_convolve_complex(20); + + printf("==== TEST COMPLEX SSE3 IMPLEMENTATION: (h_len%%8=0) ====\n"); + test_convolve_complex(16); + + printf("\n"); + printf("\n"); + + printf("==== TEST REAL BASE IMPLEMENTATION ====\n"); + test_convolve_real(17); + + printf("==== TEST REAL SSE3 IMPLEMENTATION (hlen=4) ====\n"); + test_convolve_real(4); + + printf("==== TEST REAL SSE3 IMPLEMENTATION (hlen=8) ====\n"); + test_convolve_real(8); + + printf("==== TEST REAL SSE3 IMPLEMENTATION (hlen=12) ====\n"); + test_convolve_real(12); + + printf("==== TEST REAL SSE3 IMPLEMENTATION (hlen=16) ====\n"); + test_convolve_real(16); + + printf("==== TEST REAL SSE3 IMPLEMENTATION (hlen=20) ====\n"); + test_convolve_real(20); + + printf("==== TEST REAL SSE3 IMPLEMENTATION (h_len%%4=0) ====\n"); + test_convolve_real(24); + + return 0; +} diff --git a/tests/Transceiver52M/convolve_test.ok b/tests/Transceiver52M/convolve_test.ok new file mode 100644 index 0000000..5766252 --- /dev/null +++ b/tests/Transceiver52M/convolve_test.ok @@ -0,0 +1,72 @@ +==== TEST COMPLEX BASE IMPLEMENTATION ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995,0.446645} + +float y[] = {0.389293,10.824917,-0.676577,10.619646,0.283489,11.279525,0.384482,11.586230,0.711259,11.540458,-0.391531,11.281723,0.019900,12.278080,-0.070459,11.104558,0.087938,11.825965,-1.003252,11.698885,0.358887,11.911197,-0.678904,11.933812,0.245140,11.886644} + +==== TEST COMPLEX SSE3 IMPLEMENTATION: (h_len%4=0) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995,0.446645,0.327805,0.785346,0.676628} + +float y[] = {-0.641594,12.367426,-0.970113,12.963129,-0.466783,13.747334,0.637486,13.341836,-0.168561,14.091346,0.306652,15.018833,0.233741,14.726789,-0.011241,15.034849,0.000155,13.639509,0.558827,15.495646,-0.406179,14.103148,-0.000244,15.591370,-0.492319,14.785577} + +==== TEST COMPLEX SSE3 IMPLEMENTATION: (h_len%8=0) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995} + +float y[] = {-0.278295,10.097409,0.919633,11.502825,0.340383,10.979163,0.891132,11.679869,0.425363,11.186544,1.099703,12.121126,0.188196,11.180099,0.228905,12.436676,0.149904,11.522589,0.543155,11.703615,0.033465,12.425473,0.561782,12.373415,-0.218184,12.154579} + + + +==== TEST REAL BASE IMPLEMENTATION ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995,0.446645} + +float y[] = {5.354852,5.387001,4.829278,5.046340,5.849788,5.775999,5.653334,5.372714,5.999860,5.593828,5.628739,5.178002,6.010774,6.186034,6.337766,5.538046,5.616131,6.289612,5.486091,5.835261,6.277413,5.894117,5.563587,6.082063,5.828556,6.160175} + +==== TEST REAL SSE3 IMPLEMENTATION (hlen=4) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604} + +float y[] = {1.154625,1.856899,1.754012,1.866038,1.759821,1.614741,1.946849,1.905307,2.034228,1.369325,1.929276,1.644739,1.911431,1.455565,1.751712,1.711433,1.206255,1.551974,1.351406,1.252433,1.410497,1.527218,1.666560,1.330974,1.544475,1.701906} + +==== TEST REAL SSE3 IMPLEMENTATION (hlen=8) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771} + +float y[] = {2.966950,2.964003,3.035802,3.567513,2.983864,3.487861,3.089418,3.836586,2.979637,3.173361,3.524760,3.308944,3.511707,2.951268,3.500564,3.466951,3.174077,2.778949,3.124344,2.816606,3.196814,2.774090,3.272130,2.980138,2.646414,3.090803} + +==== TEST REAL SSE3 IMPLEMENTATION (hlen=12) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811} + +float y[] = {3.906606,3.831477,4.613783,4.371631,4.441847,4.311853,4.446086,5.089131,4.708794,4.314635,4.866886,4.812932,4.678810,4.796319,4.687846,5.426141,4.119072,4.687284,4.516533,4.303559,4.733458,4.146965,5.133350,4.832816,4.598291,4.252030} + +==== TEST REAL SSE3 IMPLEMENTATION (hlen=16) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995} + +float y[] = {4.845784,5.086479,6.160082,6.147918,5.549072,5.538811,6.264142,6.083664,5.942431,5.214122,6.458036,6.120992,6.385656,5.751343,6.099504,6.738166,5.942206,5.756058,6.343914,6.239408,6.090616,6.325348,6.214744,6.674619,5.691174,6.413076} + +==== TEST REAL SSE3 IMPLEMENTATION (hlen=20) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995,0.446645,0.327805,0.785346,0.676628} + +float y[] = {6.148925,6.262301,5.792440,6.652380,6.759685,6.515733,6.943458,6.334218,6.539823,6.542612,7.766725,7.472028,7.258010,6.947061,7.347066,7.503224,7.134092,6.244353,7.690946,7.584768,7.779833,6.845586,7.351567,8.099596,7.393943,7.176465} + +==== TEST REAL SSE3 IMPLEMENTATION (h_len%4=0) ==== +float x[] = {0.828957,0.675654,0.904170,0.191112,0.394521,0.706067,0.868924,0.547397,0.738959,0.932485,0.233119,0.926576,0.551443,0.933420,0.494407,0.552568,0.939129,0.799646,0.814139,0.594497,0.657201,0.995300,0.935852,0.324541,0.874309,0.589157,0.637771,0.759324,0.775421,0.794910,0.262785,0.604379,0.470564,0.166955} + +float h[] = {0.726144,0.746635,0.470674,0.211604,0.963092,0.264553,0.265818,0.725771,0.590649,0.313560,0.547613,0.946811,0.793753,0.690502,0.276120,0.792995,0.446645,0.327805,0.785346,0.676628,0.906507,0.279178,0.015699,0.609179} + +float y[] = {7.032490,7.904466,6.745667,7.146502,6.958916,7.972230,7.314566,6.972099,7.773273,7.740826,7.380684,7.907260,8.446323,7.862378,8.022881,7.726059,7.748359,7.602177,8.926439,8.905205,8.569546,7.948394,8.588051,8.850824,8.592319,7.636216} + diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..f84225e --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,53 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([BitVectorTest]) +AT_KEYWORDS([BitVectorTest]) +cat $abs_srcdir/CommonLibs/BitVectorTest.ok > expout +AT_CHECK([$abs_top_builddir/tests/CommonLibs/BitVectorTest], [], [expout], []) +AT_CLEANUP + +AT_SETUP([InterthreadTest]) +AT_KEYWORDS([InterthreadTest]) +cat $abs_srcdir/CommonLibs/InterthreadTest.ok > expout +AT_CHECK([$abs_top_builddir/tests/CommonLibs/InterthreadTest], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([LogTest]) +AT_KEYWORDS([LogTest]) +cat $abs_srcdir/CommonLibs/LogTest.ok > expout +cat $abs_srcdir/CommonLibs/LogTest.err > experr +AT_CHECK([$abs_top_builddir/tests/CommonLibs/LogTest], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([PRBSTest]) +AT_KEYWORDS([PRBSTest]) +cat $abs_srcdir/CommonLibs/PRBSTest.ok > expout +AT_CHECK([$abs_top_builddir/tests/CommonLibs/PRBSTest], [], [expout], []) +AT_CLEANUP + +AT_SETUP([SocketsTest]) +AT_KEYWORDS([SocketsTest]) +cat $abs_srcdir/CommonLibs/SocketsTest.ok > expout +AT_CHECK([$abs_top_builddir/tests/CommonLibs/SocketsTest], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([TimevalTest]) +AT_KEYWORDS([TimevalTest]) +cat $abs_srcdir/CommonLibs/TimevalTest.ok > expout +AT_CHECK([$abs_top_builddir/tests/CommonLibs/TimevalTest], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([VectorTest]) +AT_KEYWORDS([VectorTest]) +cat $abs_srcdir/CommonLibs/VectorTest.ok > expout +AT_CHECK([$abs_top_builddir/tests/CommonLibs/VectorTest], [], [expout], []) +AT_CLEANUP + +AT_SETUP([convolve_test]) +AT_KEYWORDS([convolve_test]) +# Different results for i686, x86_64 and ARM. see OS#2826, OS#2828, and https://lists.osmocom.org/pipermail/openbsc/2018-January/011655.html +AT_SKIP_IF(true) +cat $abs_srcdir/Transceiver52M/convolve_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/Transceiver52M/convolve_test], [], [expout], []) +AT_CLEANUP diff --git a/utils/clockdump.sh b/utils/clockdump.sh new file mode 100755 index 0000000..c2a6574 --- /dev/null +++ b/utils/clockdump.sh @@ -0,0 +1,3 @@ +#!/bin/sh +sudo tcpdump -i lo0 -A udp port 5700 + |