pax_global_header00006660000000000000000000000064130676534160014525gustar00rootroot0000000000000052 comment=b29e2017118fcdb518945c5052f565fd0410f8ed osmo-trx-0~20170323git2af1440+dfsg/000077500000000000000000000000001306765341600163635ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/.gitignore000066400000000000000000000010371306765341600203540ustar00rootroot00000000000000# build results *.o *.lo *.la Transceiver52M/osmo-trx # tests CommonLibs/BitVectorTest CommonLibs/ConfigurationTest CommonLibs/F16Test CommonLibs/InterthreadTest CommonLibs/LogTest CommonLibs/RegexpTest CommonLibs/SocketsTest CommonLibs/TimevalTest CommonLibs/URLEncodeTest CommonLibs/VectorTest # 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 # vim *.sw? osmo-trx-0~20170323git2af1440+dfsg/.gitreview000066400000000000000000000000621306765341600203670ustar00rootroot00000000000000[gerrit] host=gerrit.osmocom.org project=osmo-trx osmo-trx-0~20170323git2af1440+dfsg/AUTHORS000066400000000000000000000120521306765341600174330ustar00rootroot00000000000000# # 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/BitVectorTest.cpp CommonLibs/Configuration.cpp CommonLibs/Configuration.h CommonLibs/ConfigurationTest.cpp CommonLibs/Interthread.h CommonLibs/InterthreadTest.cpp CommonLibs/LinkedLists.cpp CommonLibs/LinkedLists.h CommonLibs/Regexp.h CommonLibs/RegexpTest.cpp CommonLibs/Sockets.cpp CommonLibs/Sockets.h CommonLibs/SocketsTest.cpp CommonLibs/Threads.cpp CommonLibs/Threads.h CommonLibs/Timeval.cpp CommonLibs/Timeval.h CommonLibs/TimevalTest.cpp CommonLibs/Vector.h CommonLibs/VectorTest.cpp Control/CallControl.cpp Control/ControlCommon.cpp Control/ControlCommon.h Control/FACCHDispatch.cpp Control/MobilityManagement.cpp Control/PagerTest.cpp Control/RadioResource.cpp Control/SDCCHDispatch.cpp 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 SIP/SIPEngine.h SIP/SIPInterface.h SMS/SMSMessages.cpp SMS/SMSMessages.h SMS/SMSTransfer.cpp SMS/SMSTransfer.h TRXManager/TRXManager.cpp Transceiver/Complex.h apps/OpenBTS900.cpp apps/OpenBTS850.cpp apps/OpenBTS25c3.cpp tests/AGCHTest.cpp tests/BeaconTest.cpp tests/CallTest.cpp tests/CallTest2.cpp tests/LAPDmTest.cpp tests/LoopbackTest.cpp tests/RegistrationTest.cpp tests/TRXSimulator.cpp Harvind S. Samra, hssamra@kestrelsp.com: Control/PagerTest.cpp Control/RadioResource.cpp 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: Control/CallControl.cpp Control/ControlCommon.cpp Control/ControlCommon.h Control/FACCHDispatch.cpp Control/MobilityManagement.cpp Control/PagerTest.cpp Control/RadioResource.cpp 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 SIP/SIPEngine.cpp SIP/SIPInterface.cpp SIP/SIPInterface.h SIP/SIPMessage.cpp SIP/SIPMessage.h SIP/SIPUtility.cpp SIP/SIPUtility.h SMS/CMMessage.cpp SMS/CMMessage.h SMS/CMProcessor.cpp SMS/CMProcessor.h SMS/CMTest.cpp SMS/RLMessage.cpp SMS/RLMessage.h SMS/RLProcessor.cpp SMS/RLProcessor.h SMS/SMSMessages.cpp SMS/SMSMessages.h SMS/SMSProcessors.cpp SMS/SMSProcessors.h SMS/SMSTransfer.cpp SMS/SMSTransfer.h SMS/TLMessage.cpp SMS/TLMessage.h SMS/TLProcessor.cpp SMS/TLProcessor.h TRXManager/TRXManager.h Alon Levy, alonlevy1@gmail.com RRLPMessages.cpp RRLPMessages.h RRLPTest.cpp osmo-trx-0~20170323git2af1440+dfsg/COPYING000066400000000000000000001101671306765341600174240ustar00rootroot00000000000000 GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. 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. Copyright (C) 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 . 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 . ========================================================================= 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 OPENBTS Permissive Terms Supplementing the License 1. Remote Interaction Through IP Networks. OpenBTS includes an implementation of the GSM network cellular air interface, as well as other interfaces to IP networks. The interaction of cellular handsets with the OpenBTS 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. Non-Permissive Terms Supplementing The License 1. Trademarks. "OpenBTS" is a trademark of Range Networks, Inc., registered with the US Patent and Trademark Office. Your use of OpenBTS software under a GPL license does not include the right to use the OpenBTS trademark in commerce. This additional non-permissive term is consistent with Section 7 of the AGPLv3 license. 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 OpenBTS cellular network. At a minimum, that text message should include the string "OpenBTS AGPLv3" and a URL that can be used to access the OpenBTS source code. This message need not be delivered to handsets that are denied registration with the network, since those handsets have been denied service. In OpenBTS 2.6, such text messages can be delivered with the "Welcome Message" feature. See the OpenBTS.config.example file for more information on the use of this feature for AGPLv3 compliance. osmo-trx-0~20170323git2af1440+dfsg/ChangeLog000066400000000000000000000000001306765341600201230ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/000077500000000000000000000000001306765341600204255ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/BitVector.cpp000066400000000000000000000161621306765341600230400ustar00rootroot00000000000000/* * 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 . */ #include "BitVector.h" #include #include #include #include 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>i; return sum & 0x01; } BitVector::BitVector(const char *valString) :Vector(strlen(valString)) { uint32_t accum = 0; for (size_t i=0; 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=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(); } uint64_t BitVector::syndrome(Generator& gen) const { gen.clear(); const char *dp = mStart; while (dp0.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; i0.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> (8-rem),rem); } void BitVector::hex(ostream& os) const { os << std::hex; unsigned digits = size()/4; size_t wp=0; for (unsigned i=0; i0) { if (sscanf(src+digits, "%1x", &val) < 1) { return false; } fillField(whole,val,rem); } return true; } // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/BitVector.h000066400000000000000000000177151306765341600225120ustar00rootroot00000000000000/* * 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 . */ #ifndef FECVECTORS_H #define FECVECTORS_H #include "Vector.h" #include class BitVector; class SoftVector; /** Shift-register (LFSR) generator. */ class Generator { private: uint64_t mCoeff; ///< polynomial coefficients. LSB is zero exponent. uint64_t mState; ///< shift register state. LSB is most recent. uint64_t mMask; ///< mask for reading state unsigned mLen; ///< number of bits used in shift register unsigned mLen_1; ///< mLen - 1 public: Generator(uint64_t wCoeff, unsigned wLen) :mCoeff(wCoeff),mState(0), mMask((1ULL<>(mLen_1)) & 0x01; mState = (mState<<1) ^ (inBit & 0x01); if (fb) mState ^= mCoeff; } /** Update the generator state by one cycle. This is in the .h for inlining. */ void encoderShift(unsigned inBit) { const unsigned fb = ((mState>>(mLen_1)) ^ inBit) & 0x01; mState <<= 1; if (fb) mState ^= mCoeff; } }; class BitVector : public Vector { public: /**@name Constructors. */ //@{ /**@name Casts of Vector constructors. */ //@{ BitVector(char* wData, char* wStart, char* wEnd) :Vector(wData,wStart,wEnd) { } BitVector(size_t len=0):Vector(len) {} BitVector(const Vector& source):Vector(source) {} BitVector(Vector& source):Vector(source) {} BitVector(const Vector& source1, const Vector source2):Vector(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::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); } /**@name FEC operations. */ //@{ /** Calculate the syndrome of the vector with the given Generator. */ uint64_t syndrome(Generator& gen) const; /** Calculate the parity word for the vector with the given Generator. */ uint64_t parity(Generator& gen) const; //@} /** 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 { public: /** Build a SoftVector of a given length. */ SoftVector(size_t wSize=0):Vector(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(wData,length) {} SoftVector(float* wData, float* wStart, float* wEnd) :Vector(wData,wStart,wEnd) { } /** Casting from a Vector. Note that this is NOT pass-by-reference. */ SoftVector(Vector source) :Vector(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::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(dp0.0F; } /** Slice the whole signal into bits. */ BitVector sliced() const; }; std::ostream& operator<<(std::ostream&, const SoftVector&); #endif // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/BitVectorTest.cpp000066400000000000000000000030731306765341600236750ustar00rootroot00000000000000/* * 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 . */ #include "BitVector.h" #include #include 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; } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Configuration.cpp000066400000000000000000000727431306765341600237550ustar00rootroot00000000000000/* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2011, 2012 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 . */ #include "Configuration.h" #include "Logger.h" #include #include #include #ifdef DEBUG_CONFIG #define debugLogEarly gLogEarly #else #define debugLogEarly(x,y,z) #endif using namespace std; char gCmdName[20] = {0}; // Use a char* to avoid avoid static initialization of string, and race at startup. static const char* createConfigTable = { "CREATE TABLE IF NOT EXISTS CONFIG (" "KEYSTRING TEXT UNIQUE NOT NULL, " "VALUESTRING TEXT, " "STATIC INTEGER DEFAULT 0, " "OPTIONAL INTEGER DEFAULT 0, " "COMMENTS TEXT DEFAULT ''" ")" }; static std::string replaceAll(const std::string input, const std::string search, const std::string replace) { std::string output = input; int index = 0; while (true) { index = output.find(search, index); if (index == std::string::npos) { break; } output.replace(index, replace.length(), replace); index += replace.length(); } return output; } float ConfigurationRecord::floatNumber() const { float val; sscanf(mValue.c_str(),"%f",&val); return val; } ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema) { gLogEarly(LOG_INFO, "opening configuration table from path %s", filename); // Connect to the database. int rc = sqlite3_open(filename,&mDB); // (pat) When I used malloc here, sqlite3 sporadically crashes. if (wCmdName) { strncpy(gCmdName,wCmdName,18); gCmdName[18] = 0; strcat(gCmdName,":"); } if (rc) { gLogEarly(LOG_EMERG, "cannot open configuration database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); sqlite3_close(mDB); mDB = NULL; return; } // Create the table, if needed. if (!sqlite3_command(mDB,createConfigTable)) { gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); } // Build CommonLibs schema ConfigurationKey *tmp; tmp = new ConfigurationKey("Log.Alarms.Max","20", "alarms", ConfigurationKey::CUSTOMER, ConfigurationKey::VALRANGE, "10:20",// educated guess false, "Maximum number of alarms to remember inside the application." ); mSchema[tmp->getName()] = *tmp; free(tmp); tmp = new ConfigurationKey("Log.File","", "", ConfigurationKey::DEVELOPER, ConfigurationKey::FILEPATH_OPT,// audited "", false, "Path to use for textfile based logging. " "By default, this feature is disabled. " "To enable, specify an absolute path to the file you wish to use, eg: /tmp/my-debug.log. " "To disable again, execute \"unconfig Log.File\"." ); mSchema[tmp->getName()] = *tmp; free(tmp); tmp = new ConfigurationKey("Log.Level","NOTICE", "", ConfigurationKey::CUSTOMER, ConfigurationKey::CHOICE, "EMERG|EMERGENCY - report serious faults associated with service failure or hardware damage," "ALERT|ALERT - report likely service disruption caused by misconfiguration or poor connectivity," "CRIT|CRITICAL - report anomalous events that are likely to degrade service," "ERR|ERROR - report internal errors of the software that may result in degradation of service in unusual circumstances," "WARNING|WARNING - report anomalous events that may indicate a degradation of normal service," "NOTICE|NOTICE - report anomalous events that probably do not affect service but may be of interest to network operators," "INFO|INFORMATION - report normal events," "DEBUG|DEBUG - only for use by developers and will degrade system performance", false, "Default logging level when no other level is defined for a file." ); mSchema[tmp->getName()] = *tmp; free(tmp); // Add application specific schema mSchema.insert(wSchema.begin(), wSchema.end()); // Init the cross checking callback to something predictable mCrossCheck = NULL; } string ConfigurationTable::getDefaultSQL(const std::string& program, const std::string& version) { stringstream ss; ConfigurationKeyMap::iterator mp; ss << "--" << endl; ss << "-- This file was generated using: " << program << " --gensql" << endl; ss << "-- binary version: " << version << endl; ss << "--" << endl; ss << "-- Future changes should not be put in this file directly but" << endl; ss << "-- rather in the program's ConfigurationKey schema." << endl; ss << "--" << endl; ss << "PRAGMA foreign_keys=OFF;" << endl; ss << "BEGIN TRANSACTION;" << endl; ss << "CREATE TABLE CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT '');" << endl; mp = mSchema.begin(); while (mp != mSchema.end()) { ss << "INSERT INTO \"CONFIG\" VALUES("; // name ss << "'" << mp->first << "',"; // default ss << "'" << mp->second.getDefaultValue() << "',"; // static if (mp->second.isStatic()) { ss << "1"; } else { ss << "0"; } ss << ","; // optional ss << "0,"; // description ss << "'"; if (mp->second.getType() == ConfigurationKey::BOOLEAN) { ss << "1=enabled, 0=disabled - "; } ss << mp->second.getDescription(); if (mp->second.isStatic()) { ss << " Static."; } ss << "'"; ss << ");" << endl; mp++; } ss << "COMMIT;" << endl; ss << endl; return ss.str(); } string ConfigurationTable::getTeX(const std::string& program, const std::string& version) { stringstream ss; ConfigurationKeyMap::iterator mp; ss << "% START AUTO-GENERATED CONTENT" << endl; ss << "% -- these sections were generated using: " << program << " --gentex" << endl; ss << "% -- binary version: " << version << endl; ss << "\\subsection{Customer Site Parameters}" << endl; ss << "These parameters must be changed to fit your site." << endl; ss << "\\begin{itemize}" << endl; mp = mSchema.begin(); while (mp != mSchema.end()) { if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) { ss << " \\item "; // name ss << mp->first << " -- "; // description ss << mp->second.getDescription(); ss << endl; } mp++; } ss << "\\end{itemize}" << endl; ss << endl; ss << "\\subsection{Customer Tuneable Parameters}" << endl; ss << "These parameters can be changed to optimize your site." << endl; ss << "\\begin{itemize}" << endl; mp = mSchema.begin(); while (mp != mSchema.end()) { if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE && ( mp->second.getVisibility() == ConfigurationKey::CUSTOMER || mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE || mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN )) { ss << " \\item "; // name ss << mp->first << " -- "; // description ss << mp->second.getDescription(); ss << endl; } mp++; } ss << "\\end{itemize}" << endl; ss << endl; ss << "\\subsection{Developer/Factory Parameters}" << endl; ss << "These parameters should only be changed by when developing new code." << endl; ss << "\\begin{itemize}" << endl; mp = mSchema.begin(); while (mp != mSchema.end()) { if (mp->second.getVisibility() == ConfigurationKey::FACTORY || mp->second.getVisibility() == ConfigurationKey::DEVELOPER) { ss << " \\item "; // name ss << mp->first << " -- "; // description ss << mp->second.getDescription(); ss << endl; } mp++; } ss << "\\end{itemize}" << endl; ss << "% END AUTO-GENERATED CONTENT" << endl; ss << endl; string tmp = replaceAll(ss.str(), "^", "\\^"); return replaceAll(tmp, "_", "\\_"); } bool ConfigurationTable::defines(const string& key) { try { ScopedLock lock(mLock); return lookup(key).defined(); } catch (ConfigurationTableKeyNotFound) { debugLogEarly(LOG_ALERT, "configuration parameter %s not found", key.c_str()); return false; } } bool ConfigurationTable::keyDefinedInSchema(const std::string& name) { return mSchema.find(name) == mSchema.end() ? false : true; } bool ConfigurationTable::isValidValue(const std::string& name, const std::string& val) { bool ret = false; ConfigurationKey key = mSchema[name]; switch (key.getType()) { case ConfigurationKey::BOOLEAN: { if (val == "1" || val == "0") { ret = true; } break; } case ConfigurationKey::CHOICE_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::CHOICE: { int startPos = -1; uint endPos = 0; std::string tmp = key.getValidValues(); do { startPos++; if ((endPos = tmp.find('|', startPos)) != std::string::npos) { if (val == tmp.substr(startPos, endPos-startPos)) { ret = true; break; } } else { if (val == tmp.substr(startPos, tmp.find(',', startPos)-startPos)) { ret = true; break; } } } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos); break; } case ConfigurationKey::CIDR_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::CIDR: { uint delimiter; std::string ip; int cidr = -1; delimiter = val.find('/'); if (delimiter != std::string::npos) { ip = val.substr(0, delimiter); std::stringstream(val.substr(delimiter+1)) >> cidr; if (ConfigurationKey::isValidIP(ip) && 0 <= cidr && cidr <= 32) { ret = true; } } break; } case ConfigurationKey::FILEPATH_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::FILEPATH: { regex_t r; const char* expression = "^[a-zA-Z0-9/_.-]+$"; int result = regcomp(&r, expression, REG_EXTENDED); if (result) { char msg[256]; regerror(result,&r,msg,255); break;//abort(); } if (regexec(&r, val.c_str(), 0, NULL, 0)==0) { ret = true; } regfree(&r); break; } case ConfigurationKey::IPADDRESS_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::IPADDRESS: { ret = ConfigurationKey::isValidIP(val); break; } case ConfigurationKey::IPANDPORT: { uint delimiter; std::string ip; int port = -1; delimiter = val.find(':'); if (delimiter != std::string::npos) { ip = val.substr(0, delimiter); std::stringstream(val.substr(delimiter+1)) >> port; if (ConfigurationKey::isValidIP(ip) && 1 <= port && port <= 65535) { ret = true; } } break; } case ConfigurationKey::MIPADDRESS_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::MIPADDRESS: { int startPos = -1; uint endPos = 0; do { startPos++; endPos = val.find(' ', startPos); if (ConfigurationKey::isValidIP(val.substr(startPos, endPos-startPos))) { ret = true; } else { ret = false; break; } } while ((startPos = endPos) != (int)std::string::npos); break; } case ConfigurationKey::PORT_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::PORT: { int intVal; std::stringstream(val) >> intVal; if (1 <= intVal && intVal <= 65535) { ret = true; } break; } case ConfigurationKey::REGEX_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::REGEX: { regex_t r; const char* expression = val.c_str(); int result = regcomp(&r, expression, REG_EXTENDED); if (result) { char msg[256]; regerror(result,&r,msg,255); } else { ret = true; } regfree(&r); break; } case ConfigurationKey::STRING_OPT: { if (val.length() == 0) { ret = true; break; } } case ConfigurationKey::STRING: { regex_t r; const char* expression = key.getValidValues().c_str(); int result = regcomp(&r, expression, REG_EXTENDED); if (result) { char msg[256]; regerror(result,&r,msg,255); break;//abort(); } if (regexec(&r, val.c_str(), 0, NULL, 0)==0) { ret = true; } regfree(&r); break; } case ConfigurationKey::VALRANGE: { regex_t r; int result; if (key.getValidValues().find('.') != std::string::npos) { result = regcomp(&r, "^[0-9.-]+$", REG_EXTENDED); } else { result = regcomp(&r, "^[0-9-]+$", REG_EXTENDED); } if (result) { char msg[256]; regerror(result,&r,msg,255); break;//abort(); } if (regexec(&r, val.c_str(), 0, NULL, 0)!=0) { ret = false; } else if (key.getValidValues().find('.') != std::string::npos) { ret = ConfigurationKey::isInValRange(key, val, false); } else { ret = ConfigurationKey::isInValRange(key, val, true); } regfree(&r); break; } } return ret; } ConfigurationKeyMap ConfigurationTable::getSimilarKeys(const std::string& snippet) { ConfigurationKeyMap tmp; ConfigurationKeyMap::const_iterator mp = mSchema.begin(); while (mp != mSchema.end()) { if (mp->first.find(snippet) != std::string::npos) { tmp[mp->first] = mp->second; } mp++; } return tmp; } const ConfigurationRecord& ConfigurationTable::lookup(const string& key) { assert(mDB); checkCacheAge(); // We assume the caller holds mLock. // So it is OK to return a reference into the cache. // Check the cache. // This is cheap. ConfigurationMap::const_iterator where = mCache.find(key); if (where!=mCache.end()) { if (where->second.defined()) return where->second; throw ConfigurationTableKeyNotFound(key); } // Check the database. // This is more expensive. char *value = NULL; sqlite3_single_lookup(mDB,"CONFIG", "KEYSTRING",key.c_str(),"VALUESTRING",value); // value found, cache the result if (value) { mCache[key] = ConfigurationRecord(value); // key definition found, cache the default } else if (keyDefinedInSchema(key)) { mCache[key] = ConfigurationRecord(mSchema[key].getDefaultValue()); // total miss, cache the error } else { mCache[key] = ConfigurationRecord(false); throw ConfigurationTableKeyNotFound(key); } free(value); // Leave mLock locked. The caller holds it still. return mCache[key]; } bool ConfigurationTable::isStatic(const string& key) { if (keyDefinedInSchema(key)) { return mSchema[key].isStatic(); } else { return false; } } string ConfigurationTable::getStr(const string& key) { // We need the lock because rec is a reference into the cache. try { ScopedLock lock(mLock); return lookup(key).value(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } bool ConfigurationTable::getBool(const string& key) { try { return getNum(key) != 0; } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } long ConfigurationTable::getNum(const string& key) { // We need the lock because rec is a reference into the cache. try { ScopedLock lock(mLock); return lookup(key).number(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } float ConfigurationTable::getFloat(const string& key) { try { ScopedLock lock(mLock); return lookup(key).floatNumber(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } std::vector ConfigurationTable::getVectorOfStrings(const string& key) { // Look up the string. char *line=NULL; try { ScopedLock lock(mLock); const ConfigurationRecord& rec = lookup(key); line = strdup(rec.value().c_str()); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } assert(line); char *lp = line; // Parse the string. std::vector retVal; while (lp) { while (*lp==' ') lp++; if (*lp == '\0') break; char *tp = strsep(&lp," "); if (!tp) break; retVal.push_back(tp); } free(line); return retVal; } std::vector ConfigurationTable::getVector(const string& key) { // Look up the string. char *line=NULL; try { ScopedLock lock(mLock); const ConfigurationRecord& rec = lookup(key); line = strdup(rec.value().c_str()); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } assert(line); char *lp = line; // Parse the string. std::vector retVal; while (lp) { // Watch for multiple or trailing spaces. while (*lp==' ') lp++; if (*lp=='\0') break; retVal.push_back(strtol(lp,NULL,0)); strsep(&lp," "); } free(line); return retVal; } bool ConfigurationTable::remove(const string& key) { assert(mDB); ScopedLock lock(mLock); // Clear the cache entry and the database. ConfigurationMap::iterator where = mCache.find(key); if (where!=mCache.end()) mCache.erase(where); // Really remove it. string cmd = "DELETE FROM CONFIG WHERE KEYSTRING=='"+key+"'"; return sqlite3_command(mDB,cmd.c_str()); } void ConfigurationTable::find(const string& pat, ostream& os) const { // Prepare the statement. string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG WHERE KEYSTRING LIKE \"%" + pat + "%\""; sqlite3_stmt *stmt; if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return; // Read the result. int src = sqlite3_run_query(mDB,stmt); while (src==SQLITE_ROW) { const char* value = (const char*)sqlite3_column_text(stmt,1); os << sqlite3_column_text(stmt,0) << " "; int len = 0; if (value) { len = strlen(value); } if (len && value) os << value << endl; else os << "(disabled)" << endl; src = sqlite3_run_query(mDB,stmt); } sqlite3_finalize(stmt); } ConfigurationRecordMap ConfigurationTable::getAllPairs() const { ConfigurationRecordMap tmp; // Prepare the statement. string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG"; sqlite3_stmt *stmt; if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return tmp; // Read the result. int src = sqlite3_run_query(mDB,stmt); while (src==SQLITE_ROW) { const char* key = (const char*)sqlite3_column_text(stmt,0); const char* value = (const char*)sqlite3_column_text(stmt,1); if (key && value) { tmp[string(key)] = ConfigurationRecord(value); } else if (key && !value) { tmp[string(key)] = ConfigurationRecord(false); } src = sqlite3_run_query(mDB,stmt); } sqlite3_finalize(stmt); return tmp; } bool ConfigurationTable::set(const string& key, const string& value) { assert(mDB); ScopedLock lock(mLock); string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)"; bool success = sqlite3_command(mDB,cmd.c_str()); // Cache the result. if (success) mCache[key] = ConfigurationRecord(value); return success; } bool ConfigurationTable::set(const string& key, long value) { char buffer[30]; sprintf(buffer,"%ld",value); return set(key,buffer); } bool ConfigurationTable::set(const string& key) { assert(mDB); ScopedLock lock(mLock); string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",NULL,1)"; bool success = sqlite3_command(mDB,cmd.c_str()); if (success) mCache[key] = ConfigurationRecord(true); return success; } void ConfigurationTable::checkCacheAge() { // mLock is set by caller static time_t timeOfLastPurge = 0; time_t now = time(NULL); // purge every 3 seconds // purge period cannot be configuration parameter if (now - timeOfLastPurge < 3) return; timeOfLastPurge = now; // this is purge() without the lock ConfigurationMap::iterator mp = mCache.begin(); while (mp != mCache.end()) { ConfigurationMap::iterator prev = mp; mp++; mCache.erase(prev); } } void ConfigurationTable::purge() { ScopedLock lock(mLock); ConfigurationMap::iterator mp = mCache.begin(); while (mp != mCache.end()) { ConfigurationMap::iterator prev = mp; mp++; mCache.erase(prev); } } void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char const *,sqlite3_int64)) { assert(mDB); sqlite3_update_hook(mDB,func,NULL); } void ConfigurationTable::setCrossCheckHook(vector (*wCrossCheck)(const string&)) { mCrossCheck = wCrossCheck; } vector ConfigurationTable::crossCheck(const string& key) { vector ret; if (mCrossCheck != NULL) { ret = mCrossCheck(key); } return ret; } void HashString::computeHash() { // FIXME -- Someone needs to review this hash function. const char* cstr = c_str(); mHash = 0; for (unsigned i=0; i> 32); mHash = mHash*127 + cstr[i]; } } void SimpleKeyValue::addItem(const char* pair_orig) { char *pair = strdup(pair_orig); char *key = pair; char *mark = strchr(pair,'='); if (!mark) return; *mark = '\0'; char *value = mark+1; mMap[key] = value; free(pair); } const char* SimpleKeyValue::get(const char* key) const { HashStringMap::const_iterator p = mMap.find(key); if (p==mMap.end()) return NULL; return p->second.c_str(); } void SimpleKeyValue::addItems(const char* pairs_orig) { char *pairs = strdup(pairs_orig); char *thisPair; while ((thisPair=strsep(&pairs," "))!=NULL) { addItem(thisPair); } free(pairs); } bool ConfigurationKey::isValidIP(const std::string& ip) { struct sockaddr_in sa; return inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr)) != 0; } void ConfigurationKey::getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping) { uint delimiter; int startPos; uint endPos; std::string tmp = key.getValidValues(); stepping = "1"; // grab steps if they're defined startPos = tmp.find('('); if (startPos != (int)std::string::npos) { endPos = tmp.find(')'); stepping = tmp.substr(startPos+1, endPos-startPos-1); tmp = tmp.substr(0, startPos); } startPos = 0; delimiter = tmp.find(':', startPos); min = tmp.substr(startPos, delimiter-startPos); max = tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1); } template bool ConfigurationKey::isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger) { bool ret = false; T convVal; T min; T max; T steps; std::string strMin; std::string strMax; std::string strSteps; std::stringstream(val) >> convVal; ConfigurationKey::getMinMaxStepping(key, strMin, strMax, strSteps); std::stringstream(strMin) >> min; std::stringstream(strMax) >> max; std::stringstream(strSteps) >> steps; // TODO : only ranges checked, steps not enforced if (isInteger) { if (val.find('.') == std::string::npos && min <= convVal && convVal <= max) { ret = true; } } else { if (min <= convVal && convVal <= max) { ret = true; } } return ret; } const std::string ConfigurationKey::getARFCNsString() { stringstream ss; int i; float downlink; float uplink; // 128:251 GSM850 downlink = 869.2; uplink = 824.2; for (i = 128; i <= 251; i++) { ss << i << "|GSM850 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; downlink += 0.2; uplink += 0.2; } // 1:124 PGSM900 downlink = 935.2; uplink = 890.2; for (i = 1; i <= 124; i++) { ss << i << "|PGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; downlink += 0.2; uplink += 0.2; } // 512:885 DCS1800 downlink = 1805.2; uplink = 1710.2; for (i = 512; i <= 885; i++) { ss << i << "|DCS1800 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; downlink += 0.2; uplink += 0.2; } // 512:810 PCS1900 downlink = 1930.2; uplink = 1850.2; for (i = 512; i <= 810; i++) { ss << i << "|PCS1900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; downlink += 0.2; uplink += 0.2; } ss << endl; return ss.str(); } const std::string ConfigurationKey::visibilityLevelToString(const ConfigurationKey::VisibilityLevel& visibility) { std::string ret = "UNKNOWN ERROR"; switch (visibility) { case ConfigurationKey::CUSTOMER: ret = "customer - can be freely changed by the customer without any detriment to their system"; break; case ConfigurationKey::CUSTOMERSITE: ret = "customer site - these values are different for each BTS and should not be left default"; break; case ConfigurationKey::CUSTOMERTUNE: ret = "customer tune - should only be changed to tune an installation to better suit the physical environment or MS usage pattern"; break; case ConfigurationKey::CUSTOMERWARN: ret = "customer warn - a warning will be presented and confirmation required before changing this sensitive setting"; break; case ConfigurationKey::DEVELOPER: ret = "developer - should only be changed by developers to debug/optimize the implementation"; break; case ConfigurationKey::FACTORY: ret = "factory - set once at the factory, should never be changed"; break; } return ret; } const std::string ConfigurationKey::typeToString(const ConfigurationKey::Type& type) { std::string ret = "UNKNOWN ERROR"; switch (type) { case BOOLEAN: ret = "boolean"; break; case CHOICE_OPT: ret = "multiple choice (optional)"; break; case CHOICE: ret = "multiple choice"; break; case CIDR_OPT: ret = "CIDR notation (optional)"; break; case CIDR: ret = "CIDR notation"; break; case FILEPATH_OPT: ret = "file path (optional)"; break; case FILEPATH: ret = "file path"; break; case IPADDRESS_OPT: ret = "IP address (optional)"; break; case IPADDRESS: ret = "IP address"; break; case IPANDPORT: ret = "IP address and port"; break; case MIPADDRESS_OPT: ret = "space-separated list of IP addresses (optional)"; break; case MIPADDRESS: ret = "space-separated list of IP addresses"; break; case PORT_OPT: ret = "IP port (optional)"; break; case PORT: ret = "IP port"; break; case REGEX_OPT: ret = "regular expression (optional)"; break; case REGEX: ret = "regular expression"; break; case STRING_OPT: ret = "string (optional)"; break; case STRING: ret = "string"; break; case VALRANGE: ret = "value range"; break; } return ret; } void ConfigurationKey::printKey(const ConfigurationKey &key, const std::string& currentValue, ostream& os) { os << key.getName() << " "; if (!currentValue.length()) { os << "(disabled)"; } else { os << currentValue; } if (currentValue.compare(key.getDefaultValue()) == 0) { os << " [default]"; } os << endl; } void ConfigurationKey::printDescription(const ConfigurationKey &key, ostream& os) { std::string tmp; os << " - description: " << key.getDescription() << std::endl; if (key.getUnits().length()) { os << " - units: " << key.getUnits() << std::endl; } os << " - type: " << ConfigurationKey::typeToString(key.getType()) << std::endl; if (key.getDefaultValue().length()) { os << " - default value: " << key.getDefaultValue() << std::endl; } os << " - visibility level: " << ConfigurationKey::visibilityLevelToString(key.getVisibility()) << std::endl; os << " - static: " << key.isStatic() << std::endl; tmp = key.getValidValues(); if (key.getType() == ConfigurationKey::VALRANGE) { int startPos = tmp.find('('); uint delimiter = 0; if (startPos != (int)std::string::npos) { tmp = tmp.substr(0, startPos); } startPos = -1; do { startPos++; delimiter = tmp.find(':', startPos); os << " - valid values: " << "from " << tmp.substr(startPos, delimiter-startPos) << " to " << tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1) << std::endl; } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos); } else if (key.getType() == ConfigurationKey::CHOICE) { int startPos = -1; uint endPos = 0; do { startPos++; if ((endPos = tmp.find('|', startPos)) != std::string::npos) { os << " - valid values: " << tmp.substr(startPos, endPos-startPos); os << " = " << tmp.substr(endPos+1, tmp.find(',', endPos)-endPos-1) << std::endl; } else { os << " - valid values: " << tmp.substr(startPos, tmp.find(',', startPos)-startPos) << std::endl; } } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos); } else if (key.getType() == ConfigurationKey::BOOLEAN) { os << " - valid values: 0 = disabled" << std::endl; os << " - valid values: 1 = enabled" << std::endl; } else if (key.getType() == ConfigurationKey::STRING) { os << " - valid val regex: " << tmp << std::endl; } else if (key.getValidValues().length()) { os << " - raw valid values: " << tmp << std::endl; } } // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Configuration.h000066400000000000000000000247001306765341600234100ustar00rootroot00000000000000/* * Copyright 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2011, 2012 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 . */ #ifndef CONFIGURATION_H #define CONFIGURATION_H #include "sqlite3util.h" #include #include #include #include #include #include #include #include #include #include #include #include /** A class for configuration file errors. */ class ConfigurationTableError {}; extern char gCmdName[]; // Gotta be global, gotta be char*, gotta love it. /** An exception thrown when a given config key isn't found. */ class ConfigurationTableKeyNotFound : public ConfigurationTableError { private: std::string mKey; public: ConfigurationTableKeyNotFound(const std::string& wKey) :mKey(wKey) { } const std::string& key() const { return mKey; } }; class ConfigurationRecord { private: std::string mValue; long mNumber; bool mDefined; public: ConfigurationRecord(bool wDefined=true): mDefined(wDefined) { } ConfigurationRecord(const std::string& wValue): mValue(wValue), mNumber(strtol(wValue.c_str(),NULL,0)), mDefined(true) { } ConfigurationRecord(const char* wValue): mValue(std::string(wValue)), mNumber(strtol(wValue,NULL,0)), mDefined(true) { } const std::string& value() const { return mValue; } long number() const { return mNumber; } bool defined() const { return mDefined; } float floatNumber() const; }; /** A string class that uses a hash function for comparison. */ class HashString : public std::string { protected: uint64_t mHash; void computeHash(); public: HashString(const char* src) :std::string(src) { computeHash(); } HashString(const std::string& src) :std::string(src) { computeHash(); } HashString() { mHash=0; } HashString& operator=(std::string& src) { std::string::operator=(src); computeHash(); return *this; } HashString& operator=(const char* src) { std::string::operator=(src); computeHash(); return *this; } bool operator==(const HashString& other) { return mHash==other.mHash; } bool operator<(const HashString& other) { return mHash(const HashString& other) { return mHash ConfigurationRecordMap; typedef std::map ConfigurationMap; class ConfigurationKey; typedef std::map ConfigurationKeyMap; /** A class for maintaining a configuration key-value table, based on sqlite3 and a local map-based cache. Thread-safe, too. */ class ConfigurationTable { private: sqlite3* mDB; ///< database connection ConfigurationMap mCache; ///< cache of recently access configuration values mutable Mutex mLock; ///< control for multithreaded access to the cache std::vector (*mCrossCheck)(const std::string&); ///< cross check callback pointer public: ConfigurationKeyMap mSchema;///< definition of configuration default values and validation logic ConfigurationTable(const char* filename = ":memory:", const char *wCmdName = 0, ConfigurationKeyMap wSchema = ConfigurationKeyMap()); /** Generate an up-to-date example sql file for new installs. */ std::string getDefaultSQL(const std::string& program, const std::string& version); /** Generate an up-to-date TeX snippet. */ std::string getTeX(const std::string& program, const std::string& version); /** Return true if the key is used in the table. */ bool defines(const std::string& key); /** Return true if the application's schema knows about this key. */ bool keyDefinedInSchema(const std::string& name); /** Return true if the provided value validates correctly against the defined schema. */ bool isValidValue(const std::string& name, const std::string& val); /** Return true if the provided value validates correctly against the defined schema. */ bool isValidValue(const std::string& name, const int val) { std::stringstream ss; ss << val; return isValidValue(name, ss.str()); } /** Return a map of all similar keys in the defined schema. */ ConfigurationKeyMap getSimilarKeys(const std::string& snippet); /** Return true if this key is identified as static. */ bool isStatic(const std::string& key); /** Get a string parameter from the table. Throw ConfigurationTableKeyNotFound if not found. */ std::string getStr(const std::string& key); /** Get a boolean from the table. Return false if NULL or 0, true otherwise. */ bool getBool(const std::string& key); /** Get a numeric parameter from the table. Throw ConfigurationTableKeyNotFound if not found. */ long getNum(const std::string& key); /** Get a vector of strings from the table. */ std::vector getVectorOfStrings(const std::string& key); /** Get a float from the table. Throw ConfigurationTableKeyNotFound if not found. */ float getFloat(const std::string& key); /** Get a numeric vector from the table. */ std::vector getVector(const std::string& key); /** Get length of a vector */ unsigned getVectorLength(const std::string &key) { return getVector(key).size(); } /** Set or change a value in the table. */ bool set(const std::string& key, const std::string& value); /** Set or change a value in the table. */ bool set(const std::string& key, long value); /** Create an entry in the table, no value though. */ bool set(const std::string& key); /** Remove an entry from the table. Will not alter required values. @param key The key of the item to be removed. @return true if anything was actually removed. */ bool remove(const std::string& key); /** Search the table, dumping to a stream. */ void find(const std::string& pattern, std::ostream&) const; /** Return all key/value pairs stored in the ConfigurationTable */ ConfigurationRecordMap getAllPairs() const; /** Define the callback to purge the cache whenever the database changes. */ void setUpdateHook(void(*)(void *,int ,char const *,char const *,sqlite3_int64)); /** Define the callback for cross checking. */ void setCrossCheckHook(std::vector (*wCrossCheck)(const std::string&)); /** Execute the application specific value cross checking logic. */ std::vector crossCheck(const std::string& key); /** purege cache if it exceeds a certain age */ void checkCacheAge(); /** Delete all records from the cache. */ void purge(); private: /** Attempt to lookup a record, cache if needed. Throw ConfigurationTableKeyNotFound if not found. Caller should hold mLock because the returned reference points into the cache. */ const ConfigurationRecord& lookup(const std::string& key); }; typedef std::map HashStringMap; class SimpleKeyValue { protected: HashStringMap mMap; public: /** Take a C string "A=B" and set map["A"]="B". */ void addItem(const char*); /** Take a C string "A=B C=D E=F ..." and add all of the pairs to the map. */ void addItems(const char*s); /** Return a reference to the string at map["key"]. */ const char* get(const char*) const; }; class ConfigurationKey { public: enum VisibilityLevel { CUSTOMER, CUSTOMERSITE, CUSTOMERTUNE, CUSTOMERWARN, DEVELOPER, FACTORY }; enum Type { BOOLEAN, CHOICE_OPT, CHOICE, CIDR_OPT, CIDR, FILEPATH_OPT, FILEPATH, IPADDRESS_OPT, IPADDRESS, IPANDPORT, MIPADDRESS_OPT, MIPADDRESS, PORT_OPT, PORT, REGEX_OPT, REGEX, STRING_OPT, STRING, VALRANGE }; private: std::string mName; std::string mDefaultValue; std::string mUnits; VisibilityLevel mVisibility; Type mType; std::string mValidValues; bool mIsStatic; std::string mDescription; public: ConfigurationKey(const std::string& wName, const std::string& wDefaultValue, const std::string& wUnits, const VisibilityLevel wVisibility, const Type wType, const std::string& wValidValues, bool wIsStatic, const std::string& wDescription): mName(wName), mDefaultValue(wDefaultValue), mUnits(wUnits), mVisibility(wVisibility), mType(wType), mValidValues(wValidValues), mIsStatic(wIsStatic), mDescription(wDescription) { } ConfigurationKey() { } const std::string& getName() const { return mName; } const std::string& getDefaultValue() const { return mDefaultValue; } void updateDefaultValue(const std::string& newValue) { mDefaultValue = newValue; } void updateDefaultValue(const int newValue) { std::stringstream ss; ss << newValue; updateDefaultValue(ss.str()); } const std::string& getUnits() const { return mUnits; } const VisibilityLevel& getVisibility() const { return mVisibility; } const Type& getType() const { return mType; } const std::string& getValidValues() const { return mValidValues; } bool isStatic() const { return mIsStatic; } const std::string& getDescription() const { return mDescription; } static bool isValidIP(const std::string& ip); static void getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping); template static bool isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger); static const std::string visibilityLevelToString(const VisibilityLevel& visibility); static const std::string typeToString(const ConfigurationKey::Type& type); static void printKey(const ConfigurationKey &key, const std::string& currentValue, std::ostream& os); static void printDescription(const ConfigurationKey &key, std::ostream& os); static const std::string getARFCNsString(); }; #endif // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/ConfigurationTest.cpp000066400000000000000000000074551306765341600246130ustar00rootroot00000000000000/* * 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 . */ #include "Configuration.h" #include #include using namespace std; ConfigurationKeyMap getConfigurationKeys(); ConfigurationTable gConfig("exampleconfig.db","test", getConfigurationKeys()); void purgeConfig(void*,int,char const*, char const*, sqlite3_int64) { //cout << "update hook" << endl; gConfig.purge(); } int main(int argc, char *argv[]) { gConfig.setUpdateHook(purgeConfig); char *keys[5] = {"key1", "key2", "key3", "key4", "key5"}; for (int i=0; i<5; i++) { gConfig.set(keys[i],i); } for (int i=0; i<5; i++) { cout << "table[" << keys[i] << "]=" << gConfig.getStr(keys[i]) << endl; cout << "table[" << keys[i] << "]=" << gConfig.getNum(keys[i]) << endl; } for (int i=0; i<5; i++) { cout << "defined table[" << keys[i] << "]=" << gConfig.defines(keys[i]) << endl; } gConfig.set("key5","100 200 300 400 "); std::vector vect = gConfig.getVector("key5"); cout << "vect length " << vect.size() << ": "; for (unsigned i=0; i svect = gConfig.getVectorOfStrings("key5"); cout << "vect length " << svect.size() << ": "; for (unsigned i=0; igetName()] = *tmp; free(tmp); tmp = new ConfigurationKey("numnumber","42", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "0-100", false, "" ); map[tmp->getName()] = *tmp; free(tmp); tmp = new ConfigurationKey("newstring","new string value", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING, "", false, "" ); map[tmp->getName()] = *tmp; free(tmp); return map; } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Interthread.h000066400000000000000000000333771306765341600230640ustar00rootroot00000000000000/* * 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 . */ #ifndef INTERTHREAD_H #define INTERTHREAD_H #include "Timeval.h" #include "Threads.h" #include "LinkedLists.h" #include #include #include /**@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 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 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 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 InterthreadMap { protected: typedef std::map 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 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 Cmp = PointerCompare > class InterthreadPriorityQueue { protected: std::priority_queue 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 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/InterthreadTest.cpp000066400000000000000000000043471306765341600242520ustar00rootroot00000000000000/* * 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 . */ #include "Threads.h" #include "Interthread.h" #include using namespace std; InterthreadQueue gQ; InterthreadMap gMap; void* qWriter(void*) { int *p; for (int i=0; i<20; i++) { p = new int; *p = i; COUT("queue write " << *p); gQ.write(p); 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(); COUT("queue read " << *p); if (*p<0) done=true; delete p; } return NULL; } void* mapWriter(void*) { int *p; for (int i=0; i<20; i++) { p = new int; *p = i; COUT("map write " << *p); gMap.write(i,p); if (random()%2) sleep(1); } return NULL; } void* mapReader(void*) { for (int i=0; i<20; i++) { int *p = gMap.read(i); COUT("map read " << *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(); } // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/LinkedLists.cpp000066400000000000000000000037721306765341600233670ustar00rootroot00000000000000/* * 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 . */ #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; } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/LinkedLists.h000066400000000000000000000106461306765341600230320ustar00rootroot00000000000000/* * 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 . */ #ifndef LINKEDLISTS_H #define LINKEDLISTS_H #include #include /** 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 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 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/LogTest.cpp000066400000000000000000000042731306765341600225200ustar00rootroot00000000000000/* * 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 . */ #include #include #include "Logger.h" #include "Configuration.h" ConfigurationTable gConfig; //ConfigurationTable gConfig("example.config"); void printAlarms() { std::ostream_iterator output( std::cout, "\n" ); std::list alarms = gGetLoggerAlarms(); std::cout << "# alarms = " << alarms.size() << std::endl; std::copy( alarms.begin(), alarms.end(), output ); } int main(int argc, char *argv[]) { gLogInit("LogTest","NOTICE",LOG_LOCAL7); LOG(EMERG) << " testing the logger."; LOG(ALERT) << " testing the logger."; LOG(CRIT) << " testing the logger."; LOG(ERR) << " testing the logger."; LOG(WARNING) << " testing the logger."; LOG(NOTICE) << " testing the logger."; LOG(INFO) << " testing the logger."; LOG(DEBUG) << " testing the logger."; std::cout << "\n\n\n"; std::cout << "testing Alarms\n"; std::cout << "you should see three lines:" << std::endl; printAlarms(); std::cout << "----------- generating 20 alarms ----------" << std::endl; for (int i = 0 ; i < 20 ; ++i) { LOG(ALERT) << i; } std::cout << "you should see ten lines with the numbers 10..19:" << std::endl; printAlarms(); } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Logger.cpp000066400000000000000000000207671306765341600223640ustar00rootroot00000000000000/* * Copyright 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2011, 2012 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 . */ #include #include #include #include #include #include // For gettimeofday #include "Configuration.h" #include "Logger.h" #include "Threads.h" // pat added using namespace std; // Switches to enable/disable logging targets // MUST BE DEFINED BEFORE gConfig FOR gLogEarly() TO WORK CORRECTLY bool gLogToConsole = true; bool gLogToSyslog = false; FILE *gLogToFile = NULL; Mutex gLogToLock; // Reference to a global config table, used all over the system. extern ConfigurationTable gConfig; /**@ The global alarms table. */ //@{ Mutex alarmsLock; list alarmsList; void addAlarm(const string&); //@} // (pat) If Log messages are printed before the classes in this module are inited // (which happens when static classes have constructors that do work) // the OpenBTS just crashes. // Prevent that by setting sLoggerInited to true when this module is inited. static bool sLoggerInited = 0; static struct CheckLoggerInitStatus { CheckLoggerInitStatus() { sLoggerInited = 1; } } sCheckloggerInitStatus; /** Names of the logging levels. */ const char *levelNames[] = { "EMERG", "ALERT", "CRIT", "ERR", "WARNING", "NOTICE", "INFO", "DEBUG" }; int numLevels = 8; int levelStringToInt(const string& name) { // Reverse search, since the numerically larger levels are more common. for (int i=numLevels-1; i>=0; i--) { if (name == levelNames[i]) return i; } // Common substitutions. if (name=="INFORMATION") return 6; if (name=="WARN") return 4; if (name=="ERROR") return 3; if (name=="CRITICAL") return 2; if (name=="EMERGENCY") return 0; // Unknown level. return -1; } /** Given a string, return the corresponding level name. */ int lookupLevel(const string& key) { string val = gConfig.getStr(key); int level = levelStringToInt(val); if (level == -1) { string defaultLevel = gConfig.mSchema["Log.Level"].getDefaultValue(); level = levelStringToInt(defaultLevel); _LOG(CRIT) << "undefined logging level (" << key << " = \"" << val << "\") defaulting to \"" << defaultLevel << ".\" Valid levels are: EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO or DEBUG"; gConfig.set(key, defaultLevel); } return level; } static std::string format(const char *fmt, ...) { va_list ap; char buf[300]; va_start(ap,fmt); int n = vsnprintf(buf,300,fmt,ap); va_end(ap); if (n >= (300-4)) { strcpy(&buf[(300-4)],"..."); } return std::string(buf); } const std::string timestr() { struct timeval tv; struct tm tm; gettimeofday(&tv,NULL); localtime_r(&tv.tv_sec,&tm); unsigned tenths = tv.tv_usec / 100000; // Rounding down is ok. return format(" %02d:%02d:%02d.%1d",tm.tm_hour,tm.tm_min,tm.tm_sec,tenths); } std::ostream& operator<<(std::ostream& os, std::ostringstream& ss) { return os << ss.str(); } int getLoggingLevel(const char* filename) { // Default level? if (!filename) return lookupLevel("Log.Level"); // This can afford to be inefficient since it is not called that often. const string keyName = string("Log.Level.") + string(filename); if (gConfig.defines(keyName)) return lookupLevel(keyName); return lookupLevel("Log.Level"); } int gGetLoggingLevel(const char* filename) { // This is called a lot and needs to be efficient. static Mutex sLogCacheLock; static map sLogCache; static unsigned sCacheCount; static const unsigned sCacheRefreshCount = 1000; if (filename==NULL) return gGetLoggingLevel(""); HashString hs(filename); uint64_t key = hs.hash(); sLogCacheLock.lock(); // Time for a cache flush? if (sCacheCount>sCacheRefreshCount) { sLogCache.clear(); sCacheCount=0; } // Is it cached already? map::const_iterator where = sLogCache.find(key); sCacheCount++; if (where!=sLogCache.end()) { int retVal = where->second; sLogCacheLock.unlock(); return retVal; } // Look it up in the config table and cache it. // FIXME: Figure out why unlock and lock below fix the config table deadlock. // (pat) Probably because getLoggingLevel may call LOG recursively via lookupLevel(). sLogCacheLock.unlock(); int level = getLoggingLevel(filename); sLogCacheLock.lock(); sLogCache.insert(pair(key,level)); sLogCacheLock.unlock(); return level; } // copies the alarm list and returns it. list supposed to be small. list gGetLoggerAlarms() { alarmsLock.lock(); list ret; // excuse the "complexity", but to use std::copy with a list you need // an insert_iterator - copy technically overwrites, doesn't insert. insert_iterator< list > ii(ret, ret.begin()); copy(alarmsList.begin(), alarmsList.end(), ii); alarmsLock.unlock(); return ret; } /** Add an alarm to the alarm list. */ void addAlarm(const string& s) { alarmsLock.lock(); alarmsList.push_back(s); unsigned maxAlarms = gConfig.getNum("Log.Alarms.Max"); while (alarmsList.size() > maxAlarms) alarmsList.pop_front(); alarmsLock.unlock(); } Log::~Log() { if (mDummyInit) return; // Anything at or above LOG_CRIT is an "alarm". // Save alarms in the local list and echo them to stderr. if (mPriority <= LOG_ERR) { if (sLoggerInited) addAlarm(mStream.str().c_str()); cerr << mStream.str() << endl; } // Current logging level was already checked by the macro. So just log. // Log to syslog if (gLogToSyslog) { syslog(mPriority, "%s", mStream.str().c_str()); } // Log to file and console if (gLogToConsole||gLogToFile) { int mlen = mStream.str().size(); int neednl = (mlen==0 || mStream.str()[mlen-1] != '\n'); ScopedLock lock(gLogToLock); if (gLogToConsole) { // The COUT() macro prevents messages from stomping each other but adds uninteresting thread numbers, // so just use std::cout. std::cout << mStream.str(); if (neednl) std::cout<<"\n"; } if (gLogToFile) { fputs(mStream.str().c_str(),gLogToFile); if (neednl) {fputc('\n',gLogToFile);} fflush(gLogToFile); } } } Log::Log(const char* name, const char* level, int facility) { mDummyInit = true; gLogInit(name, level, facility); } ostringstream& Log::get() { assert(mPriority3) { // strlen because a garbage char is getting in sometimes. gLogToFile = fopen(fn,"w"); // New log file each time we start. if (gLogToFile) { time_t now; time(&now); fprintf(gLogToFile,"Starting at %s",ctime(&now)); fflush(gLogToFile); std::cout << "Logging to file: " << fn << "\n"; } } } // Open the log connection. openlog(name,0,facility); } void gLogEarly(int level, const char *fmt, ...) { va_list args; va_start(args, fmt); if (gLogToSyslog) { va_list args_copy; va_copy(args_copy, args); vsyslog(level | LOG_USER, fmt, args_copy); va_end(args_copy); } if (gLogToConsole) { va_list args_copy; va_copy(args_copy, args); vprintf(fmt, args_copy); printf("\n"); va_end(args_copy); } if (gLogToFile) { va_list args_copy; va_copy(args_copy, args); vfprintf(gLogToFile, fmt, args_copy); fprintf(gLogToFile, "\n"); va_end(args_copy); } va_end(args); } // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Logger.h000066400000000000000000000110201306765341600220070ustar00rootroot00000000000000/* * 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 . */ // (pat) WARNING is stupidly defined in /usr/local/include/osipparser2/osip_const.h. // This must be outside the #ifndef LOGGER_H to fix it as long as Logger.h included after the above file. #ifdef WARNING #undef WARNING #endif #ifndef LOGGER_H #define LOGGER_H #include #include #include #include #include #include #include #define _LOG(level) \ Log(LOG_##level).get() << pthread_self() \ << timestr() << " " __FILE__ ":" << __LINE__ << ":" << __FUNCTION__ << ": " #define IS_LOG_LEVEL(wLevel) (gGetLoggingLevel(__FILE__)>=LOG_##wLevel) #ifdef NDEBUG #define LOG(wLevel) \ if (LOG_##wLevel!=LOG_DEBUG && IS_LOG_LEVEL(wLevel)) _LOG(wLevel) #else #define LOG(wLevel) \ if (IS_LOG_LEVEL(wLevel)) _LOG(wLevel) #endif // pat: And for your edification here are the 'levels' as defined in syslog.h: // LOG_EMERG 0 system is unusable // LOG_ALERT 1 action must be taken immediately // LOG_CRIT 2 critical conditions // LOG_ERR 3 error conditions // LOG_WARNING 4 warning conditions // LOG_NOTICE 5 normal, but significant, condition // LOG_INFO 6 informational message // LOG_DEBUG 7 debug-level message // (pat) added - print out a var and its name. // Use like this: int descriptive_name; LOG(INFO)< gGetLoggerAlarms(); ///< Get a copy of the recent alarm list. const std::string timestr(); // A timestamp to print in messages. std::ostream& operator<<(std::ostream& os, std::ostringstream& ss); /**@ Global control and initialization of the logging system. */ //@{ /** Initialize the global logging system. */ void gLogInit(const char* name, const char* level=NULL, int facility=LOG_USER); /** Get the logging level associated with a given file. */ int gGetLoggingLevel(const char *filename=NULL); /** Allow early logging when still in constructors */ void gLogEarly(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3))); //@} #endif // vim: ts=4 sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Makefile.am000066400000000000000000000044651306765341600224720ustar00rootroot00000000000000# # 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 . # include $(top_srcdir)/Makefile.common AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread EXTRA_DIST = \ example.config \ README.common noinst_LTLIBRARIES = libcommon.la libcommon_la_SOURCES = \ BitVector.cpp \ LinkedLists.cpp \ Sockets.cpp \ Threads.cpp \ Timeval.cpp \ Logger.cpp \ Configuration.cpp \ sqlite3util.cpp noinst_PROGRAMS = \ BitVectorTest \ InterthreadTest \ SocketsTest \ TimevalTest \ VectorTest \ ConfigurationTest \ LogTest # ReportingTest noinst_HEADERS = \ BitVector.h \ Interthread.h \ LinkedLists.h \ Sockets.h \ Threads.h \ Timeval.h \ Vector.h \ Configuration.h \ Logger.h \ sqlite3util.h BitVectorTest_SOURCES = BitVectorTest.cpp BitVectorTest_LDADD = libcommon.la $(SQLITE3_LIBS) InterthreadTest_SOURCES = InterthreadTest.cpp InterthreadTest_LDADD = libcommon.la InterthreadTest_LDFLAGS = -lpthread SocketsTest_SOURCES = SocketsTest.cpp SocketsTest_LDADD = libcommon.la SocketsTest_LDFLAGS = -lpthread TimevalTest_SOURCES = TimevalTest.cpp TimevalTest_LDADD = libcommon.la VectorTest_SOURCES = VectorTest.cpp VectorTest_LDADD = libcommon.la $(SQLITE3_LIBS) ConfigurationTest_SOURCES = ConfigurationTest.cpp ConfigurationTest_LDADD = libcommon.la $(SQLITE3_LIBS) # ReportingTest_SOURCES = ReportingTest.cpp # ReportingTest_LDADD = libcommon.la $(SQLITE_LA) LogTest_SOURCES = LogTest.cpp LogTest_LDADD = libcommon.la $(SQLITE3_LIBS) MOSTLYCLEANFILES += testSource testDestination osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Sockets.cpp000066400000000000000000000170201306765341600225440ustar00rootroot00000000000000/* * 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 . */ #include #include #include #include #include #include "Threads.h" #include "Sockets.h" #include #include #include #include #include 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(unsigned short wSrcPort) :DatagramSocket() { open(wSrcPort); } UDPSocket::UDPSocket(unsigned short wSrcPort, const char * wDestIP, unsigned short wDestPort ) :DatagramSocket() { open(wSrcPort); destination(wDestPort, wDestIP); } void UDPSocket::destination( unsigned short wDestPort, const char * wDestIP ) { resolveAddress((sockaddr_in*)mDestination, wDestIP, wDestPort ); } void UDPSocket::open(unsigned short localPort) { // 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 = htonl(INADDR_LOOPBACK); 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); } UDDSocket::UDDSocket(const char* localPath, const char* remotePath) :DatagramSocket() { if (localPath!=NULL) open(localPath); if (remotePath!=NULL) destination(remotePath); } void UDDSocket::open(const char* localPath) { // create mSocketFD = socket(AF_UNIX,SOCK_DGRAM,0); if (mSocketFD<0) { perror("socket() failed"); throw SocketError(); } // bind struct sockaddr_un address; size_t length = sizeof(address); bzero(&address,length); address.sun_family = AF_UNIX; strcpy(address.sun_path,localPath); unlink(localPath); if (bind(mSocketFD,(struct sockaddr*)&address,length)<0) { perror("bind() failed"); throw SocketError(); } } void UDDSocket::destination(const char* remotePath) { struct sockaddr_un* unAddr = (struct sockaddr_un*)mDestination; strcpy(unAddr->sun_path,remotePath); } // vim:ts=4:sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Sockets.h000066400000000000000000000123511306765341600222130ustar00rootroot00000000000000/* * 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 . */ #ifndef SOCKETS_H #define SOCKETS_H #include #include #include #include #include #include #include #include #include #include #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 ":". */ 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( unsigned short localPort=0); /** Given a full specification, open the socket and set the dest address. */ UDPSocket( 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); /** 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); } }; /** Unix Domain Datagram Socket */ class UDDSocket : public DatagramSocket { public: UDDSocket(const char* localPath=NULL, const char* remotePath=NULL); void destination(const char* remotePath); void open(const char* localPath); /** Give the return address of the most recently received packet. */ const struct sockaddr_un* source() const { return (const struct sockaddr_un*)mSource; } size_t addressSize() const { return sizeof(struct sockaddr_un); } }; #endif // vim:ts=4:sw=4 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/SocketsTest.cpp000066400000000000000000000044051306765341600234070ustar00rootroot00000000000000/* * 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 . */ #include "Sockets.h" #include "Threads.h" #include #include static const int gNumToSend = 10; void *testReaderIP(void *) { UDPSocket readSocket(5934, "localhost", 5061); readSocket.nonblocking(); int rc = 0; while (rc0) { COUT("read: " << buf); rc++; } else { sleep(2); } } return NULL; } void *testReaderUnix(void *) { UDDSocket readSocket("testDestination"); readSocket.nonblocking(); int rc = 0; while (rc0) { COUT("read: " << buf); rc++; } else { sleep(2); } } return NULL; } int main(int argc, char * argv[] ) { Thread readerThreadIP; readerThreadIP.start(testReaderIP,NULL); Thread readerThreadUnix; readerThreadUnix.start(testReaderUnix,NULL); UDPSocket socket1(5061, "127.0.0.1",5934); UDDSocket socket1U("testSource","testDestination"); COUT("socket1: " << socket1.port()); // give the readers time to open sleep(1); for (int i=0; i. */ #include "Threads.h" #include "Timeval.h" 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 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 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Threads.h000066400000000000000000000102251306765341600221700ustar00rootroot00000000000000/* * 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 . */ #ifndef THREADS_H #define THREADS_H #include #include #include #include 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); /** 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 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Timeval.cpp000066400000000000000000000043701306765341600225360ustar00rootroot00000000000000/* * 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 . */ #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 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Timeval.h000066400000000000000000000051151306765341600222010ustar00rootroot00000000000000/* * 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 . */ #ifndef TIMEVAL_H #define TIMEVAL_H #include #include "sys/time.h" #include #include /** 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 osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/TimevalTest.cpp000066400000000000000000000025411306765341600233740ustar00rootroot00000000000000/* * 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 . */ #include "Timeval.h" #include using namespace std; int main(int argc, char *argv[]) { Timeval then(10000); cout << then.elapsed() << endl; while (!then.passed()) { cout << "now: " << Timeval() << " then: " << then << " remaining: " << then.remaining() << endl; usleep(500000); } cout << "now: " << Timeval() << " then: " << then << " remaining: " << then.remaining() << endl; } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/Vector.h000066400000000000000000000161621306765341600220460ustar00rootroot00000000000000/**@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 . */ #ifndef VECTOR_H #define VECTOR_H #include #include #include // 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 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& other) { resize(other.size()); memcpy(mData,other.mStart,other.bytes()); } //@{ /** Build an empty Vector of a given size. */ Vector(size_t wSize=0):mData(NULL) { resize(wSize); } /** Build a Vector by shifting the data block. */ Vector(Vector& other) :mData(other.mData),mStart(other.mStart),mEnd(other.mEnd) { other.mData=NULL; } /** Build a Vector by copying another. */ Vector(const Vector& 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& other1, const Vector& 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& other) { clear(); mData=other.mData; mStart=other.mStart; mEnd=other.mEnd; other.mData=NULL; } /** Assign from another Vector, copying. */ void operator=(const Vector& other) { clone(other); } //@} //@{ /** Return an alias to a segment of this Vector. */ Vector segment(size_t start, size_t span) { T* wStart = mStart + start; T* wEnd = wStart + span; assert(wEnd<=mEnd); return Vector(NULL,wStart,wEnd); } /** Return an alias to a segment of this Vector. */ const Vector segment(size_t start, size_t span) const { T* wStart = mStart + start; T* wEnd = wStart + span; assert(wEnd<=mEnd); return Vector(NULL,wStart,wEnd); } Vector head(size_t span) { return segment(0,span); } const Vector head(size_t span) const { return segment(0,span); } Vector tail(size_t start) { return segment(start,size()-start); } const Vector 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& other, size_t start, size_t span) const { T* base = other.mStart + start; assert(base+span<=other.mEnd); assert(mStart+span<=mEnd); memcpy(base,mStart,span*sizeof(T)); } /** Copy all of this Vector to a segment of another Vector. */ void copyToSegment(Vector& other, size_t start=0) const { copyToSegment(other,start,size()); } void copyTo(Vector& 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& 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 std::ostream& operator<<(std::ostream& os, const Vector& v) { for (unsigned i=0; i. */ #include "Vector.h" #include // We must have a gConfig now to include Vector. #include "Configuration.h" ConfigurationTable gConfig; using namespace std; typedef Vector 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; } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/sqlite3util.cpp000066400000000000000000000104151306765341600234140ustar00rootroot00000000000000/* * Copyright 2010 Kestrel Signal Processing, Inc. * All rights reserved. */ #include #include "sqlite3util.h" #include #include #include // Wrappers to sqlite operations. // These will eventually get moved to commonlibs. int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* query) { int src = SQLITE_BUSY; while (src==SQLITE_BUSY) { src = sqlite3_prepare_v2(DB,query,strlen(query),stmt,NULL); if (src==SQLITE_BUSY) { usleep(100000); } } if (src) { fprintf(stderr,"sqlite3_prepare_v2 failed for \"%s\": %s\n",query,sqlite3_errmsg(DB)); sqlite3_finalize(*stmt); } return src; } int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt) { int src = SQLITE_BUSY; while (src==SQLITE_BUSY) { src = sqlite3_step(stmt); if (src==SQLITE_BUSY) { usleep(100000); } } if ((src!=SQLITE_DONE) && (src!=SQLITE_ROW)) { fprintf(stderr,"sqlite3_run_query failed: %s: %s\n", sqlite3_sql(stmt), sqlite3_errmsg(DB)); } return src; } bool sqlite3_exists(sqlite3* DB, const char *tableName, const char* keyName, const char* keyData) { size_t stringSize = 100 + strlen(tableName) + strlen(keyName) + strlen(keyData); char query[stringSize]; sprintf(query,"SELECT * FROM %s WHERE %s == \"%s\"",tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; if (sqlite3_prepare_statement(DB,&stmt,query)) return false; // Read the result. int src = sqlite3_run_query(DB,stmt); sqlite3_finalize(stmt); // Anything there? return (src == SQLITE_ROW); } bool sqlite3_single_lookup(sqlite3* DB, const char *tableName, const char* keyName, const char* keyData, const char* valueName, unsigned &valueData) { size_t stringSize = 100 + strlen(valueName) + strlen(tableName) + strlen(keyName) + strlen(keyData); char query[stringSize]; sprintf(query,"SELECT %s FROM %s WHERE %s == \"%s\"",valueName,tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; if (sqlite3_prepare_statement(DB,&stmt,query)) return false; // Read the result. int src = sqlite3_run_query(DB,stmt); bool retVal = false; if (src == SQLITE_ROW) { valueData = (unsigned)sqlite3_column_int64(stmt,0); retVal = true; } sqlite3_finalize(stmt); return retVal; } // This function returns an allocated string that must be free'd by the caller. bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, const char* keyData, const char* valueName, char* &valueData) { valueData=NULL; size_t stringSize = 100 + strlen(valueName) + strlen(tableName) + strlen(keyName) + strlen(keyData); char query[stringSize]; sprintf(query,"SELECT %s FROM %s WHERE %s == \"%s\"",valueName,tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; if (sqlite3_prepare_statement(DB,&stmt,query)) return false; // Read the result. int src = sqlite3_run_query(DB,stmt); bool retVal = false; if (src == SQLITE_ROW) { const char* ptr = (const char*)sqlite3_column_text(stmt,0); if (ptr) valueData = strdup(ptr); retVal = true; } sqlite3_finalize(stmt); return retVal; } // This function returns an allocated string that must be free'd by tha caller. bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, unsigned keyData, const char* valueName, char* &valueData) { valueData=NULL; size_t stringSize = 100 + strlen(valueName) + strlen(tableName) + strlen(keyName) + 20; char query[stringSize]; sprintf(query,"SELECT %s FROM %s WHERE %s == %u",valueName,tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; if (sqlite3_prepare_statement(DB,&stmt,query)) return false; // Read the result. int src = sqlite3_run_query(DB,stmt); bool retVal = false; if (src == SQLITE_ROW) { const char* ptr = (const char*)sqlite3_column_text(stmt,0); if (ptr) valueData = strdup(ptr); retVal = true; } sqlite3_finalize(stmt); return retVal; } bool sqlite3_command(sqlite3* DB, const char* query) { // Prepare the statement. sqlite3_stmt *stmt; if (sqlite3_prepare_statement(DB,&stmt,query)) return false; // Run the query. int src = sqlite3_run_query(DB,stmt); sqlite3_finalize(stmt); return src==SQLITE_DONE; } osmo-trx-0~20170323git2af1440+dfsg/CommonLibs/sqlite3util.h000066400000000000000000000017161306765341600230650ustar00rootroot00000000000000#ifndef SQLITE3UTIL_H #define SQLITE3UTIL_H #include int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* query); int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt); bool sqlite3_single_lookup(sqlite3* DB, const char *tableName, const char* keyName, const char* keyData, const char* valueName, unsigned &valueData); bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, const char* keyData, const char* valueName, char* &valueData); // This function returns an allocated string that must be free'd by the caller. bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, unsigned keyData, const char* valueName, char* &valueData); bool sqlite3_exists(sqlite3* DB, const char* tableName, const char* keyName, const char* keyData); /** Run a query, ignoring the result; return true on success. */ bool sqlite3_command(sqlite3* DB, const char* query); #endif osmo-trx-0~20170323git2af1440+dfsg/GSM/000077500000000000000000000000001306765341600170115ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/GSM/GSMCommon.cpp000066400000000000000000000064301306765341600213170ustar00rootroot00000000000000/* * 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 . */ #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"); const BitVector GSM::gRACHSynchSequence("01001011011111111001100110101010001111000"); // |-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 osmo-trx-0~20170323git2af1440+dfsg/GSM/GSMCommon.h000066400000000000000000000123021306765341600207570ustar00rootroot00000000000000/**@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 . */ #ifndef GSMCOMMON_H #define GSMCOMMON_H #include #include #include #include #include #include #include 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 gRACHSynchSequence; /** 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 v17) { 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(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 osmo-trx-0~20170323git2af1440+dfsg/GSM/Makefile.am000066400000000000000000000020061306765341600210430ustar00rootroot00000000000000# # 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 . # 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 osmo-trx-0~20170323git2af1440+dfsg/INSTALLATION000066400000000000000000000014621306765341600202120ustar00rootroot00000000000000Installation Requirements OpenBTS 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 OpenBTS, the following should be installed: Asterisk (http://www.asterisk.org), running SIP on port 5060. libosip2 (http://www.gnu.org/software/osip/) libortp (http://freshmeat.net/projects/ortp/) libusrp (http://gnuradio.org). This is part of the GNURadio installation. It is the only part used by OpenBTS. OpenBTS logs to syslogd as facility LOG_LOCAL7. Please set your /etc/syslog.conf accordingly. For information on specific executables, see tests/README.tests and apps/README.apps. See http://gnuradio.org/redmine/wiki/gnuradio/OpenBTS/BuildingAndRunning for more information. osmo-trx-0~20170323git2af1440+dfsg/LEGAL000066400000000000000000000050751306765341600171410ustar00rootroot00000000000000OpenBTS 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. Trademark "OpenBTS" is a registered trademark of Range Networks, Inc. (Range), a California corporation. Range reserves the right to control the use of this trademark. Do not use this trademark in commerce without permission and do not rebrand OpenBTS under a different trademark. Telecom and Radio Spectrum Laws The primary function of OpenBTS 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 OpenBTS 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 OpenBTS 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 A subset of OpenBTS is distributed publicly under AGPLv3. Range reserves the right to distribute most of this source code other licenses as well. See the COPYING file for more information on the license for this distribution. osmo-trx-0~20170323git2af1440+dfsg/Makefile.am000066400000000000000000000024231306765341600204200ustar00rootroot00000000000000# # 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 . # include $(top_srcdir)/Makefile.common ACLOCAL_AMFLAGS = -I config AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USB_INCLUDES) $(WITH_INCLUDES) $(SQLITE3_CFLAGS) AM_CXXFLAGS = -Wall -pthread -ldl #AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread -ldl #AM_CFLAGS = -Wall -O2 -NDEBUG -pthread -ldl # Order must be preserved SUBDIRS = \ CommonLibs \ GSM \ Transceiver52M EXTRA_DIST = \ autogen.sh \ INSTALLATION \ LEGAL \ COPYING \ README dox: FORCE doxygen doxconfig FORCE: osmo-trx-0~20170323git2af1440+dfsg/Makefile.common000066400000000000000000000022261306765341600213140ustar00rootroot00000000000000# # 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 . # top_srcdir = $(abs_top_srcdir) top_builddir = $(abs_top_builddir) 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 MOSTLYCLEANFILES = *~ osmo-trx-0~20170323git2af1440+dfsg/NEWS000066400000000000000000000000001306765341600170500ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/README000066400000000000000000000076601306765341600172540ustar00rootroot00000000000000This 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 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 [params] The is the actual command. Parameters are optional depending on the commands type. Every response is of the form: RSP [result] The 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 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 SETPOWER sets output power in dB wrt full scale. This command fails if the transmitter and receiver are not running. CMD SETPOWER RSP SETPOWER 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 RSP ADJPOWER 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 RSP RXTUNE 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 RSP TXTUNE Timeslot Control SETSLOT sets the format of the uplink timeslots in the ARFCN. The indicates the timeslot of interest. The indicates the type of channel that occupies the timeslot. A chantype of zero indicates the timeslot is off. CMD SETSLOT RSP SETSLOT 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 osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/000077500000000000000000000000001306765341600211345ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Channelizer.cpp000066400000000000000000000047411306765341600241100ustar00rootroot00000000000000/* * Polyphase channelizer * * Copyright (C) 2012-2014 Tom Tsou * 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 . * See the COPYING file in the main directory for details. */ #include #include #include #include #include #include "Logger.h" #include "Channelizer.h" extern "C" { #include "common/fft.h" #include "common/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() { } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Channelizer.h000066400000000000000000000020301306765341600235420ustar00rootroot00000000000000#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_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/ChannelizerBase.cpp000066400000000000000000000130441306765341600246770ustar00rootroot00000000000000/* * Polyphase channelizer * * Copyright (C) 2012-2014 Tom Tsou * 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 . * See the COPYING file in the main directory for details. */ #include #include #include #include #include #include "Logger.h" #include "ChannelizerBase.h" extern "C" { #include "common/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); } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/ChannelizerBase.h000066400000000000000000000015131306765341600243420ustar00rootroot00000000000000#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_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Complex.h000066400000000000000000000154721306765341600227250ustar00rootroot00000000000000/**@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 #include template 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& z) {r=z.r; i=z.i;} // x=complex(z) Complex(const Complex& z) {r=z.r; i=z.i;} // x=complex(z) Complex(const Complex& 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& a) const { return Complex(r+a.r, i+a.i); } Complex operator+(Real a) const { return Complex(r+a,i); } Complex operator-(const Complex& a) const { return Complex(r-a.r, i-a.i); } Complex operator-(Real a) const { return Complex(r-a,i); } Complex operator*(const Complex& a) const { return Complex(r*a.r-i*a.i, r*a.i+i*a.r); } Complex operator*(Real a) const { return Complex(r*a, i*a); } Complex operator/(const Complex& a) const { return operator*(a.inv()); } Complex operator/(Real a) const { return Complex(r/a, i/a); } //@} /*@name component-wise product */ //@{ Complex operator&(const Complex& a) const { return Complex(r*a.r, i*a.i); } //@} /*@name inplace operations */ //@{ Complex& operator+=(const Complex&); Complex& operator-=(const Complex&); Complex& operator*=(const Complex&); Complex& operator/=(const Complex&); Complex& operator+=(Real); Complex& operator-=(Real); Complex& operator*=(Real); Complex& operator/=(Real); //@} //@} /**@name comparisons */ //@{ bool operator==(const Complex& a) const { return ((i==a.i)&&(r==a.r)); } bool operator!=(const Complex& a) const { return ((i!=a.i)||(r!=a.r)); } bool operator<(const Complex& a) const { return norm2()(const Complex& a) const { return norm2()>a.norm2(); } //@} /// reciprocation Complex inv() const; // unary functions -- inlined /**@name unary functions */ //@{ /**@name inlined */ //@{ Complex conj() const { return Complex(r,-i); } Real norm2() const { return i*i+r*r; } Complex flip() const { return Complex(i,r); } Real real() const { return r;} Real imag() const { return i;} Complex neg() const { return Complex(-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 complex; typedef Complex dcomplex; typedef Complex complex16; typedef Complex complex32; //@} template inline Complex Complex::inv() const { Real nVal; nVal = norm2(); return Complex(r/nVal, -i/nVal); } template Complex& Complex::operator+=(const Complex& a) { r += a.r; i += a.i; return *this; } template Complex& Complex::operator*=(const Complex& a) { operator*(a); return *this; } template Complex& Complex::operator-=(const Complex& a) { r -= a.r; i -= a.i; return *this; } template Complex& Complex::operator/=(const Complex& a) { operator/(a); return *this; } /* op= style operations with reals */ template Complex& Complex::operator+=(Real a) { r += a; return *this; } template Complex& Complex::operator*=(Real a) { r *=a; i *=a; return *this; } template Complex& Complex::operator-=(Real a) { r -= a; return *this; } template Complex& Complex::operator/=(Real a) { r /= a; i /= a; return *this; } template Complex Complex::unit() const { Real absVal = abs(); return (Complex(r/absVal, i/absVal)); } /**@name complex functions outside of the Complex<> class. */ //@{ /** this allows type-commutative multiplication */ template Complex operator*(Real a, const Complex& z) { return Complex(z.r*a, z.i*a); } /** this allows type-commutative addition */ template Complex operator+(Real a, const Complex& z) { return Complex(z.r+a, z.i); } /** this allows type-commutative subtraction */ template Complex operator-(Real a, const Complex& z) { return Complex(z.r-a, z.i); } /// e^jphi template Complex expj(Real phi) { return Complex(cos(phi),sin(phi)); } /// phasor expression of a complex number template Complex phasor(Real C, Real phi) { return (expj(phi)*C); } /// formatted stream output template std::ostream& operator<<(std::ostream& os, const Complex& z) { os << z.r << ' '; //os << z.r << ", "; //if (z.i>=0) { os << "+"; } os << z.i << "j"; return os; } //@} #endif osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Makefile.am000066400000000000000000000044211306765341600231710ustar00rootroot00000000000000# # 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 . # include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/common AM_CXXFLAGS = -ldl -lpthread SUBDIRS = arm x86 if ARCH_ARM ARCH_LA = arm/libarch.la else ARCH_LA = x86/libarch.la endif if USRP1 AM_CPPFLAGS += $(USRP_CFLAGS) else AM_CPPFLAGS += $(UHD_CFLAGS) endif EXTRA_DIST = \ README \ README.Talgorithm noinst_LTLIBRARIES = libtransceiver.la COMMON_SOURCES = \ radioInterface.cpp \ radioVector.cpp \ radioClock.cpp \ radioBuffer.cpp \ sigProcLib.cpp \ signalVector.cpp \ Transceiver.cpp \ ChannelizerBase.cpp \ Channelizer.cpp \ Synthesis.cpp \ common/fft.c libtransceiver_la_SOURCES = \ $(COMMON_SOURCES) \ Resampler.cpp \ radioInterfaceResamp.cpp \ radioInterfaceMulti.cpp \ radioInterfaceDiversity.cpp bin_PROGRAMS = osmo-trx noinst_HEADERS = \ Complex.h \ radioInterface.h \ radioVector.h \ radioClock.h \ radioDevice.h \ radioBuffer.h \ sigProcLib.h \ signalVector.h \ Transceiver.h \ USRPDevice.h \ Resampler.h \ ChannelizerBase.h \ Channelizer.h \ Synthesis.h \ common/convolve.h \ common/convert.h \ common/scale.h \ common/mult.h \ common/fft.h osmo_trx_SOURCES = osmo-trx.cpp osmo_trx_LDADD = \ libtransceiver.la \ $(ARCH_LA) \ $(GSM_LA) \ $(COMMON_LA) $(SQLITE3_LIBS) if USRP1 libtransceiver_la_SOURCES += USRPDevice.cpp osmo_trx_LDADD += $(USRP_LIBS) else libtransceiver_la_SOURCES += UHDDevice.cpp osmo_trx_LDADD += $(UHD_LIBS) $(FFTWF_LIBS) endif osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/README000066400000000000000000000032711306765341600220170ustar00rootroot00000000000000The 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. osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/README.DFEsymbolspaced000066400000000000000000000005421306765341600250200ustar00rootroot00000000000000signalVectors 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 osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Resampler.cpp000066400000000000000000000121111306765341600235660ustar00rootroot00000000000000/* * Rational Sample Rate Conversion * Copyright (C) 2012, 2013 Thomas Tsou * * 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 #include #include #include #include #include "Resampler.h" extern "C" { #include "convolve.h" } #ifndef M_PI #define M_PI 3.14159265358979323846264338327f #endif #define MAX_OUTPUT_LEN 4096 static float sinc(float x) { if (x == 0.0) return 0.9999999999; return sin(M_PI * x) / (M_PI * x); } bool Resampler::initFilters(float bw) { size_t proto_len = p * filt_len; float *proto, val, cutoff; float sum = 0.0f, scale = 0.0f; float midpt = (float) (proto_len - 1.0) / 2.0; /* * 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. */ proto = new float[proto_len]; if (!proto) return false; partitions = (float **) malloc(sizeof(float *) * p); if (!partitions) { delete[] proto; return false; } for (size_t i = 0; i < p; i++) { partitions[i] = (float *) memalign(16, filt_len * 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 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; for (size_t i = 0; i < proto_len; i++) { proto[i] = sinc(((float) i - midpt) / cutoff * bw); proto[i] *= a0 - a1 * cos(2 * M_PI * i / (proto_len - 1)) + a2 * cos(4 * M_PI * i / (proto_len - 1)) - a3 * cos(6 * M_PI * i / (proto_len - 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][2 * i + 0] = proto[i * p + n] * scale; partitions[n][2 * i + 1] = 0.0f; } } /* For convolution, we store the filter taps in reverse */ for (size_t n = 0; n < p; n++) { for (size_t i = 0; i < filt_len / 2; i++) { val = partitions[n][2 * i]; partitions[n][2 * i] = partitions[n][2 * (filt_len - 1 - i)]; partitions[n][2 * (filt_len - 1 - i)] = val; } } delete proto; return true; } void Resampler::releaseFilters() { if (partitions) { for (size_t i = 0; i < p; i++) free(partitions[i]); } free(partitions); partitions = NULL; } 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; } void Resampler::computePath() { for (int i = 0; i < MAX_OUTPUT_LEN; i++) { in_index[i] = (q * i) / p; out_path[i] = (q * i) % p; } } 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, partitions[path], filt_len, &out[2 * i], out_len - i, n, 1, 1, 0); } return out_len; } bool Resampler::init(float bw) { /* Filterbank filter internals */ if (!initFilters(bw)) return false; /* Precompute filterbank paths */ in_index = new size_t[MAX_OUTPUT_LEN]; out_path = new size_t[MAX_OUTPUT_LEN]; computePath(); return true; } size_t Resampler::len() { return filt_len; } Resampler::Resampler(size_t p, size_t q, size_t filt_len) : in_index(NULL), out_path(NULL), partitions(NULL) { this->p = p; this->q = q; this->filt_len = filt_len; } Resampler::~Resampler() { releaseFilters(); delete in_index; delete out_path; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Resampler.h000066400000000000000000000050131306765341600232360ustar00rootroot00000000000000/* * Rational Sample Rate Conversion * Copyright (C) 2012, 2013 Thomas Tsou * * 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_ 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; size_t *in_index; size_t *out_path; float **partitions; bool initFilters(float bw); void releaseFilters(); void computePath(); }; #endif /* _RESAMPLER_H_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Synthesis.cpp000066400000000000000000000051771306765341600236430ustar00rootroot00000000000000/* * Polyphase synthesis filter * * Copyright (C) 2012-2014 Tom Tsou * 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 . * See the COPYING file in the main directory for details. */ #include #include #include #include #include #include "Logger.h" #include "Synthesis.h" extern "C" { #include "common/fft.h" #include "common/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() { } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Synthesis.h000066400000000000000000000020371306765341600233000ustar00rootroot00000000000000#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_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Transceiver.cpp000066400000000000000000000724231306765341600241350ustar00rootroot00000000000000/* * 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 . */ #include #include // std::setprecision #include #include "Transceiver.h" #include #ifdef HAVE_CONFIG_H #include "config.h" #endif using namespace GSM; #define USB_LATENCY_INTRVL 10,0 #if USE_UHD # define USB_LATENCY_MIN 6,7 #else # define USB_LATENCY_MIN 1,1 #endif /* 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(int 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 Transceiver::FILLER_DUMMY: burst = generateDummyBurst(sps, n); break; case Transceiver::FILLER_NORM_RAND: burst = genRandNormalBurst(rtsc, sps, n); break; case Transceiver::FILLER_EDGE_RAND: burst = generateEdgeBurst(rtsc); break; case Transceiver::FILLER_ACCESS_RAND: burst = genRandAccessBurst(rach_delay, sps, n); break; case Transceiver::FILLER_ZERO: default: burst = generateEmptyBurst(sps, n); } scaleVector(*burst, scale); fillerTable[i][n] = burst; } if ((filler == Transceiver::FILLER_NORM_RAND) || (filler == Transceiver::FILLER_EDGE_RAND)) { chanType[n] = TSC; } } return false; } Transceiver::Transceiver(int wBasePort, const char *wTRXAddress, size_t tx_sps, size_t rx_sps, size_t chans, GSM::Time wTransmitLatency, RadioInterface *wRadioInterface, double wRssiOffset) : mBasePort(wBasePort), mAddr(wTRXAddress), mClockSocket(wBasePort, wTRXAddress, mBasePort + 100), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), rssiOffset(wRssiOffset), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mEdge(false), mOn(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(int 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(c_srcport, mAddr.c_str(), c_dstport); mDataSockets[i] = new UDPSocket(d_srcport, mAddr.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; } /* * 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); } writeClockInterface(); 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(); 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(); } mTxLowerLoopThread->join(); mRxLowerLoopThread->join(); delete mTxLowerLoopThread; delete mRxLowerLoopThread; 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 bursts(mChans); std::vector zeros(mChans); std::vector filler(mChans, true); for (size_t i = 0; i < mChans; i ++) { state = &mStates[i]; while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) { LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface"; 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<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(); } void Transceiver::driveControl(size_t chan) { int MAX_PACKET_LENGTH = 100; // check control socket char buffer[MAX_PACKET_LENGTH]; int msgLen = -1; buffer[0] = '\0'; msgLen = mCtrlSockets[chan]->read(buffer, sizeof(buffer)); if (msgLen < 1) { return; } char cmdcheck[4]; char command[MAX_PACKET_LENGTH]; char response[MAX_PACKET_LENGTH]; sscanf(buffer,"%3s %s",cmdcheck,command); if (!chan) writeClockInterface(); if (strcmp(cmdcheck,"CMD")!=0) { LOG(WARNING) << "bogus message on control interface"; return; } LOG(INFO) << "command is " << buffer; if (strcmp(command,"POWEROFF")==0) { stop(); sprintf(response,"RSP POWEROFF 0"); } else if (strcmp(command,"POWERON")==0) { 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 (strcmp(command,"HANDOVER")==0){ int ts=0,ss=0; sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&ts,&ss); mHandover[ts][ss] = true; sprintf(response,"RSP HANDOVER 0 %d %d",ts,ss); } else if (strcmp(command,"NOHANDOVER")==0){ int ts=0,ss=0; sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&ts,&ss); mHandover[ts][ss] = false; sprintf(response,"RSP NOHANDOVER 0 %d %d",ts,ss); } else if (strcmp(command,"SETMAXDLY")==0) { //set expected maximum time-of-arrival int maxDelay; sscanf(buffer,"%3s %s %d",cmdcheck,command,&maxDelay); mMaxExpectedDelayAB = maxDelay; // 1 GSM symbol is approx. 1 km sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay); } else if (strcmp(command,"SETMAXDLYNB")==0) { //set expected maximum time-of-arrival int maxDelay; sscanf(buffer,"%3s %s %d",cmdcheck,command,&maxDelay); mMaxExpectedDelayNB = maxDelay; // 1 GSM symbol is approx. 1 km sprintf(response,"RSP SETMAXDLYNB 0 %d",maxDelay); } else if (strcmp(command,"SETRXGAIN")==0) { //set expected maximum time-of-arrival int newGain; sscanf(buffer,"%3s %s %d",cmdcheck,command,&newGain); newGain = mRadioInterface->setRxGain(newGain, chan); sprintf(response,"RSP SETRXGAIN 0 %d",newGain); } else if (strcmp(command,"NOISELEV")==0) { 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 (!strcmp(command, "SETPOWER")) { int power; sscanf(buffer, "%3s %s %d", cmdcheck, command, &power); power = mRadioInterface->setPowerAttenuation(power, chan); mStates[chan].mPower = power; sprintf(response, "RSP SETPOWER 0 %d", power); } else if (!strcmp(command,"ADJPOWER")) { int power, step; sscanf(buffer, "%3s %s %d", cmdcheck, command, &step); power = mStates[chan].mPower + step; power = mRadioInterface->setPowerAttenuation(power, chan); mStates[chan].mPower = power; sprintf(response, "RSP ADJPOWER 0 %d", power); } else if (strcmp(command,"RXTUNE")==0) { // tune receiver int freqKhz; sscanf(buffer,"%3s %s %d",cmdcheck,command,&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 (strcmp(command,"TXTUNE")==0) { // tune txmtr int freqKhz; sscanf(buffer,"%3s %s %d",cmdcheck,command,&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 (!strcmp(command,"SETTSC")) { // set TSC unsigned TSC; sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC); if ((TSC < 0) || (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 (strcmp(command,"SETSLOT")==0) { // set slot type int corrCode; int timeslot; sscanf(buffer,"%3s %s %d %d",cmdcheck,command,×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 (strcmp(command,"_SETBURSTTODISKMASK")==0) { // debug command! may change or disapear without notice // set a mask which bursts to dump to disk int mask; sscanf(buffer,"%3s %s %d",cmdcheck,command,&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() { if (!mRadioInterface->driveReceiveRadio()) { usleep(100000); } else { if (mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0)) 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; mLatencyUpdateTime = radioClock->get(); } } else { // if underrun hasn't occurred in the last sec (216 frames) drop // transmit latency by a timeslot if (mTransmitLatency > GSM::Time(USB_LATENCY_MIN)) { 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) { Transceiver *trx = chan->trx; size_t num = chan->num; delete chan; trx->setPriority(0.42); while (1) { trx->driveReceiveFIFO(num); pthread_testcancel(); } return NULL; } void *RxLowerLoopAdapter(Transceiver *transceiver) { transceiver->setPriority(0.45); while (1) { transceiver->driveReceiveRadio(); pthread_testcancel(); } return NULL; } void *TxLowerLoopAdapter(Transceiver *transceiver) { transceiver->setPriority(0.44); while (1) { transceiver->driveTxFIFO(); pthread_testcancel(); } return NULL; } void *ControlServiceLoopAdapter(TransceiverChannel *chan) { Transceiver *trx = chan->trx; size_t num = chan->num; delete chan; while (1) { trx->driveControl(num); pthread_testcancel(); } return NULL; } void *TxUpperLoopAdapter(TransceiverChannel *chan) { Transceiver *trx = chan->trx; size_t num = chan->num; delete chan; trx->setPriority(0.40); while (1) { trx->driveTxPriorityQueue(num); pthread_testcancel(); } return NULL; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/Transceiver.h000066400000000000000000000230671306765341600236020ustar00rootroot00000000000000/* * 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 . */ #include "radioInterface.h" #include "Interthread.h" #include "GSMCommon.h" #include "Sockets.h" #include #include 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(int 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 manager, 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, 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(int 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; }; /** 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; enum FillerType { FILLER_DUMMY, FILLER_ZERO, FILLER_NORM_RAND, FILLER_EDGE_RAND, FILLER_ACCESS_RAND, }; private: int mBasePort; std::string mAddr; std::vector mDataSockets; ///< socket for writing to/reading from GSM core std::vector mCtrlSockets; ///< socket for writing/reading control commands from GSM core UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core std::vector mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core std::vector mReceiveFIFO; ///< radioInterface FIFO of receive bursts std::vector 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 mControlServiceLoopThreads; ///< thread to process control messages from GSM core std::vector 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 /** 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 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 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 *); osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/UHDDevice.cpp000066400000000000000000001246471306765341600234160ustar00rootroot00000000000000/* * Device support for Ettus Research UHD driver * * Copyright 2010,2011 Free Software Foundation, Inc. * Copyright (C) 2015 Ettus Research LLC * * Author: Tom Tsou * * 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 . * See the COPYING file in the main directory for details. */ #include "radioDevice.h" #include "Threads.h" #include "Logger.h" #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef USE_UHD_3_11 #include #endif #define B2XX_CLK_RT 26e6 #define B2XX_MCBTS_CLK_RT 51.2e6 #define E1XX_CLK_RT 52e6 #define LIMESDR_CLK_RT (GSMRATE*32) #define B100_BASE_RT 400000 #define USRP2_BASE_RT 390625 #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, NUM_USRP_TYPES, }; struct uhd_dev_offset { enum uhd_dev_type type; size_t tx_sps; size_t rx_sps; double offset; const std::string desc; }; /* * 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. */ static struct uhd_dev_offset uhd_offsets[] = { { USRP1, 1, 1, 0.0, "USRP1 not supported" }, { USRP1, 4, 1, 0.0, "USRP1 not supported"}, { USRP2, 1, 1, 1.2184e-4, "N2XX 1 SPS" }, { USRP2, 4, 1, 7.6547e-5, "N2XX 4/1 SPS" }, { B100, 1, 1, 1.2104e-4, "B100 1 SPS" }, { B100, 4, 1, 7.9307e-5, "B100 4 SPS" }, { B200, 1, 1, B2XX_TIMING_1SPS, "B200 1 SPS" }, { B200, 4, 1, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" }, { B210, 1, 1, B2XX_TIMING_1SPS, "B210 1 SPS" }, { B210, 4, 1, B2XX_TIMING_4SPS, "B210 4/1 Tx/Rx SPS" }, { B2XX_MCBTS, 4, 4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" }, { E1XX, 1, 1, 9.5192e-5, "E1XX 1 SPS" }, { E1XX, 4, 1, 6.5571e-5, "E1XX 4/1 Tx/Rx SPS" }, { E3XX, 1, 1, 1.84616e-4, "E3XX 1 SPS" }, { E3XX, 4, 1, 1.29231e-4, "E3XX 4/1 Tx/Rx SPS" }, { X3XX, 1, 1, 1.5360e-4, "X3XX 1 SPS"}, { X3XX, 4, 1, 1.1264e-4, "X3XX 4/1 Tx/Rx SPS"}, { UMTRX, 1, 1, 9.9692e-5, "UmTRX 1 SPS" }, { UMTRX, 4, 1, 7.3846e-5, "UmTRX 4/1 Tx/Rx SPS" }, { USRP2, 4, 4, 4.6080e-5, "N2XX 4 SPS" }, { B200, 4, 4, B2XX_TIMING_4_4SPS, "B200 4 SPS" }, { B210, 4, 4, B2XX_TIMING_4_4SPS, "B210 4 SPS" }, { X3XX, 4, 4, 5.6567e-5, "X3XX 4 SPS"}, { UMTRX, 4, 4, 5.1503e-5, "UmTRX 4 SPS" }, { LIMESDR, 4, 4, 16.5/GSMRATE, "STREAM/LimeSDR (4 SPS TX/RX)" }, }; #define NUM_UHD_OFFSETS (sizeof(uhd_offsets)/sizeof(uhd_offsets[0])) /* * Offset handling for special cases. Currently used for UmTRX dual channel * diversity receiver only. */ static struct uhd_dev_offset special_offsets[] = { { UMTRX, 1, 1, 8.0875e-5, "UmTRX diversity, 1 SPS" }, { UMTRX, 4, 1, 5.2103e-5, "UmTRX diversity, 4 SPS" }, }; /* * Select sample rate based on device type and requested samples-per-symbol. * The base rate is either GSM symbol rate, 270.833 kHz, or the minimum * usable channel spacing of 400 kHz. */ static double select_rate(uhd_dev_type type, int sps, RadioDevice::InterfaceType iface) { if (iface == RadioDevice::DIVERSITY) { if (type == UMTRX) return GSMRATE * 4; LOG(ALERT) << "Diversity supported on UmTRX only"; return -9999.99; } if ((sps != 4) && (sps != 1)) return -9999.99; if (iface == RadioDevice::MULTI_ARFCN) { switch (type) { case B2XX_MCBTS: return 4 * MCBTS_SPACING; default: LOG(ALERT) << "Invalid device combination"; return -9999.99; } } switch (type) { case USRP2: case X3XX: return USRP2_BASE_RT * sps; case B100: return B100_BASE_RT * sps; case B200: case B210: case E1XX: case E3XX: case UMTRX: case LIMESDR: return GSMRATE * sps; default: break; } LOG(ALERT) << "Unknown device type " << type; return -9999.99; } /* 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); ~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 &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun, unsigned *RSSI); int writeSamples(std::vector &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(); 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; size_t tx_sps, rx_sps, chans; double tx_rate, rx_rate; double tx_gain_min, tx_gain_max; double rx_gain_min, rx_gain_max; double offset; std::vector tx_gains, rx_gains; std::vector 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 rx_buffers; void init_gains(); double get_dev_offset(); int set_master_clk(double rate); int set_rates(double tx_rate, double rx_rate); 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; InterfaceType iface; Mutex tune_lock; }; void *async_event_loop(uhd_device *dev) { 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: LOG(INFO) << msg; break; case uhd::msg::warning: LOG(WARNING) << msg; break; case uhd::msg::error: LOG(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 offset) : 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) { this->tx_sps = tx_sps; this->rx_sps = rx_sps; this->chans = chans; this->offset = offset; this->iface = iface; } 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 gain_stages = usrp_dev->get_tx_gain_names(0); if (gain_stages[0] == "VGA") { LOG(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(); } LOG(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(); LOG(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; LOG(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; LOG(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; } double uhd_device::get_dev_offset() { struct uhd_dev_offset *offset = NULL; /* Reject USRP1 */ if (dev_type == USRP1) { LOG(ERR) << "Invalid device type"; return 0.0; } /* Special cases (e.g. diversity receiver) */ if (iface == DIVERSITY) { if ((dev_type != UMTRX) || (rx_sps != 1)) { LOG(ALERT) << "Unsupported device configuration"; return 0.0; } switch (tx_sps) { case 1: offset = &special_offsets[0]; break; case 4: default: offset = &special_offsets[1]; } } else { /* Search for matching offset value */ for (size_t i = 0; i < NUM_UHD_OFFSETS; i++) { if ((dev_type == uhd_offsets[i].type) && (tx_sps == uhd_offsets[i].tx_sps) && (rx_sps == uhd_offsets[i].rx_sps)) { offset = &uhd_offsets[i]; break; } } } if (!offset) { LOG(ERR) << "Invalid device configuration"; return 0.0; } std::cout << "-- Setting " << offset->desc << std::endl; return offset->offset; } int uhd_device::set_master_clk(double clk_rate) { double actual, offset, limit = 1.0; try { usrp_dev->set_master_clock_rate(clk_rate); } catch (const std::exception &ex) { LOG(ALERT) << "UHD clock rate setting failed: " << clk_rate; LOG(ALERT) << ex.what(); return -1; } actual = usrp_dev->get_master_clock_rate(); offset = fabs(clk_rate - actual); if (offset > limit) { LOG(ALERT) << "Failed to set master clock rate"; LOG(ALERT) << "Requested clock rate " << clk_rate; LOG(ALERT) << "Actual clock rate " << actual; return -1; } return 0; } int uhd_device::set_rates(double tx_rate, double rx_rate) { double offset_limit = 1.0; double tx_offset, rx_offset; /* B2XX and E1xx are the only device where we set FPGA clocking */ if ((dev_type == B200) || (dev_type == B210) || (dev_type == E3XX)) { if (set_master_clk(B2XX_CLK_RT) < 0) return -1; } else if (dev_type == E1XX) { if (set_master_clk(E1XX_CLK_RT) < 0) return -1; } else if (dev_type == B2XX_MCBTS) { if (set_master_clk(B2XX_MCBTS_CLK_RT) < 0) return -1; } else if (dev_type == LIMESDR) { if (set_master_clk(LIMESDR_CLK_RT) < 0) return -1; } // Set sample rates try { usrp_dev->set_tx_rate(tx_rate); usrp_dev->set_rx_rate(rx_rate); } catch (const std::exception &ex) { LOG(ALERT) << "UHD rate setting failed"; LOG(ALERT) << ex.what(); return -1; } this->tx_rate = usrp_dev->get_tx_rate(); this->rx_rate = usrp_dev->get_rx_rate(); tx_offset = fabs(this->tx_rate - tx_rate); rx_offset = fabs(this->rx_rate - rx_rate); if ((tx_offset > offset_limit) || (rx_offset > offset_limit)) { LOG(ALERT) << "Actual sample rate differs from desired rate"; LOG(ALERT) << "Tx/Rx (" << this->tx_rate << "/" << this->rx_rate << ")"; return -1; } return 0; } double uhd_device::setTxGain(double db, size_t chan) { if (iface == MULTI_ARFCN) chan = 0; if (chan >= tx_gains.size()) { LOG(ALERT) << "Requested non-existent channel" << chan; return 0.0f; } if (dev_type == UMTRX) { std::vector 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); LOG(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 (chan >= rx_gains.size()) { LOG(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); LOG(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()) { LOG(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() { std::string mboard_str, dev_str; uhd::property_tree::sptr prop_tree; size_t usrp1_str, usrp2_str, e100_str, e110_str, e310_str, e3xx_str, b100_str, b200_str, b210_str, x300_str, x310_str, umtrx_str, limesdr_str; prop_tree = usrp_dev->get_device()->get_tree(); dev_str = prop_tree->access("/name").get(); mboard_str = usrp_dev->get_mboard_name(); usrp1_str = dev_str.find("USRP1"); usrp2_str = dev_str.find("USRP2"); b100_str = mboard_str.find("B100"); b200_str = mboard_str.find("B200"); b210_str = mboard_str.find("B210"); e100_str = mboard_str.find("E100"); e110_str = mboard_str.find("E110"); e310_str = mboard_str.find("E310"); e3xx_str = mboard_str.find("E3XX"); x300_str = mboard_str.find("X300"); x310_str = mboard_str.find("X310"); umtrx_str = dev_str.find("UmTRX"); // LimeSDR is based on STREAM board, so it's advertized as such limesdr_str = dev_str.find("STREAM"); if (usrp1_str != std::string::npos) { LOG(ALERT) << "USRP1 is not supported using the UHD driver"; LOG(ALERT) << "Please compile with GNU Radio libusrp support"; dev_type = USRP1; return false; } if (b100_str != std::string::npos) { tx_window = TX_WINDOW_USRP1; dev_type = B100; } else if (b200_str != std::string::npos) { tx_window = TX_WINDOW_USRP1; dev_type = B200; } else if (b210_str != std::string::npos) { tx_window = TX_WINDOW_USRP1; dev_type = B210; } else if (e100_str != std::string::npos) { tx_window = TX_WINDOW_FIXED; dev_type = E1XX; } else if (e110_str != std::string::npos) { tx_window = TX_WINDOW_FIXED; dev_type = E1XX; } else if (usrp2_str != std::string::npos) { tx_window = TX_WINDOW_FIXED; dev_type = USRP2; } else if ((e310_str != std::string::npos) || (e3xx_str != std::string::npos)) { tx_window = TX_WINDOW_FIXED; dev_type = E3XX; } else if (x300_str != std::string::npos) { tx_window = TX_WINDOW_FIXED; dev_type = X3XX; } else if (x310_str != std::string::npos) { tx_window = TX_WINDOW_FIXED; dev_type = X3XX; } else if (umtrx_str != std::string::npos) { tx_window = TX_WINDOW_FIXED; dev_type = UMTRX; } else if (limesdr_str != std::string::npos) { tx_window = TX_WINDOW_USRP1; dev_type = LIMESDR; } else { LOG(ALERT) << "Unknown UHD device type " << dev_str << " " << mboard_str; return false; } if (tx_window == TX_WINDOW_USRP1) { LOG(INFO) << "Using USRP1 type transmit window for " << dev_str << " " << mboard_str; } else { LOG(INFO) << "Using fixed transmit window for " << dev_str << " " << mboard_str; } return true; } /* * 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; } 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) { LOG(ALERT) << "No UHD devices found with address '" << args << "'"; return -1; } // Use the first found device LOG(INFO) << "Using discovered UHD device " << dev_addrs[0].to_string(); try { usrp_dev = uhd::usrp::multi_usrp::make(addr); } catch(...) { LOG(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()) { LOG(ALERT) << "E3XX requires UHD 003.009.000 or greater"; return -1; } // Verify and set channels if (iface == MULTI_ARFCN) { if ((dev_type != B200) && (dev_type != B210)) { LOG(ALERT) << "Unsupported device configuration"; return -1; } dev_type = B2XX_MCBTS; chans = 1; } else if (chans == 2) { if (dev_type == B210) { } else if (dev_type == UMTRX) { uhd::usrp::subdev_spec_t subdev_spec(swap_channels?"B:0 A:0":"A:0 B:0"); usrp_dev->set_tx_subdev_spec(subdev_spec); usrp_dev->set_rx_subdev_spec(subdev_spec); } else { LOG(ALERT) << "Invalid device configuration"; return -1; } } else if (chans != 1) { LOG(ALERT) << "Invalid channel combination for device"; 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: LOG(ALERT) << "Invalid reference type"; return -1; } usrp_dev->set_clock_source(refstr); // Set rates double _rx_rate = select_rate(dev_type, rx_sps, iface); double _tx_rate = select_rate(dev_type, tx_sps, iface); if (iface == DIVERSITY) _rx_rate = select_rate(dev_type, 1, iface); if ((_tx_rate < 0.0) || (_rx_rate < 0.0)) return -1; if (set_rates(_tx_rate, _rx_rate) < 0) 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); if (iface != DIVERSITY) 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(5e6, i); usrp_dev->set_rx_bandwidth(5e6, 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); // Set receive chain sample offset. Trigger the EDGE offset // table by checking for 4 SPS on the receive path. No other // configuration supports using 4 SPS. double offset = get_dev_offset(); if (offset == 0.0) { LOG(ERR) << "Unsupported configuration, no correction applied"; ts_offset = 0; } else { ts_offset = (TIMESTAMP) (offset * rx_rate); } // Initialize and shadow gain values init_gains(); // Print configuration LOG(INFO) << "\n" << usrp_dev->get_pp_string(); if (iface == DIVERSITY) return DIVERSITY; 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 > pkt_bufs(chans, std::vector(2 * rx_spp)); std::vector 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: LOG(ALERT) << "Device timed out"; return false; default: continue; } } ts_initial = md.time_spec.to_ticks(rx_rate); } LOG(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() { LOG(INFO) << "Starting USRP..."; if (started) { LOG(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(); LOG(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) { LOG(ERR) << str_code(md); switch (md.error_code) { case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: LOG(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) { LOG(ALERT) << "UHD: Received packet missing timestamp"; return ERROR_UNRECOVERABLE; } // Monotonicity check if (md.time_spec < prev_ts) { LOG(ALERT) << "UHD: Loss of monotonic time"; LOG(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 &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) { LOG(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); LOG(DEBUG) << "Requested timestamp = " << ts.get_real_secs(); // Check that timestamp is valid rc = rx_buffers[0]->avail_smpls(timestamp); if (rc < 0) { LOG(ERR) << rx_buffers[0]->str_code(rc); LOG(ERR) << rx_buffers[0]->str_status(timestamp); return 0; } // Create vector buffer std::vector > pkt_bufs(chans, std::vector(2 * rx_spp)); std::vector 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: LOG(ALERT) << "UHD: Version " << uhd::get_version_string(); LOG(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; LOG(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)) { LOG(ERR) << rx_buffers[i]->str_code(rc); LOG(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)) { LOG(ERR) << rx_buffers[i]->str_code(rc); LOG(ERR) << rx_buffers[i]->str_status(timestamp); return 0; } } return len; } int uhd_device::writeSamples(std::vector &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) { LOG(ERR) << "Control packets not supported"; return 0; } if (bufs.size() != chans) { LOG(ALERT) << "Invalid channel combination " << bufs.size(); return -1; } // Drop a fixed number of packets (magic value) if (!aligned) { drop_cnt++; if (drop_cnt == 1) { LOG(DEBUG) << "Aligning transmitter: stop burst"; *underrun = true; metadata.end_of_burst = true; } else if (drop_cnt < 30) { LOG(DEBUG) << "Aligning transmitter: packet advance"; return len; } else { LOG(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) { LOG(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 freqs; uhd::tune_request_t treq(freq); if (dev_type == UMTRX) { if (offset != 0.0) return uhd::tune_request_t(freq, 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 (offset == 0.0) return treq; return uhd::tune_request_t(freq, offset); } else if ((dev_type != B210) || (chans > 2) || (chan > 1)) { LOG(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 > B2XX_CLK_RT) { LOG(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 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); } LOG(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); } LOG(INFO) << "\n" << tres.to_pp_string() << std::endl; } return true; } bool uhd_device::setTxFreq(double wFreq, size_t chan) { if (chan >= tx_freqs.size()) { LOG(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()) { LOG(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()) { LOG(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()) { LOG(ALERT) << "Requested non-existent channel " << chan; return 0.0; } return rx_freqs[chan]; } /* * 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) 2047 * 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() { if (dev_type == LIMESDR) return (double) 2047; 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)) { LOG(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) { LOG(ERR) << "Overwriting old buffer data: timestamp="<. */ /* Compilation Flags SWLOOPBACK compile for software loopback testing */ #include #include #include #include "Threads.h" #include "USRPDevice.h" #include #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 sps) { LOG(INFO) << "creating USRP device..."; this->sps = sps; decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) 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 (sps == 1) pingOffset = 272; else if (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(); LOG(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 * sps, 1, -1, usrp_standard_rx::FPGA_MODE_NORMAL, 1024, 16 * 8, rbf)); m_uRx->set_fpga_master_clock_freq(masterClockRate); } catch(...) { LOG(ALERT) << "make failed on Rx"; m_uRx.reset(); return -1; } if (m_uRx->fpga_master_clock_freq() != masterClockRate) { LOG(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(...) { LOG(ALERT) << "make failed on Tx"; m_uTx.reset(); return -1; } if (m_uTx->fpga_master_clock_freq() != masterClockRate) { LOG(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() { LOG(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) { LOG(ALERT) << "Invalid channel " << chan; return 0.0; } writeLock.lock(); if (dB > maxTxGain()) dB = maxTxGain(); if (dB < minTxGain()) dB = minTxGain(); LOG(NOTICE) << "Setting TX gain to " << dB << " dB."; if (!m_dbTx->set_gain(dB)) LOG(ERR) << "Error setting TX gain"; writeLock.unlock(); return dB; } double USRPDevice::setRxGain(double dB, size_t chan) { if (chan) { LOG(ALERT) << "Invalid channel " << chan; return 0.0; } dB = 47.0; writeLock.lock(); if (dB > maxRxGain()) dB = maxRxGain(); if (dB < minRxGain()) dB = minRxGain(); LOG(NOTICE) << "Setting RX gain to " << dB << " dB."; if (!m_dbRx->set_gain(dB)) LOG(ERR) << "Error setting RX gain"; writeLock.unlock(); return dB; } // NOTE: Assumes sequential reads int USRPDevice::readSamples(std::vector &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; LOG(DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp; bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp); if (incrementHi32 && (timeStart!=0)) { LOG(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; LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset; timestamp += timestampOffset; isAligned = true; } continue; } if (chan != 0) { LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz; continue; } if ((word0 >> 28) & 0x04) { if (underrun) *underrun = true; LOG(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); LOG(DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp; } } // copy desired data to buf unsigned bufStart = dataStart+(timestamp-timeStart); if (bufStart + len < currDataSize/2) { LOG(DEBUG) << "bufStart: " << bufStart; memcpy(buf,data+bufStart*2,len*2*sizeof(short)); memset(data+bufStart*2,0,len*2*sizeof(short)); } else { LOG(DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart; unsigned firstLength = (currDataSize/2-bufStart); LOG(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 &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 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) { LOG(ALERT) << "Invalid channel " << chan; return false; } if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) { LOG(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 { LOG(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) { LOG(ALERT) << "Invalid channel " << chan; return false; } if (m_uRx->tune(0, m_dbRx, wFreq, &result)) { LOG(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 { LOG(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, size_t chans, bool diversity, double) { return new USRPDevice(tx_sps); } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/USRPDevice.h000066400000000000000000000140331306765341600232170ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include typedef boost::shared_ptr usrp_standard_tx_sptr; typedef boost::shared_ptr 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; int sps; 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 sps); /** 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 &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 &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); /** 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_ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/000077500000000000000000000000001306765341600217135ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/Makefile.am000066400000000000000000000006001306765341600237430ustar00rootroot00000000000000if ARCH_ARM if ARCH_ARM_A15 ARCH_FLAGS = -mfpu=neon-vfpv4 else ARCH_FLAGS = -mfpu=neon endif AM_CFLAGS = -Wall $(ARCH_FLAGS) -std=gnu99 -I../common AM_CCASFLAGS = $(ARCH_FLAGS) noinst_LTLIBRARIES = libarch.la libarch_la_SOURCES = \ ../common/convolve_base.c \ convert.c \ convert_neon.S \ convolve.c \ convolve_neon.S \ scale.c \ scale_neon.S \ mult.c \ mult_neon.S endif osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/convert.c000066400000000000000000000047611306765341600235470ustar00rootroot00000000000000/* * NEON type conversions * Copyright (C) 2012, 2013 Thomas Tsou * * 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 #include #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); #ifndef HAVE_NEON static void convert_si16_ps(float *out, const short *in, int len) { for (int i = 0; i < len; i++) out[i] = in[i]; } static void convert_ps_si16(short *out, const float *in, float scale, int len) { for (int i = 0; i < len; i++) out[i] = in[i] * scale; } #else /* 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)); } #endif 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 convert_ps_si16(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 convert_si16_ps(out, in, len); #endif } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/convert_neon.S000066400000000000000000000032541306765341600245420ustar00rootroot00000000000000/* * NEON type conversions * Copyright (C) 2012, 2013 Thomas Tsou * * 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 osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/convolve.c000066400000000000000000000070041306765341600237130ustar00rootroot00000000000000/* * NEON Convolution * Copyright (C) 2012, 2013 Thomas Tsou * * 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 #include #include #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: 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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/convolve_neon.S000066400000000000000000000167531306765341600247250ustar00rootroot00000000000000/* * NEON Convolution * Copyright (C) 2012, 2013 Thomas Tsou * * 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 osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/mult.c000066400000000000000000000027261306765341600230470ustar00rootroot00000000000000/* * NEON scaling * Copyright (C) 2012,2013 Thomas Tsou * * 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 #include #include #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 } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/mult_neon.S000066400000000000000000000026051306765341600240420ustar00rootroot00000000000000/* * NEON complex multiplication * Copyright (C) 2012,2013 Thomas Tsou * * 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 osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/scale.c000066400000000000000000000027261306765341600231550ustar00rootroot00000000000000/* * NEON scaling * Copyright (C) 2012,2013 Thomas Tsou * * 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 #include #include #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 } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/arm/scale_neon.S000066400000000000000000000030141306765341600241430ustar00rootroot00000000000000/* * ARM NEON Scaling * Copyright (C) 2013 Thomas Tsou * * 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 osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/000077500000000000000000000000001306765341600224245ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/convert.h000066400000000000000000000003201306765341600242500ustar00rootroot00000000000000#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); #endif /* _CONVERT_H_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/convolve.h000066400000000000000000000013771306765341600244400ustar00rootroot00000000000000#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); #endif /* _CONVOLVE_H_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/convolve_base.c000066400000000000000000000100121306765341600254070ustar00rootroot00000000000000/* * Convolution * Copyright (C) 2012, 2013 Thomas Tsou * * 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 #include #include #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 } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/fft.c000066400000000000000000000055741306765341600233620ustar00rootroot00000000000000/* * Fast Fourier transform * * Copyright (C) 2012 Tom Tsou * * 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 . * See the COPYING file in the main directory for details. */ #include #include #include #include #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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/fft.h000066400000000000000000000004711306765341600233560ustar00rootroot00000000000000#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_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/mult.h000066400000000000000000000001651306765341600235600ustar00rootroot00000000000000#ifndef _MULT_H_ #define _MULT_H_ void mul_complex(float *out, float *a, float *b, int len); #endif /* _MULT_H_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/common/scale.h000066400000000000000000000001771306765341600236710ustar00rootroot00000000000000#ifndef _SCALE_H_ #define _SCALE_H_ void scale_complex(float *out, float *in, float *scale, int len); #endif /* _SCALE_H_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/inband-signaling-usb000066400000000000000000000303611306765341600250550ustar00rootroot00000000000000This 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 | ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-//-+-+-+-+-+-+-+-+ 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 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/laurent.m000066400000000000000000000041361306765341600227700ustar00rootroot00000000000000% % 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 % % 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'); osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/osmo-trx.cpp000066400000000000000000000352721306765341600234410ustar00rootroot00000000000000/* * Copyright (C) 2013 Thomas Tsou * * 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 #include #include #include #include #include #include /* 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 * Note that these values are only used if the particular key does not * exist in the configuration database. IP port and address values will * typically be overwritten by the OpenBTS.db values. Other values will * not be in the database by default. */ #define DEFAULT_TRX_PORT 5700 #define DEFAULT_TRX_IP "127.0.0.1" #define DEFAULT_EXTREF false #define DEFAULT_DIVERSITY false #define DEFAULT_CHANS 1 struct trx_config { std::string log_level; std::string addr; std::string dev_args; unsigned port; unsigned tx_sps; unsigned rx_sps; unsigned chans; unsigned rtsc; unsigned rach_delay; bool extref; bool gpsref; Transceiver::FillerType filler; bool diversity; bool mcbts; double offset; double rssi_offset; bool swap_channels; bool edge; }; ConfigurationTable gConfig; volatile bool gshutdown = false; /* Run sanity check on configuration table * The global table constructor cannot provide notification in the * event of failure. Make sure that we can access the database, * write to it, and that it contains the bare minimum required keys. */ bool testConfig() { int val = 9999; std::string test = "asldfkjsaldkf"; const char *key = "Log.Level"; /* Attempt to query */ try { gConfig.getStr(key); } catch (...) { std::cerr << std::endl; std::cerr << "Config: Failed query required key " << key << std::endl; return false; } /* Attempt to set a test value in the global config */ if (!gConfig.set(test, val)) { std::cerr << std::endl; std::cerr << "Config: Failed to set test key" << std::endl; return false; } else { gConfig.remove(test); } return true; } /* Setup configuration values * Don't query the existence of the Log.Level because it's a * mandatory value. That is, if it doesn't exist, the configuration * table will crash or will have already crashed. Everything else we * can survive without and use default values if the database entries * are empty. */ bool trx_setup_config(struct trx_config *config) { std::string refstr, fillstr, divstr, mcstr, edgestr; if (!testConfig()) return false; if (config->log_level == "") config->log_level = gConfig.getStr("Log.Level"); if (!config->port) { if (gConfig.defines("TRX.Port")) config->port = gConfig.getNum("TRX.Port"); else config->port = DEFAULT_TRX_PORT; } if (config->addr == "") { if (gConfig.defines("TRX.IP")) config->addr = gConfig.getStr("TRX.IP"); else config->addr = DEFAULT_TRX_IP; } if (!config->extref) { if (gConfig.defines("TRX.Reference")) config->extref = gConfig.getNum("TRX.Reference"); else config->extref = DEFAULT_EXTREF; } if (!config->diversity) { if (gConfig.defines("TRX.Diversity")) config->diversity = gConfig.getNum("TRX.Diversity"); else config->diversity = DEFAULT_DIVERSITY; } if (!config->chans) config->chans = DEFAULT_CHANS; if (config->mcbts && ((config->chans < 0) || (config->chans > 5))) { std::cout << "Unsupported number of channels" << std::endl; return false; } edgestr = config->edge ? "Enabled" : "Disabled"; divstr = config->diversity ? "Enabled" : "Disabled"; mcstr = config->mcbts ? "Enabled" : "Disabled"; if (config->extref) refstr = "External"; else if (config->gpsref) refstr = "GPS"; else refstr = "Internal"; switch (config->filler) { case Transceiver::FILLER_DUMMY: fillstr = "Dummy bursts"; break; case Transceiver::FILLER_ZERO: fillstr = "Disabled"; break; case Transceiver::FILLER_NORM_RAND: fillstr = "Normal busrts with random payload"; break; case Transceiver::FILLER_EDGE_RAND: fillstr = "EDGE busrts with random payload"; break; case Transceiver::FILLER_ACCESS_RAND: fillstr = "Access busrts with random payload"; break; } std::ostringstream ost(""); ost << "Config Settings" << std::endl; ost << " Log Level............... " << config->log_level << std::endl; ost << " Device args............. " << config->dev_args << std::endl; ost << " TRX Base Port........... " << config->port << std::endl; ost << " TRX Address............. " << config->addr << std::endl; ost << " Channels................ " << config->chans << std::endl; ost << " Tx Samples-per-Symbol... " << config->tx_sps << std::endl; ost << " Rx Samples-per-Symbol... " << config->rx_sps << std::endl; ost << " EDGE support............ " << edgestr << std::endl; ost << " Reference............... " << refstr << std::endl; ost << " C0 Filler Table......... " << fillstr << std::endl; ost << " Multi-Carrier........... " << mcstr << std::endl; ost << " Diversity............... " << divstr << std::endl; ost << " Tuning offset........... " << config->offset << std::endl; ost << " RSSI to dBm offset...... " << config->rssi_offset << std::endl; ost << " Swap channels........... " << config->swap_channels << std::endl; std::cout << ost << std::endl; return true; } /* 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_config *config, RadioDevice *usrp, int type) { RadioInterface *radio = NULL; switch (type) { case RadioDevice::NORMAL: radio = new RadioInterface(usrp, config->tx_sps, config->rx_sps, config->chans); break; case RadioDevice::RESAMP_64M: case RadioDevice::RESAMP_100M: radio = new RadioInterfaceResamp(usrp, config->tx_sps, config->rx_sps); break; case RadioDevice::DIVERSITY: radio = new RadioInterfaceDiversity(usrp, config->tx_sps, config->chans); break; case RadioDevice::MULTI_ARFCN: radio = new RadioInterfaceMulti(usrp, config->tx_sps, config->rx_sps, config->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; } /* 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. */ Transceiver *makeTransceiver(struct trx_config *config, RadioInterface *radio) { Transceiver *trx; VectorFIFO *fifo; trx = new Transceiver(config->port, config->addr.c_str(), config->tx_sps, config->rx_sps, config->chans, GSM::Time(3,0), radio, config->rssi_offset); if (!trx->init(config->filler, config->rtsc, config->rach_delay, config->edge)) { LOG(ALERT) << "Failed to initialize transceiver"; delete trx; return NULL; } for (size_t i = 0; i < config->chans; i++) { fifo = radio->receiveFIFO(i); if (fifo && trx->receiveFIFO(fifo, i)) continue; LOG(ALERT) << "Could not attach FIFO to channel " << i; delete trx; return NULL; } return trx; } static void sig_handler(int signo) { fprintf(stdout, "Received shutdown signal"); gshutdown = true; } static void setup_signal_handlers() { if (signal(SIGINT, sig_handler) == SIG_ERR) { fprintf(stderr, "Failed to install SIGINT signal handler\n"); exit(EXIT_FAILURE); } if (signal(SIGTERM, sig_handler) == SIG_ERR) { fprintf(stderr, "Couldn't install SIGTERM signal handler\n"); exit( EXIT_FAILURE); } } static void print_help() { fprintf(stdout, "Options:\n" " -h This text\n" " -a UHD device args\n" " -l Logging level (%s)\n" " -i IP address of GSM core\n" " -p Base port number\n" " -e Enable EDGE receiver\n" " -d Enable dual channel diversity receiver (deprecated)\n" " -m Enable multi-ARFCN transceiver (default=disabled)\n" " -x Enable external 10 MHz reference\n" " -g Enable GPSDO reference\n" " -s Tx samples-per-symbol (1 or 4)\n" " -b Rx samples-per-symbol (1 or 4)\n" " -c Number of ARFCN channels (default=1)\n" " -f Enable C0 filler table\n" " -o Set baseband frequency offset (default=auto)\n" " -r Random Normal Burst test mode with TSC\n" " -A Random Access Burst test mode with delay\n" " -R RSSI to dBm offset in dB (default=0)\n" " -S Swap channels (UmTRX only)\n", "EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG"); } static void handle_options(int argc, char **argv, struct trx_config *config) { int option; config->port = 0; config->tx_sps = DEFAULT_TX_SPS; config->rx_sps = DEFAULT_RX_SPS; config->chans = DEFAULT_CHANS; config->rtsc = 0; config->rach_delay = 0; config->extref = false; config->gpsref = false; config->filler = Transceiver::FILLER_ZERO; config->mcbts = false; config->diversity = false; config->offset = 0.0; config->rssi_offset = 0.0; config->swap_channels = false; config->edge = false; while ((option = getopt(argc, argv, "ha:l:i:p:c:dmxgfo:s:b:r:A:R:Se")) != -1) { switch (option) { case 'h': print_help(); exit(0); break; case 'a': config->dev_args = optarg; break; case 'l': config->log_level = optarg; break; case 'i': config->addr = optarg; break; case 'p': config->port = atoi(optarg); break; case 'c': config->chans = atoi(optarg); break; case 'm': config->mcbts = true; break; case 'd': config->diversity = true; break; case 'x': config->extref = true; break; case 'g': config->gpsref = true; break; case 'f': config->filler = Transceiver::FILLER_DUMMY; break; case 'o': config->offset = atof(optarg); break; case 's': config->tx_sps = atoi(optarg); break; case 'b': config->rx_sps = atoi(optarg); break; case 'r': config->rtsc = atoi(optarg); config->filler = Transceiver::FILLER_NORM_RAND; break; case 'A': config->rach_delay = atoi(optarg); config->filler = Transceiver::FILLER_ACCESS_RAND; break; case 'R': config->rssi_offset = atof(optarg); break; case 'S': config->swap_channels = true; break; case 'e': config->edge = true; break; default: print_help(); exit(0); } } /* Force 4 SPS for EDGE or multi-ARFCN configurations */ if ((config->edge) || (config->mcbts)) { config->tx_sps = 4; config->rx_sps = 4; } if (config->gpsref && config->extref) { printf("External and GPSDO references unavailable at the same time\n\n"); goto bad_config; } /* Special restrictions on (deprecated) diversity configuration */ if (config->diversity) { if (config->mcbts || config->edge) { std::cout << "Multi-carrier/EDGE diversity unsupported" << std::endl; goto bad_config; } if (config->rx_sps != 1) { std::cout << "Diversity only supported with 1 SPS" << std::endl; goto bad_config; } if (config->chans != 2) { std::cout << "Diversity only supported with 2 channels" << std::endl; goto bad_config; } } if (config->edge && (config->filler == Transceiver::FILLER_NORM_RAND)) config->filler = Transceiver::FILLER_EDGE_RAND; if ((config->tx_sps != 1) && (config->tx_sps != 4) && (config->rx_sps != 1) && (config->rx_sps != 4)) { printf("Unsupported samples-per-symbol %i\n\n", config->tx_sps); goto bad_config; } if (config->rtsc > 7) { printf("Invalid training sequence %i\n\n", config->rtsc); goto bad_config; } if (config->rach_delay > 68) { printf("RACH delay is too big %i\n\n", config->rach_delay); goto bad_config; } return; bad_config: print_help(); exit(0); } int main(int argc, char *argv[]) { int type, chans, ref; RadioDevice *usrp; RadioInterface *radio = NULL; Transceiver *trx = NULL; RadioDevice::InterfaceType iface = RadioDevice::NORMAL; struct trx_config config; handle_options(argc, argv, &config); setup_signal_handlers(); /* Check database sanity */ if (!trx_setup_config(&config)) { std::cerr << "Config: Database failure - exiting" << std::endl; return EXIT_FAILURE; } gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7); srandom(time(NULL)); /* Create the low level device object */ if (config.mcbts) iface = RadioDevice::MULTI_ARFCN; if (config.extref) ref = RadioDevice::REF_EXTERNAL; else if (config.gpsref) ref = RadioDevice::REF_GPS; else ref = RadioDevice::REF_INTERNAL; usrp = RadioDevice::make(config.tx_sps, config.rx_sps, iface, config.chans, config.offset); type = usrp->open(config.dev_args, ref, config.swap_channels); if (type < 0) { LOG(ALERT) << "Failed to create radio device" << std::endl; goto shutdown; } /* Setup the appropriate device interface */ radio = makeRadioInterface(&config, usrp, type); if (!radio) goto shutdown; /* Create the transceiver core */ trx = makeTransceiver(&config, radio); if (!trx) goto shutdown; chans = trx->numChans(); std::cout << "-- Transceiver active with " << chans << " channel(s)" << std::endl; while (!gshutdown) sleep(1); shutdown: std::cout << "Shutting down transceiver..." << std::endl; delete trx; delete radio; delete usrp; return 0; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/pulseApproximate.m000066400000000000000000000004121306765341600246510ustar00rootroot00000000000000pp = [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; osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioBuffer.cpp000066400000000000000000000126411306765341600240740ustar00rootroot00000000000000/* * Segmented Ring Buffer * * Copyright (C) 2015 Ettus Research LLC * * Author: Tom Tsou * * 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 . * See the COPYING file in the main directory for details. */ #include #include #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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioBuffer.h000066400000000000000000000017641306765341600235450ustar00rootroot00000000000000#include #include #include 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 segments; bool outDirection; }; osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioClock.cpp000066400000000000000000000024371306765341600237200ustar00rootroot00000000000000/* * Written by Thomas Tsou * Based on code by Harvind S Samra * * 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 . * 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); } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioClock.h000066400000000000000000000022111306765341600233530ustar00rootroot00000000000000/* * Written by Thomas Tsou * Based on code by Harvind S Samra * * 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 . * 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 */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioDevice.h000066400000000000000000000111301306765341600235170ustar00rootroot00000000000000/* * 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 #include #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 }; /* Radio interface types */ enum InterfaceType { NORMAL, RESAMP_64M, RESAMP_100M, MULTI_ARFCN, DIVERSITY, }; enum ReferenceType { REF_INTERNAL, REF_EXTERNAL, REF_GPS, }; static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chans = 1, double offset = 0.0); /** 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 &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 &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; /** 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; }; #endif osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioInterface.cpp000066400000000000000000000211141306765341600245560ustar00rootroot00000000000000/* * 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 . * See the COPYING file in the main directory for details. */ #include "radioInterface.h" #include "Resampler.h" #include 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, size_t diversity, int wReceiveOffset, GSM::Time wStartTime) : mRadio(wRadio), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mMIMO(diversity), underrun(false), overrun(false), receiveOffset(wReceiveOffset), mOn(false) { mClock.set(wStartTime); } RadioInterface::~RadioInterface(void) { close(); } bool RadioInterface::init(int type) { if ((type != RadioDevice::NORMAL) || (mMIMO > 1) || !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); } bool RadioInterface::start() { if (mOn) return true; LOG(INFO) << "Starting radio device"; #ifdef USRP1 mAlignRadioServiceLoopThread.start((void * (*)(void*))AlignRadioServiceLoopAdapter, (void*)this); #endif 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; } #ifdef USRP1 void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface) { while (1) { radioInterface->alignRadio(); pthread_testcancel(); } return NULL; } void RadioInterface::alignRadio() { sleep(60); mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000); } #endif void RadioInterface::driveTransmitRadio(std::vector &bursts, std::vector &zeros) { if (!mOn) return; for (size_t i = 0; i < mChans; i++) radioifyVector(*bursts[i], i, zeros[i]); while (pushBuffer()); } bool RadioInterface::driveReceiveRadio() { radioVector *burst = NULL; if (!mOn) return false; pullBuffer(); 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::gRACHSynchSequence.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, mMIMO); for (size_t n = 0; n < mMIMO; n++) unRadioifyVector(burst->getVector(n), 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 true; } 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 */ void RadioInterface::pullBuffer() { bool local_underrun; size_t numRecv, segmentLen = recvBuffer[0]->getSegmentLen(); if (recvBuffer[0]->getFreeSegments() <= 0) return; /* Outer buffer access size is fixed */ numRecv = mRadio->readSamples(convertRecvBuffer, segmentLen, &overrun, readTimestamp, &local_underrun); if (numRecv != segmentLen) { LOG(ALERT) << "Receive error " << numRecv; return; } for (size_t i = 0; i < mChans; i++) { convert_short_float(recvBuffer[i]->getWriteSegment(), convertRecvBuffer[i], segmentLen * 2); } underrun |= local_underrun; readTimestamp += numRecv; } /* 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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioInterface.h000066400000000000000000000132341306765341600242270ustar00rootroot00000000000000/* * 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 mReceiveFIFO; ///< FIFO that holds receive bursts RadioDevice *mRadio; ///< the USRP object size_t mSPSTx; size_t mSPSRx; size_t mChans; size_t mMIMO; std::vector sendBuffer; std::vector recvBuffer; std::vector convertRecvBuffer; std::vector convertSendBuffer; std::vector 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 void 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, size_t diversity = 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 &bursts, std::vector &zeros); /** drive reception of GSM bursts */ bool 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(); } #if USRP1 protected: /** drive synchronization of Tx/Rx of USRP */ void alignRadio(); friend void *AlignRadioServiceLoopAdapter(RadioInterface*); #endif }; #if USRP1 /** synchronization thread loop */ void *AlignRadioServiceLoopAdapter(RadioInterface*); #endif class RadioInterfaceResamp : public RadioInterface { private: signalVector *outerSendBuffer; signalVector *outerRecvBuffer; bool pushBuffer(); void 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(); void pullBuffer(); signalVector *outerSendBuffer; signalVector *outerRecvBuffer; std::vector history; std::vector 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); }; class RadioInterfaceDiversity : public RadioInterface { public: RadioInterfaceDiversity(RadioDevice* wRadio, size_t tx_sps, size_t chans); ~RadioInterfaceDiversity(); bool init(int type); void close(); bool tuneRx(double freq, size_t chan); private: Resampler *dnsampler; std::vector phases; signalVector *outerRecvBuffer; bool mDiversity; double mFreqSpacing; bool setupDiversityChannels(); void pullBuffer(); }; osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioInterfaceDiversity.cpp000066400000000000000000000136121306765341600264650ustar00rootroot00000000000000/* * SSE Convolution * Copyright (C) 2013 Thomas Tsou * * 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 #include #include "Resampler.h" extern "C" { #include "convert.h" } /* Resampling parameters for 64 MHz clocking */ #define RESAMP_64M_INRATE 20 #define RESAMP_64M_OUTRATE 80 /* Downlink block size */ #define CHUNK 625 /* Universal resampling parameters */ #define NUMCHUNKS 48 /* * 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 size_t resamp_inrate = 0; static size_t resamp_inchunk = 0; static size_t resamp_outrate = 0; static size_t resamp_outchunk = 0; RadioInterfaceDiversity::RadioInterfaceDiversity(RadioDevice *wRadio, size_t tx_sps, size_t chans) : RadioInterface(wRadio, tx_sps, 1, chans, 2), outerRecvBuffer(NULL), mDiversity(false), mFreqSpacing(0.0) { } RadioInterfaceDiversity::~RadioInterfaceDiversity() { close(); } void RadioInterfaceDiversity::close() { delete outerRecvBuffer; delete dnsampler; dnsampler = NULL; outerRecvBuffer = NULL; if (recvBuffer.size()) recvBuffer[0] = NULL; RadioInterface::close(); } bool RadioInterfaceDiversity::setupDiversityChannels() { size_t inner_rx_len; /* Inner and outer rates */ resamp_inrate = RESAMP_64M_INRATE; resamp_outrate = RESAMP_64M_OUTRATE; resamp_inchunk = resamp_inrate * 4; resamp_outchunk = resamp_outrate * 4; /* Buffer lengths */ inner_rx_len = NUMCHUNKS * resamp_inchunk; /* Inside buffer must hold at least 2 bursts */ if (inner_rx_len < 157 * mSPSRx * 2) { LOG(ALERT) << "Invalid inner buffer size " << inner_rx_len; return false; } dnsampler = new Resampler(resamp_inrate, resamp_outrate); if (!dnsampler->init()) { LOG(ALERT) << "Rx resampler failed to initialize"; return false; } /* One Receive buffer and downsampler per diversity channel */ for (size_t i = 0; i < mMIMO * mChans; i++) { recvBuffer[i] = new RadioBuffer(NUMCHUNKS, resamp_inchunk, 0, false); } return true; } /* Initialize I/O specific objects */ bool RadioInterfaceDiversity::init(int type) { int outer_rx_len; if ((mMIMO != 2) || (mChans != 2)) { LOG(ALERT) << "Unsupported channel configuration " << mChans; return false; } /* Resize for channel combination */ sendBuffer.resize(mChans); recvBuffer.resize(mChans * mMIMO); convertSendBuffer.resize(mChans); convertRecvBuffer.resize(mChans); mReceiveFIFO.resize(mChans); phases.resize(mChans); if (!setupDiversityChannels()) return false; outer_rx_len = resamp_outchunk; for (size_t i = 0; i < mChans; i++) { /* Full rate float and integer outer receive buffers */ convertRecvBuffer[i] = new short[outer_rx_len * 2]; /* Send buffers (not-resampled) */ sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true); convertSendBuffer[i] = new short[CHUNK * mSPSTx * 2]; } outerRecvBuffer = new signalVector(outer_rx_len, dnsampler->len()); return true; } bool RadioInterfaceDiversity::tuneRx(double freq, size_t chan) { double f0, f1; if (chan > 1) return false; if (!mRadio->setRxFreq(freq, chan)) return false; f0 = mRadio->getRxFreq(0); f1 = mRadio->getRxFreq(1); mFreqSpacing = f1 - f0; if (abs(mFreqSpacing) <= 600e3) mDiversity = true; else mDiversity = false; return true; } /* Receive a timestamped chunk from the device */ void RadioInterfaceDiversity::pullBuffer() { bool local_underrun; int rc, num, path0, path1; signalVector *shift, *base; float *in, *out, rate = -mFreqSpacing * 2.0 * M_PI / 1.08333333e6; if (recvBuffer[0]->getFreeSegments() <= 0) return; /* Outer buffer access size is fixed */ num = mRadio->readSamples(convertRecvBuffer, resamp_outchunk, &overrun, readTimestamp, &local_underrun); if ((size_t) num != resamp_outchunk) { LOG(ALERT) << "Receive error " << num; return; } for (size_t i = 0; i < mChans; i++) { convert_short_float((float *) outerRecvBuffer->begin(), convertRecvBuffer[i], 2 * resamp_outchunk); if (!i) { path0 = 0; path1 = 2; } else { path0 = 3; path1 = 1; } /* Diversity path 1 */ base = outerRecvBuffer; in = (float *) base->begin(); out = (float *) recvBuffer[path0]->getWriteSegment(); rc = dnsampler->rotate(in, resamp_outchunk, out, resamp_inchunk); if (rc < 0) { LOG(ALERT) << "Sample rate downsampling error"; } /* Enable path 2 if Nyquist bandwidth is sufficient */ if (!mDiversity) continue; /* Diversity path 2 */ shift = new signalVector(base->size(), base->getStart()); in = (float *) shift->begin(); out = (float *) recvBuffer[path1]->getWriteSegment(); rate = i ? -rate : rate; if (!frequencyShift(shift, base, rate, phases[i], &phases[i])) { LOG(ALERT) << "Frequency shift failed"; } rc = dnsampler->rotate(in, resamp_outchunk, out, resamp_inchunk); if (rc < 0) { LOG(ALERT) << "Sample rate downsampling error"; } delete shift; } underrun |= local_underrun; readTimestamp += (TIMESTAMP) resamp_outchunk; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioInterfaceMulti.cpp000066400000000000000000000220001306765341600255640ustar00rootroot00000000000000/* * Multi-carrier radio interface * * Copyright (C) 2016 Ettus Research LLC * * Author: Tom Tsou * * 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 . * See the COPYING file in the main directory for details. */ #include #include #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 */ void RadioInterfaceMulti::pullBuffer() { bool local_underrun; size_t num; float *buf; if (recvBuffer[0]->getFreeSegments() <= 0) return; /* 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; } 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(); size_t hSize = 2 * hLen * sizeof(float); memcpy(&buf[2 * -hLen], history[lchan]->begin(), hSize); memcpy(history[lchan]->begin(), &buf[2 * (cLen - hLen)], hSize); 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"; } } } /* 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(); } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioInterfaceResamp.cpp000066400000000000000000000140431306765341600257310ustar00rootroot00000000000000/* * Radio device interface with sample rate conversion * * Copyright (C) 2011-2014 Free Software Foundation, Inc. * Copyright (C) 2015 Ettus Research LLC * * Author: Tom Tsou * * 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 . * See the COPYING file in the main directory for details. */ #include #include #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 */ void RadioInterfaceResamp::pullBuffer() { bool local_underrun; int rc, num_recv; if (recvBuffer[0]->getFreeSegments() <= 0) return; /* 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; } 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(); } /* 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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioVector.cpp000066400000000000000000000057251306765341600241320ustar00rootroot00000000000000/* * Written by Thomas Tsou * Based on code by Harvind S Samra * * 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 . * 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(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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/radioVector.h000066400000000000000000000037231306765341600235730ustar00rootroot00000000000000/* * Written by Thomas Tsou * Based on code by Harvind S Samra * * 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 . * 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 vectors; GSM::Time mTime; }; class noiseVector : std::vector { public: noiseVector(size_t size = 0); bool insert(float val); float avg() const; private: size_t itr; }; class VectorFIFO : public InterthreadQueue { }; class VectorQueue : public InterthreadPriorityQueue { public: GSM::Time nextTime() const; radioVector* getStaleBurst(const GSM::Time& targTime); radioVector* getCurrentBurst(const GSM::Time& targTime); }; #endif /* RADIOVECTOR_H */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/sigProcLib.cpp000066400000000000000000001467041306765341600237110ustar00rootroot00000000000000/* * 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 . */ #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 */ float cosTable[TABLESIZE+1]; // add 1 element for wrap around float sinTable[TABLESIZE+1]; float sincTable[TABLESIZE+1]; /** Constants */ static const float M_PI_F = (float)M_PI; static const float M_2PI_F = (float)(2.0*M_PI); static const float M_1_2PI_F = 1/M_2PI_F; /* 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 Complex psk8_table[8] = { Complex(-0.70710678, 0.70710678), Complex( 0.0, -1.0), Complex( 0.0, 1.0), Complex( 0.70710678, -0.70710678), Complex(-1.0, 0.0), Complex(-0.70710678, -0.70710678), Complex( 0.70710678, 0.70710678), Complex( 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 *gRACHSequence = 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; } delete GMSKRotation1; delete GMSKReverseRotation1; delete GMSKRotation4; delete GMSKReverseRotation4; delete gRACHSequence; delete GSMPulse1; delete GSMPulse4; delete dnsampler; GMSKRotation1 = NULL; GMSKRotation4 = NULL; GMSKReverseRotation4 = NULL; GMSKReverseRotation1 = NULL; gRACHSequence = NULL; GSMPulse1 = NULL; GSMPulse4 = NULL; } // dB relative to 1.0. // if > 1.0, then return 0 dB float dB(float x) { float arg = 1.0F; float dB = 0.0F; if (x >= 1.0F) return 0.0F; if (x <= 0.0F) return -200.0F; float prevArg = arg; float prevdB = dB; float stepSize = 16.0F; float dBstepSize = 12.0F; while (stepSize > 1.0F) { do { prevArg = arg; prevdB = dB; arg /= stepSize; dB -= dBstepSize; } while (arg > x); arg = prevArg; dB = prevdB; stepSize *= 0.5F; dBstepSize -= 3.0F; } return ((arg-x)*(dB-3.0F) + (x-arg*0.5F)*dB)/(arg - arg*0.5F); } // 10^(-dB/10), inverse of dB func. float dBinv(float x) { float arg = 1.0F; float dB = 0.0F; if (x >= 0.0F) return 1.0F; if (x <= -200.0F) return 0.0F; float prevArg = arg; float prevdB = dB; float stepSize = 16.0F; float dBstepSize = 12.0F; while (stepSize > 1.0F) { do { prevArg = arg; prevdB = dB; arg /= stepSize; dB -= dBstepSize; } while (dB > x); arg = prevArg; dB = prevdB; stepSize *= 0.5F; dBstepSize -= 3.0F; } return ((dB-x)*(arg*0.5F)+(x-(dB-3.0F))*(arg))/3.0F; } 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; } float vectorPower(const signalVector &x) { return vectorNorm2(x)/x.size(); } /** compute cosine via lookup table */ float cosLookup(const float x) { float arg = x*M_1_2PI_F; while (arg > 1.0F) arg -= 1.0F; while (arg < 0.0F) arg += 1.0F; const float argT = arg*((float)TABLESIZE); const int argI = (int)argT; const float delta = argT-argI; const float iDelta = 1.0F-delta; return iDelta*cosTable[argI] + delta*cosTable[argI+1]; } /** compute sine via lookup table */ float sinLookup(const float x) { float arg = x*M_1_2PI_F; while (arg > 1.0F) arg -= 1.0F; while (arg < 0.0F) arg += 1.0F; const float argT = arg*((float)TABLESIZE); const int argI = (int)argT; const float delta = argT-argI; const float iDelta = 1.0F-delta; return iDelta*sinTable[argI] + delta*sinTable[argI+1]; } /** compute e^(-jx) via lookup table. */ static complex expjLookup(float x) { float arg = x*M_1_2PI_F; while (arg > 1.0F) arg -= 1.0F; while (arg < 0.0F) arg += 1.0F; const float argT = arg*((float)TABLESIZE); const int argI = (int)argT; const float delta = argT-argI; const float iDelta = 1.0F-delta; return complex(iDelta*cosTable[argI] + delta*cosTable[argI+1], iDelta*sinTable[argI] + delta*sinTable[argI+1]); } /** Library setup functions */ static void initTrigTables() { for (int i = 0; i < TABLESIZE+1; i++) { cosTable[i] = cos(2.0*M_PI*i/TABLESIZE); sinTable[i] = sin(2.0*M_PI*i/TABLESIZE); } } /* * 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(); float phase = 0.0; while (rotPtr != GMSKRotation4->end()) { *rotPtr++ = expjLookup(phase); *revPtr++ = expjLookup(-phase); phase += M_PI_F / 2.0F / 4.0; } GMSKRotation1 = new signalVector(len1); GMSKReverseRotation1 = new signalVector(len1); rotPtr = GMSKRotation1->begin(); revPtr = GMSKReverseRotation1->begin(); phase = 0.0; while (rotPtr != GMSKRotation1->end()) { *rotPtr++ = expjLookup(phase); *revPtr++ = expjLookup(-phase); phase += M_PI_F / 2.0F; } } 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; } signalVector *convolve(const signalVector *x, const signalVector *h, signalVector *y, ConvType spanType, size_t start, size_t len, size_t step, int offset) { 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; } signalVector* frequencyShift(signalVector *y, signalVector *x, float freq, float startPhase, float *finalPhase) { if (!x) return NULL; if (y==NULL) { y = new signalVector(x->size()); y->isReal(x->isReal()); if (y==NULL) return NULL; } if (y->size() < x->size()) return NULL; float phase = startPhase; signalVector::iterator yP = y->begin(); signalVector::iterator xPEnd = x->end(); signalVector::iterator xP = x->begin(); if (x->isReal()) { while (xP < xPEnd) { (*yP++) = expjLookup(phase)*( (xP++)->real() ); phase += freq; } } else { while (xP < xPEnd) { (*yP++) = (*xP++)*expjLookup(phase); phase += freq; if (phase > 2 * M_PI) phase -= 2 * M_PI; else if (phase < -2 * M_PI) phase += 2 * M_PI; } } if (finalPhase) *finalPhase = phase; return y; } signalVector* reverseConjugate(signalVector *b) { signalVector *tmp = new signalVector(b->size()); tmp->isReal(b->isReal()); signalVector::iterator bP = b->begin(); signalVector::iterator bPEnd = b->end(); signalVector::iterator tmpP = tmp->end()-1; if (!b->isReal()) { while (bP < bPEnd) { *tmpP-- = bP->conj(); bP++; } } else { while (bP < bPEnd) { *tmpP-- = bP->real(); bP++; } } return tmp; } 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, *shaped; 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 */ shaped = convolve(&rotated, pulse, NULL, START_ONLY); if (!shaped) return NULL; return shaped; } static void rotateBurst2(signalVector &burst, double phase) { Complex rot = Complex(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_burst; signalVector *c1_burst, *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; c0_burst = new signalVector(burst_len, c0_pulse->size()); c0_burst->isReal(true); c0_itr = c0_burst->begin(); c1_burst = new signalVector(burst_len, c1_pulse->size()); c1_burst->isReal(true); 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(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(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(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 c0_burst; delete c1_burst; 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 rot = Complex(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 rot = Complex(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 *burst, *shape; signalVector::iterator burst_itr; nsyms = symbols.size(); if (nsyms * sps > nsamps) nsyms = 156; burst = new signalVector(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 rot = Complex(cos(phase), sin(phase)); *burst_itr = symbols[i] * rot; burst_itr += sps; } /* Single Gaussian pulse approximation shaping */ shape = convolve(burst, GSMPulse4->c0, NULL, START_ONLY); delete burst; return shape; } /* * 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 = new BitVector(148); signalVector *burst; /* Tail bits */ for (; i < 4; i++) (*bits)[i] = 0; /* Random bits */ for (; i < 61; i++) (*bits)[i] = rand() % 2; /* Training sequence */ for (int n = 0; i < 87; i++, n++) (*bits)[i] = gTrainingSequence[tsc][n]; /* Random bits */ for (; i < 144; i++) (*bits)[i] = rand() % 2; /* Tail bits */ for (; i < 148; i++) (*bits)[i] = 0; int guard = 8 + !(tn % 4); burst = modulateBurst(*bits, guard, sps); delete bits; return burst; } /* * 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 = new BitVector(88+delay); signalVector *burst; /* 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); burst = modulateBurst(*bits, guard, sps); delete bits; return burst; } 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 *shape, *burst = new signalVector(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]; shape = shapeEdgeBurst(*burst); delete burst; return shape; } /* * 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, *burst, *shaped; signalVector::iterator burst_itr; if (sps == 1) pulse = GSMPulse1->c0; else pulse = GSMPulse4->c0; burst_len = sps * (bits.size() + guard_len); burst = new signalVector(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 */ shaped = convolve(burst, pulse, NULL, START_ONLY); delete burst; return shaped; } /* 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() { float x; for (int i = 0; i < TABLESIZE; i++) { x = (float) i / TABLESIZE * 8 * M_PI; if (fabs(x) < 0.01) { sincTable[i] = 1.0f; continue; } sincTable[i] = sinf(x) / x; } } 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. */ 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; } signalVector *gaussianNoise(int length, float variance, complex mean) { signalVector *noise = new signalVector(length); signalVector::iterator nPtr = noise->begin(); float stddev = sqrtf(variance); while (nPtr < noise->end()) { float u1 = (float) rand()/ (float) RAND_MAX; while (u1==0.0) u1 = (float) rand()/ (float) RAND_MAX; float u2 = (float) rand()/ (float) RAND_MAX; float arg = 2.0*M_PI*u2; *nPtr = mean + stddev*complex(cos(arg),sin(arg))*sqrtf(-2.0*log(u1)); nPtr++; } return noise; } 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; } 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 */ 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++; } } // in-place addition!! bool addVector(signalVector &x, signalVector &y) { signalVector::iterator xP = x.begin(); signalVector::iterator yP = y.begin(); signalVector::iterator xPEnd = x.end(); signalVector::iterator yPEnd = y.end(); while ((xP < xPEnd) && (yP < yPEnd)) { *xP = *xP + *yP; xP++; yP++; } return true; } // in-place multiplication!! bool multVector(signalVector &x, signalVector &y) { signalVector::iterator xP = x.begin(); signalVector::iterator yP = y.begin(); signalVector::iterator xPEnd = x.end(); signalVector::iterator yPEnd = y.end(); while ((xP < xPEnd) && (yP < yPEnd)) { *xP = (*xP) * (*yP); xP++; yP++; } return true; } 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); memcpy(_midMidamble->begin(), midMidamble->begin(), midMidamble->size() * sizeof(complex)); 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; } 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); memcpy(_midamble->begin(), midamble->begin(), midamble->size() * sizeof(complex)); /* Channel gain is an empirically measured value */ seq = new CorrelationSequence; seq->buffer = data; seq->sequence = _midamble; seq->gain = Complex(-19.6432, 19.5006) / 1.18; seq->toa = 0; delete midamble; return seq; } static bool generateRACHSequence(int sps) { bool status = true; float toa; complex *data = NULL; signalVector *autocorr = NULL; signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL; delete gRACHSequence; seq0 = modulateBurst(gRACHSynchSequence, 0, sps, false); if (!seq0) return false; seq1 = modulateBurst(gRACHSynchSequence.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); memcpy(_seq1->begin(), seq1->begin(), seq1->size() * sizeof(complex)); autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY); if (!autocorr) { status = false; goto release; } gRACHSequence = new CorrelationSequence; gRACHSequence->sequence = _seq1; gRACHSequence->buffer = data; gRACHSequence->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) gRACHSequence->toa = toa - 20.5; else gRACHSequence->toa = 0.0; release: delete autocorr; delete seq0; delete seq1; if (!status) { delete _seq1; free(data); gRACHSequence = 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; } /* * 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; signalVector *corr; 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; corr = new signalVector(len); rc = detectBurst(rxBurst, *corr, sync, thresh, sps, &, &toa, start, len); delete corr; 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 */ int detectRACHBurst(const signalVector &burst, float threshold, int sps, complex &litude, float &toa, unsigned max_toa) { int rc, target, head, tail; CorrelationSequence *sync; target = 8 + 40; head = 8; tail = 8 + max_toa; sync = gRACHSequence; rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa, target, head, tail, sync); 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 */ 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; } 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 RACH: rc = detectRACHBurst(burst, threshold, sps, amp, toa, max_toa); break; default: LOG(ERR) << "Invalid correlation type"; } if (rc > 0) return type; return rc; } signalVector *downsampleBurst(const signalVector &burst) { signalVector *in, *out; in = new signalVector(DOWNSAMPLE_IN_LEN, dnsampler->len()); out = new signalVector(DOWNSAMPLE_OUT_LEN); memcpy(in->begin(), burst.begin(), DOWNSAMPLE_IN_LEN * 2 * sizeof(float)); if (dnsampler->rotate((float *) in->begin(), DOWNSAMPLE_IN_LEN, (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) { delete out; out = NULL; } delete in; return out; }; signalVector *decimateVector(signalVector &wVector, size_t factor) { signalVector *dec; if (factor <= 1) return NULL; dec = new signalVector(wVector.size() / factor); dec->isReal(wVector.isReal()); signalVector::iterator itr = dec->begin(); for (size_t i = 0; i < wVector.size(); i += factor) *itr++ = wVector[i]; return dec; } /* * 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(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. */ 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. */ 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() { initTrigTables(); generateSincTable(); initGMSKRotationTables(); GSMPulse1 = generateGSMPulse(1); GSMPulse4 = generateGSMPulse(4); generateRACHSequence(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; } std::string corrTypeToString(CorrType corr) { switch (corr) { case OFF: return "OFF"; case TSC: return "TSC"; case RACH: return "RACH"; case EDGE: return "EDGE"; case IDLE: return "IDLE"; default: return "unknown"; } } std::ostream& operator<<(std::ostream& os, CorrType corr) { os << corrTypeToString(corr); return os; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/sigProcLib.h000066400000000000000000000277151306765341600233560ustar00rootroot00000000000000/* * 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) /** Convolution type indicator */ enum ConvType { START_ONLY, NO_DELAY, CUSTOM, UNDEFINED, }; /** Codes for burst types of received bursts*/ enum CorrType{ OFF, ///< timeslot is off TSC, ///< timeslot should contain a normal burst RACH, ///< timeslot should contain an access burst EDGE, ///< timeslot should contain an EDGE burst IDLE ///< timeslot is an idle (or dummy) burst }; std::string corrTypeToString(CorrType corr); std::ostream& operator<<(std::ostream& os, CorrType corr); 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 /** Convert a linear number to a dB value */ float dB(float x); /** Convert a dB value into a linear value */ float dBinv(float x); /** Compute the energy of a vector */ float vectorNorm2(const signalVector &x); /** Compute the average power of a vector */ float vectorPower(const signalVector &x); /** Setup the signal processing library */ bool sigProcLibSetup(); /** Destroy the signal processing library */ void sigProcLibDestroy(void); /** Convolve two vectors. @param a,b The vectors to be convolved. @param c, A preallocated vector to hold the convolution result. @param spanType The type/span of the convolution. @return The convolution result or NULL on error. */ signalVector *convolve(const signalVector *a, const signalVector *b, signalVector *c, ConvType spanType, size_t start = 0, size_t len = 0, size_t step = 1, int offset = 0); /** Frequency shift a vector. @param y The frequency shifted vector. @param x The vector to-be-shifted. @param freq The digital frequency shift @param startPhase The starting phase of the oscillator @param finalPhase The final phase of the oscillator @return The frequency shifted vector. */ signalVector* frequencyShift(signalVector *y, signalVector *x, float freq = 0.0, float startPhase = 0.0, float *finalPhase=NULL); /** Correlate two vectors. @param a,b The vectors to be correlated. @param c, A preallocated vector to hold the correlation result. @param spanType The type/span of the correlation. @return The correlation result. */ signalVector* correlate(signalVector *a, signalVector *b, signalVector *c, ConvType spanType, bool bReversedConjugated = false, unsigned startIx = 0, unsigned len = 0); /** 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); /** Sinc function */ float sinc(float x); /** Delay a vector */ signalVector *delayVector(const signalVector *in, signalVector *out, float delay); /** Add two vectors in-place */ bool addVector(signalVector &x, signalVector &y); /** Multiply two vectors in-place*/ bool multVector(signalVector &x, signalVector &y); /** Generate a vector of gaussian noise */ signalVector *gaussianNoise(int length, float variance = 1.0, complex mean = complex(0.0)); /** Given a non-integer index, interpolate a sample. @param inSig The signal from which to interpolate. @param ix The index. @return The interpolated signal value. */ complex interpolatePoint(const signalVector &inSig, float ix); /** Given a correlator output, locate the correlation peak. @param rxBurst The correlator result. @param peakIndex Pointer to value to receive interpolated peak index. @param avgPower Power to value to receive mean power. @return Peak value. */ complex peakDetect(const signalVector &rxBurst, float *peakIndex, float *avgPwr); /** 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); /** RACH aka Access Burst correlator/detector. @param burst The received GSM burst of interest. @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 RACH burst. @param toa The estimate time-of-arrival of received RACH burst. @param max_toa The maximum expected time-of-arrival @return 1 if threshold value is reached, negative value (-SignalError) on error, zero (SIGERR_NONE) if no burst is detected */ int detectRACHBurst(const signalVector &burst, float threshold, int sps, complex &litude, float &toa, unsigned max_toa); /** GMSK Normal Burst correlator/detector. @param rxBurst 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. @param max_toa The maximum expected time-of-arrival @return 1 if threshold value is reached, negative value (-SignalError) on error, zero (SIGERR_NONE) if no burst is detected */ int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float threshold, int sps, complex &litude, float &toa, unsigned max_toa); /** EDGE/8-PSK Normal Burst correlator/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. @param max_toa The maximum expected time-of-arrival @return 1 if threshold value is reached, negative value (-SignalError) on error, zero (SIGERR_NONE) if no burst is detected */ int detectEdgeBurst(const signalVector &burst, unsigned tsc, float threshold, int sps, complex &litude, float &toa, unsigned max_toa); /** 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); /** Downsample 4 SPS to 1 SPS using a polyphase filterbank @param burst Input burst of at least 624 symbols @return Decimated signal vector of 156 symbols */ signalVector *downsampleBurst(const signalVector &burst); /** Decimate a vector. @param wVector The vector of interest. @param factor Decimation factor. @return The decimated signal vector. */ signalVector *decimateVector(signalVector &wVector, size_t factor); /** Demodulates a GMSK burst using a soft-slicer. @param rxBurst The burst to be demodulated. @param gsmPulse The GSM pulse. @param sps The number of samples per GSM symbol. @param channel The amplitude estimate of the received burst. @param TOA The time-of-arrival of the received burst. @return The demodulated bit sequence. */ SoftVector *demodGmskBurst(const signalVector &rxBurst, int sps, complex channel, float TOA); /** Demodulate 8-PSK EDGE burst with soft symbol ooutput @param rxBurst The burst to be demodulated. @param sps The number of samples per GSM symbol. @param channel The amplitude estimate of the received burst. @param TOA The time-of-arrival of the received burst. @return The demodulated bit sequence. */ SoftVector *demodEdgeBurst(const signalVector &rxBurst, int sps, complex channel, float 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 */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/signalVector.cpp000066400000000000000000000035671306765341600243130ustar00rootroot00000000000000#include "signalVector.h" signalVector::signalVector(size_t size) : Vector(size), real(false), aligned(false), symmetry(NONE) { } signalVector::signalVector(size_t size, size_t start) : Vector(size + start), real(false), aligned(false), symmetry(NONE) { mStart = mData + start; } signalVector::signalVector(complex *data, size_t start, size_t span) : Vector(NULL, data + start, data + start + span), real(false), aligned(false), symmetry(NONE) { } signalVector::signalVector(const signalVector &vector) : Vector(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(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()); 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(); 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; } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/signalVector.h000066400000000000000000000023651306765341600237530ustar00rootroot00000000000000#ifndef _SIGNALVECTOR_H_ #define _SIGNALVECTOR_H_ #include #include /** Vector symmetry */ enum Symmetry { NONE = 0, ABSSYM = 1 }; class signalVector: public Vector { 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_ */ osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/x86/000077500000000000000000000000001306765341600215615ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/x86/Makefile.am000066400000000000000000000003031306765341600236110ustar00rootroot00000000000000if !ARCH_ARM AM_CFLAGS = -Wall -std=gnu99 -march=native -I${srcdir}/../common noinst_LTLIBRARIES = libarch.la libarch_la_SOURCES = \ ../common/convolve_base.c \ convert.c \ convolve.c endif osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/x86/convert.c000066400000000000000000000122551306765341600234120ustar00rootroot00000000000000/* * SSE type conversions * Copyright (C) 2013 Thomas Tsou * * 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 #include #include "convert.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_SSE3 #include #include #ifdef HAVE_SSE4_1 #include /* 16*N 16-bit signed integer converted to single precision floats */ static 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 */ static 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 /* HAVE_SSE4_1 */ /* 8*N single precision floats scaled and converted to 16-bit signed integer */ static 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 */ static 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 */ static 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); } } #else /* HAVE_SSE3 */ static void convert_scale_ps_si16(short *out, const float *in, float scale, int len) { for (int i = 0; i < len; i++) out[i] = in[i] * scale; } #endif #ifndef HAVE_SSE4_1 static void convert_si16_ps(float *out, const short *in, int len) { for (int i = 0; i < len; i++) out[i] = in[i]; } #endif void convert_float_short(short *out, const float *in, float scale, int len) { #ifdef HAVE_SSE3 if (!(len % 16)) _sse_convert_scale_ps_si16_16n(out, in, scale, len); else if (!(len % 8)) _sse_convert_scale_ps_si16_8n(out, in, scale, len); else _sse_convert_scale_ps_si16(out, in, scale, len); #else convert_scale_ps_si16(out, in, scale, len); #endif } void convert_short_float(float *out, const short *in, int len) { #ifdef HAVE_SSE4_1 if (!(len % 16)) _sse_convert_si16_ps_16n(out, in, len); else _sse_convert_si16_ps(out, in, len); #else convert_si16_ps(out, in, len); #endif } osmo-trx-0~20170323git2af1440+dfsg/Transceiver52M/x86/convolve.c000066400000000000000000000404171306765341600235660ustar00rootroot00000000000000/* * SSE Convolution * Copyright (C) 2012, 2013 Thomas Tsou * * 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 #include #include #include "convolve.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif /* 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); #ifdef HAVE_SSE3 #include #include /* 4-tap SSE complex-real convolution */ static void sse_conv_real4(const float *restrict x, const float *restrict h, float *restrict y, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7; /* 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 */ static void sse_conv_real8(const float *restrict x, const float *restrict h, float *restrict y, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7, m8, m9; /* 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 */ static void sse_conv_real12(const float *restrict x, const float *restrict h, float *restrict y, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7; __m128 m8, m9, m10, m11, m12, m13, m14; /* 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 */ static void sse_conv_real16(const float *restrict x, const float *restrict h, float *restrict y, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7; __m128 m8, m9, m10, m11, m12, m13, m14, m15; /* 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 */ static void sse_conv_real20(const float *restrict x, const float *restrict h, float *restrict y, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7; __m128 m8, m9, m11, m12, m13, m14, m15; /* 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 */ static void sse_conv_real4n(const float *x, const float *h, float *y, int h_len, int len) { __m128 m0, m1, m2, m4, m5, m6, m7; 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 */ static void sse_conv_cmplx_4n(const float *x, const float *h, float *y, int h_len, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7; 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 */ static void sse_conv_cmplx_8n(const float *x, const float *h, float *y, int h_len, int len) { __m128 m0, m1, m2, m3, m4, m5, m6, m7; __m128 m8, m9, m10, m11, m12, m13, m14, m15; 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 /* 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) { void (*conv_func)(const float *, const float *, float *, int) = NULL; void (*conv_func_n)(const float *, const 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_SSE3 if (step <= 4) { switch (h_len) { case 4: conv_func = sse_conv_real4; break; case 8: conv_func = sse_conv_real8; break; case 12: conv_func = sse_conv_real12; break; case 16: conv_func = sse_conv_real16; break; case 20: conv_func = sse_conv_real20; break; default: if (!(h_len % 4)) conv_func_n = sse_conv_real4n; } } #endif if (conv_func) { conv_func(&x[2 * (-(h_len - 1) + start)], h, y, len); } else if (conv_func_n) { conv_func_n(&x[2 * (-(h_len - 1) + start)], h, y, h_len, 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(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 (*conv_func)(const float *, const 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_SSE3 if (step <= 4) { if (!(h_len % 8)) conv_func = sse_conv_cmplx_8n; else if (!(h_len % 4)) conv_func = sse_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; } osmo-trx-0~20170323git2af1440+dfsg/autogen.sh000077500000000000000000001344311306765341600203720ustar00rootroot00000000000000#!/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 # # Patches: # Sebastian Pipping # ###################################################################### # 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 < # Copyright (c) 2011 Maarten Bosmans # # 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 . # # 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 osmo-trx-0~20170323git2af1440+dfsg/config/ax_ext.m4000066400000000000000000000170531306765341600213700ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_ext.html # =========================================================================== # # SYNOPSIS # # AX_EXT # # DESCRIPTION # # Find supported SIMD extensions by requesting cpuid. When an SIMD # extension is found, 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_MMX / HAVE_SSE / HAVE_SSE2 / HAVE_SSE3 / HAVE_SSSE3 / HAVE_SSE4.1 / HAVE_SSE4.2 / HAVE_AVX # # LICENSE # # Copyright (c) 2007 Christophe Tournayre # Copyright (c) 2013 Michael Petch # # 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 12 AC_DEFUN([AX_EXT], [ AC_REQUIRE([AC_CANONICAL_HOST]) case $host_cpu in i[[3456]]86*|x86_64*|amd64*) AC_REQUIRE([AX_GCC_X86_CPUID]) AC_REQUIRE([AX_GCC_X86_AVX_XGETBV]) AX_GCC_X86_CPUID(0x00000001) ecx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 3` edx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 4` AC_CACHE_CHECK([whether mmx is supported], [ax_cv_have_mmx_ext], [ ax_cv_have_mmx_ext=no if test "$((0x$edx>>23&0x01))" = 1; then ax_cv_have_mmx_ext=yes fi ]) AC_CACHE_CHECK([whether sse is supported], [ax_cv_have_sse_ext], [ ax_cv_have_sse_ext=no if test "$((0x$edx>>25&0x01))" = 1; then ax_cv_have_sse_ext=yes fi ]) AC_CACHE_CHECK([whether sse2 is supported], [ax_cv_have_sse2_ext], [ ax_cv_have_sse2_ext=no if test "$((0x$edx>>26&0x01))" = 1; then ax_cv_have_sse2_ext=yes fi ]) AC_CACHE_CHECK([whether sse3 is supported], [ax_cv_have_sse3_ext], [ ax_cv_have_sse3_ext=no if test "$((0x$ecx&0x01))" = 1; then ax_cv_have_sse3_ext=yes fi ]) AC_CACHE_CHECK([whether ssse3 is supported], [ax_cv_have_ssse3_ext], [ ax_cv_have_ssse3_ext=no if test "$((0x$ecx>>9&0x01))" = 1; then ax_cv_have_ssse3_ext=yes fi ]) AC_CACHE_CHECK([whether sse4.1 is supported], [ax_cv_have_sse41_ext], [ ax_cv_have_sse41_ext=no if test "$((0x$ecx>>19&0x01))" = 1; then ax_cv_have_sse41_ext=yes fi ]) AC_CACHE_CHECK([whether sse4.2 is supported], [ax_cv_have_sse42_ext], [ ax_cv_have_sse42_ext=no if test "$((0x$ecx>>20&0x01))" = 1; then ax_cv_have_sse42_ext=yes fi ]) AC_CACHE_CHECK([whether avx is supported by processor], [ax_cv_have_avx_cpu_ext], [ ax_cv_have_avx_cpu_ext=no if test "$((0x$ecx>>28&0x01))" = 1; then ax_cv_have_avx_cpu_ext=yes fi ]) if test x"$ax_cv_have_avx_cpu_ext" = x"yes"; then AX_GCC_X86_AVX_XGETBV(0x00000000) xgetbv_eax="0" if test x"$ax_cv_gcc_x86_avx_xgetbv_0x00000000" != x"unknown"; then xgetbv_eax=`echo $ax_cv_gcc_x86_avx_xgetbv_0x00000000 | cut -d ":" -f 1` fi AC_CACHE_CHECK([whether avx is supported by operating system], [ax_cv_have_avx_ext], [ ax_cv_have_avx_ext=no if test "$((0x$ecx>>27&0x01))" = 1; then if test "$((0x$xgetbv_eax&0x6))" = 6; then ax_cv_have_avx_ext=yes fi fi ]) if test x"$ax_cv_have_avx_ext" = x"no"; then AC_MSG_WARN([Your processor supports AVX, but your operating system doesn't]) fi fi if test "$ax_cv_have_mmx_ext" = yes; then AX_CHECK_COMPILE_FLAG(-mmmx, ax_cv_support_mmx_ext=yes, []) if test x"$ax_cv_support_mmx_ext" = x"yes"; then SIMD_FLAGS="$SIMD_FLAGS -mmmx" AC_DEFINE(HAVE_MMX,,[Support mmx instructions]) else AC_MSG_WARN([Your processor supports mmx instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_sse_ext" = yes; then AX_CHECK_COMPILE_FLAG(-msse, ax_cv_support_sse_ext=yes, []) if test x"$ax_cv_support_sse_ext" = x"yes"; then SIMD_FLAGS="$SIMD_FLAGS -msse" AC_DEFINE(HAVE_SSE,,[Support SSE (Streaming SIMD Extensions) instructions]) else AC_MSG_WARN([Your processor supports sse instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_sse2_ext" = yes; then AX_CHECK_COMPILE_FLAG(-msse2, ax_cv_support_sse2_ext=yes, []) if test x"$ax_cv_support_sse2_ext" = x"yes"; then SIMD_FLAGS="$SIMD_FLAGS -msse2" AC_DEFINE(HAVE_SSE2,,[Support SSE2 (Streaming SIMD Extensions 2) instructions]) else AC_MSG_WARN([Your processor supports sse2 instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_sse3_ext" = yes; then 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]) else AC_MSG_WARN([Your processor supports sse3 instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_ssse3_ext" = yes; then AX_CHECK_COMPILE_FLAG(-mssse3, ax_cv_support_ssse3_ext=yes, []) if test x"$ax_cv_support_ssse3_ext" = x"yes"; then SIMD_FLAGS="$SIMD_FLAGS -mssse3" AC_DEFINE(HAVE_SSSE3,,[Support SSSE3 (Supplemental Streaming SIMD Extensions 3) instructions]) else AC_MSG_WARN([Your processor supports ssse3 instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_sse41_ext" = yes; then 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 SSSE4.1 (Streaming SIMD Extensions 4.1) instructions]) else AC_MSG_WARN([Your processor supports sse4.1 instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_sse42_ext" = yes; then AX_CHECK_COMPILE_FLAG(-msse4.2, ax_cv_support_sse42_ext=yes, []) if test x"$ax_cv_support_sse42_ext" = x"yes"; then SIMD_FLAGS="$SIMD_FLAGS -msse4.2" AC_DEFINE(HAVE_SSE4_2,,[Support SSSE4.2 (Streaming SIMD Extensions 4.2) instructions]) else AC_MSG_WARN([Your processor supports sse4.2 instructions but not your compiler, can you try another compiler?]) fi fi if test "$ax_cv_have_avx_ext" = yes; then AX_CHECK_COMPILE_FLAG(-mavx, ax_cv_support_avx_ext=yes, []) if test x"$ax_cv_support_avx_ext" = x"yes"; then SIMD_FLAGS="$SIMD_FLAGS -mavx" AC_DEFINE(HAVE_AVX,,[Support AVX (Advanced Vector Extensions) instructions]) else AC_MSG_WARN([Your processor supports avx instructions but not your compiler, can you try another compiler?]) fi fi ;; esac AC_SUBST(SIMD_FLAGS) ]) osmo-trx-0~20170323git2af1440+dfsg/config/ax_gcc_x86_avx_xgetbv.m4000066400000000000000000000064031306765341600242630ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_avx_xgetbv.html # =========================================================================== # # SYNOPSIS # # AX_GCC_X86_AVX_XGETBV # # DESCRIPTION # # On later x86 processors with AVX SIMD support, with gcc or a compiler # that has a compatible syntax for inline assembly instructions, run a # small program that executes the xgetbv instruction with input OP. This # can be used to detect if the OS supports AVX instruction usage. # # On output, the values of the eax and edx registers are stored as # hexadecimal strings as "eax:edx" in the cache variable # ax_cv_gcc_x86_avx_xgetbv. # # If the xgetbv instruction fails (because you are running a # cross-compiler, or because you are not using gcc, or because you are on # a processor that doesn't have this instruction), # ax_cv_gcc_x86_avx_xgetbv_OP is set to the string "unknown". # # This macro mainly exists to be used in AX_EXT. # # LICENSE # # Copyright (c) 2013 Michael Petch # # 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 . # # 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 1 AC_DEFUN([AX_GCC_X86_AVX_XGETBV], [AC_REQUIRE([AC_PROG_CC]) AC_LANG_PUSH([C]) AC_CACHE_CHECK(for x86-AVX xgetbv $1 output, ax_cv_gcc_x86_avx_xgetbv_$1, [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include ], [ int op = $1, eax, edx; FILE *f; /* Opcodes for xgetbv */ __asm__(".byte 0x0f, 0x01, 0xd0" : "=a" (eax), "=d" (edx) : "c" (op)); f = fopen("conftest_xgetbv", "w"); if (!f) return 1; fprintf(f, "%x:%x\n", eax, edx); fclose(f); return 0; ])], [ax_cv_gcc_x86_avx_xgetbv_$1=`cat conftest_xgetbv`; rm -f conftest_xgetbv], [ax_cv_gcc_x86_avx_xgetbv_$1=unknown; rm -f conftest_xgetbv], [ax_cv_gcc_x86_avx_xgetbv_$1=unknown])]) AC_LANG_POP([C]) ]) osmo-trx-0~20170323git2af1440+dfsg/config/ax_gcc_x86_cpuid.m4000066400000000000000000000063611306765341600232150ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html # =========================================================================== # # SYNOPSIS # # AX_GCC_X86_CPUID(OP) # # DESCRIPTION # # On Pentium and later x86 processors, with gcc or a compiler that has a # compatible syntax for inline assembly instructions, run a small program # that executes the cpuid instruction with input OP. This can be used to # detect the CPU type. # # On output, the values of the eax, ebx, ecx, and edx registers are stored # as hexadecimal strings as "eax:ebx:ecx:edx" in the cache variable # ax_cv_gcc_x86_cpuid_OP. # # If the cpuid instruction fails (because you are running a # cross-compiler, or because you are not using gcc, or because you are on # a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP # is set to the string "unknown". # # This macro mainly exists to be used in AX_GCC_ARCHFLAG. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2008 Matteo Frigo # # 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 . # # 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 7 AC_DEFUN([AX_GCC_X86_CPUID], [AC_REQUIRE([AC_PROG_CC]) AC_LANG_PUSH([C]) AC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1, [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include ], [ int op = $1, eax, ebx, ecx, edx; FILE *f; __asm__("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (op)); f = fopen("conftest_cpuid", "w"); if (!f) return 1; fprintf(f, "%x:%x:%x:%x\n", eax, ebx, ecx, edx); fclose(f); return 0; ])], [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid], [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid], [ax_cv_gcc_x86_cpuid_$1=unknown])]) AC_LANG_POP([C]) ]) osmo-trx-0~20170323git2af1440+dfsg/configure.ac000066400000000000000000000075601306765341600206610ustar00rootroot00000000000000dnl 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 . dnl AC_INIT(openbts,P2.8TRUNK) 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_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])]) AM_PROG_AS AC_PROG_CXX AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_INSTALL AC_PATH_PROG([RM_PROG], [rm]) 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 AC_ARG_WITH(usrp1, [ AS_HELP_STRING([--with-usrp1], [enable USRP1 gnuradio 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_usrp1" != "xyes"],[ 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)] )] ) AC_DEFINE(USE_UHD, 1, All UHD versions) PKG_CHECK_MODULES(FFTWF, fftw3f) ]) 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_EXT ]) AM_CONDITIONAL(USRP1, [test "x$with_usrp1" = "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"]) AC_CHECK_LIB(sqlite3, sqlite3_open, , AC_MSG_ERROR(sqlite3 is not available)) PKG_CHECK_MODULES(LIBUSB, libusb-1.0) PKG_CHECK_MODULES(SQLITE3, sqlite3) AC_CHECK_HEADER([boost/config.hpp],[], [AC_MSG_ERROR([boost/config.hpp not found, install e.g. libboost-dev])]) dnl Output files AC_CONFIG_FILES([\ Makefile \ CommonLibs/Makefile \ GSM/Makefile \ Transceiver52M/Makefile \ Transceiver52M/arm/Makefile \ Transceiver52M/x86/Makefile \ ]) AC_OUTPUT osmo-trx-0~20170323git2af1440+dfsg/contrib/000077500000000000000000000000001306765341600200235ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/contrib/jenkins.sh000077500000000000000000000001631306765341600220230ustar00rootroot00000000000000#!/bin/sh set -ex autoreconf --install --force ./configure $MAKE $PARALLEL_MAKE $MAKE check \ || cat-testlogs.sh osmo-trx-0~20170323git2af1440+dfsg/debian/000077500000000000000000000000001306765341600176055ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/debian/changelog000066400000000000000000000005211306765341600214550ustar00rootroot00000000000000osmo-trx (0.1.9) trusty; urgency=medium * Ask Ivan, really -- Kirill Zakharenko Thu, 16 Jul 2015 12:13:46 +0000 osmo-trx (0.1.8) precise; urgency=low * Initial release (Closes: #nnnn) Sun, 9 Mar 2014 14:10:10 +0400 osmo-trx-0~20170323git2af1440+dfsg/debian/compat000066400000000000000000000000021306765341600210030ustar00rootroot000000000000009 osmo-trx-0~20170323git2af1440+dfsg/debian/control000066400000000000000000000031741306765341600212150ustar00rootroot00000000000000Source: osmo-trx Section: net Priority: optional Maintainer: Ivan Klyuchnikov Build-Depends: debhelper (>= 9), autotools-dev, autoconf-archive, libdbd-sqlite3, libsqlite3-dev, pkg-config, dh-autoreconf, libuhd-dev, libusb-1.0-0-dev, libboost-all-dev, hardening-wrapper, libfftw3-dev 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 Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, libdbd-sqlite3 Description: SDR transceiver that implements Layer 1 of a GSM BTS 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-dbg Architecture: any Section: debug Priority: extra Depends: osmo-trx (= ${binary:Version}), ${misc:Depends} Description: Debug symbols for the osmo-trx Make debugging possible osmo-trx-0~20170323git2af1440+dfsg/debian/copyright000066400000000000000000000141761306765341600215510ustar00rootroot00000000000000Format: 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 License: LGPL-2.1+ Files: config/ax_check_compile_flag.m4 Copyright: 2008 Guido U. Draheim 2011 Maarten Bosmans License: GPL-3+ Files: config/ax_gcc_x86_cpuid.m4 Copyright: 2008 Steven G. Johnson 2008 Matteo Frigo License: GPL-3+ Files: config/ax_ext.m4 Copyright: 2007 Christophe Tournayre 2013 Michael Petch License: license_for_ax_ext_m4 Files: config/ax_gcc_x86_avx_xgetbv.m4 Copyright: 2013 Michael Petch 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: CommonLibs/sqlite3util.cpp Copyright: 2010 Kestrel Signal Processing Inc. License: none No license described for file. Comment: In the previous version of the file in the git repository at upstream it is written: Written by David A. Burgess, Kestrel Signal Processing, Inc., 2010 The author disclaims copyright to this source code. In the git log, this is written: I do not claim any copyright over this change, as it's very basic. Looking forward to see it merged into mainline. See revision e766abbf82f02473038a83fd2f78befd08544cab at https://github.com/osmocom/osmo-trx Files: debian/* Copyright: 2015 Ruben Undheim 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 . 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 . 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 . 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. osmo-trx-0~20170323git2af1440+dfsg/debian/osmo-trx.install000066400000000000000000000000221306765341600227570ustar00rootroot00000000000000/usr/bin/osmo-trx osmo-trx-0~20170323git2af1440+dfsg/debian/rules000077500000000000000000000003561306765341600206710ustar00rootroot00000000000000#!/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_strip: dh_strip --dbg-package=osmo-trx-dbg osmo-trx-0~20170323git2af1440+dfsg/debian/source/000077500000000000000000000000001306765341600211055ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/debian/source/format000066400000000000000000000000141306765341600223130ustar00rootroot000000000000003.0 (native)osmo-trx-0~20170323git2af1440+dfsg/utils/000077500000000000000000000000001306765341600175235ustar00rootroot00000000000000osmo-trx-0~20170323git2af1440+dfsg/utils/clockdump.sh000077500000000000000000000000601306765341600220370ustar00rootroot00000000000000#!/bin/sh sudo tcpdump -i lo0 -A udp port 5700