pax_global_header00006660000000000000000000000064126002642620014512gustar00rootroot0000000000000052 comment=f1fb0fa3af174c605f60458388bba61ef4f40fa8 osmo-bts-0.4.0/000077500000000000000000000000001260026426200132565ustar00rootroot00000000000000osmo-bts-0.4.0/.gitignore000066400000000000000000000013401260026426200152440ustar00rootroot00000000000000*.o *.a Makefile.in Makefile .deps btsconfig.h btsconfig.h.in aclocal.m4 autom4te.cache config.log config.status config.guess config.sub configure compile depcomp install-sh missing stamp-h1 core core.* contrib/sysmobts-calib/sysmobts-calib src/osmo-bts-sysmo/l1fwd-proxy src/osmo-bts-sysmo/sysmobts src/osmo-bts-sysmo/sysmobts-remote src/osmo-bts-sysmo/sysmobts-mgr src/osmo-bts-trx/osmobts-trx tests/atconfig tests/package.m4 tests/agch/agch_test tests/paging/paging_test tests/cipher/cipher_test tests/sysmobts/sysmobts_test tests/misc/misc_test tests/bursts/bursts_test tests/handover/handover_test tests/testsuite tests/testsuite.log # Possible generated file doc/vty_reference.xml # Backups, vi, merges *.sw? *.orig *.sav osmo-bts-0.4.0/COPYING000066400000000000000000001033301260026426200143110ustar00rootroot00000000000000 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 . osmo-bts-0.4.0/Makefile.am000066400000000000000000000007711260026426200153170ustar00rootroot00000000000000AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 SUBDIRS = include src tests # package the contrib and doc EXTRA_DIST = \ contrib/dump_docs.py contrib/screenrc-l1fwd contrib/sysmobts.service \ contrib/l1fwd.init contrib/screenrc-sysmobts contrib/respawn.sh \ contrib/sysmobts.init contrib/sysmobts-calib/Makefile \ contrib/sysmobts-calib/sysmobts-calib.c \ contrib/sysmobts-calib/sysmobts-layer1.c \ contrib/sysmobts-calib/sysmobts-layer1.h \ doc/examples/osmo-bts.cfg \ doc/examples/sysmobts-mgr.cfg osmo-bts-0.4.0/README000066400000000000000000000044301260026426200141370ustar00rootroot00000000000000Repository forw the Osmocom BTS implementation. This code implementes the Layer 2 and higher of a more or less conventional GSM BTS (Base Transceiver Station) - however, using an Abis/IP interface, rather than the old-fashioned E1/T1. Specifically, this includes * BTS-Side implementation of TS 08.58 (RSL) and TS 12.21 (OML) * BTS-Side implementation of LAPDm (using libosmocore/libosmogsm) * A somewhat separated interface between those higher layer parts and the Layer1 interface. Right now, only one hardware and Layer1 are supported: The sysmocom sysmoBTS. There is some experimental and way incomplete code to use a couple of OsmocomBB phones and run them in the BTS. However, the required code for the Calypso DSP code have not been written yet. This would still require a lot of work. Some additional work is being done in using some parts of the OpenBTS L1FEC and glue it against omso-bts. This code is called osmo-trx and requires the jolly/trx branch of this repository. == Known Limitations == As of August 20, 2015, the following known limitations exist in this implementation: === Common Core === * No Extended BCCH support * System Information limited to 1,2,2bis,2ter,2quater,3,4,5,6,9,13 * No RATSCCH in AMR * No OML (TS 12.21) alarms yet (temperature, ...) * Only single-TRX BTS at this point * Will reject TS 12.21 STARTING TIME in SET BTS ATTR / SET CHAN ATTR * No support for frequency hopping * No reporting of interference levels as part of TS 08.58 RF RES IND * No error reporting in case PAGING COMMAND fails due to queue overflow * No use of TS 08.58 BS Power and MS Power parameters * No support of TS 08.58 MultiRate Control * No support of TS 08.58 Supported Codec Types * No support of Bter frame / ENHANCED MEASUREMENT REPORT === osmo-bts-sysmo === * No CSD / ECSD support (not planned) * GSM-R frequency band supported, but no NCH/ASCI/SoLSA * All timeslots on one TRX have to use same training sequence (TSC) * No multi-TRX support yet, though hardware+L1 support stacking * Makes no use of 12.21 Intave Parameters and Interference Level Boundaries * Doesn't yet include MAC address in Abis/IP Identity message * MphConfig.CNF can be returned to the wrong callback. E.g. with Tx Power and ciphering. The dispatch should take a look at the hLayer3. osmo-bts-0.4.0/configure.ac000066400000000000000000000060001260026426200155400ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script AC_INIT([osmo-bts], m4_esyscmd([./git-version-gen .tarball-version]), [openbsc-devel@lists.openbsc.org]) AM_INIT_AUTOMAKE([dist-bzip2]) AC_CONFIG_TESTDIR(tests) dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) dnl checks for programs AC_PROG_MAKE_SET AC_PROG_CC AC_PROG_INSTALL AC_PROG_RANLIB dnl checks for header files AC_HEADER_STDC dnl Checks for typedefs, structures and compiler characteristics dnl checks for libraries PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.9) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty) PKG_CHECK_MODULES(LIBOSMOTRAU, libosmotrau >= 0.0.7) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.3.3) PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl) PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis) PKG_CHECK_MODULES(LIBGPS, libgps) PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec) AC_MSG_CHECKING([whether to enable sysmocom-bts hardware support]) AC_ARG_ENABLE(sysmocom-bts, AC_HELP_STRING([--enable-sysmocom-bts], [enable code for sysmocom femto-bts [default=no]]), [enable_sysmocom_bts="yes"],[enable_sysmocom_bts="no"]) AC_MSG_RESULT([$enable_sysmocom_bts]) AM_CONDITIONAL(ENABLE_SYSMOBTS, test "x$enable_sysmocom_bts" = "xyes") AC_MSG_CHECKING([whether to enable trx hardware support]) AC_ARG_ENABLE(trx, AC_HELP_STRING([--enable-trx], [enable code for trx hardware [default=no]]), [enable_trx="yes"],[enable_trx="no"]) AC_MSG_RESULT([$enable_trx]) AM_CONDITIONAL(ENABLE_TRX, test "x$enable_trx" = "xyes") # We share gsm_data.h with OpenBSC and need to be pointed to the source # directory of OpenBSC for now. AC_ARG_WITH([openbsc], [AS_HELP_STRING([--with-openbsc=INCLUDE_DIR], [OpenBSC include directory for openbsc/gsm_data_shared.h])], [openbsc_incdir="$withval"], [openbsc_incdir="`cd $srcdir; pwd`/../openbsc/openbsc/include"]) AC_SUBST([OPENBSC_INCDIR], $openbsc_incdir) oldCPPFLAGS=$CPPFLAGS CPPFLAGS="$CPPFLAGS -I$OPENBSC_INCDIR -I$srcdir/include $LIBOSMOCORE_CFLAGS" AC_CHECK_HEADER([openbsc/gsm_data_shared.h],[], [AC_MSG_ERROR([openbsc/gsm_data_shared.h can not be found in $openbsc_incdir])], [#include ]) CPPFLAGS=$oldCPPFLAGS # Check for the sbts2050_header.h that was added after the 3.6 release oldCPPFLAGS=$CPPFLAGS CPPFLAGS="$CPPFLAGS -I$OPENBSC_INCDIR $LIBOSMOCORE_CFLAGS" AC_CHECK_HEADER([sysmocom/femtobts/sbts2050_header.h], [sysmo_uc_header="yes"],[]) CPPFLAGS=$oldCPPFLAGS if test "$sysmo_uc_header" = "yes" ; then AC_DEFINE(BUILD_SBTS2050, 1, [Define if we want to build SBTS2050]) fi AM_CONDITIONAL(BUILD_SBTS2050, test "x$sysmo_uc_header" = "xyes") AM_CONFIG_HEADER(btsconfig.h) AC_OUTPUT( src/Makefile src/common/Makefile src/osmo-bts-sysmo/Makefile src/osmo-bts-trx/Makefile include/Makefile include/osmo-bts/Makefile tests/Makefile tests/paging/Makefile tests/agch/Makefile tests/cipher/Makefile tests/sysmobts/Makefile tests/misc/Makefile tests/bursts/Makefile tests/handover/Makefile Makefile) osmo-bts-0.4.0/contrib/000077500000000000000000000000001260026426200147165ustar00rootroot00000000000000osmo-bts-0.4.0/contrib/dump_docs.py000077500000000000000000000015111260026426200172460ustar00rootroot00000000000000#!/usr/bin/env python """ Start the process and dump the documentation to the doc dir """ import socket, subprocess, time,os env = os.environ env['L1FWD_BTS_HOST'] = '127.0.0.1' bts_proc = subprocess.Popen(["./src/osmo-bts-sysmo/sysmobts-remote", "-c", "./doc/examples/osmo-bts.cfg"], env = env, stdin=None, stdout=None) time.sleep(1) try: sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sck.setblocking(1) sck.connect(("localhost", 4241)) sck.recv(4096) # Now send the command sck.send("show online-help\r") xml = "" while True: data = sck.recv(4096) xml = "%s%s" % (xml, data) if data.endswith('\r\nOsmoBTS> '): break # Now write everything until the end to the file out = open('doc/vty_reference.xml', 'w') out.write(xml[18:-11]) out.close() finally: # Clean-up bts_proc.kill() bts_proc.wait() osmo-bts-0.4.0/contrib/l1fwd.init000077500000000000000000000012551260026426200166260ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: l1fwd # Required-Start: # Required-Stop: $local_fs # Default-Start: 5 # Default-Stop: 0 6 # Short-Description: Start screen session with l1fwd software # Description: ### END INIT INFO . /etc/default/rcS case "$1" in start) /usr/bin/screen -d -m -c /etc/osmocom/screenrc-l1fwd ;; stop) echo "This script doesn't support stop" exit 1 ;; restart|reload|force-reload) exit 0 ;; show) ;; *) echo "Usage: sysmobts {start|stop|show|reload|restart}" >&2 exit 1 ;; esac osmo-bts-0.4.0/contrib/respawn-only.sh000077500000000000000000000003401260026426200177100ustar00rootroot00000000000000#!/bin/sh PID=$$ echo "-1000" > /proc/$PID/oom_score_adj trap "{ kill 0; kill -2 0; }" EXIT while [ -f $1 ]; do (echo "0" > /proc/self/oom_score_adj && exec nice -n -20 $*) & LAST_PID=$! wait $LAST_PID sleep 10s done osmo-bts-0.4.0/contrib/respawn.sh000077500000000000000000000006201260026426200167320ustar00rootroot00000000000000#!/bin/sh PID=$$ echo "-1000" > /proc/$PID/oom_score_adj trap "kill 0" EXIT while [ -e /etc/passwd ]; do cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 sleep 2s cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0 sleep 1s echo "0" > /sys/class/leds/activity_led/brightness (echo "0" > /proc/self/oom_score_adj && exec nice -n -20 $*) & LAST_PID=$! wait $LAST_PID sleep 10s done osmo-bts-0.4.0/contrib/screenrc-l1fwd000066400000000000000000000001171260026426200174570ustar00rootroot00000000000000chdir /tmp screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/l1fwd-proxy detach osmo-bts-0.4.0/contrib/screenrc-sysmobts000066400000000000000000000004671260026426200203350ustar00rootroot00000000000000chdir /tmp screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/sysmobts -c /etc/osmocom/osmo-bts.cfg -r 1 -M screen -t PCU 1 /etc/osmocom/respawn-only.sh /usr/bin/osmo-pcu -c /etc/osmocom/osmo-pcu.cfg -e screen -t MGR 2 /etc/osmocom/respawn-only.sh /usr/bin/sysmobts-mgr -n -c /etc/osmocom/sysmobts-mgr.cfg detach osmo-bts-0.4.0/contrib/sysmobts-calib/000077500000000000000000000000001260026426200176515ustar00rootroot00000000000000osmo-bts-0.4.0/contrib/sysmobts-calib/Makefile000066400000000000000000000004071260026426200213120ustar00rootroot00000000000000CFLAGS=`pkg-config --cflags libosmocore` -Wall -Werror LIBS=`pkg-config --libs libosmocore libosmogsm` all: sysmobts-calib sysmobts-calib: sysmobts-calib.o sysmobts-layer1.o $(CC) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ -lrt $(LIBS) clean: @rm -f sysmobts-calib *.o osmo-bts-0.4.0/contrib/sysmobts-calib/sysmobts-calib.c000066400000000000000000000310501260026426200227470ustar00rootroot00000000000000/* OCXO/TCXO based calibration utility */ /* * (C) 2012-2013 Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #define _GNU_SOURCE #include #include #include #include #include #include "sysmobts-layer1.h" enum actions { ACTION_SCAN, ACTION_CALIB, ACTION_BCCH, ACTION_BCCH_CCCH, }; static const char *modes[] = { [ACTION_SCAN] = "scan", [ACTION_CALIB] = "calibrate", [ACTION_BCCH] = "bcch", [ACTION_BCCH_CCCH] = "bcch_ccch", }; static const char *bands[] = { [GsmL1_FreqBand_850] = "850", [GsmL1_FreqBand_900] = "900", [GsmL1_FreqBand_1800] = "1800", [GsmL1_FreqBand_1900] = "1900", }; struct channel_pair { int min; int max; }; static const struct channel_pair arfcns[] = { [GsmL1_FreqBand_850] = { .min = 128, .max = 251 }, [GsmL1_FreqBand_900] = { .min = 1, .max = 124 }, [GsmL1_FreqBand_1800] = { .min = 512, .max = 885 }, [GsmL1_FreqBand_1900] = { .min = 512, .max = 810 }, }; static const char *clk_source[] = { [SuperFemto_ClkSrcId_Ocxo] = "ocxo", [SuperFemto_ClkSrcId_Tcxo] = "tcxo", [SuperFemto_ClkSrcId_External] = "external", [SuperFemto_ClkSrcId_GpsPps] = "gps", [SuperFemto_ClkSrcId_Trx] = "trx", [SuperFemto_ClkSrcId_Rx] = "rx", [SuperFemto_ClkSrcId_Edge] = "edge", [SuperFemto_ClkSrcId_NetList] = "netlisten", }; static const struct value_string sapi_names[GsmL1_Sapi_NUM+1] = { { GsmL1_Sapi_Fcch, "FCCH" }, { GsmL1_Sapi_Sch, "SCH" }, { GsmL1_Sapi_Sacch, "SACCH" }, { GsmL1_Sapi_Sdcch, "SDCCH" }, { GsmL1_Sapi_Bcch, "BCCH" }, { GsmL1_Sapi_Pch, "PCH" }, { GsmL1_Sapi_Agch, "AGCH" }, { GsmL1_Sapi_Cbch, "CBCH" }, { GsmL1_Sapi_Rach, "RACH" }, { GsmL1_Sapi_TchF, "TCH/F" }, { GsmL1_Sapi_FacchF, "FACCH/F" }, { GsmL1_Sapi_TchH, "TCH/H" }, { GsmL1_Sapi_FacchH, "FACCH/H" }, { GsmL1_Sapi_Nch, "NCH" }, { GsmL1_Sapi_Pdtch, "PDTCH" }, { GsmL1_Sapi_Pacch, "PACCH" }, { GsmL1_Sapi_Pbcch, "PBCCH" }, { GsmL1_Sapi_Pagch, "PAGCH" }, { GsmL1_Sapi_Ppch, "PPCH" }, { GsmL1_Sapi_Pnch, "PNCH" }, { GsmL1_Sapi_Ptcch, "PTCCH" }, { GsmL1_Sapi_Prach, "PRACH" }, { 0, NULL } }; static int action = ACTION_SCAN; static int band = GsmL1_FreqBand_900; static int calib = SuperFemto_ClkSrcId_Ocxo; static int source = SuperFemto_ClkSrcId_NetList; static int dsp_flags = 0x0; static int cal_arfcn = 0; static int initial_cor = 0; static int steps = -1; static void print_usage(void) { printf("Usage: sysmobts-calib ARGS\n"); } static void print_help(void) { printf(" -h --help this text\n"); printf(" -c --clock " "ocxo|tcxo|external|gps|trx|rx|edge\n"); printf(" -s --calibration-source " "ocxo|tcxo|external|gps|trx|rx|edge|netlisten\n"); printf(" -b --band 850|900|1800|1900\n"); printf(" -m --mode scan|calibrate|bcch|bcch_ccch\n"); printf(" -a --arfcn NR arfcn for calibration\n"); printf(" -d --dsp-flags NR dsp mask for debug log\n"); printf(" -t --threshold level\n"); printf(" -i --initial-clock-correction COR.\n"); printf(" -t --steps STEPS\n"); } static int find_value(const char **array, int size, char *value) { int i = 0; for (i = 0; i < size; ++i) { if (array[i] == NULL) continue; if (strcmp(value, array[i]) == 0) return i; } printf("Failed to find: '%s'\n", value); exit(-2); } static void handle_options(int argc, char **argv) { while (1) { int option_index = 0, c; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"calibration-source", 1, 0, 's'}, {"clock", 1, 0, 'c'}, {"mode", 1, 0, 'm'}, {"band", 1, 0, 'b'}, {"dsp-flags", 1, 0, 'd'}, {"arfcn", 1, 0, 'a'}, {"initial-clock-correction", 1, 0, 'i'}, {"steps", 1, 0, 't'}, {0, 0, 0, 0}, }; c = getopt_long(argc, argv, "hs:c:m:b:d:a:i:t:", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': print_usage(); print_help(); exit(0); case 's': source = find_value(clk_source, ARRAY_SIZE(clk_source), optarg); break; case 'c': calib = find_value(clk_source, ARRAY_SIZE(clk_source), optarg); break; case 'm': action = find_value(modes, ARRAY_SIZE(modes), optarg); break; case 'b': band = find_value(bands, ARRAY_SIZE(bands), optarg); break; case 'd': dsp_flags = strtol(optarg, NULL, 16); break; case 'a': cal_arfcn = atoi(optarg); break; case 'i': initial_cor = atoi(optarg); break; case 't': steps = atoi(optarg); break; default: printf("Unhandled option, terminating.\n"); exit(-1); } } if (source == calib) { printf("Clock source and reference clock may not be the same.\n"); exit(-3); } if (calib == SuperFemto_ClkSrcId_NetList) { printf("Clock may not be network listen.\n"); exit(-4); } if (action == ACTION_CALIB && source == SuperFemto_ClkSrcId_NetList) { if (cal_arfcn == 0) { printf("Please specify the reference ARFCN.\n"); exit(-5); } if (cal_arfcn < arfcns[band].min || cal_arfcn > arfcns[band].max) { printf("ARFCN(%d) is not in the given band.\n", cal_arfcn); exit(-6); } } } #define CHECK_RC(rc) \ if (rc != 0) \ return EXIT_FAILURE; #define CHECK_RC_MSG(rc, msg) \ if (rc != 0) { \ printf("%s: %d\n", msg, rc); \ return EXIT_FAILURE; \ } #define CHECK_COND_MSG(cond, rc, msg) \ if (cond) { \ printf("%s: %d\n", msg, rc); \ return EXIT_FAILURE; \ } struct scan_result { uint16_t arfcn; float rssi; }; static int scan_cmp(const void *arg1, const void *arg2) { struct scan_result *elem1 = (struct scan_result *) arg1; struct scan_result *elem2 = (struct scan_result * )arg2; float diff = elem1->rssi - elem2->rssi; if (diff > 0.0) return 1; else if (diff < 0.0) return -1; else return 0; } static int scan_band() { int arfcn, rc, i; /* Scan results.. at most 400 items */ struct scan_result results[400]; memset(&results, 0, sizeof(results)); int num_scan_results = 0; printf("Going to scan bands.\n"); for (arfcn = arfcns[band].min; arfcn <= arfcns[band].max; ++arfcn) { float mean_rssi; printf("."); fflush(stdout); rc = power_scan(band, arfcn, 10, &mean_rssi); CHECK_RC_MSG(rc, "Power Measurement failed"); results[num_scan_results].arfcn = arfcn; results[num_scan_results].rssi = mean_rssi; num_scan_results++; } qsort(results, num_scan_results, sizeof(struct scan_result), scan_cmp); printf("\nSorted scan results (weakest first):\n"); for (i = 0; i < num_scan_results; ++i) printf("ARFCN %3d: %.4f\n", results[i].arfcn, results[i].rssi); return 0; } static int calib_get_clock_error(void) { int rc, clkErr, clkErrRes; printf("Going to determine the clock offset.\n"); rc = rf_clock_info(&clkErr, &clkErrRes); CHECK_RC_MSG(rc, "Clock info failed.\n"); if (clkErr == 0 && clkErrRes == 0) { printf("Failed to get the clock info. Are both clocks present?\n"); return -1; } /* * Empiric gps error determination. With revE and firmware v3.3 * the clock error for TCXO to GPS appears to have a different * sign. The device in question doesn't have a networklisten mode * so it is impossible to verify that this only applies to GPS. */ if (source == SuperFemto_ClkSrcId_GpsPps) clkErr *= -1; /* this is an absolute clock error */ printf("The calibration value is: %d\n", clkErr); return 0; } static int calib_clock_after_sync(void) { int rc, clkErr, clkErrRes, iteration, cor; iteration = 0; cor = initial_cor; printf("Trying to calibrate now and reducing clock error.\n"); for (iteration = 0; iteration < steps || steps <= 0; ++iteration) { if (steps > 0) printf("Iteration %d/%d with correction: %d\n", iteration, steps, cor); else printf("Iteration %d with correction: %d\n", iteration, cor); rc = rf_clock_info(&clkErr, &clkErrRes); CHECK_RC_MSG(rc, "Clock info failed.\n"); /* * TODO: use the clock error resolution here, implement it as a * a PID controller.. */ /* Picocell class requires 0.1ppm.. but that is 'too easy' */ if (fabs(clkErr / 1000.0f) <= 0.05f) { printf("The calibration value is: %d\n", cor); return 1; } cor -= clkErr / 2; rc = set_clock_cor(cor, calib, source); CHECK_RC_MSG(rc, "Clock correction failed.\n"); } return -1; } static int find_initial_clock(HANDLE layer1, int *clock) { int i; printf("Trying to find an initial clock value.\n"); for (i = 0; i < 1000; ++i) { int rc; int cor = i * 150; rc = wait_for_sync(layer1, cor, calib, source); if (rc == 1) { printf("Found initial clock offset: %d\n", cor); *clock = cor; break; } else { CHECK_RC_MSG(rc, "Failed to set new clock value.\n"); } cor = i * -150; rc = wait_for_sync(layer1, cor, calib, source); if (rc == 1) { printf("Found initial clock offset: %d\n", cor); *clock = cor; break; } else { CHECK_RC_MSG(rc, "Failed to set new clock value.\n"); } } return 0; } static int calib_clock_netlisten(void) { int rc, cor = initial_cor; float mean_rssi; HANDLE layer1; rc = power_scan(band, cal_arfcn, 10, &mean_rssi); CHECK_RC_MSG(rc, "ARFCN measurement scan failed"); if (mean_rssi < -118.0f) printf("ARFCN has weak signal for calibration: %f\n", mean_rssi); /* initial lock */ rc = follow_sch(band, cal_arfcn, calib, source, &layer1); if (rc == -23) rc = find_initial_clock(layer1, &cor); CHECK_RC_MSG(rc, "Following SCH failed"); /* now try to calibrate it */ rc = set_clock_cor(cor, calib, source); CHECK_RC_MSG(rc, "Clock setup failed."); calib_clock_after_sync(); rc = mph_close(layer1); CHECK_RC_MSG(rc, "MPH-Close"); return EXIT_SUCCESS; } static int calib_clock(void) { int rc; /* now try to calibrate it */ rc = set_clock_cor(initial_cor, calib, source); CHECK_RC_MSG(rc, "Clock setup failed."); calib_get_clock_error(); return EXIT_SUCCESS; } static int bcch_follow(void) { int rc, cor = initial_cor; float mean_rssi; HANDLE layer1; rc = power_scan(band, cal_arfcn, 10, &mean_rssi); CHECK_RC_MSG(rc, "ARFCN measurement scan failed"); if (mean_rssi < -118.0f) printf("ARFCN has weak signal for calibration: %f\n", mean_rssi); /* initial lock */ rc = follow_sch(band, cal_arfcn, calib, source, &layer1); if (rc == -23) rc = find_initial_clock(layer1, &cor); CHECK_RC_MSG(rc, "Following SCH failed"); /* identify the BSIC and set it as TSC */ rc = find_bsic(); CHECK_COND_MSG(rc < 0, rc, "Identifying the BSIC failed"); rc = set_tsc_from_bsic(layer1, rc); CHECK_RC_MSG(rc, "Setting the TSC failed"); /* follow the bcch */ rc = follow_bcch(layer1); CHECK_RC_MSG(rc, "Follow BCCH"); /* follow the pch */ if (action == ACTION_BCCH_CCCH) { rc = follow_pch(layer1); CHECK_RC_MSG(rc, "Follow BCCH/CCCH"); } /* now wait for the PhDataInd */ for (;;) { uint32_t fn; uint8_t block; uint8_t data[23]; size_t size; struct gsm_time gsmtime; GsmL1_Sapi_t sapi; rc = wait_for_data(data, &size, &fn, &block, &sapi); if (rc == 1) continue; CHECK_RC_MSG(rc, "No Data Indication"); gsm_fn2gsmtime(&gsmtime, fn); printf("%02u/%02u/%02u %6s %s\n", gsmtime.t1, gsmtime.t2, gsmtime.t3, get_value_string(sapi_names, sapi), osmo_hexdump(data, size)); } rc = mph_close(layer1); CHECK_RC_MSG(rc, "MPH-Close"); return EXIT_SUCCESS; } int main(int argc, char **argv) { int rc; handle_options(argc, argv); printf("Initializing the Layer1\n"); rc = initialize_layer1(dsp_flags); CHECK_RC(rc); printf("Fetching system info.\n"); rc = print_system_info(); CHECK_RC(rc); printf("Opening RF frontend with clock(%d) and correction(%d)\n", calib, initial_cor); rc = activate_rf_frontend(calib, initial_cor); CHECK_RC(rc); if (action == ACTION_SCAN) return scan_band(); else if (action == ACTION_BCCH || action == ACTION_BCCH_CCCH) return bcch_follow(); else { if (source == SuperFemto_ClkSrcId_NetList) return calib_clock_netlisten(); return calib_clock(); } return EXIT_SUCCESS; } osmo-bts-0.4.0/contrib/sysmobts-calib/sysmobts-layer1.c000066400000000000000000000433351260026426200231030ustar00rootroot00000000000000/* Layer1 handling for the DSP/FPGA */ /* * (C) 2012-2013 Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include "sysmobts-layer1.h" #define ARRAY_SIZE(ar) (sizeof(ar)/sizeof((ar)[0])) #define BTS_DSP2ARM "/dev/msgq/superfemto_dsp2arm" #define BTS_ARM2DSP "/dev/msgq/superfemto_arm2dsp" #define L1_SIG_ARM2DSP "/dev/msgq/gsml1_sig_arm2dsp" #define L1_SIG_DSP2ARM "/dev/msgq/gsml1_sig_dsp2arm" int set_clock_cor(int clock_cor, int calib, int source); static int wait_read_ignore(int seconds); static int sys_dsp2arm = -1, sys_arm2dsp = -1, sig_dsp2arm = -1, sig_arm2dsp = -1; static int sync_indicated = 0; static int time_indicated = 0; static int open_devices() { sys_dsp2arm = open(BTS_DSP2ARM, O_RDONLY); if (sys_dsp2arm == -1) { perror("Failed to open dsp2arm system queue"); return -1; } sys_arm2dsp = open(BTS_ARM2DSP, O_WRONLY); if (sys_arm2dsp == -1) { perror("Failed to open arm2dsp system queue"); return -2; } sig_dsp2arm = open(L1_SIG_DSP2ARM, O_RDONLY); if (sig_dsp2arm == -1) { perror("Failed to open dsp2arm sig queue"); return -3; } sig_arm2dsp = open(L1_SIG_ARM2DSP, O_WRONLY); if (sig_arm2dsp == -1) { perror("Failed to open arm2dsp sig queue"); return -4; } return 0; } /** * Send a primitive to the system queue */ static int send_primitive(int primitive, SuperFemto_Prim_t *prim) { prim->id = primitive; return write(sys_arm2dsp, prim, sizeof(*prim)) != sizeof(*prim); } /** * Wait for a confirmation */ static int wait_primitive(int wait_for, SuperFemto_Prim_t *prim) { memset(prim, 0, sizeof(*prim)); int rc = read(sys_dsp2arm, prim, sizeof(*prim)); if (rc != sizeof(*prim)) { printf("Short read in %s: %d\n", __func__, rc); return -1; } if (prim->id != wait_for) { printf("Got primitive %d but waited for %d\n", prim->id, wait_for); return -2; } return 0; } /* The Cnf for the Req, assume it is a +1 */ static int answer_for(int primitive) { return primitive + 1; } static int send_recv_primitive(int p, SuperFemto_Prim_t *prim) { int rc; rc = send_primitive(p, prim); if (rc != 0) return -1; rc = wait_primitive(answer_for(p), prim); if (rc != 0) return -2; return 0; } static int answer_for_sig(int prim) { static const GsmL1_PrimId_t cnf[] = { [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf, [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf, [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf, [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf, [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf, [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf, }; if (prim < 0 || prim >= ARRAY_SIZE(cnf)) { printf("Unknown primitive: %d\n", prim); exit(-3); } return cnf[prim]; } static int is_indication(int prim) { return prim == GsmL1_PrimId_MphTimeInd || prim == GsmL1_PrimId_MphSyncInd || prim == GsmL1_PrimId_PhConnectInd || prim == GsmL1_PrimId_PhReadyToSendInd || prim == GsmL1_PrimId_PhDataInd || prim == GsmL1_PrimId_PhRaInd; } static int send_recv_sig_prim(int p, GsmL1_Prim_t *prim) { int rc; prim->id = p; rc = write(sig_arm2dsp, prim, sizeof(*prim)); if (rc != sizeof(*prim)) { printf("Failed to write: %d\n", rc); return -1; } do { rc = read(sig_dsp2arm, prim, sizeof(*prim)); if (rc != sizeof(*prim)) { printf("Failed to read: %d\n", rc); return -2; } } while (is_indication(prim->id)); if (prim->id != answer_for_sig(p)) { printf("Wrong L1 result got %d wanted %d for prim: %d\n", prim->id, answer_for_sig(p), p); return -3; } return 0; } static int wait_for_indication(int p, GsmL1_Prim_t *prim) { int rc; memset(prim, 0, sizeof(*prim)); struct timespec start_time, now_time; clock_gettime(CLOCK_MONOTONIC, &start_time); /* * TODO: select.... with timeout. The below will work 99% as we will * get time indications very soonish after the connect */ for (;;) { clock_gettime(CLOCK_MONOTONIC, &now_time); if (now_time.tv_sec - start_time.tv_sec > 10) { printf("Timeout waiting for indication.\n"); return -4; } rc = read(sig_dsp2arm, prim, sizeof(*prim)); if (rc != sizeof(*prim)) { printf("Failed to read.\n"); return -1; } if (!is_indication(prim->id)) { printf("No indication: %d\n", prim->id); return -2; } if (p != prim->id && prim->id == GsmL1_PrimId_MphSyncInd) { printf("Got sync.\n"); sync_indicated = 1; continue; } if (p != prim->id && prim->id == GsmL1_PrimId_MphTimeInd) { time_indicated = 1; continue; } if (p != prim->id) { printf("Wrong indication got %d wanted %d\n", prim->id, p); return -3; } break; } return 0; } static int set_trace_flags(uint32_t dsp) { SuperFemto_Prim_t prim; memset(&prim, 0, sizeof(prim)); prim.u.setTraceFlagsReq.u32Tf = dsp; return send_primitive(SuperFemto_PrimId_SetTraceFlagsReq, &prim); } static int reset_and_wait() { int rc; SuperFemto_Prim_t prim; memset(&prim, 0, sizeof(prim)); rc = send_recv_primitive(SuperFemto_PrimId_Layer1ResetReq, &prim); if (rc != 0) return -1; if (prim.u.layer1ResetCnf.status != GsmL1_Status_Success) return -2; return 0; } /** * Open the message queues and (re-)initialize the DSP and FPGA */ int initialize_layer1(uint32_t dsp_flags) { if (open_devices() != 0) { printf("Failed to open devices.\n"); return -1; } if (set_trace_flags(dsp_flags) != 0) { printf("Failed to set dsp flags.\n"); return -2; } if (reset_and_wait() != 0) { printf("Failed to reset the firmware.\n"); return -3; } return 0; } /** * Print systems infos */ int print_system_info() { int rc; SuperFemto_Prim_t prim; memset(&prim, 0, sizeof(prim)); rc = send_recv_primitive(SuperFemto_PrimId_SystemInfoReq, &prim); if (rc != 0) { printf("Failed to send SystemInfoRequest.\n"); return -1; } if (prim.u.systemInfoCnf.status != GsmL1_Status_Success) { printf("Failed to request SystemInfoRequest.\n"); return -2; } #define INFO_DSP(x) x.u.systemInfoCnf.dspVersion #define INFO_FPGA(x) x.u.systemInfoCnf.fpgaVersion #ifdef FEMTOBTS_NO_BOARD_VERSION #define BOARD_REV(x) -1 #define BOARD_OPT(x) -1 #else #define BOARD_REV(x) x.u.systemInfoCnf.boardVersion.rev #define BOARD_OPT(x) x.u.systemInfoCnf.boardVersion.option #endif printf("DSP v%d.%d.%d FPGA v%d.%d.%d Rev: %d Option: %d\n", INFO_DSP(prim).major, INFO_DSP(prim).minor, INFO_DSP(prim).build, INFO_FPGA(prim).major, INFO_FPGA(prim).minor, INFO_FPGA(prim).build, BOARD_REV(prim), BOARD_OPT(prim)); #undef INFO_DSP #undef INFO_FPGA #undef BOARD_REV #undef BOARD_OPT return 0; } int activate_rf_frontend(int clock_source, int initial_cor) { int rc; SuperFemto_Prim_t prim; memset(&prim, 0, sizeof(prim)); prim.u.activateRfReq.timing.u8TimSrc = 1; prim.u.activateRfReq.msgq.u8UseTchMsgq = 0; prim.u.activateRfReq.msgq.u8UsePdtchMsgq = 0; prim.u.activateRfReq.rfTrx.iClkCor = initial_cor; prim.u.activateRfReq.rfTrx.clkSrc = clock_source; #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) prim.u.activateRfReq.rfRx.iClkCor = initial_cor; prim.u.activateRfReq.rfRx.clkSrc = clock_source; #endif rc = send_recv_primitive(SuperFemto_PrimId_ActivateRfReq, &prim); return rc; } static int mph_init(int band, int arfcn, HANDLE *layer1) { int rc; GsmL1_Prim_t prim; memset(&prim, 0, sizeof(prim)); prim.u.mphInitReq.deviceParam.devType = GsmL1_DevType_Rxd; prim.u.mphInitReq.deviceParam.freqBand = band; prim.u.mphInitReq.deviceParam.u16Arfcn = arfcn; prim.u.mphInitReq.deviceParam.u16BcchArfcn = arfcn; prim.u.mphInitReq.deviceParam.fRxPowerLevel = -75.f; prim.u.mphInitReq.deviceParam.u8AutoTA = 1; rc = send_recv_sig_prim(GsmL1_PrimId_MphInitReq, &prim); if (rc != 0) { printf("Failed to initialize the physical channel.\n"); return -1; } if (prim.u.mphInitCnf.status != GsmL1_Status_Success) { printf("MPH Init failed.\n"); return -2; } #if 0 if (prim.u.mphInitCnf.freqBand != band) { printf("Layer1 ignored the band: %d\n", prim.u.mphInitCnf.freqBand); return -3; } #endif *layer1 = prim.u.mphInitCnf.hLayer1; return 0; } int mph_close(HANDLE layer1) { int rc; GsmL1_Prim_t prim; memset(&prim, 0, sizeof(prim)); prim.u.mphCloseReq.hLayer1 = layer1; rc = send_recv_sig_prim(GsmL1_PrimId_MphCloseReq, &prim); if (rc != 0) { printf("Failed to close the MPH\n"); return -6; } if (prim.u.mphCloseCnf.status != GsmL1_Status_Success) { printf("MPH Close failed.\n"); return -7; } return 0; } int follow_sch(int band, int arfcn, int clock, int ref, HANDLE *layer1) { int rc; GsmL1_Prim_t prim; time_indicated = 0; sync_indicated = 0; rc = mph_init(band, arfcn, layer1); if (rc != 0) return rc; /* 1.) Connect */ memset(&prim, 0, sizeof(prim)); prim.u.mphConnectReq.hLayer1 = *layer1; prim.u.mphConnectReq.u8Tn = 0; prim.u.mphConnectReq.logChComb = GsmL1_LogChComb_IV; rc = send_recv_sig_prim(GsmL1_PrimId_MphConnectReq, &prim); if (rc != 0) { printf("Failed to connect.\n"); return -1; } if (prim.u.mphConnectCnf.status != GsmL1_Status_Success) { printf("Connect failed.\n"); return -2; } if (prim.u.mphConnectCnf.u8Tn != 0) { printf("Wrong timeslot.\n"); return -3; } /* 2.) Activate */ memset(&prim, 0, sizeof(prim)); prim.u.mphActivateReq.hLayer1 = *layer1; prim.u.mphActivateReq.u8Tn = 0; prim.u.mphActivateReq.sapi = GsmL1_Sapi_Sch; prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink; rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim); if (rc != 0) { printf("Activation failed.\n"); return -4; } if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) { printf("Activation not successful.\n"); return -5; } /* 3.) Wait for indication... TODO: check... */ printf("Waiting for connect indication.\n"); rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim); if (rc != 0) { printf("Didn't get a connect indication.\n"); return rc; } /* 4.) Indication Syndication TODO: check... */ if (!sync_indicated) { printf("Waiting for sync indication.\n"); rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim); if (rc < 0) { printf("Didn't get a sync indication.\n"); return -23; } else if (rc == 0) { if (!prim.u.mphSyncInd.u8Synced) { printf("Failed to get sync.\n"); return -23; } else { printf("Synced.\n"); } } } else { printf("Already synced.\n"); } return 0; } static int follow_sapi(HANDLE layer1, const GsmL1_Sapi_t sapi) { int rc; GsmL1_Prim_t prim; /* 1.) Activate BCCH or such... */ memset(&prim, 0, sizeof(prim)); prim.u.mphActivateReq.hLayer1 = layer1; prim.u.mphActivateReq.u8Tn = 0; prim.u.mphActivateReq.sapi = sapi; prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink; rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim); if (rc != 0) { printf("Activation failed.\n"); return -4; } if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) { printf("Activation not successful.\n"); return -5; } /* 2.) Wait for indication... */ printf("Waiting for connect indication.\n"); rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim); if (rc != 0) { printf("Didn't get a connect indication.\n"); return rc; } if (prim.u.phConnectInd.sapi != sapi) { printf("Got a connect indication for the wrong type: %d\n", prim.u.phConnectInd.sapi); return -6; } /* 3.) Wait for PhDataInd... */ printf("Waiting for data.\n"); rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); if (rc != 0) { printf("Didn't get data.\n"); return rc; } return 0; } int follow_bcch(HANDLE layer1) { return follow_sapi(layer1, GsmL1_Sapi_Bcch); } int follow_pch(HANDLE layer1) { return follow_sapi(layer1, GsmL1_Sapi_Pch); } int find_bsic(void) { int rc, i; GsmL1_Prim_t prim; printf("Waiting for SCH data.\n"); for (i = 0; i < 10; ++i) { uint8_t bsic; rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); if (rc < 0) { printf("Didn't get SCH data.\n"); return rc; } if (prim.u.phDataInd.sapi != GsmL1_Sapi_Sch) continue; bsic = (prim.u.phDataInd.msgUnitParam.u8Buffer[0] >> 2) & 0xFF; return bsic; } printf("Giving up finding the SCH\n"); return -1; } int set_tsc_from_bsic(HANDLE layer1, int bsic) { int rc; int tsc = bsic & 0x7; GsmL1_Prim_t prim; memset(&prim, 0, sizeof(prim)); prim.u.mphConfigReq.hLayer3 = 0x23; prim.u.mphConfigReq.hLayer1 = layer1; prim.u.mphConfigReq.cfgParamId = GsmL1_ConfigParamId_SetNbTsc; prim.u.mphConfigReq.cfgParams.setNbTsc.u8NbTsc = tsc; rc = send_recv_sig_prim(GsmL1_PrimId_MphConfigReq, &prim); if (rc != 0) { printf("Failed to send configure.\n"); } if (prim.u.mphConfigCnf.status != GsmL1_Status_Success) { printf("Failed to set the config cnf.\n"); return -1; } return 0; } int set_clock_cor(int clock_cor, int calib, int source) { int rc; SuperFemto_Prim_t prim; memset(&prim, 0, sizeof(prim)); prim.u.rfClockSetupReq.rfTrx.iClkCor = clock_cor; prim.u.rfClockSetupReq.rfTrx.clkSrc = calib; #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) prim.u.rfClockSetupReq.rfRx.iClkCor = clock_cor; prim.u.rfClockSetupReq.rfRx.clkSrc = calib; #endif prim.u.rfClockSetupReq.rfTrxClkCal.clkSrc = source; rc = send_recv_primitive(SuperFemto_PrimId_RfClockSetupReq, &prim); if (rc != 0) { printf("Failed to set the clock setup.\n"); return -1; } if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) { printf("Clock setup was not successfull.\n"); return -2; } return 0; } int rf_clock_info(int *clkErr, int *clkErrRes) { SuperFemto_Prim_t prim; memset(&prim, 0, sizeof(prim)); int rc; /* reset the counter */ prim.u.rfClockInfoReq.u8RstClkCal = 1; rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim); if (rc != 0) { printf("Failed to reset the clock info.\n"); return -1; } /* wait for a value */ wait_read_ignore(15); /* ask for the current counter/error */ memset(&prim, 0, sizeof(prim)); prim.u.rfClockInfoReq.u8RstClkCal = 0; rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim); if (rc != 0) { printf("Failed to get the clock info.\n"); return -2; } printf("Error: %d Res: %d\n", prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr, prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes); *clkErr = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr; *clkErrRes = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes; return 0; } int power_scan(int band, int arfcn, int duration, float *mean_rssi) { int rc; HANDLE layer1; GsmL1_Prim_t prim; /* init */ rc = mph_init(band, arfcn, &layer1); if (rc != 0) return rc; /* mph measure request */ memset(&prim, 0, sizeof(prim)); prim.u.mphMeasureReq.hLayer1 = layer1; prim.u.mphMeasureReq.u32Duration = duration; rc = send_recv_sig_prim(GsmL1_PrimId_MphMeasureReq, &prim); if (rc != 0) { printf("Failed to send measurement request.\n"); return -4; } if (prim.u.mphMeasureCnf.status != GsmL1_Status_Success) { printf("MphMeasureReq was not confirmed.\n"); return -5; } *mean_rssi = prim.u.mphMeasureCnf.fMeanRssi; /* close */ rc = mph_close(layer1); return rc; } /** * Wait for indication... */ int wait_for_sync(HANDLE layer1, int cor, int calib, int source) { GsmL1_Prim_t prim; int rc; rc = set_clock_cor(cor, calib, source); if (rc != 0) { printf("Failed to set the clock correction.\n"); return -1; } sync_indicated = 0; rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim); if (rc < 0 && rc != -4) { return rc; } else if (rc == 0) { if (!prim.u.mphSyncInd.u8Synced) { printf("Failed to get sync.\n"); return 0; } printf("Synced.\n"); return 1; } return 0; } int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sap) { GsmL1_Prim_t prim; int rc; rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); if (rc < 0) return rc; if (prim.u.phDataInd.sapi == GsmL1_Sapi_Sch) return 1; *size = prim.u.phDataInd.msgUnitParam.u8Size; *fn = prim.u.phDataInd.u32Fn; *block = prim.u.phDataInd.u8BlockNbr; *sap = prim.u.phDataInd.sapi; memcpy(data, prim.u.phDataInd.msgUnitParam.u8Buffer, *size); return 0; } /** * Make sure the pipe is not running full. * */ static int wait_read_ignore(int seconds) { int max, rc; fd_set fds; struct timeval timeout; max = sys_dsp2arm > sig_dsp2arm ? sys_dsp2arm : sig_dsp2arm; timeout.tv_sec = seconds; timeout.tv_usec = 0; while (1) { FD_ZERO(&fds); FD_SET(sys_dsp2arm, &fds); FD_SET(sig_dsp2arm, &fds); rc = select(max + 1, &fds, NULL, NULL, &timeout); if (rc == -1) { printf("Failed to select.\n"); return -1; } else if (rc) { if (FD_ISSET(sys_dsp2arm, &fds)) { SuperFemto_Prim_t prim; rc = read(sys_dsp2arm, &prim, sizeof(prim)); if (rc != sizeof(prim)) { perror("Failed to read system primitive"); return -2; } } if (FD_ISSET(sig_dsp2arm, &fds)) { GsmL1_Prim_t prim; rc = read(sig_dsp2arm, &prim, sizeof(prim)); if (rc != sizeof(prim)) { perror("Failed to read signal primitiven"); return -3; } } } else if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0) { break; } #ifndef __linux__ #error "Non portable code" #endif } return 0; } osmo-bts-0.4.0/contrib/sysmobts-calib/sysmobts-layer1.h000066400000000000000000000043521260026426200231040ustar00rootroot00000000000000#ifndef SYSMOBTS_LAYER_H #define SYSMOBTS_LAYER_H #include #ifdef FEMTOBTS_API_VERSION #define SuperFemto_PrimId_t FemtoBts_PrimId_t #define SuperFemto_Prim_t FemtoBts_Prim_t #define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq #define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf #define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t #define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd #define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq #define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf #define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq #define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf #define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq #define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq #define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf #define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq #define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf #define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq #define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf #define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM #define HW_SYSMOBTS_V1 1 #define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z) #endif extern int initialize_layer1(uint32_t dsp_flags); extern int print_system_info(); extern int activate_rf_frontend(int clock_source, int clock_cor); extern int power_scan(int band, int arfcn, int duration, float *mean_rssi); extern int follow_sch(int band, int arfcn, int calib, int reference, HANDLE *layer1); extern int follow_bch(HANDLE layer1); extern int find_bsic(void); extern int set_tsc_from_bsic(HANDLE layer1, int bsic); extern int set_clock_cor(int clock_corr, int calib, int source); extern int rf_clock_info(int *clkErr, int *clkErrRes); extern int mph_close(HANDLE layer1); extern int wait_for_sync(HANDLE layer1, int cor, int calib, int source); extern int follow_bcch(HANDLE layer1); extern int follow_pch(HANDLE layer1); extern int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sapi); #endif osmo-bts-0.4.0/contrib/sysmobts-mgr.service000066400000000000000000000003141260026426200207440ustar00rootroot00000000000000[Unit] Description=sysmocom sysmoBTS manager [Service] Type=simple ExecStart=/usr/bin/sysmobts-mgr -ns -c /etc/osmocom/sysmobts-mgr.cfg Restart=always RestartSec=2 [Install] WantedBy=multi-user.target osmo-bts-0.4.0/contrib/sysmobts.init000077500000000000000000000012551260026426200174740ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: sysmobts # Required-Start: # Required-Stop: $local_fs # Default-Start: 5 # Default-Stop: 0 6 # Short-Description: Start screen session with sysmobts software # Description: ### END INIT INFO case "$1" in start) /usr/bin/screen -d -m -c /etc/osmocom/screenrc-sysmobts -S sysmobts ;; stop) /usr/bin/screen -d -r sysmobts -X quit exit 1 ;; restart|reload|force-reload) exit 0 ;; show) ;; *) echo "Usage: sysmobts {start|stop|show|reload|restart}" >&2 exit 1 ;; esac osmo-bts-0.4.0/contrib/sysmobts.service000066400000000000000000000011451260026426200201640ustar00rootroot00000000000000[Unit] Description=sysmocom sysmoBTS [Service] Type=simple ExecStartPre=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' ExecStart=/usr/bin/sysmobts -s -c /etc/osmocom/osmo-bts.cfg -M ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' ExecStopPost=/bin/sh -c 'cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 ; sleep 3s; cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0; sleep 1s' Restart=always RestartSec=2 RestartPreventExitStatus=1 # The msg queues must be read fast enough CPUSchedulingPolicy=rr CPUSchedulingPriority=1 [Install] WantedBy=multi-user.target osmo-bts-0.4.0/doc/000077500000000000000000000000001260026426200140235ustar00rootroot00000000000000osmo-bts-0.4.0/doc/control_interface.txt000066400000000000000000000034071260026426200202700ustar00rootroot00000000000000The osmo-bts control interface is currently supporting the following operations: h2. generic h3. trx.0.thermal-attenuation The idea of this paramter is to attenuate the system output power as part of thermal management. In some cases the PA might be passing a critical level, so an external control process can use this attribute to reduce the system output power. Please note that all values in the context of transmit power calculation are integers in milli-dB (1/10000 bel), so the below example is setting the attenuation at 3 dB:
bsc_control.py -d localhost -p 4238 -s trx.0.thermal-attenuation 3000
Got message: SET_REPLY 1 trx.0.thermal-attenuation 3000
bsc_control.py -d localhost -p 4238 -g trx.0.thermal-attenuation
Got message: GET_REPLY 1 trx.0.thermal-attenuation 3000
h2. sysmobts specific h3. trx.0.clock-info obtain information on the current clock status:
bsc_control.py -d localhost -p 4238 -g trx.0.clock-info
Got message: GET_REPLY 1 trx.0.clock-info -100,ocxo,0,0,gps
which is to be interpreted as: * current clock correction value is -100 ppb * current clock source is OCXO * deviation between clock source and calibration source is 0 ppb * resolution of clock error measurement is 0 ppt (0 means no result yet) * current calibration source is GPS When this attribute is set, any value passed on is discarded, but the clock calibration process is re-started. h3. trx.0.clock-correction This attribute can get and set the current clock correction value:
bsc_control.py -d localhost -p 4238 -g trx.0.clock-correction
Got message: GET_REPLY 1 trx.0.clock-correction -100
bsc_control.py -d localhost -p 4238 -s trx.0.clock-correction -- -99
Got message: SET_REPLY 1 trx.0.clock-correction success
osmo-bts-0.4.0/doc/examples/000077500000000000000000000000001260026426200156415ustar00rootroot00000000000000osmo-bts-0.4.0/doc/examples/osmo-bts.cfg000066400000000000000000000007411260026426200200670ustar00rootroot00000000000000! ! OsmoBTS () configuration saved from vty !! ! log stderr logging color 0 logging timestamp 0 logging level all everything logging level rsl info logging level oml info logging level rll notice logging level rr notice logging level meas notice logging level pag info logging level l1c info logging level l1p info logging level dsp debug logging level abis notice ! line vty no login ! bts 0 band 1800 ipa unit-id 1234 0 oml remote-ip 192.168.100.11 osmo-bts-0.4.0/doc/examples/sysmobts-mgr.cfg000066400000000000000000000007401260026426200207710ustar00rootroot00000000000000! ! SysmoMgr (0.3.0.141-33e5) configuration saved from vty !! ! log stderr logging filter all 1 logging color 1 logging timestamp 0 logging level all everything logging level temp info logging level fw info logging level find info logging level lglobal notice logging level llapd notice logging level linp notice logging level lmux notice logging level lmi notice logging level lmib notice logging level lsms notice ! line vty no login ! sysmobts-mgr osmo-bts-0.4.0/git-version-gen000077500000000000000000000125001260026426200162170ustar00rootroot00000000000000#!/bin/sh # Print a version string. scriptversion=2010-01-28.01 # Copyright (C) 2007-2010 Free Software Foundation, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. # It may be run two ways: # - from a git repository in which the "git describe" command below # produces useful output (thus requiring at least one signed tag) # - from a non-git-repo directory containing a .tarball-version file, which # presumes this script is invoked like "./git-version-gen .tarball-version". # In order to use intra-version strings in your project, you will need two # separate generated version string files: # # .tarball-version - present only in a distribution tarball, and not in # a checked-out repository. Created with contents that were learned at # the last time autoconf was run, and used by git-version-gen. Must not # be present in either $(srcdir) or $(builddir) for git-version-gen to # give accurate answers during normal development with a checked out tree, # but must be present in a tarball when there is no version control system. # Therefore, it cannot be used in any dependencies. GNUmakefile has # hooks to force a reconfigure at distribution time to get the value # correct, without penalizing normal development with extra reconfigures. # # .version - present in a checked-out repository and in a distribution # tarball. Usable in dependencies, particularly for files that don't # want to depend on config.h but do want to track version changes. # Delete this file prior to any autoconf run where you want to rebuild # files to pick up a version string change; and leave it stale to # minimize rebuild time after unrelated changes to configure sources. # # It is probably wise to add these two files to .gitignore, so that you # don't accidentally commit either generated file. # # Use the following line in your configure.ac, so that $(VERSION) will # automatically be up-to-date each time configure is run (and note that # since configure.ac no longer includes a version string, Makefile rules # should not depend on configure.ac for version updates). # # AC_INIT([GNU project], # m4_esyscmd([build-aux/git-version-gen .tarball-version]), # [bug-project@example]) # # Then use the following lines in your Makefile.am, so that .version # will be present for dependencies, and so that .tarball-version will # exist in distribution tarballs. # # BUILT_SOURCES = $(top_srcdir)/.version # $(top_srcdir)/.version: # echo $(VERSION) > $@-t && mv $@-t $@ # dist-hook: # echo $(VERSION) > $(distdir)/.tarball-version case $# in 1) ;; *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; esac tarball_version_file=$1 nl=' ' # First see if there is a tarball-only version file. # then try "git describe", then default. if test -f $tarball_version_file then v=`cat $tarball_version_file` || exit 1 case $v in *$nl*) v= ;; # reject multi-line output [0-9]*) ;; *) v= ;; esac test -z "$v" \ && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 fi if test -n "$v" then : # use $v elif v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ || git describe --abbrev=4 HEAD 2>/dev/null` \ && case $v in [0-9]*) ;; v[0-9]*) ;; *) (exit 1) ;; esac then # Is this a new git that lists number of commits since the last # tag or the previous older version that did not? # Newer: v6.10-77-g0f8faeb # Older: v6.10-g0f8faeb case $v in *-*-*) : git describe is okay three part flavor ;; *-*) : git describe is older two part flavor # Recreate the number of commits and rewrite such that the # result is the same as if we were using the newer version # of git describe. vtag=`echo "$v" | sed 's/-.*//'` numcommits=`git rev-list "$vtag"..HEAD | wc -l` v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; ;; esac # Change the first '-' to a '.', so version-comparing tools work properly. # Remove the "g" in git describe's output string, to save a byte. v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; else v=UNKNOWN fi v=`echo "$v" |sed 's/^v//'` # Don't declare a version "dirty" merely because a time stamp has changed. git status > /dev/null 2>&1 dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= case "$dirty" in '') ;; *) # Append the suffix only if there isn't one already. case $v in *-dirty) ;; *) v="$v-dirty" ;; esac ;; esac # Omit the trailing newline, so that m4_esyscmd can use the result directly. echo "$v" | tr -d '\012' # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-end: "$" # End: osmo-bts-0.4.0/include/000077500000000000000000000000001260026426200147015ustar00rootroot00000000000000osmo-bts-0.4.0/include/Makefile.am000066400000000000000000000000701260026426200167320ustar00rootroot00000000000000SUBDIRS = osmo-bts noinst_HEADERS = openbsc/gsm_data.h osmo-bts-0.4.0/include/openbsc/000077500000000000000000000000001260026426200163325ustar00rootroot00000000000000osmo-bts-0.4.0/include/openbsc/gsm_data.h000077700000000000000000000000001260026426200241632../osmo-bts/gsm_data.hustar00rootroot00000000000000osmo-bts-0.4.0/include/osmo-bts/000077500000000000000000000000001260026426200164445ustar00rootroot00000000000000osmo-bts-0.4.0/include/osmo-bts/Makefile.am000066400000000000000000000003531260026426200205010ustar00rootroot00000000000000noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \ oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \ handover.h msg_utils.h tx_power.h control_if.h cbch.h l1sap.h \ power_control.h osmo-bts-0.4.0/include/osmo-bts/abis.h000066400000000000000000000011141260026426200175300ustar00rootroot00000000000000#ifndef _ABIS_H #define _ABIS_H #include #include #include #define OML_RETRY_TIMER 5 #define OML_PING_TIMER 20 enum { LINK_STATE_IDLE = 0, LINK_STATE_RETRYING, LINK_STATE_CONNECTING, LINK_STATE_CONNECT, }; void abis_init(struct gsm_bts *bts); struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host, char *model_name); int abis_oml_sendmsg(struct msgb *msg); int abis_bts_rsl_sendmsg(struct msgb *msg); uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link); #endif /* _ABIS_H */ osmo-bts-0.4.0/include/osmo-bts/amr.h000066400000000000000000000006621260026426200174000ustar00rootroot00000000000000#ifndef _OSMO_BTS_AMR_H #define _OSMO_BTS_AMR_H #include #define AMR_TOC_QBIT 0x04 #define AMR_CMR_NONE 0xF void amr_log_mr_conf(int ss, int logl, const char *pfx, struct amr_multirate_conf *amr_mrc); int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc, const uint8_t *mr_conf, unsigned int len); unsigned int amr_get_initial_mode(struct gsm_lchan *lchan); #endif /* _OSMO_BTS_AMR_H */ osmo-bts-0.4.0/include/osmo-bts/bts.h000066400000000000000000000026051260026426200174100ustar00rootroot00000000000000#ifndef _BTS_H #define _BTS_H #include enum bts_global_status { BTS_STATUS_RF_ACTIVE, BTS_STATUS_RF_MUTE, BTS_STATUS_LAST, }; extern void *tall_bts_ctx; int bts_init(struct gsm_bts *bts); void bts_shutdown(struct gsm_bts *bts, const char *reason); struct gsm_bts *create_bts(uint8_t num_trx, char *id); int create_ms(struct gsm_bts_trx *trx, int maskc, uint8_t *maskv_tx, uint8_t *maskv_rx); void destroy_bts(struct gsm_bts *bts); int work_bts(struct gsm_bts *bts); int bts_link_estab(struct gsm_bts *bts); int trx_link_estab(struct gsm_bts_trx *trx); void bts_new_si(void *arg); void bts_setup_slot(struct gsm_bts_trx_ts *slot, uint8_t comb); int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg); struct msgb *bts_agch_dequeue(struct gsm_bts *bts); void bts_update_agch_max_queue_length(struct gsm_bts *bts); int bts_agch_max_queue_length(int T, int bcch_conf); int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, int is_ag_res); uint8_t *bts_sysinfo_get(struct gsm_bts *bts, struct gsm_time *g_time); uint8_t *lchan_sacch_get(struct gsm_lchan *lchan); int lchan_init_lapdm(struct gsm_lchan *lchan); void load_timer_start(struct gsm_bts *bts); void bts_update_status(enum bts_global_status which, int on); int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx); struct gsm_time *get_time(struct gsm_bts *bts); #endif /* _BTS_H */ osmo-bts-0.4.0/include/osmo-bts/bts_model.h000066400000000000000000000024661260026426200205750ustar00rootroot00000000000000#ifndef BTS_MODEL_H #define BTS_MODEL_H #include #include #include #include /* BTS model specific functions needed by the common code */ int bts_model_init(struct gsm_bts *bts); int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj); int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int obj_kind, void *obj); int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj); int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state); int bts_model_trx_deact_rf(struct gsm_bts_trx *trx); int bts_model_trx_close(struct gsm_bts_trx *trx); int bts_model_vty_init(struct gsm_bts *bts); void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts); void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx); int bts_model_oml_estab(struct gsm_bts *bts); int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm); int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan); int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); void bts_model_abis_close(struct gsm_bts *bts); #endif osmo-bts-0.4.0/include/osmo-bts/cbch.h000066400000000000000000000010531260026426200175130ustar00rootroot00000000000000#pragma once #include #include #include #include /* incoming SMS broadcast command from RSL */ int bts_process_smscb_cmd(struct gsm_bts *bts, struct rsl_ie_cb_cmd_type cmd_type, uint8_t msg_len, const uint8_t *msg); /* call-back from bts model specific code when it wants to obtain a CBCH * block for a given gsm_time. outbuf must have 23 bytes of space. */ int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time); osmo-bts-0.4.0/include/osmo-bts/control_if.h000066400000000000000000000001741260026426200207550ustar00rootroot00000000000000#pragma once int bts_ctrl_cmds_install(struct gsm_bts *bts); struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts); osmo-bts-0.4.0/include/osmo-bts/gsm_data.h000066400000000000000000000062331260026426200204000ustar00rootroot00000000000000#ifndef _GSM_DATA_H #define _GSM_DATA_H #include #include #include #include #include #define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT 41 #define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE 999999 #define GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT 41 #define GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT 91 struct pcu_sock_state; struct smscb_msg; struct gsm_network { struct llist_head bts_list; unsigned int num_bts; uint16_t mcc, mnc; struct pcu_sock_state *pcu_state; }; /* data structure for BTS related data specific to the BTS role */ struct gsm_bts_role_bts { struct { /* Interference Boundaries for OML */ int16_t boundary[6]; uint8_t intave; } interference; unsigned int t200_ms[7]; unsigned int t3105_ms; struct { uint8_t overload_period; struct { /* Input parameters from OML */ uint8_t load_ind_thresh; /* percent */ uint8_t load_ind_period; /* seconds */ /* Internal data */ struct osmo_timer_list timer; unsigned int pch_total; unsigned int pch_used; } ccch; struct { /* Input parameters from OML */ int16_t busy_thresh; /* in dBm */ uint16_t averaging_slots; /* Internal data */ unsigned int total; /* total nr */ unsigned int busy; /* above busy_thresh */ unsigned int access; /* access bursts */ } rach; } load; uint8_t ny1; uint8_t max_ta; /* AGCH queuing */ struct llist_head agch_queue; int agch_queue_length; int agch_max_queue_length; int agch_queue_thresh_level; /* Cleanup threshold in percent of max len */ int agch_queue_low_level; /* Low water mark in percent of max len */ int agch_queue_high_level; /* High water mark in percent of max len */ /* TODO: Use a rate counter group instead */ uint64_t agch_queue_dropped_msgs; uint64_t agch_queue_merged_msgs; uint64_t agch_queue_rejected_msgs; uint64_t agch_queue_agch_msgs; uint64_t agch_queue_pch_msgs; struct paging_state *paging_state; char *bsc_oml_host; unsigned int rtp_jitter_buf_ms; struct { uint8_t ciphers; /* flags A5/1==0x1, A5/2==0x2, A5/3==0x4 */ } support; struct { uint8_t tc4_ctr; } si; struct gsm_time gsm_time; uint8_t radio_link_timeout; int ul_power_target; /* Uplink Rx power target */ /* used by the sysmoBTS to adjust band */ uint8_t auto_band; struct { struct llist_head queue; /* list of struct smscb_msg */ struct smscb_msg *cur_msg; /* current SMS-CB */ } smscb_state; }; enum lchan_ciph_state { LCHAN_CIPH_NONE, LCHAN_CIPH_RX_REQ, LCHAN_CIPH_RX_CONF, LCHAN_CIPH_RXTX_REQ, LCHAN_CIPH_RX_CONF_TX_REQ, LCHAN_CIPH_RXTX_CONF, }; #define bts_role_bts(x) ((struct gsm_bts_role_bts *)(x)->role) #include "openbsc/gsm_data_shared.h" struct femtol1_hdl; static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx) { return trx->role_bts.l1h; } struct trx_l1h; static inline struct trx_l1h *trx_l1h_hdl(struct gsm_bts_trx *trx) { return trx->role_bts.l1h; } void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state); /* cipher code */ #define CIPHER_A5(x) (1 << (x-1)) int bts_supports_cipher(struct gsm_bts_role_bts *bts, int rsl_cipher); #endif /* _GSM_DATA_H */ osmo-bts-0.4.0/include/osmo-bts/handover.h000066400000000000000000000003771260026426200204320ustar00rootroot00000000000000#pragma once enum { HANDOVER_NONE = 0, HANDOVER_ENABLED, HANDOVER_WAIT_FRAME, }; void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay); void handover_frame(struct gsm_lchan *lchan); void handover_reset(struct gsm_lchan *lchan); osmo-bts-0.4.0/include/osmo-bts/l1sap.h000066400000000000000000000052611260026426200176410ustar00rootroot00000000000000#ifndef L1SAP_H #define L1SAP_H /* timeslot and subslot from chan_nr */ #define L1SAP_CHAN2TS(chan_nr) (chan_nr & 7) #define L1SAP_CHAN2SS_TCHH(chan_nr) ((chan_nr >> 3) & 1) #define L1SAP_CHAN2SS_SDCCH4(chan_nr) ((chan_nr >> 3) & 3) #define L1SAP_CHAN2SS_SDCCH8(chan_nr) ((chan_nr >> 3) & 7) /* logical channel from chan_nr + link_id */ #define L1SAP_IS_LINK_SACCH(link_id) ((link_id & 0xC0) == 0x40) #define L1SAP_IS_CHAN_TCHF(chan_nr) ((chan_nr & 0xf8) == 0x08) #define L1SAP_IS_CHAN_TCHH(chan_nr) ((chan_nr & 0xf0) == 0x10) #define L1SAP_IS_CHAN_SDCCH4(chan_nr) ((chan_nr & 0xe0) == 0x20) #define L1SAP_IS_CHAN_SDCCH8(chan_nr) ((chan_nr & 0xc0) == 0x40) #define L1SAP_IS_CHAN_BCCH(chan_nr) ((chan_nr & 0xf8) == 0x80) #define L1SAP_IS_CHAN_RACH(chan_nr) ((chan_nr & 0xf8) == 0x88) #define L1SAP_IS_CHAN_AGCH_PCH(chan_nr) ((chan_nr & 0xf8) == 0x90) /* rach type from ra */ #define L1SAP_IS_PACKET_RACH(ra) ((ra & 0xf0) == 0x70) /* CCCH block from frame number */ #define L1SAP_FN2CCCHBLOCK(fn) ((fn % 51) / 5 - 1) /* PTCH layout from frame number */ #define L1SAP_FN2MACBLOCK(fn) ((fn % 52) / 4) #define L1SAP_FN2PTCCHBLOCK(fn) ((fn / 52) & 7) #define L1SAP_IS_PTCCH(fn) ((fn % 52) == 12) /* subslot from any chan_nr */ static inline uint8_t l1sap_chan2ss(uint8_t chan_nr) { if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) return L1SAP_CHAN2SS_SDCCH8(chan_nr); if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) return L1SAP_CHAN2SS_SDCCH4(chan_nr); if (L1SAP_IS_CHAN_TCHH(chan_nr)) return L1SAP_CHAN2SS_TCHH(chan_nr); return 0; } /* allocate a msgb containing a osmo_phsap_prim + optional l2 data */ struct msgb *l1sap_msgb_alloc(unsigned int l2_len); /* any L1 prim received from bts model */ int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); /* pcu (socket interface) sends us a data request primitive */ int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len); /* call-back function for incoming RTP */ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, unsigned int rtp_pl_len); /* channel control */ int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp); int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr); int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr); int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr); extern const struct value_string gsmtap_sapi_names[]; extern struct gsmtap_inst *gsmtap; extern uint32_t gsmtap_sapi_mask; extern uint8_t gsmtap_sapi_acch; #define msgb_l1sap_prim(msg) ((struct osmo_phsap_prim *)(msg)->l1h) int bts_check_for_first_ciphrd(struct gsm_lchan *lchan, uint8_t *data, int len); #endif /* L1SAP_H */ osmo-bts-0.4.0/include/osmo-bts/logging.h000066400000000000000000000005061260026426200202440ustar00rootroot00000000000000#ifndef _LOGGING_H #define _LOGGING_H #define DEBUG #include enum { DRSL, DOML, DRLL, DRR, DMEAS, DPAG, DL1C, DL1P, DDSP, DPCU, DHO, DTRX, DLOOP, DABIS, DRTP, DSUM, }; extern const struct log_info bts_log_info; int bts_log_init(const char *category_mask); #endif /* _LOGGING_H */ osmo-bts-0.4.0/include/osmo-bts/measurement.h000066400000000000000000000004771260026426200211520ustar00rootroot00000000000000#ifndef OSMO_BTS_MEAS_H #define OSMO_BTS_MEAS_H int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm); int trx_meas_check_compute(struct gsm_bts_trx *trx, uint32_t fn); /* build the 3 byte RSL uplinke measurement IE content */ int lchan_build_rsl_ul_meas(struct gsm_lchan *, uint8_t *buf); #endif osmo-bts-0.4.0/include/osmo-bts/msg_utils.h000066400000000000000000000005751260026426200206320ustar00rootroot00000000000000/* * Routines to check the structurally integrity of messages */ #pragma once struct msgb; /** * Classification of OML message. ETSI for plain GSM 12.21 * messages and IPA/Osmo for manufacturer messages. */ enum { OML_MSG_TYPE_ETSI, OML_MSG_TYPE_IPA, OML_MSG_TYPE_OSMO, }; int msg_verify_ipa_structure(struct msgb *msg); int msg_verify_oml_structure(struct msgb *msg); osmo-bts-0.4.0/include/osmo-bts/oml.h000066400000000000000000000024271260026426200174110ustar00rootroot00000000000000#ifndef _OML_H #define _OML_H struct gsm_bts; struct gsm_abis_mo; struct msgb; int oml_init(void); int down_oml(struct gsm_bts *bts, struct msgb *msg); struct msgb *oml_msgb_alloc(void); int oml_send_msg(struct msgb *msg, int is_mauf); int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type); int oml_mo_opstart_ack(struct gsm_abis_mo *mo); int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause); int oml_mo_statechg_ack(struct gsm_abis_mo *mo); int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause); /* Change the state and send STATE CHG REP */ int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state); /* First initialization of MO, does _not_ generate state changes */ void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state); /* Update admin state and send ACK/NACK */ int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], int success); /* Transmit STATE CHG REP even if there was no state change */ int oml_tx_state_changed(struct gsm_abis_mo *mo); int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo); int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause); int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type, uint8_t cause); #endif // _OML_H */ osmo-bts-0.4.0/include/osmo-bts/paging.h000066400000000000000000000033111260026426200200600ustar00rootroot00000000000000#ifndef OSMO_BTS_PAGING_H #define OSMO_BTS_PAGING_H #include #include #include struct paging_state; struct gsm_bts_role_bts; /* initialize paging code */ struct paging_state *paging_init(struct gsm_bts_role_bts *btsb, unsigned int num_paging_max, unsigned int paging_lifetime); /* (re) configure paging code */ void paging_config(struct paging_state *ps, unsigned int num_paging_max, unsigned int paging_lifetime); void paging_reset(struct paging_state *ps); /* The max number of paging entries */ unsigned int paging_get_queue_max(struct paging_state *ps); void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max); /* The lifetime of a paging entry */ unsigned int paging_get_lifetime(struct paging_state *ps); void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime); /* update with new SYSTEM INFORMATION parameters */ int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc); /* Add an identity to the paging queue */ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, const uint8_t *identity_lv, uint8_t chan_needed); /* Add an IMM.ASS message to the paging queue */ int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data, uint8_t len); /* generate paging message for given gsm time */ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt, int *is_empty); /* inspection methods below */ int paging_group_queue_empty(struct paging_state *ps, uint8_t group); int paging_queue_length(struct paging_state *ps); int paging_buffer_space(struct paging_state *ps); #endif osmo-bts-0.4.0/include/osmo-bts/pcu_if.h000066400000000000000000000012221260026426200200570ustar00rootroot00000000000000#ifndef _PCU_IF_H #define _PCU_IF_H int pcu_tx_info_ind(void); int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr); int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len, int8_t rssi); int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint8_t ra, uint32_t fn); int pcu_tx_time_ind(uint32_t fn); int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed); int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len); int pcu_sock_init(void); void pcu_sock_exit(void); #endif /* _PCU_IF_H */ osmo-bts-0.4.0/include/osmo-bts/pcuif_proto.h000066400000000000000000000076671260026426200211660ustar00rootroot00000000000000#ifndef _PCUIF_PROTO_H #define _PCUIF_PROTO_H #define PCU_IF_VERSION 0x05 /* msg_type */ #define PCU_IF_MSG_DATA_REQ 0x00 /* send data to given channel */ #define PCU_IF_MSG_DATA_CNF 0x01 /* confirm (e.g. transmission on PCH) */ #define PCU_IF_MSG_DATA_IND 0x02 /* receive data from given channel */ #define PCU_IF_MSG_RTS_REQ 0x10 /* ready to send request */ #define PCU_IF_MSG_RACH_IND 0x22 /* receive RACH */ #define PCU_IF_MSG_INFO_IND 0x32 /* retrieve BTS info */ #define PCU_IF_MSG_ACT_REQ 0x40 /* activate/deactivate PDCH */ #define PCU_IF_MSG_TIME_IND 0x52 /* GSM time indication */ #define PCU_IF_MSG_PAG_REQ 0x60 /* paging request */ /* sapi */ #define PCU_IF_SAPI_RACH 0x01 /* channel request on CCCH */ #define PCU_IF_SAPI_AGCH 0x02 /* assignment on AGCH */ #define PCU_IF_SAPI_PCH 0x03 /* paging/assignment on PCH */ #define PCU_IF_SAPI_BCCH 0x04 /* SI on BCCH */ #define PCU_IF_SAPI_PDTCH 0x05 /* packet data/control/ccch block */ #define PCU_IF_SAPI_PRACH 0x06 /* packet random access channel */ #define PCU_IF_SAPI_PTCCH 0x07 /* packet TA control channel */ /* flags */ #define PCU_IF_FLAG_ACTIVE (1 << 0)/* BTS is active */ #define PCU_IF_FLAG_SYSMO (1 << 1)/* access PDCH of sysmoBTS directly */ #define PCU_IF_FLAG_CS1 (1 << 16) #define PCU_IF_FLAG_CS2 (1 << 17) #define PCU_IF_FLAG_CS3 (1 << 18) #define PCU_IF_FLAG_CS4 (1 << 19) #define PCU_IF_FLAG_MCS1 (1 << 20) #define PCU_IF_FLAG_MCS2 (1 << 21) #define PCU_IF_FLAG_MCS3 (1 << 22) #define PCU_IF_FLAG_MCS4 (1 << 23) #define PCU_IF_FLAG_MCS5 (1 << 24) #define PCU_IF_FLAG_MCS6 (1 << 25) #define PCU_IF_FLAG_MCS7 (1 << 26) #define PCU_IF_FLAG_MCS8 (1 << 27) #define PCU_IF_FLAG_MCS9 (1 << 28) struct gsm_pcu_if_data { uint8_t sapi; uint8_t len; uint8_t data[162]; uint32_t fn; uint16_t arfcn; uint8_t trx_nr; uint8_t ts_nr; uint8_t block_nr; int8_t rssi; } __attribute__ ((packed)); struct gsm_pcu_if_rts_req { uint8_t sapi; uint8_t spare[3]; uint32_t fn; uint16_t arfcn; uint8_t trx_nr; uint8_t ts_nr; uint8_t block_nr; } __attribute__ ((packed)); struct gsm_pcu_if_rach_ind { uint8_t sapi; uint8_t ra; int16_t qta; uint32_t fn; uint16_t arfcn; } __attribute__ ((packed)); struct gsm_pcu_if_info_trx { uint16_t arfcn; uint8_t pdch_mask; /* PDCH channels per TS */ uint8_t spare; uint8_t tsc[8]; /* TSC per channel */ uint32_t hlayer1; } __attribute__ ((packed)); struct gsm_pcu_if_info_ind { uint32_t version; uint32_t flags; struct gsm_pcu_if_info_trx trx[8]; /* TRX infos per BTS */ uint8_t bsic; /* RAI */ uint16_t mcc, mnc, lac, rac; /* NSE */ uint16_t nsei; uint8_t nse_timer[7]; uint8_t cell_timer[11]; /* cell */ uint16_t cell_id; uint16_t repeat_time; uint8_t repeat_count; uint16_t bvci; uint8_t t3142; uint8_t t3169; uint8_t t3191; uint8_t t3193_10ms; uint8_t t3195; uint8_t n3101; uint8_t n3103; uint8_t n3105; uint8_t cv_countdown; uint16_t dl_tbf_ext; uint16_t ul_tbf_ext; uint8_t initial_cs; uint8_t initial_mcs; /* NSVC */ uint16_t nsvci[2]; uint16_t local_port[2]; uint16_t remote_port[2]; uint32_t remote_ip[2]; } __attribute__ ((packed)); struct gsm_pcu_if_act_req { uint8_t activate; uint8_t trx_nr; uint8_t ts_nr; uint8_t spare; } __attribute__ ((packed)); struct gsm_pcu_if_time_ind { uint32_t fn; } __attribute__ ((packed)); struct gsm_pcu_if_pag_req { uint8_t sapi; uint8_t chan_needed; uint8_t identity_lv[9]; } __attribute__ ((packed)); struct gsm_pcu_if { /* context based information */ uint8_t msg_type; /* message type */ uint8_t bts_nr; /* bts number */ uint8_t spare[2]; union { struct gsm_pcu_if_data data_req; struct gsm_pcu_if_data data_cnf; struct gsm_pcu_if_data data_ind; struct gsm_pcu_if_rts_req rts_req; struct gsm_pcu_if_rach_ind rach_ind; struct gsm_pcu_if_info_ind info_ind; struct gsm_pcu_if_act_req act_req; struct gsm_pcu_if_time_ind time_ind; struct gsm_pcu_if_pag_req pag_req; } u; } __attribute__ ((packed)); #endif /* _PCUIF_PROTO_H */ osmo-bts-0.4.0/include/osmo-bts/power_control.h000066400000000000000000000002451260026426200215120ustar00rootroot00000000000000#pragma once #include #include int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, const uint8_t ms_power, const int rxLevel); osmo-bts-0.4.0/include/osmo-bts/rsl.h000066400000000000000000000025001260026426200174120ustar00rootroot00000000000000#ifndef _RSL_H #define _RSL_H /** * What kind of release/activation is done? A silent one for * the PDCH or one triggered through RSL? */ enum { LCHAN_REL_ACT_RSL, LCHAN_REL_ACT_PCU, LCHAN_REL_ACT_OML, }; int msgb_queue_flush(struct llist_head *list); int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg); int rsl_tx_rf_res(struct gsm_bts_trx *trx); int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime, uint8_t ra, uint8_t acc_delay); int rsl_tx_est_ind(struct gsm_lchan *lchan, uint8_t link_id, uint8_t *data, int len); int rsl_tx_chan_act_ack(struct gsm_lchan *lchan); int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause); int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause); int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan); int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay); /* call-back for LAPDm code, called when it wants to send msgs UP */ int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx); int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause); int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail); int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total, uint16_t busy, uint16_t access); struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr); #endif // _RSL_H */ osmo-bts-0.4.0/include/osmo-bts/signal.h000066400000000000000000000003601260026426200200710ustar00rootroot00000000000000#ifndef OSMO_BTS_SIGNAL_H #define OSMO_BTS_SIGNAL_H #include enum sig_subsys { SS_GLOBAL, }; enum signals_global { S_NEW_SYSINFO, S_NEW_OP_STATE, S_NEW_NSE_ATTR, S_NEW_CELL_ATTR, S_NEW_NSVC_ATTR, }; #endif osmo-bts-0.4.0/include/osmo-bts/tx_power.h000066400000000000000000000043351260026426200204710ustar00rootroot00000000000000#pragma once #include #include /* our unit is 'milli dB" or "milli dBm", i.e. 1/1000 of a dB(m) */ #define to_mdB(x) (x * 1000) /* PA calibration table */ struct pa_calibration { int gain_mdB[1024]; /* gain provided at given ARFCN */ /* FIXME: thermal calibration */ }; /* representation of a RF power amplifier */ struct power_amp { /* nominal gain of the PA */ int nominal_gain_mdB; /* table with calibrated actual gain for each ARFCN */ struct pa_calibration calib; }; /* Transmit power related parameters of a transceiver */ struct trx_power_params { /* specified maximum output of TRX at full power, has to be * initialized by BTS model at startup*/ int trx_p_max_out_mdBm; /* intended current total system output power */ int p_total_tgt_mdBm; /* actual current total system output power, filled in by tx_power code */ int p_total_cur_mdBm; /* current temporary attenuation due to thermal management, * set by thermal management code via control interface */ int thermal_attenuation_mdB; /* external gain (+) or attenuation (-) added by the user, configured * by the user via VTY */ int user_gain_mdB; /* calibration table of internal PA */ struct power_amp pa; /* calibration table of user PA */ struct power_amp user_pa; /* power ramping related data */ struct { /* maximum initial Pout including all PAs */ int max_initial_pout_mdBm; /* temporary attenuation due to power ramping */ int attenuation_mdB; unsigned int step_size_mdB; unsigned int step_interval_sec; struct osmo_timer_list step_timer; } ramp; }; int get_p_max_out_mdBm(struct gsm_bts_trx *trx); int get_p_nominal_mdBm(struct gsm_bts_trx *trx); int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie); int get_p_target_mdBm_lchan(struct gsm_lchan *lchan); int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie); int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan); int get_p_trxout_actual_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie); int get_p_trxout_actual_mdBm_lchan(struct gsm_lchan *lchan); int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass); void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm); osmo-bts-0.4.0/include/osmo-bts/vty.h000066400000000000000000000007531260026426200174440ustar00rootroot00000000000000#ifndef OSMOBTS_VTY_H #define OSMOBTS_VTY_H #include #include enum bts_vty_node { BTS_NODE = _LAST_OSMOVTY_NODE + 1, TRX_NODE, }; extern struct cmd_element ournode_exit_cmd; extern struct cmd_element ournode_end_cmd; enum node_type bts_vty_go_parent(struct vty *vty); int bts_vty_is_config_node(struct vty *vty, int node); int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat); extern struct vty_app_info bts_vty_info; #endif osmo-bts-0.4.0/src/000077500000000000000000000000001260026426200140455ustar00rootroot00000000000000osmo-bts-0.4.0/src/Makefile.am000066400000000000000000000001611260026426200160770ustar00rootroot00000000000000SUBDIRS = common if ENABLE_SYSMOBTS SUBDIRS += osmo-bts-sysmo endif if ENABLE_TRX SUBDIRS += osmo-bts-trx endif osmo-bts-0.4.0/src/common/000077500000000000000000000000001260026426200153355ustar00rootroot00000000000000osmo-bts-0.4.0/src/common/Makefile.am000066400000000000000000000010551260026426200173720ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) noinst_LIBRARIES = libbts.a libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \ rsl.c vty.c paging.c measurement.c amr.c lchan.c \ load_indication.c pcu_sock.c handover.c msg_utils.c \ load_indication.c pcu_sock.c handover.c msg_utils.c \ tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \ l1sap.c cbch.c power_control.c osmo-bts-0.4.0/src/common/abis.c000066400000000000000000000134711260026426200164250ustar00rootroot00000000000000/* Abis/IP interface routines utilizing libosmo-abis (Pablo) */ /* (C) 2011 by Andreas Eversberg * (C) 2011-2013 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "btsconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct gsm_bts *g_bts; int abis_oml_sendmsg(struct msgb *msg) { struct gsm_bts *bts = msg->trx->bts; /* osmo-bts uses msg->trx internally, but libosmo-abis uses * the signalling link at msg->dst */ msg->dst = bts->oml_link; return abis_sendmsg(msg); } int abis_bts_rsl_sendmsg(struct msgb *msg) { /* osmo-bts uses msg->trx internally, but libosmo-abis uses * the signalling link at msg->dst */ msg->dst = msg->trx->rsl_link; return abis_sendmsg(msg); } static struct e1inp_sign_link *sign_link_up(void *unit, struct e1inp_line *line, enum e1inp_sign_type type) { struct e1inp_sign_link *sign_link = NULL; switch (type) { case E1INP_SIGN_OML: LOGP(DABIS, LOGL_INFO, "OML Signalling link up\n"); e1inp_ts_config_sign(&line->ts[E1INP_SIGN_OML-1], line); sign_link = g_bts->oml_link = e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML-1], E1INP_SIGN_OML, NULL, 255, 0); sign_link->trx = g_bts->c0; bts_link_estab(g_bts); break; case E1INP_SIGN_RSL: LOGP(DABIS, LOGL_INFO, "RSL Signalling link up\n"); e1inp_ts_config_sign(&line->ts[E1INP_SIGN_RSL-1], line); sign_link = g_bts->c0->rsl_link = e1inp_sign_link_create(&line->ts[E1INP_SIGN_RSL-1], E1INP_SIGN_RSL, NULL, 0, 0); /* FIXME: This assumes there is only one TRX! */ sign_link->trx = g_bts->c0; trx_link_estab(sign_link->trx); break; default: break; } return sign_link; } static void sign_link_down(struct e1inp_line *line) { LOGP(DABIS, LOGL_ERROR, "Signalling link down\n"); if (g_bts->c0->rsl_link) { e1inp_sign_link_destroy(g_bts->c0->rsl_link); g_bts->c0->rsl_link = NULL; trx_link_estab(g_bts->c0); } if (g_bts->oml_link) e1inp_sign_link_destroy(g_bts->oml_link); g_bts->oml_link = NULL; bts_model_abis_close(g_bts); } /* callback for incoming mesages from A-bis/IP */ static int sign_link_cb(struct msgb *msg) { struct e1inp_sign_link *link = msg->dst; /* osmo-bts code assumes msg->trx is set, but libosmo-abis works * with the sign_link stored in msg->dst, so we have to convert * here */ msg->trx = link->trx; switch (link->type) { case E1INP_SIGN_OML: down_oml(link->trx->bts, msg); break; case E1INP_SIGN_RSL: down_rsl(link->trx, msg); break; default: msgb_free(msg); break; } return 0; } uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link) { int fd = link->ts->driver.ipaccess.fd.fd; struct sockaddr_in sin; socklen_t slen = sizeof(sin); int rc; rc = getpeername(fd, (struct sockaddr *)&sin, &slen); if (rc < 0) { LOGP(DOML, LOGL_ERROR, "Cannot determine remote IP Addr: %s\n", strerror(errno)); return 0; } /* we assume that the soket is AF_INET. As Abis/IP contains * lots of hard-coded IPv4 addresses, this safe */ OSMO_ASSERT(sin.sin_family == AF_INET); return ntohl(sin.sin_addr.s_addr); } static int inp_s_cbfn(unsigned int subsys, unsigned int signal, void *hdlr_data, void *signal_data) { if (subsys != SS_L_INPUT) return 0; DEBUGP(DABIS, "Input Signal %u received\n", signal); return 0; } static struct ipaccess_unit bts_dev_info = { .unit_name = "sysmoBTS", .equipvers = "", /* FIXME: read this from hw */ .swversion = PACKAGE_VERSION, .location1 = "", .location2 = "", .serno = "", }; static struct e1inp_line_ops line_ops = { .cfg = { .ipa = { .role = E1INP_LINE_R_BTS, .dev = &bts_dev_info, }, }, .sign_link_up = sign_link_up, .sign_link_down = sign_link_down, .sign_link = sign_link_cb, }; void abis_init(struct gsm_bts *bts) { g_bts = bts; oml_init(); libosmo_abis_init(NULL); osmo_signal_register_handler(SS_L_INPUT, &inp_s_cbfn, bts); } struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host, char *model_name) { struct e1inp_line *line; /* patch in various data from VTY and othe sources */ line_ops.cfg.ipa.addr = dst_host; osmo_get_macaddr(bts_dev_info.mac_addr, "eth0"); bts_dev_info.site_id = bts->ip_access.site_id; bts_dev_info.bts_id = bts->ip_access.bts_id; bts_dev_info.unit_name = model_name; if (bts->description) bts_dev_info.unit_name = bts->description; bts_dev_info.location2 = model_name; line = e1inp_line_find(0); if (!line) line = e1inp_line_create(0, "ipa"); if (!line) return NULL; e1inp_line_bind_ops(line, &line_ops); /* This will open the OML connection now */ if (e1inp_line_update(line) < 0) return NULL; return line; } osmo-bts-0.4.0/src/common/amr.c000066400000000000000000000056201260026426200162630ustar00rootroot00000000000000#include #include #include #include #include void amr_log_mr_conf(int ss, int logl, const char *pfx, struct amr_multirate_conf *amr_mrc) { int i; LOGP(ss, logl, "%s AMR MR Conf: num_modes=%u", pfx, amr_mrc->num_modes); for (i = 0; i < amr_mrc->num_modes; i++) LOGPC(ss, logl, ", mode[%u] = %u/%u/%u", i, amr_mrc->mode[i].mode, amr_mrc->mode[i].threshold_bts, amr_mrc->mode[i].hysteresis_bts); LOGPC(ss, logl, "\n"); } /* parse a GSM 04.08 MultiRate Config IE (10.5.2.21aa) in a more * comfortable internal data structure */ int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc, const uint8_t *mr_conf, unsigned int len) { uint8_t mr_version = mr_conf[0] >> 5; uint8_t num_codecs = 0; int i, j = 0; if (mr_version != 1) { LOGP(DRSL, LOGL_ERROR, "AMR Multirate Version %u unknonw\n", mr_version); goto ret_einval; } /* check number of active codecs */ for (i = 0; i < 8; i++) { if (mr_conf[1] & (1 << i)) num_codecs++; } /* check for minimum length */ if (num_codecs == 0 || (num_codecs == 1 && len < 2) || (num_codecs == 2 && len < 4) || (num_codecs == 3 && len < 5) || (num_codecs == 4 && len < 6) || (num_codecs > 4)) { LOGP(DRSL, LOGL_ERROR, "AMR Multirate with %u modes len=%u " "not possible\n", num_codecs, len); goto ret_einval; } /* copy the first two octets of the IE */ amr_mrc->gsm48_ie[0] = mr_conf[0]; amr_mrc->gsm48_ie[1] = mr_conf[1]; amr_mrc->num_modes = num_codecs; for (i = 0; i < 8; i++) { if (mr_conf[1] & (1 << i)) { amr_mrc->mode[j++].mode = i; } } if (num_codecs >= 2) { amr_mrc->mode[0].threshold_bts = mr_conf[1] & 0x3F; amr_mrc->mode[0].hysteresis_bts = mr_conf[2] >> 4; } if (num_codecs >= 3) { amr_mrc->mode[1].threshold_bts = ((mr_conf[2] & 0xF) << 2) | (mr_conf[3] >> 6); amr_mrc->mode[1].hysteresis_bts = (mr_conf[3] >> 2) & 0xF; } if (num_codecs >= 4) { amr_mrc->mode[2].threshold_bts = ((mr_conf[3] & 0x3) << 4) | (mr_conf[4] >> 4); amr_mrc->mode[2].hysteresis_bts = mr_conf[4] & 0xF; } return num_codecs; ret_einval: return -EINVAL; } /*! \brief determine AMR initial codec mode for given logical channel * \returns integer between 0..3 for AMR codce mode 1..4 */ unsigned int amr_get_initial_mode(struct gsm_lchan *lchan) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; struct gsm48_multi_rate_conf *mr_conf = (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; if (mr_conf->icmi) { /* initial mode given, coding in TS 05.09 3.4.1 */ return mr_conf->smod; } else { /* implicit rule according to TS 05.09 Chapter 3.4.3 */ switch (amr_mrc->num_modes) { case 2: case 3: /* return the most robust */ return 0; case 4: /* return the second-most robust */ return 1; case 1: default: /* return the only mode we have */ return 0; } } } osmo-bts-0.4.0/src/common/bts.c000066400000000000000000000373021260026426200162760ustar00rootroot00000000000000/* BTS support code common to all supported BTS models */ /* (C) 2011 by Andreas Eversberg * (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct gsm_network bts_gsmnet = { .bts_list = { &bts_gsmnet.bts_list, &bts_gsmnet.bts_list }, .num_bts = 0, }; void *tall_bts_ctx; /* Table 3.1 TS 04.08: Values of parameter S */ static const uint8_t tx_integer[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50, }; static const uint8_t s_values[][2] = { { 55, 41 }, { 76, 52 }, { 109, 58 }, { 163, 86 }, { 217, 115 }, }; static int bts_signal_cbfn(unsigned int subsys, unsigned int signal, void *hdlr_data, void *signal_data) { if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) { struct gsm_bts *bts = signal_data; bts_update_agch_max_queue_length(bts); } return 0; } int bts_init(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb; struct gsm_bts_trx *trx; int rc; static int initialized = 0; /* add to list of BTSs */ llist_add_tail(&bts->list, &bts_gsmnet.bts_list); bts->band = GSM_BAND_1800; bts->role = btsb = talloc_zero(bts, struct gsm_bts_role_bts); INIT_LLIST_HEAD(&btsb->agch_queue); btsb->agch_queue_length = 0; /* enable management with default levels, * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to * disable this feature. */ btsb->agch_queue_low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT; btsb->agch_queue_high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT; btsb->agch_queue_thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT; /* configurable via VTY */ btsb->paging_state = paging_init(btsb, 200, 0); btsb->ul_power_target = -75; /* dBm default */ /* configurable via OML */ btsb->load.ccch.load_ind_period = 112; load_timer_start(bts); btsb->rtp_jitter_buf_ms = 100; btsb->max_ta = 63; btsb->ny1 = 4; btsb->t3105_ms = 300; /* default RADIO_LINK_TIMEOUT */ btsb->radio_link_timeout = 32; /* Start with the site manager */ oml_mo_state_init(&bts->site_mgr.mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); /* set BTS to dependency */ oml_mo_state_init(&bts->mo, -1, NM_AVSTATE_DEPENDENCY); oml_mo_state_init(&bts->gprs.nse.mo, -1, NM_AVSTATE_DEPENDENCY); oml_mo_state_init(&bts->gprs.cell.mo, -1, NM_AVSTATE_DEPENDENCY); oml_mo_state_init(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_DEPENDENCY); oml_mo_state_init(&bts->gprs.nsvc[1].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); /* initialize bts data structure */ llist_for_each_entry(trx, &bts->trx_list, list) { struct trx_power_params *tpp = &trx->power_params; int i; for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; int k; for (k = 0; k < ARRAY_SIZE(ts->lchan); k++) { struct gsm_lchan *lchan = &ts->lchan[k]; INIT_LLIST_HEAD(&lchan->dl_tch_queue); } } /* Default values for the power adjustments */ tpp->ramp.max_initial_pout_mdBm = to_mdB(23); tpp->ramp.step_size_mdB = to_mdB(2); tpp->ramp.step_interval_sec = 1; } osmo_rtp_init(tall_bts_ctx); rc = bts_model_init(bts); if (rc < 0) { llist_del(&bts->list); return rc; } bts_gsmnet.num_bts++; if (!initialized) { osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL); initialized = 1; } INIT_LLIST_HEAD(&btsb->smscb_state.queue); return rc; } static void shutdown_timer_cb(void *data) { fprintf(stderr, "Shutdown timer expired\n"); exit(42); } static struct osmo_timer_list shutdown_timer = { .cb = &shutdown_timer_cb, }; void bts_shutdown(struct gsm_bts *bts, const char *reason) { struct gsm_bts_trx *trx; if (osmo_timer_pending(&shutdown_timer)) { LOGP(DOML, LOGL_NOTICE, "BTS is already being shutdown.\n"); return; } LOGP(DOML, LOGL_NOTICE, "Shutting down BTS %u, Reason %s\n", bts->nr, reason); llist_for_each_entry(trx, &bts->trx_list, list) { bts_model_trx_deact_rf(trx); bts_model_trx_close(trx); } /* shedule a timer to make sure select loop logic can run again * to dispatch any pending primitives */ osmo_timer_schedule(&shutdown_timer, 3, 0); } /* main link is established, send status report */ int bts_link_estab(struct gsm_bts *bts) { int i, j; LOGP(DSUM, LOGL_INFO, "Main link established, sending Status'.\n"); /* BTS and SITE MGR are EANBLED, BTS is DEPENDENCY */ oml_tx_state_changed(&bts->site_mgr.mo); oml_tx_state_changed(&bts->mo); /* those should all be in DEPENDENCY */ oml_tx_state_changed(&bts->gprs.nse.mo); oml_tx_state_changed(&bts->gprs.cell.mo); oml_tx_state_changed(&bts->gprs.nsvc[0].mo); oml_tx_state_changed(&bts->gprs.nsvc[1].mo); /* All other objects start off-line until the BTS Model code says otherwise */ for (i = 0; i < bts->num_trx; i++) { struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i); oml_tx_state_changed(&trx->mo); oml_tx_state_changed(&trx->bb_transc.mo); for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { struct gsm_bts_trx_ts *ts = &trx->ts[j]; oml_tx_state_changed(&ts->mo); } } return bts_model_oml_estab(bts); } /* RSL link is established, send status report */ int trx_link_estab(struct gsm_bts_trx *trx) { struct e1inp_sign_link *link = trx->rsl_link; uint8_t radio_state = link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED; LOGP(DSUM, LOGL_INFO, "RSL link (TRX %02x) state changed to %s, sending Status'.\n", trx->nr, link ? "up" : "down"); oml_mo_state_chg(&trx->mo, radio_state, NM_AVSTATE_OK); if (link) rsl_tx_rf_res(trx); else bts_model_trx_deact_rf(trx); return 0; } int lchan_init_lapdm(struct gsm_lchan *lchan) { struct lapdm_channel *lc = &lchan->lapdm_ch; lapdm_channel_init(lc, LAPDM_MODE_BTS); lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY); lapdm_channel_set_l1(lc, NULL, lchan); lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan); return 0; } #define CCCH_RACH_RATIO_COMBINED256 (256*1/9) #define CCCH_RACH_RATIO_SEPARATE256 (256*10/55) int bts_agch_max_queue_length(int T, int bcch_conf) { int S, ccch_rach_ratio256, i; int T_group = 0; int is_ccch_comb = 0; if (bcch_conf == RSL_BCCH_CCCH_CONF_1_C) is_ccch_comb = 1; /* * The calculation is based on the ratio of the number RACH slots and * CCCH blocks per time: * Lmax = (T + 2*S) / R_RACH * R_CCCH * where * T3126_min = (T + 2*S) / R_RACH, as defined in GSM 04.08, 11.1.1 * R_RACH is the RACH slot rate (e.g. RACHs per multiframe) * R_CCCH is the CCCH block rate (same time base like R_RACH) * S and T are defined in GSM 04.08, 3.3.1.1.2 * The ratio is mainly influenced by the downlink only channels * (BCCH, FCCH, SCH, CBCH) that can not be used for CCCH. * An estimation with an error of < 10% is used: * ~ 1/9 if CCCH is combined with SDCCH, and * ~ 1/5.5 otherwise. */ ccch_rach_ratio256 = is_ccch_comb ? CCCH_RACH_RATIO_COMBINED256 : CCCH_RACH_RATIO_SEPARATE256; for (i = 0; i < ARRAY_SIZE(tx_integer); i++) { if (tx_integer[i] == T) { T_group = i % 5; break; } } S = s_values[T_group][is_ccch_comb]; return (T + 2 * S) * ccch_rach_ratio256 / 256; } void bts_update_agch_max_queue_length(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct gsm48_system_information_type_3 *si3; int old_max_length = btsb->agch_max_queue_length; if (!(bts->si_valid & (1<agch_max_queue_length = bts_agch_max_queue_length(si3->rach_control.tx_integer, si3->control_channel_desc.ccch_conf); if (btsb->agch_max_queue_length != old_max_length) LOGP(DRSL, LOGL_INFO, "Updated AGCH max queue length to %d\n", btsb->agch_max_queue_length); } #define REQ_REFS_PER_IMM_ASS_REJ 4 static int store_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej, struct gsm48_req_ref *req_refs, uint8_t *wait_inds, int count) { switch (count) { case 0: /* TODO: Warning ? */ return 0; default: count = 4; rej->req_ref4 = req_refs[3]; rej->wait_ind4 = wait_inds[3]; /* fall through */ case 3: rej->req_ref3 = req_refs[2]; rej->wait_ind3 = wait_inds[2]; /* fall through */ case 2: rej->req_ref2 = req_refs[1]; rej->wait_ind2 = wait_inds[1]; /* fall through */ case 1: rej->req_ref1 = req_refs[0]; rej->wait_ind1 = wait_inds[0]; break; } switch (count) { case 1: rej->req_ref2 = req_refs[0]; rej->wait_ind2 = wait_inds[0]; /* fall through */ case 2: rej->req_ref3 = req_refs[0]; rej->wait_ind3 = wait_inds[0]; /* fall through */ case 3: rej->req_ref4 = req_refs[0]; rej->wait_ind4 = wait_inds[0]; /* fall through */ default: break; } return count; } static int extract_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej, struct gsm48_req_ref *req_refs, uint8_t *wait_inds) { int count = 0; req_refs[count] = rej->req_ref1; wait_inds[count] = rej->wait_ind1; count++; if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) { req_refs[count] = rej->req_ref2; wait_inds[count] = rej->wait_ind2; count++; } if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3)) && memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) { req_refs[count] = rej->req_ref3; wait_inds[count] = rej->wait_ind3; count++; } if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4)) && memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4)) && memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) { req_refs[count] = rej->req_ref4; wait_inds[count] = rej->wait_ind4; count++; } return count; } static int try_merge_imm_ass_rej(struct gsm48_imm_ass_rej *old_rej, struct gsm48_imm_ass_rej *new_rej) { struct gsm48_req_ref req_refs[2 * REQ_REFS_PER_IMM_ASS_REJ]; uint8_t wait_inds[2 * REQ_REFS_PER_IMM_ASS_REJ]; int count = 0; int stored = 0; if (new_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ) return 0; if (old_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ) return 0; /* GSM 08.58, 5.7 * -> The BTS may combine serveral IMM.ASS.REJ messages * -> Identical request refs in one message may be squeezed * * GSM 04.08, 9.1.20.2 * -> Request ref and wait ind are duplicated to fill the message */ /* Extract all entries */ count = extract_imm_ass_rej_refs(old_rej, &req_refs[count], &wait_inds[count]); if (count == REQ_REFS_PER_IMM_ASS_REJ) return 0; count += extract_imm_ass_rej_refs(new_rej, &req_refs[count], &wait_inds[count]); /* Store entries into old message */ stored = store_imm_ass_rej_refs(old_rej, &req_refs[stored], &wait_inds[stored], count); count -= stored; if (count == 0) return 1; /* Store remaining entries into new message */ stored += store_imm_ass_rej_refs(new_rej, &req_refs[stored], &wait_inds[stored], count); return 0; } int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); int hard_limit = 1000; struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg); if (btsb->agch_queue_length > hard_limit) { LOGP(DSUM, LOGL_ERROR, "AGCH: too many messages in queue, " "refusing message type 0x%02x, length = %d/%d\n", ((struct gsm48_imm_ass *)msgb_l3(msg))->msg_type, btsb->agch_queue_length, btsb->agch_max_queue_length); btsb->agch_queue_rejected_msgs++; return -ENOMEM; } if (btsb->agch_queue_length > 0) { struct msgb *last_msg = llist_entry(btsb->agch_queue.prev, struct msgb, list); struct gsm48_imm_ass_rej *last_imm_ass_rej = msgb_l3(last_msg); if (try_merge_imm_ass_rej(last_imm_ass_rej, imm_ass_cmd)) { btsb->agch_queue_merged_msgs++; msgb_free(msg); return 0; } } msgb_enqueue(&btsb->agch_queue, msg); btsb->agch_queue_length++; return 0; } struct msgb *bts_agch_dequeue(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct msgb *msg = msgb_dequeue(&btsb->agch_queue); if (!msg) return NULL; btsb->agch_queue_length--; return msg; } /* * Remove lower prio messages if the queue has grown too long. * * \return 0 iff the number of messages in the queue would fit into the AGCH * reserved part of the CCCH. */ static void compact_agch_queue(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct msgb *msg, *msg2; int max_len, slope, offs; int level_low = btsb->agch_queue_low_level; int level_high = btsb->agch_queue_high_level; int level_thres = btsb->agch_queue_thresh_level; max_len = btsb->agch_max_queue_length; if (max_len == 0) max_len = 1; if (btsb->agch_queue_length < max_len * level_thres / 100) return; /* p^ * 1+ /''''' * | / * | / * 0+---/--+----+--> Q length * low high max_len */ offs = max_len * level_low / 100; if (level_high > level_low) slope = 0x10000 * 100 / (level_high - level_low); else slope = 0x10000 * max_len; /* p_drop >= 1 if len > offs */ llist_for_each_entry_safe(msg, msg2, &btsb->agch_queue, list) { struct gsm48_imm_ass *imm_ass_cmd = msgb_l3(msg); int p_drop; if (imm_ass_cmd->msg_type != GSM48_MT_RR_IMM_ASS_REJ) return; /* IMMEDIATE ASSIGN REJECT */ p_drop = (btsb->agch_queue_length - offs) * slope / max_len; if ((random() & 0xffff) >= p_drop) return; llist_del(&msg->list); btsb->agch_queue_length--; msgb_free(msg); btsb->agch_queue_dropped_msgs++; } return; } int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, int is_ag_res) { struct msgb *msg = NULL; struct gsm_bts_role_bts *btsb = bts->role; int rc = 0; int is_empty = 1; /* Do queue house keeping. * This needs to be done every time a CCCH message is requested, since * the queue max length is calculated based on the CCCH block rate and * PCH messages also reduce the drain of the AGCH queue. */ compact_agch_queue(bts); /* Check for paging messages first if this is PCH */ if (!is_ag_res) rc = paging_gen_msg(btsb->paging_state, out_buf, gt, &is_empty); /* Check whether the block may be overwritten */ if (!is_empty) return rc; msg = bts_agch_dequeue(bts); if (!msg) return rc; /* Copy AGCH message */ memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg)); rc = msgb_l3len(msg); msgb_free(msg); if (is_ag_res) btsb->agch_queue_agch_msgs++; else btsb->agch_queue_pch_msgs++; return rc; } int bts_supports_cipher(struct gsm_bts_role_bts *bts, int rsl_cipher) { int sup; if (rsl_cipher < 1 || rsl_cipher > 8) return -ENOTSUP; /* No encryption is always supported */ if (rsl_cipher == 1) return 1; sup = (1 << (rsl_cipher - 2)) & bts->support.ciphers; return sup > 0; } int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx) { return trx->ms_power_control == 1; } struct gsm_time *get_time(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts->role; return &btsb->gsm_time; } osmo-bts-0.4.0/src/common/bts_ctrl_commands.c000066400000000000000000000037331260026426200212040ustar00rootroot00000000000000/* Control Interface for osmo-bts */ /* (C) 2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include CTRL_CMD_DEFINE(therm_att, "thermal-attenuation"); static int get_therm_att(struct ctrl_cmd *cmd, void *data) { struct gsm_bts_trx *trx = cmd->node; struct trx_power_params *tpp = &trx->power_params; cmd->reply = talloc_asprintf(cmd, "%d", tpp->thermal_attenuation_mdB); return CTRL_CMD_REPLY; } static int set_therm_att(struct ctrl_cmd *cmd, void *data) { struct gsm_bts_trx *trx = cmd->node; struct trx_power_params *tpp = &trx->power_params; int val = atoi(cmd->value); printf("set_therm_att(trx=%p, tpp=%p)\n", trx, tpp); tpp->thermal_attenuation_mdB = val; power_ramp_start(trx, tpp->p_total_cur_mdBm, 0); return get_therm_att(cmd, data); } static int verify_therm_att(struct ctrl_cmd *cmd, const char *value, void *data) { int val = atoi(value); /* permit between 0 to 40 dB attenuation */ if (val < 0 || val > to_mdB(40)) return 1; return 0; } int bts_ctrl_cmds_install(struct gsm_bts *bts) { int rc = 0; rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_therm_att); return rc; } osmo-bts-0.4.0/src/common/bts_ctrl_lookup.c000066400000000000000000000055351260026426200207160ustar00rootroot00000000000000/* Control Interface for osmo-bts */ /* (C) 2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 extern vector ctrl_node_vec; /*! \brief control interface lookup function for bsc/bts gsm_data * \param[in] data Private data passed to controlif_setup() * \param[in] vline Vector of the line holding the command string * \param[out] node_type type (CTRL_NODE_) that was determined * \param[out] node_data private dta of node that was determined * \param i Current index into vline, up to which it is parsed */ static int bts_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i) { struct gsm_bts *bts = data; struct gsm_bts_trx *trx = NULL; struct gsm_bts_trx_ts *ts = NULL; char *token = vector_slot(vline, *i); long num; /* TODO: We need to make sure that the following chars are digits * and/or use strtol to check if number conversion was successful * Right now something like net.bts_stats will not work */ if (!strcmp(token, "trx")) { if (*node_type != CTRL_NODE_ROOT || !*node_data) goto err_missing; bts = *node_data; (*i)++; if (!ctrl_parse_get_num(vline, *i, &num)) goto err_index; trx = gsm_bts_trx_num(bts, num); if (!trx) goto err_missing; *node_data = trx; *node_type = CTRL_NODE_TRX; } else if (!strcmp(token, "ts")) { if (*node_type != CTRL_NODE_TRX || !*node_data) goto err_missing; trx = *node_data; (*i)++; if (!ctrl_parse_get_num(vline, *i, &num)) goto err_index; if ((num >= 0) && (num < TRX_NR_TS)) ts = &trx->ts[num]; if (!ts) goto err_missing; *node_data = ts; *node_type = CTRL_NODE_TS; } else return 0; return 1; err_missing: return -ENODEV; err_index: return -ERANGE; } struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts) { struct ctrl_handle *hdl; int rc = 0; hdl = ctrl_interface_setup(bts, OSMO_CTRL_PORT_BTS, bts_ctrl_node_lookup); if (!hdl) return NULL; rc = bts_ctrl_cmds_install(bts); if (rc) { /* FIXME: close control interface */ return NULL; } return hdl; } osmo-bts-0.4.0/src/common/cbch.c000066400000000000000000000124201260026426200163770ustar00rootroot00000000000000/* Cell Broadcast routines */ /* (C) 2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 struct smscb_msg { struct llist_head list; /* list in smscb_state.queue */ uint8_t msg[GSM412_MSG_LEN]; /* message buffer */ uint8_t next_seg; /* next segment number */ uint8_t num_segs; /* total number of segments */ }; static int get_smscb_null_block(uint8_t *out) { struct gsm412_block_type *block_type = (struct gsm412_block_type *) out; block_type->spare = 0; block_type->lpd = 1; block_type->seq_nr = GSM412_SEQ_NULL_MSG; block_type->lb = 0; memset(out+1, GSM_MACBLOCK_PADDING, GSM412_BLOCK_LEN); return 0; } /* get the next block of the current CB message */ static int get_smscb_block(struct gsm_bts *bts, uint8_t *out) { int to_copy; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct gsm412_block_type *block_type; struct smscb_msg *msg = btsb->smscb_state.cur_msg; if (!msg) { /* No message: Send NULL mesage */ return get_smscb_null_block(out); } block_type = (struct gsm412_block_type *) out++; /* LPD is always 01 */ block_type->spare = 0; block_type->lpd = 1; /* determine how much data to copy */ to_copy = GSM412_MSG_LEN - (msg->next_seg * GSM412_BLOCK_LEN); if (to_copy > GSM412_BLOCK_LEN) to_copy = GSM412_BLOCK_LEN; /* copy data and increment index */ memcpy(out, &msg->msg[msg->next_seg * GSM412_BLOCK_LEN], to_copy); /* set + increment sequence number */ block_type->seq_nr = msg->next_seg++; /* determine if this is the last block */ if (block_type->seq_nr + 1 == msg->num_segs) block_type->lb = 1; else block_type->lb = 0; if (block_type->lb == 1) { /* remove/release the message memory */ talloc_free(btsb->smscb_state.cur_msg); btsb->smscb_state.cur_msg = NULL; } return block_type->lb; } static const uint8_t last_block_rsl2um[4] = { [RSL_CB_CMD_LASTBLOCK_4] = 4, [RSL_CB_CMD_LASTBLOCK_1] = 1, [RSL_CB_CMD_LASTBLOCK_2] = 2, [RSL_CB_CMD_LASTBLOCK_3] = 3, }; /* incoming SMS broadcast command from RSL */ int bts_process_smscb_cmd(struct gsm_bts *bts, struct rsl_ie_cb_cmd_type cmd_type, uint8_t msg_len, const uint8_t *msg) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct smscb_msg *scm; if (msg_len > sizeof(scm->msg)) { LOGP(DLSMS, LOGL_ERROR, "Cannot process SMSCB of %u bytes (max %lu)\n", msg_len, sizeof(scm->msg)); return -EINVAL; } scm = talloc_zero_size(bts, sizeof(*scm)); /* initialize entire message with default padding */ memset(scm->msg, GSM_MACBLOCK_PADDING, sizeof(scm->msg)); /* next segment is first segment */ scm->next_seg = 0; switch (cmd_type.command) { case RSL_CB_CMD_TYPE_NORMAL: case RSL_CB_CMD_TYPE_SCHEDULE: case RSL_CB_CMD_TYPE_NULL: scm->num_segs = last_block_rsl2um[cmd_type.last_block&3]; memcpy(scm->msg, msg, msg_len); /* def_bcast is ignored */ break; case RSL_CB_CMD_TYPE_DEFAULT: /* use def_bcast, ignore command */ /* def_bcast == 0: normal mess */ break; } llist_add_tail(&scm->list, &btsb->smscb_state.queue); return 0; } static struct smscb_msg *select_next_smscb(struct gsm_bts *bts) { struct smscb_msg *msg; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); if (llist_empty(&btsb->smscb_state.queue)) return NULL; msg = llist_entry(btsb->smscb_state.queue.next, struct smscb_msg, list); llist_del(&msg->list); return msg; } /* call-back from bts model specific code when it wants to obtain a CBCH * block for a given gsm_time. outbuf must have 23 bytes of space. */ int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); uint32_t fn = gsm_gsmtime2fn(g_time); /* According to 05.02 Section 6.5.4 */ uint32_t tb = (fn / 51) % 8; int rc = 0; /* The multiframes used for the basic cell broadcast channel * shall be those in * which TB = 0,1,2 and 3. The multiframes * used for the extended cell broadcast channel shall be those * in which TB = 4, 5, 6 and 7 */ /* The SMSCB header shall be sent in the multiframe in which TB * = 0 for the basic, and TB = 4 for the extended cell * broadcast channel. */ switch (tb) { case 0: /* select a new SMSCB message */ btsb->smscb_state.cur_msg = select_next_smscb(bts); rc = get_smscb_block(bts, outbuf); break; case 1: case 2: case 3: rc = get_smscb_block(bts, outbuf); break; case 4: case 5: case 6: case 7: /* always send NULL frame in extended CBCH for now */ rc = get_smscb_null_block(outbuf); break; } return rc; } osmo-bts-0.4.0/src/common/gsm_data_shared.c000066400000000000000000000001041260026426200206010ustar00rootroot00000000000000#include "../../../openbsc/openbsc/src/libcommon/gsm_data_shared.c" osmo-bts-0.4.0/src/common/handover.c000066400000000000000000000110521260026426200173060ustar00rootroot00000000000000/* Paging message encoding + queue management */ /* (C) 2012-2013 by Harald Welte * Andreas Eversberg * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include /* Transmit a handover related PHYS INFO on given lchan */ static int ho_tx_phys_info(struct gsm_lchan *lchan) { struct msgb *msg = msgb_alloc_headroom(1024, 128, "PHYS INFO"); struct gsm48_hdr *gh; if (!msg) return -ENOMEM; LOGP(DHO, LOGL_INFO, "%s Sending PHYSICAL INFORMATION to MS.\n", gsm_lchan_name(lchan)); /* Build RSL UNITDATA REQUEST message with 04.08 PHYS INFO */ msg->l3h = msg->data; gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); gh->proto_discr = GSM48_PDISC_RR; gh->msg_type = GSM48_MT_RR_HANDO_INFO; msgb_put_u8(msg, lchan->rqd_ta); rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), 0x00, 0); lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); return 0; } /* timer call-back for T3105 (handover PHYS INFO re-transmit) */ static void ho_t3105_cb(void *data) { struct gsm_lchan *lchan = data; struct gsm_bts *bts = lchan->ts->trx->bts; struct gsm_bts_role_bts *btsb = bts->role; LOGP(DHO, LOGL_INFO, "%s T3105 timeout (%d resends left)\n", gsm_lchan_name(lchan), btsb->ny1 - lchan->ho.phys_info_count); if (lchan->state != LCHAN_S_ACTIVE) { LOGP(DHO, LOGL_NOTICE, "%s is in not active. It is in state %s. Ignoring\n", gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); return; } if (lchan->ho.phys_info_count >= btsb->ny1) { /* HO Abort */ LOGP(DHO, LOGL_NOTICE, "%s NY1 reached, sending CONNection " "FAILure to BSC.\n", gsm_lchan_name(lchan)); rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL); return; } ho_tx_phys_info(lchan); lchan->ho.phys_info_count++; osmo_timer_schedule(&lchan->ho.t3105, 0, btsb->t3105_ms * 1000); } /* received random access on dedicated channel */ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay) { struct gsm_bts *bts = lchan->ts->trx->bts; struct gsm_bts_role_bts *btsb = bts->role; /* Ignore invalid handover ref */ if (lchan->ho.ref != ra) { LOGP(DHO, LOGL_INFO, "%s RACH on dedicated channel received, but " "ra=0x%02x != expected ref=0x%02x. (This is no bug)\n", gsm_lchan_name(lchan), ra, lchan->ho.ref); return; } LOGP(DHO, LOGL_NOTICE, "%s RACH on dedicated channel received with TA=%u\n", gsm_lchan_name(lchan), acc_delay); /* Set timing advance */ lchan->rqd_ta = acc_delay; /* Stop handover detection, wait for valid frame */ lchan->ho.active = HANDOVER_WAIT_FRAME; if (l1sap_chan_modify(lchan->ts->trx, gsm_lchan2chan_nr(lchan)) != 0) { LOGP(DHO, LOGL_ERROR, "%s failed to modify channel after handover\n", gsm_lchan_name(lchan)); rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL); return; } /* Send HANDover DETect to BSC */ rsl_tx_hando_det(lchan, &lchan->rqd_ta); /* Send PHYS INFO */ lchan->ho.phys_info_count = 1; ho_tx_phys_info(lchan); /* Start T3105 */ LOGP(DHO, LOGL_DEBUG, "%s Starting T3105 with %u ms\n", gsm_lchan_name(lchan), btsb->t3105_ms); lchan->ho.t3105.cb = ho_t3105_cb; lchan->ho.t3105.data = lchan; osmo_timer_schedule(&lchan->ho.t3105, 0, btsb->t3105_ms * 1000); } /* received frist valid data frame on dedicated channel */ void handover_frame(struct gsm_lchan *lchan) { LOGP(DHO, LOGL_INFO, "%s First valid frame detected\n", gsm_lchan_name(lchan)); handover_reset(lchan); } /* release handover state */ void handover_reset(struct gsm_lchan *lchan) { /* Stop T3105 */ osmo_timer_del(&lchan->ho.t3105); /* Handover process is done */ lchan->ho.active = HANDOVER_NONE; } osmo-bts-0.4.0/src/common/l1sap.c000066400000000000000000000732221260026426200165270ustar00rootroot00000000000000/* L1 SAP primitives */ /* (C) 2011 by Harald Welte * (C) 2013 by Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = { 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B }; /* allocate a msgb containing a osmo_phsap_prim + optional l2 data * in order to wrap femtobts header arround l2 data, there must be enough space * in front and behind data pointer */ struct msgb *l1sap_msgb_alloc(unsigned int l2_len) { struct msgb *msg = msgb_alloc_headroom(512, 128, "l1sap_prim"); if (!msg) return NULL; msg->l1h = msgb_put(msg, sizeof(struct osmo_phsap_prim)); return msg; } static int l1sap_tx_ciph_req(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t downlink, uint8_t uplink) { struct osmo_phsap_prim l1sap_ciph; osmo_prim_init(&l1sap_ciph.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST, NULL); l1sap_ciph.u.info.type = PRIM_INFO_ACT_CIPH; l1sap_ciph.u.info.u.ciph_req.chan_nr = chan_nr; l1sap_ciph.u.info.u.ciph_req.downlink = downlink; l1sap_ciph.u.info.u.ciph_req.uplink = uplink; return l1sap_down(trx, &l1sap_ciph); } /* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable * uni-directional de-cryption on the uplink. We need this ugly layering * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD) * to this point in L1 */ static int check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan, uint8_t chan_nr) { /* only do this if we are in the right state */ switch (lchan->ciph_state) { case LCHAN_CIPH_NONE: case LCHAN_CIPH_RX_REQ: break; default: return 0; } /* First byte (Address Field) of LAPDm header) */ if (msg->data[0] != 0x03) return 0; /* First byte (protocol discriminator) of RR */ if ((msg->data[3] & 0xF) != GSM48_PDISC_RR) return 0; /* 2nd byte (msg type) of RR */ if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD) return 0; l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 0, 1); return 1; } struct gsmtap_inst *gsmtap = NULL; uint32_t gsmtap_sapi_mask = 0; uint8_t gsmtap_sapi_acch = 0; const struct value_string gsmtap_sapi_names[] = { { GSMTAP_CHANNEL_BCCH, "BCCH" }, { GSMTAP_CHANNEL_CCCH, "CCCH" }, { GSMTAP_CHANNEL_RACH, "RACH" }, { GSMTAP_CHANNEL_AGCH, "AGCH" }, { GSMTAP_CHANNEL_PCH, "PCH" }, { GSMTAP_CHANNEL_SDCCH, "SDCCH" }, { GSMTAP_CHANNEL_TCH_F, "TCH/F" }, { GSMTAP_CHANNEL_TCH_H, "TCH/H" }, { GSMTAP_CHANNEL_PACCH, "PACCH" }, { GSMTAP_CHANNEL_PDCH, "PDTCH" }, { GSMTAP_CHANNEL_PTCCH, "PTCCH" }, { GSMTAP_CHANNEL_CBCH51,"CBCH" }, { GSMTAP_CHANNEL_ACCH, "SACCH" }, { 0, NULL } }; /* send primitive as gsmtap */ static int gsmtap_ph_data(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, uint8_t *tn, uint8_t *ss, uint32_t *fn, uint8_t **data, int *len) { struct msgb *msg = l1sap->oph.msg; uint8_t chan_nr, link_id; *data = msg->data + sizeof(struct osmo_phsap_prim); *len = msg->len - sizeof(struct osmo_phsap_prim); *fn = l1sap->u.data.fn; *tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr); chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; if (L1SAP_IS_CHAN_TCHF(chan_nr)) { *chan_type = GSMTAP_CHANNEL_TCH_F; } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { *ss = L1SAP_CHAN2SS_TCHH(chan_nr); *chan_type = GSMTAP_CHANNEL_TCH_H; } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); *chan_type = GSMTAP_CHANNEL_SDCCH; } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); *chan_type = GSMTAP_CHANNEL_SDCCH; } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { *chan_type = GSMTAP_CHANNEL_BCCH; } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { #warning Set BS_AG_BLKS_RES /* The sapi depends on DSP configuration, not * on the actual SYSTEM INFORMATION 3. */ if (L1SAP_FN2CCCHBLOCK(*fn) >= 1) *chan_type = GSMTAP_CHANNEL_PCH; else *chan_type = GSMTAP_CHANNEL_AGCH; } if (L1SAP_IS_LINK_SACCH(link_id)) *chan_type |= GSMTAP_CHANNEL_ACCH; return 0; } static int gsmtap_pdch(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, uint8_t *tn, uint8_t *ss, uint32_t *fn, uint8_t **data, int *len) { struct msgb *msg = l1sap->oph.msg; *data = msg->data + sizeof(struct osmo_phsap_prim); *len = msg->len - sizeof(struct osmo_phsap_prim); *fn = l1sap->u.data.fn; *tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr); if (L1SAP_IS_PTCCH(*fn)) { *chan_type = GSMTAP_CHANNEL_PTCCH; *ss = L1SAP_FN2PTCCHBLOCK(*fn); if (l1sap->oph.primitive == PRIM_OP_INDICATION) { if ((*data[0]) == 7) return -EINVAL; (*data)++; (*len)--; } } else *chan_type = GSMTAP_CHANNEL_PACCH; return 0; } static int gsmtap_ph_rach(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, uint8_t *tn, uint8_t *ss, uint32_t *fn, uint8_t **data, int *len) { uint8_t chan_nr; *chan_type = GSMTAP_CHANNEL_RACH; *fn = l1sap->u.rach_ind.fn; *tn = L1SAP_CHAN2TS(l1sap->u.rach_ind.chan_nr); chan_nr = l1sap->u.rach_ind.chan_nr; if (L1SAP_IS_CHAN_TCHH(chan_nr)) *ss = L1SAP_CHAN2SS_TCHH(chan_nr); else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); *data = &l1sap->u.rach_ind.ra; *len = 1; return 0; } static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { uint8_t *data; int len; uint8_t chan_type = 0, tn = 0, ss = 0; uint32_t fn; uint16_t uplink = GSMTAP_ARFCN_F_UPLINK; int rc; if (!gsmtap) return 0; switch (OSMO_PRIM_HDR(&l1sap->oph)) { case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): uplink = 0; /* fall through */ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION): if (trx->ts[tn].pchan == GSM_PCHAN_PDCH) rc = gsmtap_pdch(l1sap, &chan_type, &tn, &ss, &fn, &data, &len); else rc = gsmtap_ph_data(l1sap, &chan_type, &tn, &ss, &fn, &data, &len); break; case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION): rc = gsmtap_ph_rach(l1sap, &chan_type, &tn, &ss, &fn, &data, &len); break; default: rc = -ENOTSUP; } if (rc) return rc; if (len == 0) return 0; if ((chan_type & GSMTAP_CHANNEL_ACCH)) { if (!gsmtap_sapi_acch) return 0; } else { if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask)) return 0; } gsmtap_send(gsmtap, trx->arfcn | uplink, tn, chan_type, ss, fn, 0, 0, data, len); return 0; } /* time information received from bts model */ static int l1sap_info_time_ind(struct gsm_bts *bts, struct osmo_phsap_prim *l1sap, struct info_time_ind_param *info_time_ind) { struct gsm_bts_trx *trx; struct gsm_bts_role_bts *btsb = bts->role; int frames_expired = info_time_ind->fn - btsb->gsm_time.fn; DEBUGP(DL1P, "MPH_INFO time ind %u\n", info_time_ind->fn); /* Update our data structures with the current GSM time */ gsm_fn2gsmtime(&btsb->gsm_time, info_time_ind->fn); /* Update time on PCU interface */ pcu_tx_time_ind(info_time_ind->fn); /* check if the measurement period of some lchan has ended * and pre-compute the respective measurement */ llist_for_each_entry(trx, &bts->trx_list, list) trx_meas_check_compute(trx, info_time_ind->fn - 1); /* increment number of RACH slots that have passed by since the * last time indication */ if (trx == bts->c0) { unsigned int num_rach_per_frame; /* 27 / 51 taken from TS 05.01 Figure 3 */ if (bts->c0->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4) num_rach_per_frame = 27; else num_rach_per_frame = 51; btsb->load.rach.total += frames_expired * num_rach_per_frame; } return 0; } /* measurement information received from bts model */ static int l1sap_info_meas_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct info_meas_ind_param *info_meas_ind) { struct bts_ul_meas ulm; struct gsm_lchan *lchan; DEBUGP(DL1P, "MPH_INFO meas ind chan_nr=%02x\n", info_meas_ind->chan_nr); lchan = &trx->ts[L1SAP_CHAN2TS(info_meas_ind->chan_nr)] .lchan[l1sap_chan2ss(info_meas_ind->chan_nr)]; /* in the GPRS case we are not interested in measurement * processing. The PCU will take care of it */ if (lchan->type == GSM_LCHAN_PDTCH) return 0; memset(&ulm, 0, sizeof(ulm)); ulm.ta_offs_qbits = info_meas_ind->ta_offs_qbits; ulm.ber10k = info_meas_ind->ber10k; ulm.inv_rssi = info_meas_ind->inv_rssi; lchan_new_ul_meas(lchan, &ulm); return 0; } /* any L1 MPH_INFO indication prim recevied from bts model */ static int l1sap_mph_info_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct mph_info_param *info) { int rc = 0; switch (info->type) { case PRIM_INFO_TIME: rc = l1sap_info_time_ind(trx->bts, l1sap, &info->u.time_ind); break; case PRIM_INFO_MEAS: rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind); break; default: LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n", info->type); break; } return rc; } /* activation confirm received from bts model */ static int l1sap_info_act_cnf(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct info_act_cnf_param *info_act_cnf) { struct gsm_lchan *lchan; LOGP(DL1P, LOGL_INFO, "activate confirm chan_nr=%02x trx=%d\n", info_act_cnf->chan_nr, trx->nr); lchan = &trx->ts[L1SAP_CHAN2TS(info_act_cnf->chan_nr)] .lchan[l1sap_chan2ss(info_act_cnf->chan_nr)]; if (info_act_cnf->cause) rsl_tx_chan_act_nack(lchan, info_act_cnf->cause); else rsl_tx_chan_act_ack(lchan); return 0; } /* activation confirm received from bts model */ static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct info_act_cnf_param *info_act_cnf) { struct gsm_lchan *lchan; LOGP(DL1P, LOGL_INFO, "deactivate confirm chan_nr=%02x trx=%d\n", info_act_cnf->chan_nr, trx->nr); lchan = &trx->ts[L1SAP_CHAN2TS(info_act_cnf->chan_nr)] .lchan[l1sap_chan2ss(info_act_cnf->chan_nr)]; rsl_tx_rf_rel_ack(lchan); return 0; } /* any L1 MPH_INFO confirm prim recevied from bts model */ static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct mph_info_param *info) { int rc = 0; switch (info->type) { case PRIM_INFO_ACTIVATE: rc = l1sap_info_act_cnf(trx, l1sap, &info->u.act_cnf); break; case PRIM_INFO_DEACTIVATE: rc = l1sap_info_rel_cnf(trx, l1sap, &info->u.act_cnf); break; default: LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO cnf type %d\n", info->type); break; } return rc; } /* PH-RTS-IND prim recevied from bts model */ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_data_param *rts_ind) { struct msgb *msg = l1sap->oph.msg; struct gsm_time g_time; struct gsm_lchan *lchan; uint8_t chan_nr, link_id; uint8_t tn, ss; uint32_t fn; uint8_t *p, *si; struct lapdm_entity *le; struct osmo_phsap_prim pp; int rc; chan_nr = rts_ind->chan_nr; link_id = rts_ind->link_id; fn = rts_ind->fn; tn = L1SAP_CHAN2TS(chan_nr); gsm_fn2gsmtime(&g_time, fn); DEBUGP(DL1P, "Rx PH-RTS.ind %02u/%02u/%02u chan_nr=%d link_id=%d\n", g_time.t1, g_time.t2, g_time.t3, chan_nr, link_id); if (trx->ts[tn].pchan == GSM_PCHAN_PDCH) { if (L1SAP_IS_PTCCH(rts_ind->fn)) { pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn)); return 0; } pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */, L1SAP_FN2MACBLOCK(fn)); return 0; } /* reuse PH-RTS.ind for PH-DATA.req */ if (!msg) { LOGP(DL1P, LOGL_FATAL, "RTS without msg to be reused. Please " "fix!\n"); abort(); } msgb_trim(msg, sizeof(*l1sap)); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST, msg); msg->l2h = msg->l1h + sizeof(*l1sap); if (L1SAP_IS_CHAN_BCCH(chan_nr)) { p = msgb_put(msg, GSM_MACBLOCK_LEN); /* get them from bts->si_buf[] */ si = bts_sysinfo_get(trx->bts, &g_time); if (si) memcpy(p, si, GSM_MACBLOCK_LEN); else memcpy(p, fill_frame, GSM_MACBLOCK_LEN); } else if (!(chan_nr & 0x80)) { /* only TCH/F, TCH/H, SDCCH/4 and SDCCH/8 have C5 bit cleared */ if (L1SAP_IS_CHAN_TCHH(chan_nr)) ss = L1SAP_CHAN2SS_TCHH(chan_nr); /* TCH/H */ else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); /* SDCCH/4 */ else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); /* SDCCH/8 */ else ss = 0; /* TCH/F */ lchan = &trx->ts[tn].lchan[ss]; if (L1SAP_IS_LINK_SACCH(link_id)) { p = msgb_put(msg, GSM_MACBLOCK_LEN); /* L1-header, if not set/modified by layer 1 */ p[0] = lchan->ms_power_ctrl.current; p[1] = lchan->rqd_ta; le = &lchan->lapdm_ch.lapdm_acch; } else le = &lchan->lapdm_ch.lapdm_dcch; rc = lapdm_phsap_dequeue_prim(le, &pp); if (rc < 0) { if (L1SAP_IS_LINK_SACCH(link_id)) { /* No SACCH data from LAPDM pending, send SACCH filling */ uint8_t *si = lchan_sacch_get(lchan); if (si) { /* The +2 is empty space where the DSP inserts the L1 hdr */ memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2); } else memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2); } else if ((!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_TCHH(chan_nr)) || lchan->rsl_cmode == RSL_CMOD_SPD_SIGN) { /* send fill frame only, if not TCH/x != Signalling, otherwise send empty frame */ p = msgb_put(msg, GSM_MACBLOCK_LEN); memcpy(p, fill_frame, GSM_MACBLOCK_LEN); } /* else the message remains empty, so TCH frames are sent */ } else { /* The +2 is empty space where the DSP inserts the L1 hdr */ if (L1SAP_IS_LINK_SACCH(link_id)) memcpy(p + 2, pp.oph.msg->data + 2, GSM_MACBLOCK_LEN - 2); else { p = msgb_put(msg, GSM_MACBLOCK_LEN); memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN); /* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */ check_for_ciph_cmd(pp.oph.msg, lchan, chan_nr); } msgb_free(pp.oph.msg); } } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { p = msgb_put(msg, GSM_MACBLOCK_LEN); #warning "TODO: Yet another assumption that BS_AG_BLKS_RES=1" /* if CCCH block is 0, it is AGCH */ rc = bts_ccch_copy_msg(trx->bts, p, &g_time, (L1SAP_FN2CCCHBLOCK(fn) < 1)); if (rc <= 0) memcpy(p, fill_frame, GSM_MACBLOCK_LEN); } DEBUGP(DL1P, "Tx PH-DATA.req %02u/%02u/%02u chan_nr=%d link_id=%d\n", g_time.t1, g_time.t2, g_time.t3, chan_nr, link_id); l1sap_down(trx, l1sap); /* don't free, because we forwarded data */ return 1; } static int check_acc_delay(struct ph_rach_ind_param *rach_ind, struct gsm_bts_role_bts *btsb, uint8_t *acc_delay) { *acc_delay = rach_ind->acc_delay; return *acc_delay <= btsb->max_ta; } /* special case where handover RACH is detected */ static int l1sap_handover_rach(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind) { struct gsm_lchan *lchan; uint8_t chan_nr; uint8_t tn, ss; chan_nr = rach_ind->chan_nr; tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); lchan = &trx->ts[tn].lchan[ss]; handover_rach(lchan, rach_ind->ra, rach_ind->acc_delay); /* must return 0, so in case of msg at l1sap, it will be freed */ return 0; } /* TCH-RTS-IND prim recevied from bts model */ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_tch_param *rts_ind) { struct msgb *resp_msg; struct osmo_phsap_prim *resp_l1sap, empty_l1sap; struct gsm_time g_time; struct gsm_lchan *lchan; uint8_t chan_nr; uint8_t tn, ss; uint32_t fn; chan_nr = rts_ind->chan_nr; fn = rts_ind->fn; tn = L1SAP_CHAN2TS(chan_nr); gsm_fn2gsmtime(&g_time, fn); DEBUGP(DL1P, "Rx TCH-RTS.ind %02u/%02u/%02u chan_nr=%d\n", g_time.t1, g_time.t2, g_time.t3, chan_nr); /* get timeslot and subslot */ tn = L1SAP_CHAN2TS(chan_nr); if (L1SAP_IS_CHAN_TCHH(chan_nr)) ss = L1SAP_CHAN2SS_TCHH(chan_nr); /* TCH/H */ else ss = 0; /* TCH/F */ lchan = &trx->ts[tn].lchan[ss]; if (!lchan->loopback && lchan->abis_ip.rtp_socket) { osmo_rtp_socket_poll(lchan->abis_ip.rtp_socket); /* FIXME: we _assume_ that we never miss TDMA * frames and that we always get to this point * for every to-be-transmitted voice frame. A * better solution would be to compute * rx_user_ts based on how many TDMA frames have * elapsed since the last call */ lchan->abis_ip.rtp_socket->rx_user_ts += GSM_RTP_DURATION; } /* get a msgb from the dl_tx_queue */ resp_msg = msgb_dequeue(&lchan->dl_tch_queue); if (!resp_msg) { LOGP(DL1P, LOGL_DEBUG, "%s DL TCH Tx queue underrun\n", gsm_lchan_name(lchan)); resp_l1sap = &empty_l1sap; } else { resp_msg->l2h = resp_msg->data; msgb_push(resp_msg, sizeof(*resp_l1sap)); resp_msg->l1h = resp_msg->data; resp_l1sap = msgb_l1sap_prim(resp_msg); } memset(resp_l1sap, 0, sizeof(*resp_l1sap)); osmo_prim_init(&resp_l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_REQUEST, resp_msg); resp_l1sap->u.tch.chan_nr = chan_nr; resp_l1sap->u.tch.fn = fn; DEBUGP(DL1P, "Tx TCH.req %02u/%02u/%02u chan_nr=%d\n", g_time.t1, g_time.t2, g_time.t3, chan_nr); l1sap_down(trx, resp_l1sap); return 0; } /* process radio link timeout counter S */ static void radio_link_timeout(struct gsm_lchan *lchan, int bad_frame) { struct gsm_bts_role_bts *btsb = lchan->ts->trx->bts->role; /* if link loss criterion already reached */ if (lchan->s == 0) { DEBUGP(DMEAS, "%s radio link counter S already 0.\n", gsm_lchan_name(lchan)); return; } if (bad_frame) { /* count down radio link counter S */ lchan->s--; DEBUGP(DMEAS, "%s counting down radio link counter S=%d\n", gsm_lchan_name(lchan), lchan->s); if (lchan->s == 0) rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); return; } if (lchan->s < btsb->radio_link_timeout) { /* count up radio link counter S */ lchan->s += 2; if (lchan->s > btsb->radio_link_timeout) lchan->s = btsb->radio_link_timeout; DEBUGP(DMEAS, "%s counting up radio link counter S=%d\n", gsm_lchan_name(lchan), lchan->s); } } static inline int check_for_first_ciphrd(struct gsm_lchan *lchan, uint8_t *data, int len) { uint8_t n_s; /* if this is the first valid message after enabling Rx * decryption, we have to enable Tx encryption */ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) return 0; /* HACK: check if it's an I frame, in order to * ignore some still buffered/queued UI frames received * before decryption was enabled */ if (data[0] != 0x01) return 0; if ((data[1] & 0x01) != 0) return 0; n_s = data[1] >> 5; if (lchan->ciph_ns != n_s) return 0; return 1; } /* public helper for the test */ int bts_check_for_first_ciphrd(struct gsm_lchan *lchan, uint8_t *data, int len) { return check_for_first_ciphrd(lchan, data, len); } /* DATA received from bts model */ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_data_param *data_ind) { struct msgb *msg = l1sap->oph.msg; struct gsm_time g_time; struct gsm_lchan *lchan; struct lapdm_entity *le; uint8_t *data = msg->l2h; int len = msgb_l2len(msg); uint8_t chan_nr, link_id; uint8_t tn, ss; uint32_t fn; int8_t rssi; rssi = data_ind->rssi; chan_nr = data_ind->chan_nr; link_id = data_ind->link_id; fn = data_ind->fn; tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); gsm_fn2gsmtime(&g_time, fn); DEBUGP(DL1P, "Rx PH-DATA.ind %02u/%02u/%02u chan_nr=%d link_id=%d\n", g_time.t1, g_time.t2, g_time.t3, chan_nr, link_id); if (trx->ts[tn].pchan == GSM_PCHAN_PDCH) { if (len == 0) return -EINVAL; if (L1SAP_IS_PTCCH(fn)) { pcu_tx_data_ind(&trx->ts[tn], 1, fn, 0 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn), data, len, rssi); return 0; } /* drop incomplete UL block */ if (data[0] != 7) return 0; /* PDTCH / PACCH frame handling */ pcu_tx_data_ind(&trx->ts[tn], 0, fn, 0 /* ARFCN */, L1SAP_FN2MACBLOCK(fn), data + 1, len - 1, rssi); return 0; } lchan = &trx->ts[tn].lchan[ss]; /* bad frame */ if (len == 0) { if (L1SAP_IS_LINK_SACCH(link_id)) radio_link_timeout(lchan, 1); return -EINVAL; } /* report first valid received frame to handover process */ if (lchan->ho.active == HANDOVER_WAIT_FRAME) handover_frame(lchan); if (L1SAP_IS_LINK_SACCH(link_id)) { radio_link_timeout(lchan, 0); le = &lchan->lapdm_ch.lapdm_acch; /* save the SACCH L1 header in the lchan struct for RSL MEAS RES */ if (len < 2) { LOGP(DL1P, LOGL_NOTICE, "SACCH with size %u<2 !?!\n", len); return -EINVAL; } /* Some brilliant engineer decided that the ordering of * fields on the Um interface is different from the * order of fields in RLS. See TS 04.04 (Chapter 7.2) * vs. TS 08.58 (Chapter 9.3.10). */ lchan->meas.l1_info[0] = data[0] << 3; lchan->meas.l1_info[0] |= ((data[0] >> 5) & 1) << 2; lchan->meas.l1_info[1] = data[1]; lchan->meas.flags |= LC_UL_M_F_L1_VALID; lchan_ms_pwr_ctrl(lchan, data[0] & 0x1f, data_ind->rssi); } else le = &lchan->lapdm_ch.lapdm_dcch; if (check_for_first_ciphrd(lchan, data, len)) l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 1, 0); /* SDCCH, SACCH and FACCH all go to LAPDm */ msgb_pull(msg, (msg->l2h - msg->data)); msg->l1h = NULL; lapdm_phsap_up(&l1sap->oph, le); /* don't free, because we forwarded data */ return 1; } /* TCH received from bts model */ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_tch_param *tch_ind) { struct msgb *msg = l1sap->oph.msg; struct gsm_time g_time; struct gsm_lchan *lchan; uint8_t tn, ss, chan_nr; uint32_t fn; chan_nr = tch_ind->chan_nr; fn = tch_ind->fn; tn = L1SAP_CHAN2TS(chan_nr); if (L1SAP_IS_CHAN_TCHH(chan_nr)) ss = L1SAP_CHAN2SS_TCHH(chan_nr); else ss = 0; lchan = &trx->ts[tn].lchan[ss]; gsm_fn2gsmtime(&g_time, fn); DEBUGP(DL1P, "Rx TCH.ind %02u/%02u/%02u chan_nr=%d\n", g_time.t1, g_time.t2, g_time.t3, chan_nr); msgb_pull(msg, sizeof(*l1sap)); /* hand msg to RTP code for transmission */ if (lchan->abis_ip.rtp_socket) osmo_rtp_send_frame(lchan->abis_ip.rtp_socket, msg->data, msg->len, 160); /* if loopback is enabled, also queue received RTP data */ if (lchan->loopback) { struct msgb *tmp; int count = 0; /* make sure the queue doesn't get too long */ llist_for_each_entry(tmp, &lchan->dl_tch_queue, list) count++; while (count >= 1) { tmp = msgb_dequeue(&lchan->dl_tch_queue); msgb_free(tmp); count--; } msgb_enqueue(&lchan->dl_tch_queue, msg); } return 0; } /* RACH received from bts model */ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind) { struct gsm_bts *bts = trx->bts; struct gsm_bts_role_bts *btsb = bts->role; struct lapdm_channel *lc; uint8_t acc_delay; DEBUGP(DL1P, "Rx PH-RA.ind"); lc = &trx->ts[0].lchan[4].lapdm_ch; /* check for under/overflow / sign */ if (!check_acc_delay(rach_ind, btsb, &acc_delay)) { LOGP(DL1C, LOGL_INFO, "ignoring RACH request %u > max_ta(%u)\n", acc_delay, btsb->max_ta); return 0; } /* check for handover rach */ if (!L1SAP_IS_CHAN_RACH(rach_ind->chan_nr)) return l1sap_handover_rach(trx, l1sap, rach_ind); /* check for packet access */ if (trx == bts->c0 && L1SAP_IS_PACKET_RACH(rach_ind->ra)) { LOGP(DL1P, LOGL_INFO, "RACH for packet access\n"); pcu_tx_rach_ind(bts, rach_ind->acc_delay << 2, rach_ind->ra, rach_ind->fn); return 0; } LOGP(DL1P, LOGL_INFO, "RACH for RR access (toa=%d, ra=%d)\n", rach_ind->acc_delay, rach_ind->ra); lapdm_phsap_up(&l1sap->oph, &lc->lapdm_dcch); return 0; } /* any L1 prim received from bts model */ int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { struct msgb *msg = l1sap->oph.msg; int rc = 0; switch (OSMO_PRIM_HDR(&l1sap->oph)) { case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_INDICATION): rc = l1sap_mph_info_ind(trx, l1sap, &l1sap->u.info); break; case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_CONFIRM): rc = l1sap_mph_info_cnf(trx, l1sap, &l1sap->u.info); break; case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION): rc = l1sap_ph_rts_ind(trx, l1sap, &l1sap->u.data); break; case OSMO_PRIM(PRIM_TCH_RTS, PRIM_OP_INDICATION): rc = l1sap_tch_rts_ind(trx, l1sap, &l1sap->u.tch); break; case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION): to_gsmtap(trx, l1sap); rc = l1sap_ph_data_ind(trx, l1sap, &l1sap->u.data); break; case OSMO_PRIM(PRIM_TCH, PRIM_OP_INDICATION): rc = l1sap_tch_ind(trx, l1sap, &l1sap->u.tch); break; case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION): to_gsmtap(trx, l1sap); rc = l1sap_ph_rach_ind(trx, l1sap, &l1sap->u.rach_ind); break; default: LOGP(DL1P, LOGL_NOTICE, "unknown prim %d op %d\n", l1sap->oph.primitive, l1sap->oph.operation); break; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(msg); return rc; } /* any L1 prim sent to bts model */ static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { if (OSMO_PRIM_HDR(&l1sap->oph) == OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST)) to_gsmtap(trx, l1sap); return bts_model_l1sap_down(trx, l1sap); } /* pcu (socket interface) sends us a data request primitive */ int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len) { struct msgb *msg; struct osmo_phsap_prim *l1sap; struct gsm_time g_time; gsm_fn2gsmtime(&g_time, fn); DEBUGP(DL1P, "TX packet data %02u/%02u/%02u is_ptcch=%d trx=%d ts=%d " "block_nr=%d, arfcn=%d, len=%d\n", g_time.t1, g_time.t2, g_time.t3, is_ptcch, ts->trx->nr, ts->nr, block_nr, arfcn, len); msg = l1sap_msgb_alloc(len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST, msg); l1sap->u.data.chan_nr = 0x08 | ts->nr; l1sap->u.data.link_id = 0x00; l1sap->u.data.fn = fn; msg->l2h = msgb_put(msg, len); memcpy(msg->l2h, data, len); return l1sap_down(ts->trx, l1sap); } /*! \brief call-back function for incoming RTP */ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, unsigned int rtp_pl_len) { struct gsm_lchan *lchan = rs->priv; struct msgb *msg, *tmp; struct osmo_phsap_prim *l1sap; int count = 0; msg = l1sap_msgb_alloc(rtp_pl_len); if (!msg) return; memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len); msgb_pull(msg, sizeof(*l1sap)); /* make sure the queue doesn't get too long */ llist_for_each_entry(tmp, &lchan->dl_tch_queue, list) count++; while (count >= 2) { tmp = msgb_dequeue(&lchan->dl_tch_queue); msgb_free(tmp); count--; } msgb_enqueue(&lchan->dl_tch_queue, msg); } static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr, enum osmo_mph_info_type type, uint8_t sacch_only) { struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST, NULL); l1sap.u.info.type = type; l1sap.u.info.u.act_req.chan_nr = chan_nr; l1sap.u.info.u.act_req.sacch_only = sacch_only; return l1sap_down(trx, &l1sap); } int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp) { struct gsm_bts_role_bts *btsb = trx->bts->role; struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; struct gsm48_chan_desc *cd; int rc; LOGP(DL1P, LOGL_INFO, "activating channel chan_nr=%02x trx=%d\n", chan_nr, trx->nr); /* osmo-pcu calls this without a valid 'tp' parameter, so we * need to make sure ew don't crash here */ if (tp && TLVP_PRESENT(tp, GSM48_IE_CHANDESC_2) && TLVP_LEN(tp, GSM48_IE_CHANDESC_2) >= sizeof(*cd)) { cd = (struct gsm48_chan_desc *) TLVP_VAL(tp, GSM48_IE_CHANDESC_2); /* our L1 only supports one global TSC for all channels * one one TRX, so we need to make sure not to activate * channels with a different TSC!! */ if (cd->h0.tsc != (lchan->ts->trx->bts->bsic & 7)) { LOGP(DRSL, LOGL_ERROR, "lchan TSC %u != BSIC-TSC %u\n", cd->h0.tsc, lchan->ts->trx->bts->bsic & 7); return -RSL_ERR_SERV_OPT_UNIMPL; } } lchan->sacch_deact = 0; lchan->s = btsb->radio_link_timeout; rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); if (rc) return -RSL_ERR_EQUIPMENT_FAIL; return 0; } int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr) { LOGP(DL1P, LOGL_INFO, "deactivating channel chan_nr=%02x trx=%d\n", chan_nr, trx->nr); return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); } int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr) { struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; LOGP(DL1P, LOGL_INFO, "deactivating sacch chan_nr=%02x trx=%d\n", chan_nr, trx->nr); lchan->sacch_deact = 1; return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 1); } int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr) { LOGP(DL1P, LOGL_INFO, "modifying channel chan_nr=%02x trx=%d\n", chan_nr, trx->nr); return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0); } osmo-bts-0.4.0/src/common/lchan.c000066400000000000000000000016071260026426200165720ustar00rootroot00000000000000/* OsmoBTS lchan interface */ /* (C) 2012 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state) { lchan->state = state; } osmo-bts-0.4.0/src/common/load_indication.c000066400000000000000000000055731260026426200206330ustar00rootroot00000000000000/* Support for generating RSL Load Indication */ /* (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 static void reset_load_counters(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); /* re-set the counters */ btsb->load.ccch.pch_used = btsb->load.ccch.pch_total = 0; } static void load_timer_cb(void *data) { struct gsm_bts *bts = data; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); unsigned int pch_percent, rach_percent; /* compute percentages */ if (btsb->load.ccch.pch_total == 0) pch_percent = 0; else pch_percent = (btsb->load.ccch.pch_used * 100) / btsb->load.ccch.pch_total; if (pch_percent >= btsb->load.ccch.load_ind_thresh) { /* send RSL load indication message to BSC */ uint16_t buffer_space = paging_buffer_space(btsb->paging_state); rsl_tx_ccch_load_ind_pch(bts, buffer_space); } else { /* This is an extenstion of TS 08.58. We don't only * send load indications if the load is above threshold, * but we also explicitly indicate that we are below * threshold by using the magic value 0xffff */ rsl_tx_ccch_load_ind_pch(bts, 0xffff); } if (btsb->load.rach.total == 0) rach_percent = 0; else rach_percent = (btsb->load.rach.busy * 100) / btsb->load.rach.total; if (rach_percent >= btsb->load.ccch.load_ind_thresh) { /* send RSL load indication message to BSC */ rsl_tx_ccch_load_ind_rach(bts, btsb->load.rach.total, btsb->load.rach.busy, btsb->load.rach.access); } reset_load_counters(bts); /* re-schedule the timer */ osmo_timer_schedule(&btsb->load.ccch.timer, btsb->load.ccch.load_ind_period, 0); } void load_timer_start(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->load.ccch.timer.data = bts; btsb->load.ccch.timer.cb = load_timer_cb; reset_load_counters(bts); osmo_timer_schedule(&btsb->load.ccch.timer, btsb->load.ccch.load_ind_period, 0); } void load_timer_stop(struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); osmo_timer_del(&btsb->load.ccch.timer); } osmo-bts-0.4.0/src/common/logging.c000066400000000000000000000071751260026426200171410ustar00rootroot00000000000000/* libosmocore logging support */ /* (C) 2011 by Andreas Eversberg * (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 static struct log_info_cat bts_log_info_cat[] = { [DRSL] = { .name = "DRSL", .description = "A-bis Radio Siganlling Link (RSL)", .color = "\033[1;35m", .enabled = 1, .loglevel = LOGL_INFO, }, [DOML] = { .name = "DOML", .description = "A-bis Network Management / O&M (NM/OML)", .color = "\033[1;36m", .enabled = 1, .loglevel = LOGL_INFO, }, [DRLL] = { .name = "DRLL", .description = "A-bis Radio Link Layer (RLL)", .color = "\033[1;31m", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DRR] = { .name = "DRR", .description = "Layer3 Radio Resource (RR)", .color = "\033[1;34m", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DMEAS] = { .name = "DMEAS", .description = "Radio Measurement Processing", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DPAG] = { .name = "DPAG", .description = "Paging Subsystem", .color = "\033[1;38m", .enabled = 1, .loglevel = LOGL_INFO, }, [DL1C] = { .name = "DL1C", .description = "Layer 1", .loglevel = LOGL_INFO, .enabled = 1, }, [DL1P] = { .name = "DL1P", .description = "Layer 1 Primitives", .loglevel = LOGL_INFO, .enabled = 0, }, [DDSP] = { .name = "DDSP", .description = "DSP Trace Messages", .loglevel = LOGL_DEBUG, .enabled = 1, }, [DABIS] = { .name = "DABIS", .description = "A-bis Intput Subsystem", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DRTP] = { .name = "DRTP", .description = "Realtime Transfer Protocol", .loglevel = LOGL_NOTICE, .enabled = 1, }, [DPCU] = { .name = "DPCU", .description = "PCU interface", .loglevel = LOGL_NOTICE, .enabled = 1, }, [DHO] = { .name = "DHO", .description = "Handover", .color = "\033[0;37m", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DTRX] = { .name = "DTRX", .description = "TRX interface", .color = "\033[1;33m", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DLOOP] = { .name = "DLOOP", .description = "Control loops", .color = "\033[0;34m", .enabled = 1, .loglevel = LOGL_NOTICE, }, #if 0 [DNS] = { .name = "DNS", .description = "GPRS Network Service (NS)", .enabled = 1, .loglevel = LOGL_INFO, }, [DBSSGP] = { .name = "DBSSGP", .description = "GPRS BSS Gateway Protocol (BSSGP)", .enabled = 1, .loglevel = LOGL_DEBUG, }, [DLLC] = { .name = "DLLC", .description = "GPRS Logical Link Control Protocol (LLC)", .enabled = 1, .loglevel = LOGL_DEBUG, }, #endif }; const struct log_info bts_log_info = { .cat = bts_log_info_cat, .num_cat = ARRAY_SIZE(bts_log_info_cat), }; int bts_log_init(const char *category_mask) { osmo_init_logging(&bts_log_info); if (category_mask) log_parse_category_mask(osmo_stderr_target, category_mask); return 0; } osmo-bts-0.4.0/src/common/measurement.c000066400000000000000000000136431260026426200200350ustar00rootroot00000000000000 #include #include #include #include #include #include /* TS 05.08, Chapter 8.4.1 */ /* measurement period ends at fn % 104 == ? */ static const uint8_t tchf_meas_rep_fn104[] = { [0] = 103, [1] = 12, [2] = 25, [3] = 38, [4] = 51, [5] = 64, [6] = 77, [7] = 90, }; static const uint8_t tchh0_meas_rep_fn104[] = { [0] = 103, [1] = 103, [2] = 25, [3] = 25, [4] = 51, [5] = 51, [6] = 77, [7] = 77, }; static const uint8_t tchh1_meas_rep_fn104[] = { [0] = 12, [1] = 12, [2] = 38, [3] = 38, [4] = 64, [5] = 64, [6] = 90, [7] = 90, }; /* determine if a measurement period ends at the given frame number */ static int is_meas_complete(enum gsm_phys_chan_config pchan, unsigned int ts, unsigned int subch, uint32_t fn) { unsigned int fn_mod; const uint8_t *tbl; int rc = 0; if (ts >= 8) return -EINVAL; if (pchan >= _GSM_PCHAN_MAX) return -EINVAL; switch (pchan) { case GSM_PCHAN_TCH_F: fn_mod = fn % 104; if (tchf_meas_rep_fn104[ts] == fn_mod) rc = 1; break; case GSM_PCHAN_TCH_H: fn_mod = fn % 104; if (subch == 0) tbl = tchh0_meas_rep_fn104; else tbl = tchh1_meas_rep_fn104; if (tbl[ts] == fn_mod) rc = 1; break; case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: fn_mod = fn % 102; if (fn_mod == 11) rc = 1; break; case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: fn_mod = fn % 102; if (fn_mod == 36) rc = 1; break; default: rc = 0; break; } return rc; } /* receive a L1 uplink measurement from L1 */ int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm) { DEBUGP(DMEAS, "%s adding measurement, num_ul_meas=%d\n", gsm_lchan_name(lchan), lchan->meas.num_ul_meas); if (lchan->state != LCHAN_S_ACTIVE) { LOGP(DMEAS, LOGL_NOTICE, "%s measurement during state: %s\n", gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); } if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) { LOGP(DMEAS, LOGL_NOTICE, "%s no space for uplink measurement\n", gsm_lchan_name(lchan)); return -ENOSPC; } memcpy(&lchan->meas.uplink[lchan->meas.num_ul_meas++], ulm, sizeof(*ulm)); return 0; } /* input: BER in steps of .01%, i.e. percent/100 */ static uint8_t ber10k_to_rxqual(uint32_t ber10k) { /* 05.08 / 8.2.4 */ if (ber10k < 20) return 0; if (ber10k < 40) return 1; if (ber10k < 80) return 2; if (ber10k < 160) return 3; if (ber10k < 320) return 4; if (ber10k < 640) return 5; if (ber10k < 1280) return 6; return 7; } static int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) { struct gsm_meas_rep_unidir *mru; uint32_t ber_full_sum = 0; uint32_t irssi_full_sum = 0; uint32_t ber_sub_sum = 0; uint32_t irssi_sub_sum = 0; int32_t taqb_sum = 0; unsigned int num_meas_sub = 0; int i; /* if measurement period is not complete, abort */ if (!is_meas_complete(lchan->ts->pchan, lchan->ts->nr, lchan->nr, fn)) return 0; /* if there are no measurements, skip computation */ if (lchan->meas.num_ul_meas == 0) return 0; /* compute the actual measurements */ /* step 1: add up */ for (i = 0; i < lchan->meas.num_ul_meas; i++) { struct bts_ul_meas *m = &lchan->meas.uplink[i]; ber_full_sum += m->ber10k; irssi_full_sum += m->inv_rssi; taqb_sum += m->ta_offs_qbits; if (m->is_sub) { num_meas_sub++; ber_sub_sum += m->ber10k; irssi_sub_sum += m->inv_rssi; } } /* step 2: divide */ ber_full_sum = ber_full_sum / lchan->meas.num_ul_meas; irssi_full_sum = irssi_full_sum / lchan->meas.num_ul_meas; taqb_sum = taqb_sum / lchan->meas.num_ul_meas; if (num_meas_sub) { ber_sub_sum = ber_sub_sum / num_meas_sub; irssi_sub_sum = irssi_sub_sum / num_meas_sub; } DEBUGP(DMEAS, "%s Computed TA(% 4dqb) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), " "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan), taqb_sum, ber_full_sum/100, ber_full_sum%100, irssi_full_sum, ber_sub_sum/100, ber_sub_sum%100, irssi_sub_sum); /* store results */ mru = &lchan->meas.ul_res; mru->full.rx_lev = dbm2rxlev((int)irssi_full_sum * -1); mru->sub.rx_lev = dbm2rxlev((int)irssi_sub_sum * -1); mru->full.rx_qual = ber10k_to_rxqual(ber_full_sum); mru->sub.rx_qual = ber10k_to_rxqual(ber_sub_sum); lchan->meas.flags |= LC_UL_M_F_RES_VALID; lchan->meas.num_ul_meas = 0; /* send a signal indicating computation is complete */ return 1; } /* build the 3 byte RSL uplinke measurement IE content */ int lchan_build_rsl_ul_meas(struct gsm_lchan *lchan, uint8_t *buf) { struct gsm_meas_rep_unidir *mru = &lchan->meas.ul_res; buf[0] = (mru->full.rx_lev & 0x3f); /* FIXME: DTXu support */ buf[1] = (mru->sub.rx_lev & 0x3f); buf[2] = ((mru->full.rx_qual & 7) << 3) | (mru->sub.rx_qual & 7); return 3; } /* Copied from OpenBSC and enlarged to _GSM_PCHAN_MAX */ static const uint8_t subslots_per_pchan[_GSM_PCHAN_MAX] = { [GSM_PCHAN_NONE] = 0, [GSM_PCHAN_CCCH] = 0, [GSM_PCHAN_CCCH_SDCCH4] = 4, [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, [GSM_PCHAN_TCH_F] = 1, [GSM_PCHAN_TCH_H] = 2, [GSM_PCHAN_SDCCH8_SACCH8C] = 8, [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, /* FIXME: what about dynamic TCH_F_TCH_H ? */ [GSM_PCHAN_TCH_F_PDCH] = 1, }; static int ts_meas_check_compute(struct gsm_bts_trx_ts *ts, uint32_t fn) { int i; const int num_subslots = subslots_per_pchan[ts->pchan]; for (i = 0; i < num_subslots; ++i) { struct gsm_lchan *lchan = &ts->lchan[i]; if (lchan->state != LCHAN_S_ACTIVE) continue; switch (lchan->type) { case GSM_LCHAN_SDCCH: case GSM_LCHAN_TCH_F: case GSM_LCHAN_TCH_H: case GSM_LCHAN_PDTCH: lchan_meas_check_compute(lchan, fn); break; default: break; } } return 0; } /* needs to be called once every TDMA frame ! */ int trx_meas_check_compute(struct gsm_bts_trx *trx, uint32_t fn) { int i; for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; ts_meas_check_compute(ts, fn); } return 0; } osmo-bts-0.4.0/src/common/msg_utils.c000066400000000000000000000116551260026426200175170ustar00rootroot00000000000000/* (C) 2014 by sysmocom s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 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 #include #include static int check_fom(struct abis_om_hdr *omh, size_t len) { if (omh->length != len) { LOGP(DL1C, LOGL_ERROR, "Incorrect OM hdr length value %d %zu\n", omh->length, len); return -1; } if (len < sizeof(struct abis_om_fom_hdr)) { LOGP(DL1C, LOGL_ERROR, "FOM header insufficient space %zu %zu\n", len, sizeof(struct abis_om_fom_hdr)); return -1; } return 0; } static int check_manuf(struct msgb *msg, struct abis_om_hdr *omh, size_t msg_size) { int type; size_t size; if (msg_size < 1) { LOGP(DL1C, LOGL_ERROR, "No ManId Length Indicator %zu\n", msg_size); return -1; } if (omh->data[0] >= msg_size - 1) { LOGP(DL1C, LOGL_ERROR, "Insufficient message space for this ManId Length %d %zu\n", omh->data[0], msg_size - 1); return -1; } if (omh->data[0] == sizeof(abis_nm_ipa_magic) && strncmp(abis_nm_ipa_magic, (const char *)omh->data + 1, sizeof(abis_nm_ipa_magic)) == 0) { type = OML_MSG_TYPE_IPA; size = sizeof(abis_nm_ipa_magic) + 1; } else if (omh->data[0] == sizeof(abis_nm_osmo_magic) && strncmp(abis_nm_osmo_magic, (const char *) omh->data + 1, sizeof(abis_nm_osmo_magic)) == 0) { type = OML_MSG_TYPE_OSMO; size = sizeof(abis_nm_osmo_magic) + 1; } else { LOGP(DL1C, LOGL_ERROR, "Manuf Label Unknown\n"); return -1; } /* we have verified that the vendor string fits */ msg->l3h = omh->data + size; if (check_fom(omh, msgb_l3len(msg)) != 0) return -1; return type; } /** * Return 0 in case the IPA structure is okay and in this * case the l2h will be set to the beginning of the data. */ int msg_verify_ipa_structure(struct msgb *msg) { struct ipaccess_head *hh; if (msgb_l1len(msg) < sizeof(struct ipaccess_head)) { LOGP(DL1C, LOGL_ERROR, "Ipa header insufficient space %d %d\n", msgb_l1len(msg), sizeof(struct ipaccess_head)); return -1; } hh = (struct ipaccess_head *) msg->l1h; if (ntohs(hh->len) != msgb_l1len(msg) - sizeof(struct ipaccess_head)) { LOGP(DL1C, LOGL_ERROR, "Incorrect ipa header msg size %d %d\n", ntohs(hh->len), msgb_l1len(msg) - sizeof(struct ipaccess_head)); return -1; } if (hh->proto == IPAC_PROTO_OSMO) { struct ipaccess_head_ext *hh_ext = (struct ipaccess_head_ext *) hh->data; if (ntohs(hh->len) < sizeof(*hh_ext)) { LOGP(DL1C, LOGL_ERROR, "IPA length shorter than OSMO header\n"); return -1; } msg->l2h = hh_ext->data; } else msg->l2h = hh->data; return 0; } /** * \brief Verify the structure of the OML message and set l3h * * This function verifies that the data in \param in msg is a proper * OML message. This code assumes that msg->l2h points to the * beginning of the OML message. In the successful case the msg->l3h * will be set and will point to the FOM header. The value is undefined * in all other cases. * * \param msg The message to analyze starting from msg->l2h. * \return In case the structure is correct a positive number will be * returned and msg->l3h will point to the FOM. The number is a * classification of the vendor type of the message. */ int msg_verify_oml_structure(struct msgb *msg) { struct abis_om_hdr *omh; if (msgb_l2len(msg) < sizeof(*omh)) { LOGP(DL1C, LOGL_ERROR, "Om header insufficient space %d %d\n", msgb_l2len(msg), sizeof(*omh)); return -1; } omh = (struct abis_om_hdr *) msg->l2h; if (omh->mdisc != ABIS_OM_MDISC_FOM && omh->mdisc != ABIS_OM_MDISC_MANUF) { LOGP(DL1C, LOGL_ERROR, "Incorrect om mdisc value %x\n", omh->mdisc); return -1; } if (omh->placement != ABIS_OM_PLACEMENT_ONLY) { LOGP(DL1C, LOGL_ERROR, "Incorrect om placement value %x %x\n", omh->placement, ABIS_OM_PLACEMENT_ONLY); return -1; } if (omh->sequence != 0) { LOGP(DL1C, LOGL_ERROR, "Incorrect om sequence value %d\n", omh->sequence); return -1; } if (omh->mdisc == ABIS_OM_MDISC_MANUF) return check_manuf(msg, omh, msgb_l2len(msg) - sizeof(*omh)); msg->l3h = omh->data; if (check_fom(omh, msgb_l3len(msg)) != 0) return -1; return OML_MSG_TYPE_ETSI; } osmo-bts-0.4.0/src/common/oml.c000066400000000000000000001006531260026426200162750ustar00rootroot00000000000000/* GSM TS 12.21 O&M / OML, BTS side */ /* (C) 2011 by Andreas Eversberg * (C) 2011-2013 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 . * */ /* * Operation and Maintainance Messages */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* FIXME: move this to libosmocore */ static struct tlv_definition abis_nm_att_tlvdef_ipa = { .def = { /* ip.access specifics */ [NM_ATT_IPACC_DST_IP] = { TLV_TYPE_FIXED, 4 }, [NM_ATT_IPACC_DST_IP_PORT] = { TLV_TYPE_FIXED, 2 }, [NM_ATT_IPACC_STREAM_ID] = { TLV_TYPE_TV, }, [NM_ATT_IPACC_SEC_OML_CFG] = { TLV_TYPE_FIXED, 6 }, [NM_ATT_IPACC_IP_IF_CFG] = { TLV_TYPE_FIXED, 8 }, [NM_ATT_IPACC_IP_GW_CFG] = { TLV_TYPE_FIXED, 12 }, [NM_ATT_IPACC_IN_SERV_TIME] = { TLV_TYPE_FIXED, 4 }, [NM_ATT_IPACC_LOCATION] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_PAGING_CFG] = { TLV_TYPE_FIXED, 2 }, [NM_ATT_IPACC_UNIT_ID] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_UNIT_NAME] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_SNMP_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_NV_FLAGS] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_FREQ_CTRL] = { TLV_TYPE_FIXED, 2 }, [NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_CUR_SW_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_TIMING_BUS] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_CGI] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_RAC] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_OBJ_VERSION] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V }, [NM_ATT_IPACC_NSEI] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_BVCI] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_NSVCI] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_NS_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_BSSGP_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_NS_LINK_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_RLC_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V }, [NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_TIB_CONTROL] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_SUPP_FEATURES] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_CODING_SCHEMES] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_RLC_CFG_2] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_HEARTB_TOUT] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_UPTIME] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_RLC_CFG_3] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_SSL_CFG] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_SEC_POSSIBLE] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_IML_SSL_STATE] = { TLV_TYPE_TL16V }, [NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V }, }, }; static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg); /* * support */ struct tlv_parsed *tlvp_copy(const struct tlv_parsed *tp_orig, void *ctx) { struct tlv_parsed *tp_out; unsigned int i; tp_out = talloc_zero(ctx, struct tlv_parsed); if (!tp_out) return NULL; /* if the original is NULL, return empty tlvp */ if (!tp_orig) return tp_out; for (i = 0; i < ARRAY_SIZE(tp_orig->lv); i++) { unsigned int len = tp_orig->lv[i].len; tp_out->lv[i].len = len; if (len && tp_out->lv[i].val) { tp_out->lv[i].val = talloc_zero_size(tp_out, len); if (!tp_out->lv[i].val) { talloc_free(tp_out); return NULL; } memcpy((uint8_t *)tp_out->lv[i].val, tp_orig->lv[i].val, len); } } return tp_out; } /* merge all attributes of 'new' into 'out' */ int tlvp_merge(struct tlv_parsed *out, const struct tlv_parsed *new) { unsigned int i; for (i = 0; i < ARRAY_SIZE(out->lv); i++) { unsigned int len = new->lv[i].len; if (len == 0 || new->lv[i].val == NULL) continue; if (out->lv[i].val) { talloc_free((uint8_t *) out->lv[i].val); out->lv[i].len = 0; } out->lv[i].val = talloc_zero_size(out, len); if (!out->lv[i].val) return -ENOMEM; memcpy((uint8_t *) out->lv[i].val, new->lv[i].val, len); } return 0; } static int oml_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len) { return tlv_parse(tp, &abis_nm_att_tlvdef_ipa, buf, len, 0, 0); } struct msgb *oml_msgb_alloc(void) { return msgb_alloc_headroom(1024, 128, "OML"); } int oml_send_msg(struct msgb *msg, int is_manuf) { struct abis_om_hdr *omh; if (is_manuf) { /* length byte, string + 0 termination */ uint8_t *manuf = msgb_push(msg, 1 + sizeof(abis_nm_ipa_magic)); manuf[0] = strlen(abis_nm_ipa_magic)+1; memcpy(manuf+1, abis_nm_ipa_magic, strlen(abis_nm_ipa_magic)); } /* Push the main OML header and send it off */ omh = (struct abis_om_hdr *) msgb_push(msg, sizeof(*omh)); if (is_manuf) omh->mdisc = ABIS_OM_MDISC_MANUF; else omh->mdisc = ABIS_OM_MDISC_FOM; omh->placement = ABIS_OM_PLACEMENT_ONLY; omh->sequence = 0; omh->length = msgb_l3len(msg); msg->l2h = (uint8_t *)omh; return abis_oml_sendmsg(msg); } int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type) { struct abis_om_fom_hdr *foh; msg->l3h = msgb_push(msg, sizeof(*foh)); foh = (struct abis_om_fom_hdr *) msg->l3h; foh->msg_type = msg_type; foh->obj_class = mo->obj_class; memcpy(&foh->obj_inst, &mo->obj_inst, sizeof(foh->obj_inst)); /* FIXME: This assumption may not always be correct */ msg->trx = mo->bts->c0; return oml_send_msg(msg, 0); } /* FIXME: move to gsm_data_shared */ static char mo_buf[128]; char *gsm_abis_mo_name(const struct gsm_abis_mo *mo) { snprintf(mo_buf, sizeof(mo_buf), "OC=%s INST=(%02x,%02x,%02x)", get_value_string(abis_nm_obj_class_names, mo->obj_class), mo->obj_inst.bts_nr, mo->obj_inst.trx_nr, mo->obj_inst.ts_nr); return mo_buf; } /* 8.8.1 sending State Changed Event Report */ int oml_tx_state_changed(struct gsm_abis_mo *mo) { struct msgb *nmsg; LOGP(DOML, LOGL_INFO, "%s Tx STATE CHG REP\n", gsm_abis_mo_name(mo)); nmsg = oml_msgb_alloc(); if (!nmsg) return -ENOMEM; /* 9.4.38 Operational State */ msgb_tv_put(nmsg, NM_ATT_OPER_STATE, mo->nm_state.operational); /* 9.4.7 Availability Status */ msgb_tl16v_put(nmsg, NM_ATT_AVAIL_STATUS, 1, &mo->nm_state.availability); return oml_mo_send_msg(mo, nmsg, NM_MT_STATECHG_EVENT_REP); } /* First initialization of MO, does _not_ generate state changes */ void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state) { mo->nm_state.availability = avail_state; mo->nm_state.operational = op_state; } int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state) { int rc = 0; if ((op_state != -1 && mo->nm_state.operational != op_state) || (avail_state != -1 && mo->nm_state.availability != avail_state)) { if (avail_state != -1) { LOGP(DOML, LOGL_INFO, "%s AVAIL STATE %s -> %s\n", gsm_abis_mo_name(mo), abis_nm_avail_name(mo->nm_state.availability), abis_nm_avail_name(avail_state)); mo->nm_state.availability = avail_state; } if (op_state != -1) { LOGP(DOML, LOGL_INFO, "%s OPER STATE %s -> %s\n", gsm_abis_mo_name(mo), abis_nm_opstate_name(mo->nm_state.operational), abis_nm_opstate_name(op_state)); mo->nm_state.operational = op_state; osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, NULL); } /* send state change report */ rc = oml_tx_state_changed(mo); } return rc; } int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type, uint8_t cause) { struct msgb *msg; uint8_t new_msg_type; msg = oml_msgb_alloc(); if (!msg) return -ENOMEM; if (cause) { new_msg_type = orig_msg_type + 2; msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause); } else { new_msg_type = orig_msg_type + 1; } return oml_mo_send_msg(mo, msg, new_msg_type); } int oml_mo_statechg_ack(struct gsm_abis_mo *mo) { struct msgb *msg; msg = oml_msgb_alloc(); if (!msg) return -ENOMEM; msgb_tv_put(msg, NM_ATT_ADM_STATE, mo->nm_state.administrative); return oml_mo_send_msg(mo, msg, NM_MT_CHG_ADM_STATE_ACK); } int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause) { return oml_mo_fom_ack_nack(mo, NM_MT_CHG_ADM_STATE, nack_cause); } int oml_mo_opstart_ack(struct gsm_abis_mo *mo) { return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, 0); } int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause) { return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, nack_cause); } int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause) { struct abis_om_hdr *old_oh = msgb_l2(old_msg); struct abis_om_fom_hdr *old_foh = msgb_l3(old_msg); struct msgb *msg; struct abis_om_fom_hdr *foh; int is_manuf = 0; msg = oml_msgb_alloc(); if (!msg) return -ENOMEM; /* make sure to respond with MANUF if request was MANUF */ if (old_oh->mdisc == ABIS_OM_MDISC_MANUF) is_manuf = 1; msg->trx = old_msg->trx; /* copy over old FOM-Header and later only change the msg_type */ msg->l3h = msgb_push(msg, sizeof(*foh)); foh = (struct abis_om_fom_hdr *) msg->l3h; memcpy(foh, old_foh, sizeof(*foh)); /* alter message type */ if (cause) { LOGP(DOML, LOGL_NOTICE, "Sending FOM NACK with cause %s.\n", abis_nm_nack_cause_name(cause)); foh->msg_type += 2; /* nack */ /* add cause */ msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause); } else { LOGP(DOML, LOGL_DEBUG, "Sending FOM ACK.\n"); foh->msg_type++; /* ack */ } return oml_send_msg(msg, is_manuf); } /* * Formatted O&M messages */ /* 8.3.7 sending SW Activated Report */ int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo) { struct msgb *nmsg; LOGP(DOML, LOGL_INFO, "%s Tx SW ACT REP\n", gsm_abis_mo_name(mo)); nmsg = oml_msgb_alloc(); if (!nmsg) return -ENOMEM; msgb_put(nmsg, sizeof(struct abis_om_fom_hdr)); return oml_mo_send_msg(mo, nmsg, NM_MT_SW_ACTIVATED_REP); } /* TS 12.21 9.4.53 */ enum abis_nm_t200_idx { T200_SDCCH = 0, T200_FACCH_F = 1, T200_FACCH_H = 2, T200_SACCH_TCH_SAPI0 = 3, T200_SACCH_SDCCH = 4, T200_SDCCH_SAPI3 = 5, T200_SACCH_TCH_SAPI3 = 6 }; /* TS 12.21 9.4.53 */ static const uint8_t abis_nm_t200_mult[] = { [T200_SDCCH] = 5, [T200_FACCH_F] = 5, [T200_FACCH_H] = 5, [T200_SACCH_TCH_SAPI0] = 10, [T200_SACCH_SDCCH] = 10, [T200_SDCCH_SAPI3] = 5, [T200_SACCH_TCH_SAPI3] = 10 }; /* 8.6.1 Set BTS Attributes has been received */ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct tlv_parsed tp, *tp_merged; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); int rc, i; const uint8_t *payload; abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx SET BTS ATTR\n"); rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); if (rc < 0) return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); /* Test for globally unsupported stuff here */ if (TLVP_PRESENT(&tp, NM_ATT_BCCH_ARFCN)) { const uint16_t *value = (const uint16_t *) TLVP_VAL(&tp, NM_ATT_BCCH_ARFCN); uint16_t arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); LOGP(DOML, LOGL_NOTICE, "MSG: %s\n", osmo_hexdump(msgb_l3(msg), msgb_l3len(msg))); LOGP(DOML, LOGL_NOTICE, "L3=%p, VAL=%p, DIF=%tu\n", msgb_l3(msg), value, (void *)value - (void *) msgb_l3(msg)); if (arfcn > 1024) { LOGP(DOML, LOGL_NOTICE, "Given ARFCN %d is not supported.\n", arfcn); return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); } } /* 9.4.52 Starting Time */ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) { return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); } /* merge existing BTS attributes with new attributes */ tp_merged = tlvp_copy(bts->mo.nm_attr, bts); tlvp_merge(tp_merged, &tp); /* Ask BTS driver to validate new merged attributes */ rc = bts_model_check_oml(bts, foh->msg_type, bts->mo.nm_attr, tp_merged, bts); if (rc < 0) { talloc_free(tp_merged); return oml_fom_ack_nack(msg, -rc); } /* Success: replace old BTS attributes with new */ talloc_free(bts->mo.nm_attr); bts->mo.nm_attr = tp_merged; /* ... and actually still parse them */ /* 9.4.25 Interference Level Boundaries */ if (TLVP_PRESENT(&tp, NM_ATT_INTERF_BOUND)) { payload = TLVP_VAL(&tp, NM_ATT_INTERF_BOUND); for (i = 0; i < 6; i++) { int16_t boundary = *payload; btsb->interference.boundary[i] = -1 * boundary; } } /* 9.4.24 Intave Parameter */ if (TLVP_PRESENT(&tp, NM_ATT_INTAVE_PARAM)) btsb->interference.intave = *TLVP_VAL(&tp, NM_ATT_INTAVE_PARAM); /* 9.4.14 Connection Failure Criterion */ if (TLVP_PRESENT(&tp, NM_ATT_CONN_FAIL_CRIT)) { const uint8_t *val = TLVP_VAL(&tp, NM_ATT_CONN_FAIL_CRIT); if (TLVP_LEN(&tp, NM_ATT_CONN_FAIL_CRIT) < 2 || val[0] != 0x01 || val[1] < 4 || val[1] > 64) { LOGP(DOML, LOGL_NOTICE, "Given Conn. Failure Criterion " "not supported. Please use critetion 0x01 with " "RADIO_LINK_TIMEOUT value of 4..64\n"); return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); } btsb->radio_link_timeout = val[1]; } /* if val[0] != 0x01: can be 'operator dependent' and needs to * be parsed by bts driver */ /* 9.4.53 T200 */ if (TLVP_PRESENT(&tp, NM_ATT_T200)) { payload = TLVP_VAL(&tp, NM_ATT_T200); for (i = 0; i < ARRAY_SIZE(btsb->t200_ms); i++) btsb->t200_ms[i] = payload[i] * abis_nm_t200_mult[i]; } /* 9.4.31 Maximum Timing Advance */ if (TLVP_PRESENT(&tp, NM_ATT_MAX_TA)) btsb->max_ta = *TLVP_VAL(&tp, NM_ATT_MAX_TA); /* 9.4.39 Overload Period */ if (TLVP_PRESENT(&tp, NM_ATT_OVERL_PERIOD)) btsb->load.overload_period = *TLVP_VAL(&tp, NM_ATT_OVERL_PERIOD); /* 9.4.12 CCCH Load Threshold */ if (TLVP_PRESENT(&tp, NM_ATT_CCCH_L_T)) btsb->load.ccch.load_ind_thresh = *TLVP_VAL(&tp, NM_ATT_CCCH_L_T); /* 9.4.11 CCCH Load Indication Period */ if (TLVP_PRESENT(&tp, NM_ATT_CCCH_L_I_P)) btsb->load.ccch.load_ind_period = *TLVP_VAL(&tp, NM_ATT_CCCH_L_I_P); /* 9.4.44 RACH Busy Threshold */ if (TLVP_PRESENT(&tp, NM_ATT_RACH_B_THRESH)) { int16_t thresh = *TLVP_VAL(&tp, NM_ATT_RACH_B_THRESH); btsb->load.rach.busy_thresh = -1 * thresh; } /* 9.4.45 RACH Load Averaging Slots */ if (TLVP_PRESENT(&tp, NM_ATT_LDAVG_SLOTS)) { btsb->load.rach.averaging_slots = ntohs(tlvp_val16_unal(&tp, NM_ATT_LDAVG_SLOTS)); } /* 9.4.10 BTS Air Timer */ if (TLVP_PRESENT(&tp, NM_ATT_BTS_AIR_TIMER)) { uint8_t t3105 = *TLVP_VAL(&tp, NM_ATT_BTS_AIR_TIMER); if (t3105 == 0) { LOGP(DOML, LOGL_NOTICE, "T3105 must have a value != 0.\n"); return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); } btsb->t3105_ms = t3105 * 10; } /* 9.4.37 NY1 */ if (TLVP_PRESENT(&tp, NM_ATT_NY1)) btsb->ny1 = *TLVP_VAL(&tp, NM_ATT_NY1); /* 9.4.8 BCCH ARFCN */ if (TLVP_PRESENT(&tp, NM_ATT_BCCH_ARFCN)) bts->c0->arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); /* 9.4.9 BSIC */ if (TLVP_PRESENT(&tp, NM_ATT_BSIC)) bts->bsic = *TLVP_VAL(&tp, NM_ATT_BSIC); /* call into BTS driver to apply new attributes to hardware */ return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_BTS, bts); } /* 8.6.2 Set Radio Attributes has been received */ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct tlv_parsed tp, *tp_merged; int rc; abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx SET RADIO CARRIER ATTR\n"); rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); if (rc < 0) return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); /* merge existing BTS attributes with new attributes */ tp_merged = tlvp_copy(trx->mo.nm_attr, trx->bts); tlvp_merge(tp_merged, &tp); /* Ask BTS driver to validate new merged attributes */ rc = bts_model_check_oml(trx->bts, foh->msg_type, trx->mo.nm_attr, tp_merged, trx); if (rc < 0) { talloc_free(tp_merged); return oml_fom_ack_nack(msg, -rc); } /* Success: replace old BTS attributes with new */ talloc_free(trx->mo.nm_attr); trx->mo.nm_attr = tp_merged; /* ... and actually still parse them */ /* 9.4.47 RF Max Power Reduction */ if (TLVP_PRESENT(&tp, NM_ATT_RF_MAXPOWR_R)) { trx->max_power_red = *TLVP_VAL(&tp, NM_ATT_RF_MAXPOWR_R) * 2; LOGP(DOML, LOGL_INFO, "Set RF Max Power Reduction = %d dBm\n", trx->max_power_red); } /* 9.4.5 ARFCN List */ #if 0 if (TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) { uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST); uint16_t _value; uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST); uint16_t arfcn; int i; for (i = 0; i < length; i++) { memcpy(&_value, value, 2); arfcn = ntohs(_value); value += 2; if (arfcn > 1024) return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); trx->arfcn_list[i] = arfcn; LOGP(DOML, LOGL_INFO, " ARFCN list = %d\n", trx->arfcn_list[i]); } trx->arfcn_num = length; } else trx->arfcn_num = 0; #else if (trx != trx->bts->c0 && TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) { const uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST); uint16_t _value; uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST); uint16_t arfcn; if (length != 2) { LOGP(DOML, LOGL_ERROR, "Expecting only one ARFCN, " "because hopping not supported\n"); /* FIXME: send NACK */ return -ENOTSUP; } memcpy(&_value, value, 2); arfcn = ntohs(_value); value += 2; if (arfcn > 1024) return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); trx->arfcn = arfcn; } #endif /* call into BTS driver to apply new attributes to hardware */ return bts_model_apply_oml(trx->bts, msg, tp_merged, NM_OC_RADIO_CARRIER, trx); } static int conf_lchans_for_pchan(struct gsm_bts_trx_ts *ts) { struct gsm_lchan *lchan; unsigned int i; switch (ts->pchan) { case GSM_PCHAN_CCCH_SDCCH4_CBCH: /* fallthrough */ case GSM_PCHAN_CCCH_SDCCH4: for (i = 0; i < 4; i++) { lchan = &ts->lchan[i]; if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH && i == 2) { lchan->type = GSM_LCHAN_CBCH; } else { lchan->type = GSM_LCHAN_SDCCH; } } /* fallthrough */ case GSM_PCHAN_CCCH: lchan = &ts->lchan[4]; lchan->type = GSM_LCHAN_CCCH; break; case GSM_PCHAN_TCH_F: lchan = &ts->lchan[0]; lchan->type = GSM_LCHAN_TCH_F; break; case GSM_PCHAN_TCH_H: for (i = 0; i < 2; i++) { lchan = &ts->lchan[i]; lchan->type = GSM_LCHAN_TCH_H; } break; case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: /* fallthrough */ case GSM_PCHAN_SDCCH8_SACCH8C: for (i = 0; i < 8; i++) { lchan = &ts->lchan[i]; if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH && i == 2) { lchan->type = GSM_LCHAN_CBCH; } else { lchan->type = GSM_LCHAN_SDCCH; } } break; case GSM_PCHAN_PDCH: lchan = &ts->lchan[0]; lchan->type = GSM_LCHAN_PDTCH; break; default: /* FIXME */ break; } return 0; } /* 8.6.3 Set Channel Attributes has been received */ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_bts *bts = ts->trx->bts; struct tlv_parsed tp, *tp_merged; int rc; abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx SET CHAN ATTR\n"); rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); if (rc < 0) return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); /* 9.4.21 HSN... */ /* 9.4.27 MAIO */ if (TLVP_PRESENT(&tp, NM_ATT_HSN) || TLVP_PRESENT(&tp, NM_ATT_MAIO)) { LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Frequency hopping not supported.\n"); return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); } /* 9.4.52 Starting Time */ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) { LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Starting time not supported.\n"); return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); } /* merge existing BTS attributes with new attributes */ tp_merged = tlvp_copy(ts->mo.nm_attr, bts); tlvp_merge(tp_merged, &tp); /* Call into BTS driver to check attribute values */ rc = bts_model_check_oml(bts, foh->msg_type, ts->mo.nm_attr, tp_merged, ts); if (rc < 0) { talloc_free(tp_merged); /* Send NACK */ return oml_fom_ack_nack(msg, -rc); } /* Success: replace old BTS attributes with new */ talloc_free(ts->mo.nm_attr); ts->mo.nm_attr = tp_merged; /* 9.4.13 Channel Combination */ if (TLVP_PRESENT(&tp, NM_ATT_CHAN_COMB)) { uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB); ts->pchan = abis_nm_pchan4chcomb(comb); conf_lchans_for_pchan(ts); } /* 9.4.5 ARFCN List */ /* 9.4.60 TSC */ if (TLVP_PRESENT(&tp, NM_ATT_TSC) && TLVP_LEN(&tp, NM_ATT_TSC) >= 1) { ts->tsc = *TLVP_VAL(&tp, NM_ATT_TSC); } else { /* If there is no TSC specified, use the BCC */ ts->tsc = bts->bsic & 0x3; } LOGP(DOML, LOGL_INFO, "%s SET CHAN ATTR (TSC = %u)\n", gsm_abis_mo_name(&ts->mo), ts->tsc); /* call into BTS driver to apply new attributes to hardware */ return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_CHANNEL, ts); } /* 8.9.2 Opstart has been received */ static int oml_rx_opstart(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_abis_mo *mo; void *obj; abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx OPSTART\n"); /* Step 1: Resolve MO by obj_class/obj_inst */ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); if (!mo || !obj) return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); /* Step 2: Do some global dependency/consistency checking */ if (mo->nm_state.operational == NM_OPSTATE_ENABLED) { DEBUGP(DOML, "... automatic ACK, OP state already was Enabled\n"); return oml_mo_opstart_ack(mo); } /* Step 3: Ask BTS driver to apply the opstart */ return bts_model_opstart(bts, mo, obj); } static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct tlv_parsed tp; struct gsm_abis_mo *mo; uint8_t adm_state; void *obj; int rc; abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx CHG ADM STATE\n"); rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); if (rc < 0) { LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: error during TLV parse\n"); return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); } if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: no ADM state attribute\n"); return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); } adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); /* Step 1: Resolve MO by obj_class/obj_inst */ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); if (!mo || !obj) return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); /* Step 2: Do some global dependency/consistency checking */ if (mo->nm_state.administrative == adm_state) LOGP(DOML, LOGL_NOTICE, "ADM state already was %s\n", get_value_string(abis_nm_adm_state_names, adm_state)); /* Step 3: Ask BTS driver to apply the state chg */ return bts_model_chg_adm_state(bts, mo, obj, adm_state); } static int down_fom(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_bts_trx *trx; int ret; if (msgb_l2len(msg) < sizeof(*foh)) { LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n"); return -EIO; } if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) { LOGP(DOML, LOGL_INFO, "Formatted O&M with BTS %d out of range.\n", foh->obj_inst.bts_nr); return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN); } switch (foh->msg_type) { case NM_MT_SET_BTS_ATTR: ret = oml_rx_set_bts_attr(bts, msg); break; case NM_MT_SET_RADIO_ATTR: trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); if (!trx) return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN); ret = oml_rx_set_radio_attr(trx, msg); break; case NM_MT_SET_CHAN_ATTR: trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); if (!trx) return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN); if (foh->obj_inst.ts_nr >= ARRAY_SIZE(trx->ts)) return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); ret = oml_rx_set_chan_attr(&trx->ts[foh->obj_inst.ts_nr], msg); break; case NM_MT_OPSTART: ret = oml_rx_opstart(bts, msg); break; case NM_MT_CHG_ADM_STATE: ret = oml_rx_chg_adm_state(bts, msg); break; case NM_MT_IPACC_SET_ATTR: ret = oml_ipa_set_attr(bts, msg); break; default: LOGP(DOML, LOGL_INFO, "unknown Formatted O&M msg_type 0x%02x\n", foh->msg_type); ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL); } return ret; } /* * manufacturer related messages */ #ifndef TLVP_PRES_LEN /* old libosmocore */ #define TLVP_PRES_LEN(tp, tag, min_len) \ (TLVP_PRESENT(tp, tag) && TLVP_LEN(tp, tag) >= min_len) #endif static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp) { struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.nse); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSEI, 2)) bts->gprs.nse.nsei = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSEI)); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_CFG, 7)) { memcpy(&bts->gprs.nse.timer, TLVP_VAL(tp, NM_ATT_IPACC_NS_CFG), 7); } if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BSSGP_CFG, 11)) { memcpy(&bts->gprs.cell.timer, TLVP_VAL(tp, NM_ATT_IPACC_BSSGP_CFG), 11); } osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSE_ATTR, bts); return 0; } static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp) { struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.cell); struct gprs_rlc_cfg *rlcc = &bts->gprs.cell.rlc_cfg; const uint8_t *cur; uint16_t _cur_s; if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RAC, 1)) bts->gprs.rac = *TLVP_VAL(tp, NM_ATT_IPACC_RAC); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_GPRS_PAGING_CFG, 2)) { cur = TLVP_VAL(tp, NM_ATT_IPACC_GPRS_PAGING_CFG); rlcc->paging.repeat_time = cur[0] * 50; rlcc->paging.repeat_count = cur[1]; } if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BVCI, 2)) bts->gprs.cell.bvci = htons(tlvp_val16_unal(tp, NM_ATT_IPACC_BVCI)); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG, 9)) { cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG); rlcc->parameter[RLC_T3142] = cur[0]; rlcc->parameter[RLC_T3169] = cur[1]; rlcc->parameter[RLC_T3191] = cur[2]; rlcc->parameter[RLC_T3193] = cur[3]; rlcc->parameter[RLC_T3195] = cur[4]; rlcc->parameter[RLC_N3101] = cur[5]; rlcc->parameter[RLC_N3103] = cur[6]; rlcc->parameter[RLC_N3105] = cur[7]; rlcc->parameter[CV_COUNTDOWN] = cur[8]; } if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_CODING_SCHEMES, 2)) { int i; rlcc->cs_mask = 0; cur = TLVP_VAL(tp, NM_ATT_IPACC_CODING_SCHEMES); for (i = 0; i < 4; i++) { if (cur[0] & (1 << i)) rlcc->cs_mask |= (1 << (GPRS_CS1+i)); } if (cur[0] & 0x80) rlcc->cs_mask |= (1 << GPRS_MCS9); for (i = 0; i < 8; i++) { if (cur[1] & (1 << i)) rlcc->cs_mask |= (1 << (GPRS_MCS1+i)); } } if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_2, 5)) { cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_2); memcpy(&_cur_s, cur, 2); rlcc->parameter[T_DL_TBF_EXT] = ntohs(_cur_s) * 10; cur += 2; memcpy(&_cur_s, cur, 2); rlcc->parameter[T_UL_TBF_EXT] = ntohs(_cur_s) * 10; cur += 2; rlcc->initial_cs = *cur; } if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_3, 1)) { rlcc->initial_mcs = *TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_3); } osmo_signal_dispatch(SS_GLOBAL, S_NEW_CELL_ATTR, bts); return 0; } static int oml_ipa_mo_set_attr_nsvc(struct gsm_bts_gprs_nsvc *nsvc, struct tlv_parsed *tp) { if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSVCI, 2)) nsvc->nsvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSVCI)); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_LINK_CFG, 8)) { const uint8_t *cur = TLVP_VAL(tp, NM_ATT_IPACC_NS_LINK_CFG); uint16_t _cur_s; uint32_t _cur_l; memcpy(&_cur_s, cur, 2); nsvc->remote_port = ntohs(_cur_s); cur += 2; memcpy(&_cur_l, cur, 4); nsvc->remote_ip = ntohl(_cur_l); cur += 4; memcpy(&_cur_s, cur, 2); nsvc->local_port = ntohs(_cur_s); } osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc); return 0; } static int oml_ipa_mo_set_attr(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, struct tlv_parsed *tp) { int rc; switch (mo->obj_class) { case NM_OC_GPRS_NSE: rc = oml_ipa_mo_set_attr_nse(obj, tp); break; case NM_OC_GPRS_CELL: rc = oml_ipa_mo_set_attr_cell(obj, tp); break; case NM_OC_GPRS_NSVC: rc = oml_ipa_mo_set_attr_nsvc(obj, tp); break; default: rc = NM_NACK_OBJINST_UNKN; } return rc; } static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_abis_mo *mo; struct tlv_parsed tp; void *obj; int rc; abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx IPA SET ATTR\n"); rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); if (rc < 0) return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); /* Resolve MO by obj_class/obj_inst */ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); if (!mo || !obj) return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); rc = oml_ipa_mo_set_attr(bts, mo, obj, &tp); return oml_fom_ack_nack(msg, rc); } static int rx_oml_ipa_rsl_connect(struct gsm_bts_trx *trx, struct msgb *msg, struct tlv_parsed *tp) { struct e1inp_sign_link *oml_link = trx->bts->oml_link; uint16_t port = IPA_TCP_PORT_RSL; uint32_t ip = get_signlink_remote_ip(oml_link); struct in_addr in; int rc; uint8_t stream_id = 0; if (TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP)) { ip = ntohl(tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP)); } if (TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP_PORT)) { port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT)); } if (TLVP_PRESENT(tp, NM_ATT_IPACC_STREAM_ID)) { stream_id = *TLVP_VAL(tp, NM_ATT_IPACC_STREAM_ID); } in.s_addr = htonl(ip); LOGP(DOML, LOGL_INFO, "Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", inet_ntoa(in), port, stream_id); rc = e1inp_ipa_bts_rsl_connect(oml_link->ts->line, inet_ntoa(in), port); if (rc < 0) { LOGP(DOML, LOGL_ERROR, "Error in abis_open(RSL): %d\n", rc); return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); } return oml_fom_ack_nack(msg, 0); } static int down_mom(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_hdr *oh = msgb_l2(msg); struct abis_om_fom_hdr *foh; struct gsm_bts_trx *trx; uint8_t idstrlen = oh->data[0]; struct tlv_parsed tp; int ret; if (msgb_l2len(msg) < sizeof(*foh)) { LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n"); return -EIO; } if (strncmp((char *)&oh->data[1], abis_nm_ipa_magic, idstrlen)) { LOGP(DOML, LOGL_ERROR, "Manufacturer OML message != ipaccess not supported\n"); return -EINVAL; } msg->l3h = oh->data + 1 + idstrlen; foh = (struct abis_om_fom_hdr *) msg->l3h; if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) { LOGP(DOML, LOGL_INFO, "Manufacturer O&M with BTS %d out of range.\n", foh->obj_inst.bts_nr); return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN); } ret = oml_tlv_parse(&tp, foh->data, oh->length - sizeof(*foh)); if (ret < 0) { LOGP(DOML, LOGL_ERROR, "TLV parse error %d\n", ret); return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN); } abis_nm_debugp_foh(DOML, foh); DEBUGPC(DOML, "Rx IPACCESS(0x%02x): ", foh->msg_type); switch (foh->msg_type) { case NM_MT_IPACC_RSL_CONNECT: trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); ret = rx_oml_ipa_rsl_connect(trx, msg, &tp); break; case NM_MT_IPACC_SET_ATTR: ret = oml_ipa_set_attr(bts, msg); break; default: LOGP(DOML, LOGL_INFO, "Manufacturer Formatted O&M msg_type 0x%02x\n", foh->msg_type); ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL); } return ret; } /* incoming OML message from BSC */ int down_oml(struct gsm_bts *bts, struct msgb *msg) { struct abis_om_hdr *oh = msgb_l2(msg); int ret = 0; if (msgb_l2len(msg) < 1) { LOGP(DOML, LOGL_NOTICE, "OML message too short\n"); msgb_free(msg); return -EIO; } msg->l3h = (unsigned char *)oh + sizeof(*oh); switch (oh->mdisc) { case ABIS_OM_MDISC_FOM: if (msgb_l2len(msg) < sizeof(*oh)) { LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n"); ret = -EIO; break; } ret = down_fom(bts, msg); break; case ABIS_OM_MDISC_MANUF: if (msgb_l2len(msg) < sizeof(*oh)) { LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n"); ret = -EIO; break; } ret = down_mom(bts, msg); break; default: LOGP(DOML, LOGL_NOTICE, "unknown OML msg_discr 0x%02x\n", oh->mdisc); ret = -EINVAL; } msgb_free(msg); return ret; } int oml_init(void) { DEBUGP(DOML, "Initializing OML attribute definitions\n"); tlv_def_patch(&abis_nm_att_tlvdef_ipa, &abis_nm_att_tlvdef); return 0; } osmo-bts-0.4.0/src/common/paging.c000066400000000000000000000370361260026426200167570ustar00rootroot00000000000000/* Paging message encoding + queue management */ /* (C) 2011-2012 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 . * */ /* TODO: * eMLPP priprity * add P1/P2/P3 rest octets */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_PAGING_BLOCKS_CCCH 9 #define MAX_BS_PA_MFRMS 9 enum paging_record_type { PAGING_RECORD_PAGING, PAGING_RECORD_IMM_ASS }; struct paging_record { struct llist_head list; enum paging_record_type type; union { struct { time_t expiration_time; uint8_t chan_needed; uint8_t identity_lv[9]; } paging; struct { uint8_t msg[GSM_MACBLOCK_LEN]; } imm_ass; } u; }; struct paging_state { struct gsm_bts_role_bts *btsb; /* parameters taken / interpreted from BCCH/CCCH configuration */ struct gsm48_control_channel_descr chan_desc; /* configured otherwise */ unsigned int paging_lifetime; /* in seconds */ unsigned int num_paging_max; /* total number of currently active paging records in queue */ unsigned int num_paging; struct llist_head paging_queue[MAX_PAGING_BLOCKS_CCCH*MAX_BS_PA_MFRMS]; }; unsigned int paging_get_lifetime(struct paging_state *ps) { return ps->paging_lifetime; } unsigned int paging_get_queue_max(struct paging_state *ps) { return ps->num_paging_max; } void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime) { ps->paging_lifetime = lifetime; } void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max) { ps->num_paging_max = queue_max; } static int tmsi_mi_to_uint(uint32_t *out, const uint8_t *tmsi_lv) { if (tmsi_lv[0] < 5) return -EINVAL; if ((tmsi_lv[1] & 7) != GSM_MI_TYPE_TMSI) return -EINVAL; *out = *((uint32_t *)(tmsi_lv+2)); return 0; } /* paging block numbers in a simple non-combined CCCH */ static const uint8_t block_by_tdma51[51] = { 255, 255, /* FCCH, SCH */ 255, 255, 255, 255, /* BCCH */ 0, 0, 0, 0, /* B0(6..9) */ 255, 255, /* FCCH, SCH */ 1, 1, 1, 1, /* B1(12..15) */ 2, 2, 2, 2, /* B2(16..19) */ 255, 255, /* FCCH, SCH */ 3, 3, 3, 3, /* B3(22..25) */ 4, 4, 4, 4, /* B3(26..29) */ 255, 255, /* FCCH, SCH */ 5, 5, 5, 5, /* B3(32..35) */ 6, 6, 6, 6, /* B3(36..39) */ 255, 255, /* FCCH, SCH */ 7, 7, 7, 7, /* B3(42..45) */ 8, 8, 8, 8, /* B3(46..49) */ 255, /* empty */ }; /* get the paging block number _within_ current 51 multiframe */ static int get_pag_idx_n(struct paging_state *ps, struct gsm_time *gt) { int blk_n = block_by_tdma51[gt->t3]; int blk_idx; if (blk_n == 255) return -EINVAL; blk_idx = blk_n - ps->chan_desc.bs_ag_blks_res; if (blk_idx < 0) return -EINVAL; return blk_idx; } /* get paging block index over multiple 51 multiframes */ static int get_pag_subch_nr(struct paging_state *ps, struct gsm_time *gt) { int pag_idx = get_pag_idx_n(ps, gt); unsigned int n_pag_blks_51 = gsm0502_get_n_pag_blocks(&ps->chan_desc); unsigned int mfrm_part; if (pag_idx < 0) return pag_idx; mfrm_part = ((gt->fn / 51) % (ps->chan_desc.bs_pa_mfrms+2)) * n_pag_blks_51; return pag_idx + mfrm_part; } int paging_buffer_space(struct paging_state *ps) { if (ps->num_paging >= ps->num_paging_max) return 0; else return ps->num_paging_max - ps->num_paging; } /* Add an identity to the paging queue */ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, const uint8_t *identity_lv, uint8_t chan_needed) { struct llist_head *group_q = &ps->paging_queue[paging_group]; struct paging_record *pr; if (ps->num_paging >= ps->num_paging_max) { LOGP(DPAG, LOGL_NOTICE, "Dropping paging, queue full (%u)\n", ps->num_paging); return -ENOSPC; } /* Check if we already have this identity */ llist_for_each_entry(pr, group_q, list) { if (pr->type != PAGING_RECORD_PAGING) continue; if (identity_lv[0] == pr->u.paging.identity_lv[0] && !memcmp(identity_lv+1, pr->u.paging.identity_lv+1, identity_lv[0])) { LOGP(DPAG, LOGL_INFO, "Ignoring duplicate paging\n"); pr->u.paging.expiration_time = time(NULL) + ps->paging_lifetime; return -EEXIST; } } pr = talloc_zero(ps, struct paging_record); if (!pr) return -ENOMEM; pr->type = PAGING_RECORD_PAGING; if (*identity_lv + 1 > sizeof(pr->u.paging.identity_lv)) { talloc_free(pr); return -E2BIG; } LOGP(DPAG, LOGL_INFO, "Add paging to queue (group=%u, queue_len=%u)\n", paging_group, ps->num_paging+1); pr->u.paging.expiration_time = time(NULL) + ps->paging_lifetime; pr->u.paging.chan_needed = chan_needed; memcpy(&pr->u.paging.identity_lv, identity_lv, identity_lv[0]+1); /* enqueue the new identity to the HEAD of the queue, * to ensure it will be paged quickly at least once. */ llist_add(&pr->list, group_q); ps->num_paging++; return 0; } /* Add an IMM.ASS message to the paging queue */ int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data, uint8_t len) { struct llist_head *group_q; struct paging_record *pr; uint16_t imsi, paging_group; if (len != GSM_MACBLOCK_LEN + 3) { LOGP(DPAG, LOGL_ERROR, "IMM.ASS invalid length %d\n", len); return -EINVAL; } len -= 3; imsi = 100 * ((*(data++)) - '0'); imsi += 10 * ((*(data++)) - '0'); imsi += (*(data++)) - '0'; paging_group = gsm0502_calc_paging_group(&ps->chan_desc, imsi); group_q = &ps->paging_queue[paging_group]; pr = talloc_zero(ps, struct paging_record); if (!pr) return -ENOMEM; pr->type = PAGING_RECORD_IMM_ASS; LOGP(DPAG, LOGL_INFO, "Add IMM.ASS to queue (group=%u)\n", paging_group); memcpy(pr->u.imm_ass.msg, data, GSM_MACBLOCK_LEN); /* enqueue the new message to the HEAD of the queue */ llist_add(&pr->list, group_q); return 0; } #define L2_PLEN(len) (((len - 1) << 2) | 0x01) static int fill_paging_type_1(uint8_t *out_buf, const uint8_t *identity1_lv, uint8_t chan1, const uint8_t *identity2_lv, uint8_t chan2) { struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf; uint8_t *cur; memset(out_buf, 0, sizeof(*pt1)); pt1->proto_discr = GSM48_PDISC_RR; pt1->msg_type = GSM48_MT_RR_PAG_REQ_1; pt1->pag_mode = GSM48_PM_NORMAL; pt1->cneed1 = chan1 & 3; pt1->cneed2 = chan2 & 3; cur = lv_put(pt1->data, identity1_lv[0], identity1_lv+1); if (identity2_lv) cur = lv_put(cur, identity2_lv[0], identity2_lv+1); pt1->l2_plen = L2_PLEN(cur - out_buf); return cur - out_buf; } static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t cneed1, const uint8_t *tmsi2_lv, uint8_t cneed2, const uint8_t *identity3_lv) { struct gsm48_paging2 *pt2 = (struct gsm48_paging2 *) out_buf; uint8_t *cur; memset(out_buf, 0, sizeof(*pt2)); pt2->proto_discr = GSM48_PDISC_RR; pt2->msg_type = GSM48_MT_RR_PAG_REQ_2; pt2->pag_mode = GSM48_PM_NORMAL; pt2->cneed1 = cneed1; pt2->cneed2 = cneed2; tmsi_mi_to_uint(&pt2->tmsi1, tmsi1_lv); tmsi_mi_to_uint(&pt2->tmsi2, tmsi2_lv); cur = out_buf + sizeof(*pt2); if (identity3_lv) cur = lv_put(pt2->data, identity3_lv[0], identity3_lv+1); pt2->l2_plen = L2_PLEN(cur - out_buf); return cur - out_buf; } static int fill_paging_type_3(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t cneed1, const uint8_t *tmsi2_lv, uint8_t cneed2, const uint8_t *tmsi3_lv, const uint8_t *tmsi4_lv) { struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf; uint8_t *cur; memset(out_buf, 0, sizeof(*pt3)); pt3->proto_discr = GSM48_PDISC_RR; pt3->msg_type = GSM48_MT_RR_PAG_REQ_3; pt3->pag_mode = GSM48_PM_NORMAL; pt3->cneed1 = cneed1; pt3->cneed2 = cneed2; tmsi_mi_to_uint(&pt3->tmsi1, tmsi1_lv); tmsi_mi_to_uint(&pt3->tmsi2, tmsi2_lv); tmsi_mi_to_uint(&pt3->tmsi3, tmsi3_lv); tmsi_mi_to_uint(&pt3->tmsi4, tmsi4_lv); cur = out_buf + sizeof(*pt3); return cur - out_buf; } static const uint8_t empty_id_lv[] = { 0x01, 0xF0 }; static struct paging_record *dequeue_pr(struct llist_head *group_q) { struct paging_record *pr; pr = llist_entry(group_q->next, struct paging_record, list); llist_del(&pr->list); return pr; } static int pr_is_imsi(struct paging_record *pr) { if ((pr->u.paging.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI) return 1; else return 0; } static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n) { int i, j; struct paging_record *t; if (n < 2) return; /* simple bubble sort */ for (i = n-2; i >= 0; i--) { for (j=0; j<=i ; j++) { if (pr_is_imsi(pr[j]) > pr_is_imsi(pr[j+1])) { t = pr[j]; pr[j] = pr[j+1]; pr[j+1] = t; } } } } /* generate paging message for given gsm time */ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt, int *is_empty) { struct llist_head *group_q; int group; int len; *is_empty = 0; ps->btsb->load.ccch.pch_total += 1; group = get_pag_subch_nr(ps, gt); if (group < 0) { LOGP(DPAG, LOGL_ERROR, "Paging called for GSM wrong time: FN %d/%d/%d/%d.\n", gt->fn, gt->t1, gt->t2, gt->t3); return -1; } group_q = &ps->paging_queue[group]; /* There is nobody to be paged, send Type1 with two empty ID */ if (llist_empty(group_q)) { //DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n"); len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0); *is_empty = 1; } else { struct paging_record *pr[4]; unsigned int num_pr = 0, imm_ass = 0; time_t now = time(NULL); unsigned int i, num_imsi = 0; ps->btsb->load.ccch.pch_used += 1; /* get (if we have) up to four paging records */ for (i = 0; i < ARRAY_SIZE(pr); i++) { if (llist_empty(group_q)) break; pr[i] = dequeue_pr(group_q); /* check for IMM.ASS */ if (pr[i]->type == PAGING_RECORD_IMM_ASS) { imm_ass = 1; break; } num_pr++; /* count how many IMSIs are among them */ if (pr_is_imsi(pr[i])) num_imsi++; } /* if we have an IMMEDIATE ASSIGNMENT */ if (imm_ass) { /* re-add paging records */ for (i = 0; i < num_pr; i++) llist_add(&pr[i]->list, group_q); /* get message and free record */ memcpy(out_buf, pr[num_pr]->u.imm_ass.msg, GSM_MACBLOCK_LEN); pcu_tx_pch_data_cnf(gt->fn, pr[num_pr]->u.imm_ass.msg, GSM_MACBLOCK_LEN); talloc_free(pr[num_pr]); return GSM_MACBLOCK_LEN; } /* make sure the TMSIs are ahead of the IMSIs in the array */ sort_pr_tmsi_imsi(pr, num_pr); if (num_pr == 4 && num_imsi == 0) { /* No IMSI: easy case, can use TYPE 3 */ DEBUGP(DPAG, "Tx PAGING TYPE 3 (4 TMSI)\n"); len = fill_paging_type_3(out_buf, pr[0]->u.paging.identity_lv, pr[0]->u.paging.chan_needed, pr[1]->u.paging.identity_lv, pr[1]->u.paging.chan_needed, pr[2]->u.paging.identity_lv, pr[3]->u.paging.identity_lv); } else if (num_pr >= 3 && num_imsi <= 1) { /* 3 or 4, of which only up to 1 is IMSI */ DEBUGP(DPAG, "Tx PAGING TYPE 2 (2 TMSI,1 xMSI)\n"); len = fill_paging_type_2(out_buf, pr[0]->u.paging.identity_lv, pr[0]->u.paging.chan_needed, pr[1]->u.paging.identity_lv, pr[1]->u.paging.chan_needed, pr[2]->u.paging.identity_lv); if (num_pr == 4) { /* re-add #4 for next time */ llist_add(&pr[3]->list, group_q); pr[3] = NULL; } } else if (num_pr == 1) { DEBUGP(DPAG, "Tx PAGING TYPE 1 (1 xMSI,1 empty)\n"); len = fill_paging_type_1(out_buf, pr[0]->u.paging.identity_lv, pr[0]->u.paging.chan_needed, NULL, 0); } else { /* 2 (any type) or * 3 or 4, of which only 2 will be sent */ DEBUGP(DPAG, "Tx PAGING TYPE 1 (2 xMSI)\n"); len = fill_paging_type_1(out_buf, pr[0]->u.paging.identity_lv, pr[0]->u.paging.chan_needed, pr[1]->u.paging.identity_lv, pr[1]->u.paging.chan_needed); if (num_pr >= 3) { /* re-add #4 for next time */ llist_add(&pr[2]->list, group_q); pr[2] = NULL; } if (num_pr == 4) { /* re-add #4 for next time */ llist_add(&pr[3]->list, group_q); pr[3] = NULL; } } for (i = 0; i < num_pr; i++) { /* skip those that we might have re-added above */ if (pr[i] == NULL) continue; /* check if we can expire the paging record, * or if we need to re-queue it */ if (pr[i]->u.paging.expiration_time <= now) { talloc_free(pr[i]); ps->num_paging--; LOGP(DPAG, LOGL_INFO, "Removed paging record, queue_len=%u\n", ps->num_paging); } else llist_add_tail(&pr[i]->list, group_q); } } memset(out_buf+len, 0x2B, GSM_MACBLOCK_LEN-len); return len; } int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc) { LOGP(DPAG, LOGL_INFO, "Paging SI update\n"); ps->chan_desc = *chan_desc; /* FIXME: do we need to re-sort the old paging_records? */ return 0; } static int paging_signal_cbfn(unsigned int subsys, unsigned int signal, void *hdlr_data, void *signal_data) { if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) { struct gsm_bts *bts = signal_data; struct gsm_bts_role_bts *btsb = bts->role; struct paging_state *ps = btsb->paging_state; struct gsm48_system_information_type_3 *si3 = (void *) bts->si_buf[SYSINFO_TYPE_3]; #warning "TODO: Remove this when setting u8NbrOfAgch is implemented properly" if (si3->control_channel_desc.bs_ag_blks_res != 1) LOGP(DPAG, LOGL_ERROR, "Paging: BS_AG_BLKS_RES = %d != 1 not fully supported\n", si3->control_channel_desc.bs_ag_blks_res); paging_si_update(ps, &si3->control_channel_desc); } return 0; } static int initialized = 0; struct paging_state *paging_init(struct gsm_bts_role_bts *btsb, unsigned int num_paging_max, unsigned int paging_lifetime) { struct paging_state *ps; unsigned int i; ps = talloc_zero(btsb, struct paging_state); if (!ps) return NULL; ps->btsb = btsb; ps->paging_lifetime = paging_lifetime; ps->num_paging_max = num_paging_max; for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) INIT_LLIST_HEAD(&ps->paging_queue[i]); if (!initialized) { osmo_signal_register_handler(SS_GLOBAL, paging_signal_cbfn, NULL); initialized = 1; } return ps; } void paging_config(struct paging_state *ps, unsigned int num_paging_max, unsigned int paging_lifetime) { ps->num_paging_max = num_paging_max; ps->paging_lifetime = paging_lifetime; } void paging_reset(struct paging_state *ps) { int i; for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) { struct llist_head *queue = &ps->paging_queue[i]; struct paging_record *pr, *pr2; llist_for_each_entry_safe(pr, pr2, queue, list) { llist_del(&pr->list); talloc_free(pr); ps->num_paging--; } } if (ps->num_paging != 0) LOGP(DPAG, LOGL_NOTICE, "num_paging != 0 after flushing all records?!?\n"); ps->num_paging = 0; } /** * \brief Helper for the unit tests */ int paging_group_queue_empty(struct paging_state *ps, uint8_t grp) { if (grp >= ARRAY_SIZE(ps->paging_queue)) return 1; return llist_empty(&ps->paging_queue[grp]); } int paging_queue_length(struct paging_state *ps) { return ps->num_paging; } osmo-bts-0.4.0/src/common/pcu_sock.c000066400000000000000000000560521260026426200173170ustar00rootroot00000000000000/* pcu_sock.c: Connect from PCU via unix domain socket */ /* (C) 2008-2010 by Harald Welte * (C) 2009-2012 by Andreas Eversberg * (C) 2012 by Holger Hans Peter Freyther * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); extern struct gsm_network bts_gsmnet; extern int pcu_direct; static int avail_lai = 0, avail_nse = 0, avail_cell = 0, avail_nsvc[2] = {0, 0}; static const char *sapi_string[] = { [PCU_IF_SAPI_RACH] = "RACH", [PCU_IF_SAPI_AGCH] = "AGCH", [PCU_IF_SAPI_PCH] = "PCH", [PCU_IF_SAPI_BCCH] = "BCCH", [PCU_IF_SAPI_PDTCH] = "PDTCH", [PCU_IF_SAPI_PRACH] = "PRACH", [PCU_IF_SAPI_PTCCH] = "PTCCH", }; static int pcu_sock_send(struct gsm_network *net, struct msgb *msg); /* FIXME: move this to libosmocore */ int osmo_unixsock_listen(struct osmo_fd *bfd, int type, const char *path); static struct gsm_bts_trx *trx_by_nr(struct gsm_bts *bts, uint8_t trx_nr) { struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) { if (trx->nr == trx_nr) return trx; } return NULL; } /* * PCU messages */ struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx"); if (!msg) return NULL; msgb_put(msg, sizeof(struct gsm_pcu_if)); pcu_prim = (struct gsm_pcu_if *) msg->data; pcu_prim->msg_type = msg_type; pcu_prim->bts_nr = bts_nr; return msg; } int pcu_tx_info_ind(void) { struct gsm_network *net = &bts_gsmnet; struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_info_ind *info_ind; struct gsm_bts *bts; struct gprs_rlc_cfg *rlcc; struct gsm_bts_gprs_nsvc *nsvc; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; int i, j; LOGP(DPCU, LOGL_INFO, "Sending info\n"); /* FIXME: allow multiple BTS */ bts = llist_entry(net->bts_list.next, struct gsm_bts, list); rlcc = &bts->gprs.cell.rlc_cfg; msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; info_ind = &pcu_prim->u.info_ind; info_ind->version = PCU_IF_VERSION; if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) { info_ind->flags |= PCU_IF_FLAG_ACTIVE; LOGP(DPCU, LOGL_INFO, "BTS is up\n"); } else LOGP(DPCU, LOGL_INFO, "BTS is down\n"); if (pcu_direct) info_ind->flags |= PCU_IF_FLAG_SYSMO; /* RAI */ info_ind->mcc = net->mcc; info_ind->mnc = net->mnc; info_ind->lac = bts->location_area_code; info_ind->rac = bts->gprs.rac; /* NSE */ info_ind->nsei = bts->gprs.nse.nsei; memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7); memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11); /* cell attributes */ info_ind->cell_id = bts->cell_identity; info_ind->repeat_time = rlcc->paging.repeat_time; info_ind->repeat_count = rlcc->paging.repeat_count; info_ind->bvci = bts->gprs.cell.bvci; info_ind->t3142 = rlcc->parameter[RLC_T3142]; info_ind->t3169 = rlcc->parameter[RLC_T3169]; info_ind->t3191 = rlcc->parameter[RLC_T3191]; info_ind->t3193_10ms = rlcc->parameter[RLC_T3193]; info_ind->t3195 = rlcc->parameter[RLC_T3195]; info_ind->n3101 = rlcc->parameter[RLC_N3101]; info_ind->n3103 = rlcc->parameter[RLC_N3103]; info_ind->n3105 = rlcc->parameter[RLC_N3105]; info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN]; if (rlcc->cs_mask & (1 << GPRS_CS1)) info_ind->flags |= PCU_IF_FLAG_CS1; if (rlcc->cs_mask & (1 << GPRS_CS2)) info_ind->flags |= PCU_IF_FLAG_CS2; if (rlcc->cs_mask & (1 << GPRS_CS3)) info_ind->flags |= PCU_IF_FLAG_CS3; if (rlcc->cs_mask & (1 << GPRS_CS4)) info_ind->flags |= PCU_IF_FLAG_CS4; if (rlcc->cs_mask & (1 << GPRS_MCS1)) info_ind->flags |= PCU_IF_FLAG_MCS1; if (rlcc->cs_mask & (1 << GPRS_MCS2)) info_ind->flags |= PCU_IF_FLAG_MCS2; if (rlcc->cs_mask & (1 << GPRS_MCS3)) info_ind->flags |= PCU_IF_FLAG_MCS3; if (rlcc->cs_mask & (1 << GPRS_MCS4)) info_ind->flags |= PCU_IF_FLAG_MCS4; if (rlcc->cs_mask & (1 << GPRS_MCS5)) info_ind->flags |= PCU_IF_FLAG_MCS5; if (rlcc->cs_mask & (1 << GPRS_MCS6)) info_ind->flags |= PCU_IF_FLAG_MCS6; if (rlcc->cs_mask & (1 << GPRS_MCS7)) info_ind->flags |= PCU_IF_FLAG_MCS7; if (rlcc->cs_mask & (1 << GPRS_MCS8)) info_ind->flags |= PCU_IF_FLAG_MCS8; if (rlcc->cs_mask & (1 << GPRS_MCS9)) info_ind->flags |= PCU_IF_FLAG_MCS9; #warning "isn't dl_tbf_ext wrong?: * 10 and no ntohs" info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT]; #warning "isn't ul_tbf_ext wrong?: * 10 and no ntohs" info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT]; info_ind->initial_cs = rlcc->initial_cs; info_ind->initial_mcs = rlcc->initial_mcs; /* NSVC */ for (i = 0; i < 2; i++) { nsvc = &bts->gprs.nsvc[i]; info_ind->nsvci[i] = nsvc->nsvci; info_ind->local_port[i] = nsvc->local_port; info_ind->remote_port[i] = nsvc->remote_port; info_ind->remote_ip[i] = nsvc->remote_ip; } for (i = 0; i < 8; i++) { trx = trx_by_nr(bts, i); if (!trx) break; info_ind->trx[i].pdch_mask = 0; info_ind->trx[i].arfcn = trx->arfcn; info_ind->trx[i].hlayer1 = trx_get_hlayer1(trx); for (j = 0; j < 8; j++) { ts = &trx->ts[j]; if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED && ts->pchan == GSM_PCHAN_PDCH) { info_ind->trx[i].pdch_mask |= (1 << j); info_ind->trx[i].tsc[j] = (ts->tsc >= 0) ? ts->tsc : bts->tsc; LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: " "available (tsc=%d arfcn=%d)\n", trx->nr, ts->nr, info_ind->trx[i].tsc[j], info_ind->trx[i].arfcn); } } } return pcu_sock_send(net, msg); } static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal, void *hdlr_data, void *signal_data) { struct gsm_network *net = &bts_gsmnet; struct gsm_bts_gprs_nsvc *nsvc; struct gsm_bts *bts; struct gsm48_system_information_type_3 *si3; int id; if (subsys != SS_GLOBAL) return -EINVAL; switch(signal) { case S_NEW_SYSINFO: bts = signal_data; if (!(bts->si_valid & (1 << SYSINFO_TYPE_3))) break; si3 = (struct gsm48_system_information_type_3 *) bts->si_buf[SYSINFO_TYPE_3]; net->mcc = ((si3->lai.digits[0] & 0x0f) << 8) | (si3->lai.digits[0] & 0xf0) | (si3->lai.digits[1] & 0x0f); net->mnc = ((si3->lai.digits[2] & 0x0f) << 8) | (si3->lai.digits[2] & 0xf0) | ((si3->lai.digits[1] & 0xf0) >> 4); if ((net->mnc & 0x00f) == 0x00f) net->mnc >>= 4; bts->location_area_code = ntohs(si3->lai.lac); bts->cell_identity = si3->cell_identity; avail_lai = 1; break; case S_NEW_NSE_ATTR: bts = signal_data; avail_nse = 1; break; case S_NEW_CELL_ATTR: bts = signal_data; avail_cell = 1; break; case S_NEW_NSVC_ATTR: nsvc = signal_data; id = nsvc->id; if (id < 0 || id > 1) return -EINVAL; avail_nsvc[id] = 1; break; case S_NEW_OP_STATE: break; default: return -EINVAL; } /* If all infos have been received, of if one info is updated after * all infos have been received, transmit info update. */ if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) pcu_tx_info_ind(); return 0; } int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_rts_req *rts_req; struct gsm_bts *bts = ts->trx->bts; LOGP(DPCU, LOGL_DEBUG, "Sending rts request: is_ptcch=%d arfcn=%d " "block=%d\n", is_ptcch, arfcn, block_nr); msg = pcu_msgb_alloc(PCU_IF_MSG_RTS_REQ, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; rts_req = &pcu_prim->u.rts_req; rts_req->sapi = (is_ptcch) ? PCU_IF_SAPI_PTCCH : PCU_IF_SAPI_PDTCH; rts_req->fn = fn; rts_req->arfcn = arfcn; rts_req->trx_nr = ts->trx->nr; rts_req->ts_nr = ts->nr; rts_req->block_nr = block_nr; return pcu_sock_send(&bts_gsmnet, msg); } int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len, int8_t rssi) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_data *data_ind; struct gsm_bts *bts = ts->trx->bts; LOGP(DPCU, LOGL_DEBUG, "Sending data indication: is_ptcch=%d arfcn=%d " "block=%d data=%s\n", is_ptcch, arfcn, block_nr, osmo_hexdump(data, len)); msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_IND, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; data_ind = &pcu_prim->u.data_ind; data_ind->sapi = (is_ptcch) ? PCU_IF_SAPI_PTCCH : PCU_IF_SAPI_PDTCH; data_ind->rssi = rssi; data_ind->fn = fn; data_ind->arfcn = arfcn; data_ind->trx_nr = ts->trx->nr; data_ind->ts_nr = ts->nr; data_ind->block_nr = block_nr; data_ind->rssi = rssi; memcpy(data_ind->data, data, len); data_ind->len = len; return pcu_sock_send(&bts_gsmnet, msg); } int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint8_t ra, uint32_t fn) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_rach_ind *rach_ind; LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, " "fn=%d\n", qta, ra, fn); msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; rach_ind = &pcu_prim->u.rach_ind; rach_ind->sapi = PCU_IF_SAPI_RACH; rach_ind->ra = ra; rach_ind->qta = qta; rach_ind->fn = fn; return pcu_sock_send(&bts_gsmnet, msg); } int pcu_tx_time_ind(uint32_t fn) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_time_ind *time_ind; uint8_t fn13 = fn % 13; /* omit frame numbers not starting at a MAC block */ if (fn13 != 0 && fn13 != 4 && fn13 != 8) return 0; msg = pcu_msgb_alloc(PCU_IF_MSG_TIME_IND, 0); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; time_ind = &pcu_prim->u.time_ind; time_ind->fn = fn; return pcu_sock_send(&bts_gsmnet, msg); } int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed) { struct pcu_sock_state *state = bts_gsmnet.pcu_state; struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_pag_req *pag_req; /* check if identity does not fit: length > sizeof(lv) - 1 */ if (identity_lv[0] >= sizeof(pag_req->identity_lv)) { LOGP(DPCU, LOGL_ERROR, "Paging identity too large (%d)\n", identity_lv[0]); return -EINVAL; } /* socket not created */ if (!state) { LOGP(DPCU, LOGL_DEBUG, "PCU socket not created, ignoring " "paging message\n"); return 0; } msg = pcu_msgb_alloc(PCU_IF_MSG_PAG_REQ, 0); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; pag_req = &pcu_prim->u.pag_req; pag_req->chan_needed = chan_needed; memcpy(pag_req->identity_lv, identity_lv, identity_lv[0] + 1); return pcu_sock_send(&bts_gsmnet, msg); } int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len) { struct gsm_network *net = &bts_gsmnet; struct gsm_bts *bts; struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_data *data_cnf; /* FIXME: allow multiple BTS */ bts = llist_entry(net->bts_list.next, struct gsm_bts, list); LOGP(DPCU, LOGL_INFO, "Sending PCH confirm\n"); msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; data_cnf = &pcu_prim->u.data_cnf; data_cnf->sapi = PCU_IF_SAPI_PCH; data_cnf->fn = fn; memcpy(data_cnf->data, data, len); data_cnf->len = len; return pcu_sock_send(&bts_gsmnet, msg); } static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, struct gsm_pcu_if_data *data_req) { uint8_t is_ptcch; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; struct msgb *msg; int rc = 0; LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d " "block=%d data=%s\n", sapi_string[data_req->sapi], data_req->arfcn, data_req->block_nr, osmo_hexdump(data_req->data, data_req->len)); switch (data_req->sapi) { case PCU_IF_SAPI_BCCH: if (data_req->len == 23) { bts->si_valid |= (1 << SYSINFO_TYPE_13); memcpy(bts->si_buf[SYSINFO_TYPE_13], data_req->data, data_req->len); } else { bts->si_valid &= ~(1 << SYSINFO_TYPE_13); } osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); break; case PCU_IF_SAPI_PCH: if (msg_type == PCU_IF_MSG_PAG_REQ) { /* FIXME: Add function to schedule paging request. * This might not be required, if PCU_IF_MSG_DATA_REQ * is used instead. */ } else { struct gsm_bts_role_bts *btsb = bts->role; paging_add_imm_ass(btsb->paging_state, data_req->data, data_req->len); } break; case PCU_IF_SAPI_AGCH: msg = msgb_alloc(data_req->len, "pcu_agch"); if (!msg) { rc = -ENOMEM; break; } msg->l3h = msgb_put(msg, data_req->len); memcpy(msg->l3h, data_req->data, data_req->len); if (bts_agch_enqueue(bts, msg) < 0) { msgb_free(msg); rc = -EIO; } break; case PCU_IF_SAPI_PDTCH: case PCU_IF_SAPI_PTCCH: trx = trx_by_nr(bts, data_req->trx_nr); if (!trx) { LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " "not existing TRX %d\n", data_req->trx_nr); rc = -EINVAL; break; } ts = &trx->ts[data_req->ts_nr]; is_ptcch = (data_req->sapi == PCU_IF_SAPI_PTCCH); rc = l1sap_pdch_req(ts, is_ptcch, data_req->fn, data_req->arfcn, data_req->block_nr, data_req->data, data_req->len); break; default: LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " "unsupported sapi %d\n", data_req->sapi); rc = -EINVAL; } return rc; } static int pcu_rx_act_req(struct gsm_bts *bts, struct gsm_pcu_if_act_req *act_req) { struct gsm_bts_trx *trx; struct gsm_lchan *lchan; LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TX=%d\n", (act_req->activate) ? "Activate" : "Deactivate", act_req->trx_nr, act_req->ts_nr); trx = trx_by_nr(bts, act_req->trx_nr); if (!trx || act_req->ts_nr >= 8) return -EINVAL; lchan = trx->ts[act_req->ts_nr].lchan; lchan->rel_act_kind = LCHAN_REL_ACT_PCU; if (lchan->type != GSM_LCHAN_PDTCH) { LOGP(DPCU, LOGL_ERROR, "Lchan is not of type PDCH, but %d.\n", lchan->type); return -EINVAL; } if (act_req->activate) l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan), NULL); else l1sap_chan_rel(trx, gsm_lchan2chan_nr(lchan)); return 0; } static int pcu_rx(struct gsm_network *net, uint8_t msg_type, struct gsm_pcu_if *pcu_prim) { int rc = 0; struct gsm_bts *bts; /* FIXME: allow multiple BTS */ bts = llist_entry(net->bts_list.next, struct gsm_bts, list); switch (msg_type) { case PCU_IF_MSG_DATA_REQ: case PCU_IF_MSG_PAG_REQ: rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); break; case PCU_IF_MSG_ACT_REQ: rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req); break; default: LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", msg_type); rc = -EINVAL; } return rc; } /* * PCU socket interface */ struct pcu_sock_state { struct gsm_network *net; struct osmo_fd listen_bfd; /* fd for listen socket */ struct osmo_fd conn_bfd; /* fd for connection to lcr */ struct llist_head upqueue; /* queue for sending messages */ }; static int pcu_sock_send(struct gsm_network *net, struct msgb *msg) { struct pcu_sock_state *state = net->pcu_state; struct osmo_fd *conn_bfd; struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; if (!state) { if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) LOGP(DPCU, LOGL_INFO, "PCU socket not created, " "dropping message\n"); msgb_free(msg); return -EINVAL; } conn_bfd = &state->conn_bfd; if (conn_bfd->fd <= 0) { if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, " "dropping message\n"); msgb_free(msg); return -EIO; } msgb_enqueue(&state->upqueue, msg); conn_bfd->when |= BSC_FD_WRITE; return 0; } static void pcu_sock_close(struct pcu_sock_state *state) { struct osmo_fd *bfd = &state->conn_bfd; struct gsm_bts *bts; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; int i, j; /* FIXME: allow multiple BTS */ bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list); LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n"); close(bfd->fd); bfd->fd = -1; osmo_fd_unregister(bfd); /* re-enable the generation of ACCEPT for new connections */ state->listen_bfd.when |= BSC_FD_READ; #if 0 /* remove si13, ... */ bts->si_valid &= ~(1 << SYSINFO_TYPE_13); osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); #endif /* release PDCH */ for (i = 0; i < 8; i++) { trx = trx_by_nr(bts, i); if (!trx) break; for (j = 0; j < 8; j++) { ts = &trx->ts[j]; if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED && ts->pchan == GSM_PCHAN_PDCH) { ts->lchan->rel_act_kind = LCHAN_REL_ACT_PCU; l1sap_chan_rel(trx, gsm_lchan2chan_nr(ts->lchan)); } } } /* flush the queue */ while (!llist_empty(&state->upqueue)) { struct msgb *msg = msgb_dequeue(&state->upqueue); msgb_free(msg); } } static int pcu_sock_read(struct osmo_fd *bfd) { struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; struct gsm_pcu_if *pcu_prim; struct msgb *msg; int rc; msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx"); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->tail; rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); if (rc == 0) goto close; if (rc < 0) { if (errno == EAGAIN) return 0; goto close; } rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim); /* as we always synchronously process the message in pcu_rx() and * its callbacks, we can free the message here. */ msgb_free(msg); return rc; close: msgb_free(msg); pcu_sock_close(state); return -1; } static int pcu_sock_write(struct osmo_fd *bfd) { struct pcu_sock_state *state = bfd->data; int rc; while (!llist_empty(&state->upqueue)) { struct msgb *msg, *msg2; struct gsm_pcu_if *pcu_prim; /* peek at the beginning of the queue */ msg = llist_entry(state->upqueue.next, struct msgb, list); pcu_prim = (struct gsm_pcu_if *)msg->data; bfd->when &= ~BSC_FD_WRITE; /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO " "bytes!\n", pcu_prim->msg_type); goto dontsend; } /* try to send it over the socket */ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); if (rc == 0) goto close; if (rc < 0) { if (errno == EAGAIN) { bfd->when |= BSC_FD_WRITE; break; } goto close; } dontsend: /* _after_ we send it, we can deueue */ msg2 = msgb_dequeue(&state->upqueue); assert(msg == msg2); msgb_free(msg); } return 0; close: pcu_sock_close(state); return -1; } static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags) { int rc = 0; if (flags & BSC_FD_READ) rc = pcu_sock_read(bfd); if (rc < 0) return rc; if (flags & BSC_FD_WRITE) rc = pcu_sock_write(bfd); return rc; } /* accept connection comming from PCU */ static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags) { struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; struct osmo_fd *conn_bfd = &state->conn_bfd; struct sockaddr_un un_addr; socklen_t len; int rc; len = sizeof(un_addr); rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); if (rc < 0) { LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n"); return -1; } if (conn_bfd->fd >= 0) { LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have " "another active connection ?!?\n"); /* We already have one PCU connected, this is all we support */ state->listen_bfd.when &= ~BSC_FD_READ; close(rc); return 0; } conn_bfd->fd = rc; conn_bfd->when = BSC_FD_READ; conn_bfd->cb = pcu_sock_cb; conn_bfd->data = state; if (osmo_fd_register(conn_bfd) != 0) { LOGP(DPCU, LOGL_ERROR, "Failed to register new connection " "fd\n"); close(conn_bfd->fd); conn_bfd->fd = -1; return -1; } LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n"); /* send current info */ pcu_tx_info_ind(); return 0; } int pcu_sock_init(void) { struct pcu_sock_state *state; struct osmo_fd *bfd; int rc; state = talloc_zero(NULL, struct pcu_sock_state); if (!state) return -ENOMEM; INIT_LLIST_HEAD(&state->upqueue); state->net = &bts_gsmnet; state->conn_bfd.fd = -1; bfd = &state->listen_bfd; rc = osmo_unixsock_listen(bfd, SOCK_SEQPACKET, "/tmp/pcu_bts"); if (rc < 0) { LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n", strerror(errno)); talloc_free(state); return rc; } bfd->when = BSC_FD_READ; bfd->cb = pcu_sock_accept; bfd->data = state; rc = osmo_fd_register(bfd); if (rc < 0) { LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n", rc); close(bfd->fd); talloc_free(state); return rc; } osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); bts_gsmnet.pcu_state = state; return 0; } void pcu_sock_exit(void) { struct pcu_sock_state *state = bts_gsmnet.pcu_state; struct osmo_fd *bfd, *conn_bfd; if (!state) return; osmo_signal_unregister_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); conn_bfd = &state->conn_bfd; if (conn_bfd->fd > 0) pcu_sock_close(state); bfd = &state->listen_bfd; close(bfd->fd); osmo_fd_unregister(bfd); talloc_free(state); bts_gsmnet.pcu_state = NULL; } /* FIXME: move this to libosmocore */ int osmo_unixsock_listen(struct osmo_fd *bfd, int type, const char *path) { struct sockaddr_un local; unsigned int namelen; int rc; bfd->fd = socket(AF_UNIX, type, 0); if (bfd->fd < 0) { fprintf(stderr, "Failed to create Unix Domain Socket.\n"); return -1; } local.sun_family = AF_UNIX; strncpy(local.sun_path, path, sizeof(local.sun_path)); local.sun_path[sizeof(local.sun_path) - 1] = '\0'; unlink(local.sun_path); /* we use the same magic that X11 uses in Xtranssock.c for * calculating the proper length of the sockaddr */ #if defined(BSD44SOCKETS) || defined(__UNIXWARE__) local.sun_len = strlen(local.sun_path); #endif #if defined(BSD44SOCKETS) || defined(SUN_LEN) namelen = SUN_LEN(&local); #else namelen = strlen(local.sun_path) + offsetof(struct sockaddr_un, sun_path); #endif rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); if (rc != 0) { fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n", local.sun_path); close(bfd->fd); bfd->fd = -1; return -1; } if (listen(bfd->fd, 0) != 0) { fprintf(stderr, "Failed to listen.\n"); close(bfd->fd); bfd->fd = -1; return -1; } return 0; } osmo-bts-0.4.0/src/common/power_control.c000066400000000000000000000052761260026426200204070ustar00rootroot00000000000000/* MS Power Control Loop L1 */ /* (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include /* * Check if manual power control is needed * Check if fixed power was selected * Check if the MS is already using our level if not * the value is bogus.. * TODO: Add a timeout.. e.g. if the ms is not capable of reaching * the value we have set. */ int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, const uint8_t ms_power, const int rxLevel) { int rx; int cur_dBm, new_dBm, new_pwr; struct gsm_bts *bts = lchan->ts->trx->bts; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); const enum gsm_band band = bts->band; if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) return 0; if (lchan->ms_power_ctrl.fixed) return 0; /* The phone hasn't reached the power level yet */ if (lchan->ms_power_ctrl.current != ms_power) return 0; /* * What is the difference between what we want and received? * Ignore a margin that is within the range of measurement * and MS output issues. */ rx = btsb->ul_power_target - rxLevel; if (rx >= 0 && rx < 1) return 0; if (rx < 0 && rx > -1) return 0; /* We don't really care about the truncation of int + float */ cur_dBm = ms_pwr_dbm(band, ms_power); new_dBm = cur_dBm + rx; /* Clamp negative values and do it depending on the band */ if (new_dBm < 0) new_dBm = 0; switch (band) { case GSM_BAND_1800: /* If MS_TX_PWR_MAX_CCH is set the values 29, * 30, 31 are not used. Avoid specifying a dBm * that would lead to these power levels. The * phone might not be able to reach them. */ if (new_dBm > 30) new_dBm = 30; break; default: break; } new_pwr = ms_pwr_ctl_lvl(band, new_dBm); if (lchan->ms_power_ctrl.current != new_pwr) { lchan->ms_power_ctrl.current = new_pwr; bts_model_adjst_ms_pwr(lchan); return 1; } return 0; } osmo-bts-0.4.0/src/common/rsl.c000066400000000000000000001535471260026426200163200ustar00rootroot00000000000000/* GSM TS 08.58 RSL, BTS Side */ /* (C) 2011 by Andreas Eversberg * (C) 2011-2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define FAKE_CIPH_MODE_COMPL static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause); /* list of RSL SI types that can occur on the SACCH */ static const unsigned int rsl_sacch_sitypes[] = { RSL_SYSTEM_INFO_5, RSL_SYSTEM_INFO_6, RSL_SYSTEM_INFO_5bis, RSL_SYSTEM_INFO_5ter, RSL_EXT_MEAS_ORDER, RSL_MEAS_INFO, }; /* FIXME: move this to libosmocore */ int osmo_in_array(unsigned int search, const unsigned int *arr, unsigned int size) { unsigned int i; for (i = 0; i < size; i++) { if (arr[i] == search) return 1; } return 0; } #define OSMO_IN_ARRAY(search, arr) osmo_in_array(search, arr, ARRAY_SIZE(arr)) int msgb_queue_flush(struct llist_head *list) { struct msgb *msg, *msg2; int count = 0; llist_for_each_entry_safe(msg, msg2, list, list) { msgb_free(msg); count++; } return count; } /* FIXME: move this to libosmocore */ void gsm48_gen_starting_time(uint8_t *out, struct gsm_time *gtime) { uint8_t t1p = gtime->t1 % 32; out[0] = (t1p << 3) | (gtime->t3 >> 3); out[1] = (gtime->t3 << 5) | gtime->t2; } /* compute lchan->rsl_cmode and lchan->tch_mode from RSL CHAN MODE IE */ static void lchan_tchmode_from_cmode(struct gsm_lchan *lchan, struct rsl_ie_chan_mode *cm) { lchan->rsl_cmode = cm->spd_ind; switch (cm->chan_rate) { case RSL_CMOD_SP_GSM1: lchan->tch_mode = GSM48_CMODE_SPEECH_V1; break; case RSL_CMOD_SP_GSM2: lchan->tch_mode = GSM48_CMODE_SPEECH_EFR; break; case RSL_CMOD_SP_GSM3: lchan->tch_mode = GSM48_CMODE_SPEECH_AMR; break; case RSL_CMOD_SP_NT_14k5: lchan->tch_mode = GSM48_CMODE_DATA_14k5; break; case RSL_CMOD_SP_NT_12k0: lchan->tch_mode = GSM48_CMODE_DATA_12k0; break; case RSL_CMOD_SP_NT_6k0: lchan->tch_mode = GSM48_CMODE_DATA_6k0; break; } } /* * support */ /** * Handle GSM 08.58 7 Error Handling for the given input. This method will * send either a CHANNEL ACTIVATION NACK, MODE MODIFY NACK or ERROR REPORT * depending on the input of the method. * * TODO: actually make the decision */ static int report_error(struct gsm_bts_trx *trx) { return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT); } #warning merge lchan_lookup with OpenBSC /* determine logical channel based on TRX and channel number IE */ struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr) { struct gsm_lchan *lchan; uint8_t ts_nr = chan_nr & 0x07; uint8_t cbits = chan_nr >> 3; uint8_t lch_idx; struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; if (cbits == 0x01) { lch_idx = 0; /* TCH/F */ if (ts->pchan != GSM_PCHAN_TCH_F && ts->pchan != GSM_PCHAN_PDCH && ts->pchan != GSM_PCHAN_TCH_F_PDCH) LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", chan_nr, ts->pchan); } else if ((cbits & 0x1e) == 0x02) { lch_idx = cbits & 0x1; /* TCH/H */ if (ts->pchan != GSM_PCHAN_TCH_H) LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", chan_nr, ts->pchan); } else if ((cbits & 0x1c) == 0x04) { lch_idx = cbits & 0x3; /* SDCCH/4 */ if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", chan_nr, ts->pchan); } else if ((cbits & 0x18) == 0x08) { lch_idx = cbits & 0x7; /* SDCCH/8 */ if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH) LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", chan_nr, ts->pchan); } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { lch_idx = 0; if (ts->pchan != GSM_PCHAN_CCCH && ts->pchan != GSM_PCHAN_CCCH_SDCCH4) LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", chan_nr, ts->pchan); /* FIXME: we should not return first sdcch4 !!! */ } else { LOGP(DRSL, LOGL_ERROR, "unknown chan_nr=0x%02x\n", chan_nr); return NULL; } lchan = &ts->lchan[lch_idx]; #if 0 log_set_context(BSC_CTX_LCHAN, lchan); if (lchan->conn) log_set_context(BSC_CTX_SUBSCR, lchan->conn->subscr); #endif return lchan; } static struct msgb *rsl_msgb_alloc(int hdr_size) { struct msgb *nmsg; hdr_size += sizeof(struct ipaccess_head); nmsg = msgb_alloc_headroom(600+hdr_size, hdr_size, "RSL"); if (!nmsg) return NULL; nmsg->l3h = nmsg->data; return nmsg; } static void rsl_trx_push_hdr(struct msgb *msg, uint8_t msg_type) { struct abis_rsl_common_hdr *th; th = (struct abis_rsl_common_hdr *) msgb_push(msg, sizeof(*th)); th->msg_discr = ABIS_RSL_MDISC_TRX; th->msg_type = msg_type; } static void rsl_cch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr) { struct abis_rsl_cchan_hdr *cch; cch = (struct abis_rsl_cchan_hdr *) msgb_push(msg, sizeof(*cch)); cch->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN; cch->c.msg_type = msg_type; cch->ie_chan = RSL_IE_CHAN_NR; cch->chan_nr = chan_nr; } static void rsl_dch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr) { struct abis_rsl_dchan_hdr *dch; dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch)); dch->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; dch->c.msg_type = msg_type; dch->ie_chan = RSL_IE_CHAN_NR; dch->chan_nr = chan_nr; } static void rsl_ipa_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr) { struct abis_rsl_dchan_hdr *dch; dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch)); dch->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; dch->c.msg_type = msg_type; dch->ie_chan = RSL_IE_CHAN_NR; dch->chan_nr = chan_nr; } /* * TRX related messages */ /* 8.6.4 sending ERROR REPORT */ static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause) { struct msgb *nmsg; LOGP(DRSL, LOGL_NOTICE, "Tx RSL Error Report: cause = 0x%02x\n", cause); nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_common_hdr)); if (!nmsg) return -ENOMEM; msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause); rsl_trx_push_hdr(nmsg, RSL_MT_ERROR_REPORT); nmsg->trx = trx; return abis_bts_rsl_sendmsg(nmsg); } /* 8.6.1 sending RF RESOURCE INDICATION */ int rsl_tx_rf_res(struct gsm_bts_trx *trx) { struct msgb *nmsg; LOGP(DRSL, LOGL_INFO, "Tx RSL RF RESource INDication\n"); nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_common_hdr)); if (!nmsg) return -ENOMEM; // FIXME: add interference levels of TRX rsl_trx_push_hdr(nmsg, RSL_MT_RF_RES_IND); nmsg->trx = trx; return abis_bts_rsl_sendmsg(nmsg); } /* * common channel releated messages */ /* 8.5.1 BCCH INFOrmation is received */ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg) { struct gsm_bts *bts = trx->bts; struct tlv_parsed tp; uint8_t rsl_si; enum osmo_sysinfo_type osmo_si; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); /* 9.3.30 System Info Type */ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR); rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE); if (OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT); osmo_si = osmo_rsl2sitype(rsl_si); if (osmo_si == SYSINFO_TYPE_NONE) { LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI 0x%02x not supported.\n", rsl_si); return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT); } /* 9.3.39 Full BCCH Information */ if (TLVP_PRESENT(&tp, RSL_IE_FULL_BCCH_INFO)) { uint8_t len = TLVP_LEN(&tp, RSL_IE_FULL_BCCH_INFO); if (len > sizeof(sysinfo_buf_t)) len = sizeof(sysinfo_buf_t); bts->si_valid |= (1 << osmo_si); memset(bts->si_buf[osmo_si], 0x2b, sizeof(sysinfo_buf_t)); memcpy(bts->si_buf[osmo_si], TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s)\n", get_value_string(osmo_sitype_strs, osmo_si)); } else if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); if (len > sizeof(sysinfo_buf_t)) len = sizeof(sysinfo_buf_t); bts->si_valid |= (1 << osmo_si); memset(bts->si_buf[osmo_si], 0x2b, sizeof(sysinfo_buf_t)); memcpy(bts->si_buf[osmo_si], TLVP_VAL(&tp, RSL_IE_L3_INFO), len); LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s)\n", get_value_string(osmo_sitype_strs, osmo_si)); } else { bts->si_valid &= ~(1 << osmo_si); LOGP(DRSL, LOGL_INFO, " RX RSL Disabling BCCH INFO (SI%s)\n", get_value_string(osmo_sitype_strs, osmo_si)); } osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); return 0; } /* 8.5.2 CCCH Load Indication (PCH) */ int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail) { struct msgb *msg; msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); if (!msg) return -ENOMEM; rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_PCH_AGCH); msgb_tv16_put(msg, RSL_IE_PAGING_LOAD, paging_avail); msg->trx = bts->c0; return abis_bts_rsl_sendmsg(msg); } /* 8.5.2 CCCH Load Indication (RACH) */ int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total, uint16_t busy, uint16_t access) { struct msgb *msg; msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); if (!msg) return -ENOMEM; rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_RACH); /* tag and length */ msgb_tv_put(msg, RSL_IE_RACH_LOAD, 6); /* content of the IE */ msgb_put_u16(msg, total); msgb_put_u16(msg, busy); msgb_put_u16(msg, access); msg->trx = bts->c0; return abis_bts_rsl_sendmsg(msg); } /* 8.5.5 PAGING COMMAND */ static int rsl_rx_paging_cmd(struct gsm_bts_trx *trx, struct msgb *msg) { struct gsm_bts_role_bts *btsb = trx->bts->role; struct tlv_parsed tp; uint8_t chan_needed = 0, paging_group; const uint8_t *identity_lv; int rc; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (!TLVP_PRESENT(&tp, RSL_IE_PAGING_GROUP) || !TLVP_PRESENT(&tp, RSL_IE_MS_IDENTITY)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR); paging_group = *TLVP_VAL(&tp, RSL_IE_PAGING_GROUP); identity_lv = TLVP_VAL(&tp, RSL_IE_MS_IDENTITY)-1; if (TLVP_PRESENT(&tp, RSL_IE_CHAN_NEEDED)) chan_needed = *TLVP_VAL(&tp, RSL_IE_CHAN_NEEDED); rc = paging_add_identity(btsb->paging_state, paging_group, identity_lv, chan_needed); if (rc < 0) { /* FIXME: notfiy the BSC somehow ?*/ } pcu_tx_pag_req(identity_lv, chan_needed); return 0; } /* 8.5.8 SMS BROADCAST COMMAND */ static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg) { struct tlv_parsed tp; struct rsl_ie_cb_cmd_type *cb_cmd_type; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (!TLVP_PRESENT(&tp, RSL_IE_CB_CMD_TYPE) || !TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR); cb_cmd_type = (struct rsl_ie_cb_cmd_type *) TLVP_VAL(&tp, RSL_IE_CB_CMD_TYPE); return bts_process_smscb_cmd(trx->bts, *cb_cmd_type, TLVP_LEN(&tp, RSL_IE_SMSCB_MSG), TLVP_VAL(&tp, RSL_IE_SMSCB_MSG)); } /* 8.6.2 SACCH FILLING */ static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg) { struct gsm_bts *bts = trx->bts; struct tlv_parsed tp; uint8_t rsl_si; enum osmo_sysinfo_type osmo_si; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); /* 9.3.30 System Info Type */ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR); rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE); if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT); osmo_si = osmo_rsl2sitype(rsl_si); if (osmo_si == SYSINFO_TYPE_NONE) { LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si); return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT); } if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); /* We have to pre-fix with the two-byte LAPDM UI header */ if (len > sizeof(sysinfo_buf_t)-2) len = sizeof(sysinfo_buf_t)-2; bts->si_valid |= (1 << osmo_si); bts->si_buf[osmo_si][0] = 0x03; /* C/R + EA */ bts->si_buf[osmo_si][1] = 0x03; /* UI frame */ memset(bts->si_buf[osmo_si]+2, 0x2b, sizeof(sysinfo_buf_t)-2); memcpy(bts->si_buf[osmo_si]+2, TLVP_VAL(&tp, RSL_IE_L3_INFO), len); LOGP(DRSL, LOGL_INFO, " Rx RSL SACCH FILLING (SI%s)\n", get_value_string(osmo_sitype_strs, osmo_si)); } else { bts->si_valid &= ~(1 << osmo_si); LOGP(DRSL, LOGL_INFO, " Rx RSL Disabling SACCH FILLING (SI%s)\n", get_value_string(osmo_sitype_strs, osmo_si)); } osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); return 0; } /* 8.5.6 IMMEDIATE ASSIGN COMMAND is received */ static int rsl_rx_imm_ass(struct gsm_bts_trx *trx, struct msgb *msg) { struct tlv_parsed tp; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (!TLVP_PRESENT(&tp, RSL_IE_FULL_IMM_ASS_INFO)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR); /* cut down msg to the 04.08 RR part */ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO); msg->data = msg->l3h; msg->l2h = NULL; msg->len = TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO); /* put into the AGCH queue of the BTS */ if (bts_agch_enqueue(trx->bts, msg) < 0) { /* if there is no space in the queue: send DELETE IND */ msgb_free(msg); } /* return 1 means: don't msgb_free() the msg */ return 1; } /* * dedicated channel related messages */ /* 8.4.19 sending RF CHANnel RELease ACKnowledge */ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) { LOGP(DRSL, LOGL_NOTICE, "%s not sending REL ACK\n", gsm_lchan_name(lchan)); return 0; } LOGP(DRSL, LOGL_NOTICE, "%s Tx RF CHAN REL ACK\n", gsm_lchan_name(lchan)); /* * Free the LAPDm resources now that the BTS * has released all the resources. */ lapdm_channel_exit(&lchan->lapdm_ch); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; rsl_dch_push_hdr(msg, RSL_MT_RF_CHAN_REL_ACK, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* 8.4.2 sending CHANnel ACTIVation ACKnowledge */ int rsl_tx_chan_act_ack(struct gsm_lchan *lchan) { struct gsm_time *gtime = get_time(lchan->ts->trx->bts); struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); uint8_t ie[2]; if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) { LOGP(DRSL, LOGL_NOTICE, "%s not sending CHAN ACT ACK\n", gsm_lchan_name(lchan)); return 0; } LOGP(DRSL, LOGL_NOTICE, "%s Tx CHAN ACT ACK\n", gsm_lchan_name(lchan)); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; gsm48_gen_starting_time(ie, gtime); msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie); rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_ACK, chan_nr); msg->trx = lchan->ts->trx; /* since activation was successful, do some lchan initialization */ lchan->meas.res_nr = 0; return abis_bts_rsl_sendmsg(msg); } /* 8.4.7 sending HANDOver DETection */ int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); LOGP(DRSL, LOGL_INFO, "Sending HANDOver DETect\n"); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; /* 9.3.17 Access Delay */ if (ho_delay) msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, *ho_delay); rsl_dch_push_hdr(msg, RSL_MT_HANDO_DET, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* 8.4.3 sending CHANnel ACTIVation Negative ACK */ int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) { LOGP(DRSL, LOGL_DEBUG, "%s not sending CHAN ACT NACK.\n", gsm_lchan_name(lchan)); return 0; } LOGP(DRSL, LOGL_NOTICE, "%s Sending Channel Activated NACK: cause = 0x%02x\n", gsm_lchan_name(lchan), cause); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; /* 9.3.26 Cause */ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_NACK, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* 8.4.4 sending CONNection FAILure */ int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); LOGP(DRSL, LOGL_NOTICE, "%s Sending Connection Failure: cause = 0x%02x\n", gsm_lchan_name(lchan), cause); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; /* 9.3.26 Cause */ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); rsl_dch_push_hdr(msg, RSL_MT_CONN_FAIL, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* 8.5.3 sending CHANnel ReQuireD */ int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime, uint8_t ra, uint8_t acc_delay) { struct msgb *nmsg; uint8_t payload[3]; LOGP(DRSL, LOGL_NOTICE, "Sending Channel Required\n"); nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); if (!nmsg) return -ENOMEM; /* 9.3.19 Request Reference */ payload[0] = ra; gsm48_gen_starting_time(payload+1, gtime); msgb_tv_fixed_put(nmsg, RSL_IE_REQ_REFERENCE, 3, payload); /* 9.3.17 Access Delay */ msgb_tv_put(nmsg, RSL_IE_ACCESS_DELAY, acc_delay); rsl_cch_push_hdr(nmsg, RSL_MT_CHAN_RQD, 0x88); // FIXME nmsg->trx = trx; return abis_bts_rsl_sendmsg(nmsg); } /* copy the SACCH related sysinfo from BTS global buffer to lchan specific buffer */ static void copy_sacch_si_to_lchan(struct gsm_lchan *lchan) { struct gsm_bts *bts = lchan->ts->trx->bts; unsigned int i; for (i = 0; i < ARRAY_SIZE(rsl_sacch_sitypes); i++) { uint8_t rsl_si = rsl_sacch_sitypes[i]; uint8_t osmo_si = osmo_rsl2sitype(rsl_si); uint8_t osmo_si_shifted = (1 << osmo_si); if (osmo_si == SYSINFO_TYPE_NONE) continue; if (!(bts->si_valid & osmo_si_shifted)) { lchan->si.valid &= ~osmo_si_shifted; continue; } lchan->si.valid |= osmo_si_shifted; memcpy(lchan->si.buf[osmo_si], bts->si_buf[osmo_si], sizeof(sysinfo_buf_t)); } } static int encr_info2lchan(struct gsm_lchan *lchan, const uint8_t *val, uint8_t len) { int rc; struct gsm_bts_role_bts *btsb = bts_role_bts(lchan->ts->trx->bts); /* check if the encryption algorithm sent by BSC is supported! */ rc = bts_supports_cipher(btsb, *val); if (rc != 1) return rc; /* length can be '1' in case of no ciphering */ if (len < 1) return -EINVAL; lchan->encr.alg_id = *val++; lchan->encr.key_len = len -1; if (lchan->encr.key_len > sizeof(lchan->encr.key)) lchan->encr.key_len = sizeof(lchan->encr.key); memcpy(lchan->encr.key, val, lchan->encr.key_len); return 0; } /* 8.4.1 CHANnel ACTIVation is received */ static int rsl_rx_chan_activ(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; struct rsl_ie_chan_mode *cm; struct tlv_parsed tp; uint8_t type; int rc; if (lchan->state != LCHAN_S_NONE) { LOGP(DRSL, LOGL_ERROR, "%s: error lchan is not available state: %s.\n", gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL); } /* Initialize channel defaults */ lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0); lchan->ms_power_ctrl.current = lchan->ms_power; lchan->ms_power_ctrl.fixed = 0; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); /* 9.3.3 Activation Type */ if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) { LOGP(DRSL, LOGL_NOTICE, "missing Activation Type\n"); return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); } type = *TLVP_VAL(&tp, RSL_IE_ACT_TYPE); /* 9.3.6 Channel Mode */ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) { LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n"); return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); } cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE); lchan_tchmode_from_cmode(lchan, cm); /* 9.3.7 Encryption Information */ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO); const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO); if (encr_info2lchan(lchan, val, len) < 0) return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } else memset(&lchan->encr, 0, sizeof(lchan->encr)); /* 9.3.9 Handover Reference */ if ((type == RSL_ACT_INTER_ASYNC || type == RSL_ACT_INTER_SYNC) && TLVP_PRESENT(&tp, RSL_IE_HANDO_REF)) { lchan->ho.active = HANDOVER_ENABLED; lchan->ho.ref = *TLVP_VAL(&tp, RSL_IE_HANDO_REF); } /* 9.3.4 BS Power */ if (TLVP_PRESENT(&tp, RSL_IE_BS_POWER)) lchan->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); /* 9.3.13 MS Power */ if (TLVP_PRESENT(&tp, RSL_IE_MS_POWER)) { lchan->ms_power = *TLVP_VAL(&tp, RSL_IE_MS_POWER); lchan->ms_power_ctrl.current = lchan->ms_power; lchan->ms_power_ctrl.fixed = 0; } /* 9.3.24 Timing Advance */ if (TLVP_PRESENT(&tp, RSL_IE_TIMING_ADVANCE)) lchan->rqd_ta = *TLVP_VAL(&tp, RSL_IE_TIMING_ADVANCE); /* 9.3.32 BS Power Parameters */ /* 9.3.31 MS Power Parameters */ /* 9.3.16 Physical Context */ /* 9.3.29 SACCH Information */ if (TLVP_PRESENT(&tp, RSL_IE_SACCH_INFO)) { uint8_t tot_len = TLVP_LEN(&tp, RSL_IE_SACCH_INFO); const uint8_t *val = TLVP_VAL(&tp, RSL_IE_SACCH_INFO); const uint8_t *cur = val; uint8_t num_msgs = *cur++; unsigned int i; for (i = 0; i < num_msgs; i++) { uint8_t rsl_si = *cur++; uint8_t si_len = *cur++; uint8_t osmo_si; uint8_t copy_len; if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); osmo_si = osmo_rsl2sitype(rsl_si); if (osmo_si == SYSINFO_TYPE_NONE) { LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si); return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } copy_len = si_len; /* We have to pre-fix with the two-byte LAPDM UI header */ if (copy_len > sizeof(sysinfo_buf_t)-2) copy_len = sizeof(sysinfo_buf_t)-2; lchan->si.valid |= (1 << osmo_si); lchan->si.buf[osmo_si][0] = 0x03; lchan->si.buf[osmo_si][1] = 0x03; memset(lchan->si.buf[osmo_si]+2, 0x2b, sizeof(sysinfo_buf_t)-2); memcpy(lchan->si.buf[osmo_si]+2, cur, copy_len); cur += si_len; if (cur >= val + tot_len) { LOGP(DRSL, LOGL_ERROR, "Error parsing SACCH INFO IE\n"); return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } } } else { /* use standard SACCH filling of the BTS */ copy_sacch_si_to_lchan(lchan); } /* 9.3.52 MultiRate Configuration */ if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) { if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) { LOGP(DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1, TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1); amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG), TLVP_LEN(&tp, RSL_IE_MR_CONFIG)); amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), &lchan->tch.amr_mr); lchan->tch.last_cmr = AMR_CMR_NONE; } /* 9.3.53 MultiRate Control */ /* 9.3.54 Supported Codec Types */ LOGP(DRSL, LOGL_INFO, " chan_nr=0x%02x type=0x%02x mode=0x%02x\n", dch->chan_nr, type, lchan->tch_mode); /* actually activate the channel in the BTS */ lchan->rel_act_kind = LCHAN_REL_ACT_RSL; rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr, &tp); if (rc < 0) return rsl_tx_chan_act_nack(lchan, -rc); return 0; } /* 8.4.14 RF CHANnel RELease is received */ static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr) { if (lchan->abis_ip.rtp_socket) { rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC); osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); lchan->abis_ip.rtp_socket = NULL; msgb_queue_flush(&lchan->dl_tch_queue); } /* release handover state */ handover_reset(lchan); lchan->rel_act_kind = LCHAN_REL_ACT_RSL; l1sap_chan_rel(lchan->ts->trx, chan_nr); lapdm_channel_exit(&lchan->lapdm_ch); return 0; } #ifdef FAKE_CIPH_MODE_COMPL /* ugly hack to send a fake CIPH MODE COMPLETE back to the BSC */ #include #include static int tx_ciph_mod_compl_hack(struct gsm_lchan *lchan, uint8_t link_id, const char *imeisv) { struct msgb *fake_msg; struct gsm48_hdr *g48h; uint8_t mid_buf[11]; int rc; fake_msg = rsl_msgb_alloc(128); if (!fake_msg) return -ENOMEM; /* generate 04.08 RR message */ g48h = (struct gsm48_hdr *) msgb_put(fake_msg, sizeof(*g48h)); g48h->proto_discr = GSM48_PDISC_RR; g48h->msg_type = GSM48_MT_RR_CIPH_M_COMPL; /* add IMEISV, if requested */ if (imeisv) { rc = gsm48_generate_mid_from_imsi(mid_buf, imeisv); if (rc > 0) { mid_buf[2] = (mid_buf[2] & 0xf8) | GSM_MI_TYPE_IMEISV; memcpy(msgb_put(fake_msg, rc), mid_buf, rc); } } rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr(lchan), link_id, 1); fake_msg->lchan = lchan; fake_msg->trx = lchan->ts->trx; /* send it back to the BTS */ return abis_bts_rsl_sendmsg(fake_msg); } struct ciph_mod_compl { struct osmo_timer_list timer; struct gsm_lchan *lchan; int send_imeisv; uint8_t link_id; }; static void cmc_timer_cb(void *data) { struct ciph_mod_compl *cmc = data; const char *imeisv = NULL; LOGP(DRSL, LOGL_NOTICE, "%s Sending FAKE CIPHERING MODE COMPLETE to BSC (Alg %u)\n", gsm_lchan_name(cmc->lchan), cmc->lchan->encr.alg_id); if (cmc->send_imeisv) imeisv = "0123456789012345"; /* We have no clue whatsoever that this lchan still exists! */ tx_ciph_mod_compl_hack(cmc->lchan, cmc->link_id, imeisv); talloc_free(cmc); } #endif /* 8.4.6 ENCRYPTION COMMAND */ static int rsl_rx_encr_cmd(struct msgb *msg) { struct gsm_lchan *lchan = msg->lchan; struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct tlv_parsed tp; uint8_t link_id; if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); if (!TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO) || !TLVP_PRESENT(&tp, RSL_IE_L3_INFO) || !TLVP_PRESENT(&tp, RSL_IE_LINK_IDENT)) return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR); /* 9.3.7 Encryption Information */ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO); const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO); if (encr_info2lchan(lchan, val, len) < 0) return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } /* 9.3.2 Link Identifier */ link_id = *TLVP_VAL(&tp, RSL_IE_LINK_IDENT); /* we have to set msg->l3h as rsl_rll_push_l3 will use it to * determine the length field of the L3_INFO IE */ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); /* pop the RSL dchan header, but keep L3 TLV */ msgb_pull(msg, msg->l3h - msg->data); /* push a fake RLL DATA REQ header */ rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, dch->chan_nr, link_id, 1); #ifdef FAKE_CIPH_MODE_COMPL if (lchan->encr.alg_id != RSL_ENC_ALG_A5(0)) { struct ciph_mod_compl *cmc; struct gsm48_hdr *g48h = (struct gsm48_hdr *) msg->l3h; cmc = talloc_zero(NULL, struct ciph_mod_compl); if (g48h->data[0] & 0x10) cmc->send_imeisv = 1; cmc->lchan = lchan; cmc->link_id = link_id; cmc->timer.cb = cmc_timer_cb; cmc->timer.data = cmc; osmo_timer_schedule(&cmc->timer, 1, 0); /* FIXME: send fake CM SERVICE ACCEPT to MS */ return 0; } else #endif { LOGP(DRSL, LOGL_INFO, "%s Fwd RSL ENCR CMD (Alg %u) to LAPDm\n", gsm_lchan_name(lchan), lchan->encr.alg_id); /* hand it into RSLms for transmission of L3_INFO to the MS */ lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); /* return 1 to make sure the msgb is not free'd */ return 1; } } /* 8.4.11 MODE MODIFY NEGATIVE ACKNOWLEDGE */ static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); LOGP(DRSL, LOGL_NOTICE, "%s Tx MODE MODIFY NACK (cause = 0x%02x)\n", gsm_lchan_name(lchan), cause); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; msg->len = 0; msg->data = msg->tail = msg->l3h; /* 9.3.26 Cause */ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_NACK, chan_nr); msg->lchan = lchan; return abis_bts_rsl_sendmsg(msg); } /* 8.4.10 MODE MODIFY ACK */ static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); LOGP(DRSL, LOGL_INFO, "%s Tx MODE MODIF ACK\n", gsm_lchan_name(lchan)); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_ACK, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* 8.4.9 MODE MODIFY */ static int rsl_rx_mode_modif(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; struct rsl_ie_chan_mode *cm; struct tlv_parsed tp; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); /* 9.3.6 Channel Mode */ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) { LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n"); msgb_free(msg); return rsl_tx_mode_modif_nack(lchan, RSL_ERR_MAND_IE_ERROR); } cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE); lchan_tchmode_from_cmode(lchan, cm); /* 9.3.7 Encryption Information */ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO); const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO); if (encr_info2lchan(lchan, val, len) < 0) return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } /* 9.3.45 Main channel reference */ /* 9.3.52 MultiRate Configuration */ if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) { if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) { LOGP(DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1, TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1); amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG), TLVP_LEN(&tp, RSL_IE_MR_CONFIG)); amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), &lchan->tch.amr_mr); lchan->tch.last_cmr = AMR_CMR_NONE; } /* 9.3.53 MultiRate Control */ /* 9.3.54 Supported Codec Types */ l1sap_chan_modify(lchan->ts->trx, dch->chan_nr); /* FIXME: delay this until L1 says OK? */ rsl_tx_mode_modif_ack(lchan); return 0; } /* 8.4.15 MS POWER CONTROL */ static int rsl_rx_ms_pwr_ctrl(struct msgb *msg) { struct gsm_lchan *lchan = msg->lchan; struct tlv_parsed tp; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (TLVP_PRESENT(&tp, RSL_IE_MS_POWER)) { uint8_t pwr = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F; lchan->ms_power_ctrl.fixed = 1; lchan->ms_power_ctrl.current = pwr; LOGP(DRSL, LOGL_NOTICE, "%s forcing power to %d\n", gsm_lchan_name(lchan), lchan->ms_power_ctrl.current); bts_model_adjst_ms_pwr(lchan); } return 0; } /* 8.4.20 SACCH INFO MODify */ static int rsl_rx_sacch_inf_mod(struct msgb *msg) { struct gsm_lchan *lchan = msg->lchan; struct tlv_parsed tp; uint8_t rsl_si, osmo_si; rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (TLVP_PRESENT(&tp, RSL_IE_STARTNG_TIME)) { LOGP(DRSL, LOGL_NOTICE, "Starting time not supported\n"); return rsl_tx_error_report(msg->trx, RSL_ERR_SERV_OPT_UNIMPL); } /* 9.3.30 System Info Type */ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR); rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE); if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); osmo_si = osmo_rsl2sitype(rsl_si); if (osmo_si == SYSINFO_TYPE_NONE) { LOGP(DRSL, LOGL_NOTICE, "%s Rx SACCH SI 0x%02x not supported.\n", gsm_lchan_name(lchan), rsl_si); return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT); } if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); /* We have to pre-fix with the two-byte LAPDM UI header */ if (len > sizeof(sysinfo_buf_t)-2) len = sizeof(sysinfo_buf_t)-2; lchan->si.valid |= (1 << osmo_si); lchan->si.buf[osmo_si][0] = 0x03; lchan->si.buf[osmo_si][1] = 0x03; memset(lchan->si.buf[osmo_si]+2, 0x2b, sizeof(sysinfo_buf_t)-2); memcpy(lchan->si.buf[osmo_si]+2, TLVP_VAL(&tp, RSL_IE_L3_INFO), len); LOGP(DRSL, LOGL_INFO, "%s Rx RSL SACCH FILLING (SI%s)\n", gsm_lchan_name(lchan), get_value_string(osmo_sitype_strs, osmo_si)); } else { lchan->si.valid &= (1 << osmo_si); LOGP(DRSL, LOGL_INFO, "%s Rx RSL Disabling SACCH FILLING (SI%s)\n", gsm_lchan_name(lchan), get_value_string(osmo_sitype_strs, osmo_si)); } return 0; } /* * ip.access related messages */ static void rsl_add_rtp_stats(struct gsm_lchan *lchan, struct msgb *msg) { struct ipa_stats { uint32_t packets_sent; uint32_t octets_sent; uint32_t packets_recv; uint32_t octets_recv; uint32_t packets_lost; uint32_t arrival_jitter; uint32_t avg_tx_delay; } __attribute__((packed)); struct ipa_stats stats; memset(&stats, 0, sizeof(stats)); osmo_rtp_socket_stats(lchan->abis_ip.rtp_socket, &stats.packets_sent, &stats.octets_sent, &stats.packets_recv, &stats.octets_recv, &stats.packets_lost, &stats.arrival_jitter); /* convert to network byte order */ stats.packets_sent = htonl(stats.packets_sent); stats.octets_sent = htonl(stats.octets_sent); stats.packets_recv = htonl(stats.packets_recv); stats.octets_recv = htonl(stats.octets_recv); stats.packets_lost = htonl(stats.packets_lost); msgb_tlv_put(msg, RSL_IE_IPAC_CONN_STAT, sizeof(stats), (uint8_t *) &stats); } int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause) { struct msgb *nmsg; LOGP(DRSL, LOGL_NOTICE, "%s Sending RTP delete indication: cause=%d\n", gsm_lchan_name(lchan), cause); nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!nmsg) return -ENOMEM; msgb_tv16_put(nmsg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id)); rsl_add_rtp_stats(lchan, nmsg); msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause); rsl_ipa_push_hdr(nmsg, RSL_MT_IPAC_DLCX_IND, gsm_lchan2chan_nr(lchan)); nmsg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(nmsg); } /* transmit an CRCX ACK for the lchan */ static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2, uint8_t orig_msgt) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); const char *name; struct in_addr ia; if (orig_msgt == RSL_MT_IPAC_CRCX) name = "CRCX"; else name = "MDCX"; ia.s_addr = htonl(lchan->abis_ip.bound_ip); LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_%s_ACK (local %s:%u, ", gsm_lchan_name(lchan), name, inet_ntoa(ia), lchan->abis_ip.bound_port); ia.s_addr = htonl(lchan->abis_ip.connect_ip); LOGPC(DRSL, LOGL_INFO, "remote %s:%u)\n", inet_ntoa(ia), lchan->abis_ip.connect_port); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; /* Connection ID */ msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id)); /* locally bound IP */ msgb_v_put(msg, RSL_IE_IPAC_LOCAL_IP); msgb_put_u32(msg, lchan->abis_ip.bound_ip); /* locally bound port */ msgb_tv16_put(msg, RSL_IE_IPAC_LOCAL_PORT, lchan->abis_ip.bound_port); if (inc_pt2) { /* RTP Payload Type 2 */ msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, lchan->abis_ip.rtp_payload2); } /* push the header in front */ rsl_ipa_push_hdr(msg, orig_msgt + 1, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } static int rsl_tx_ipac_dlcx_ack(struct gsm_lchan *lchan, int inc_conn_id) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_ACK\n", gsm_lchan_name(lchan)); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; if (inc_conn_id) { msgb_tv_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); rsl_add_rtp_stats(lchan, msg); } rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_ACK, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } static int rsl_tx_ipac_dlcx_nack(struct gsm_lchan *lchan, int inc_conn_id, uint8_t cause) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_NACK\n", gsm_lchan_name(lchan)); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; if (inc_conn_id) msgb_tv_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_NACK, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* transmit an CRCX NACK for the lchan */ static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause, int inc_ipport, uint8_t orig_msgtype) { struct msgb *msg; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); /* FIXME: allocate new msgb and copy old over */ LOGP(DRSL, LOGL_NOTICE, "%s RSL Tx IPAC_BIND_NACK\n", gsm_lchan_name(lchan)); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; if (inc_ipport) { /* remote IP */ msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP); msgb_put_u32(msg, lchan->abis_ip.connect_ip); /* remote port */ msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, htons(lchan->abis_ip.connect_port)); } /* 9.3.26 Cause */ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); /* push the header in front */ rsl_ipa_push_hdr(msg, orig_msgtype + 2, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } static char *get_rsl_local_ip(struct gsm_bts_trx *trx) { struct e1inp_ts *ts = trx->rsl_link->ts; struct sockaddr_storage ss; socklen_t sa_len = sizeof(ss); static char hostbuf[256]; int rc; rc = getsockname(ts->driver.ipaccess.fd.fd, (struct sockaddr *) &ss, &sa_len); if (rc < 0) return NULL; rc = getnameinfo((struct sockaddr *)&ss, sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NUMERICHOST); if (rc < 0) return NULL; return hostbuf; } static int rsl_rx_ipac_XXcx(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct tlv_parsed tp; struct gsm_lchan *lchan = msg->lchan; struct gsm_bts_role_bts *btsb = bts_role_bts(msg->lchan->ts->trx->bts); const uint8_t *payload_type, *speech_mode, *payload_type2; uint32_t connect_ip = 0; uint16_t connect_port = 0; int rc, inc_ip_port = 0, port; char *name; struct in_addr ia; if (dch->c.msg_type == RSL_MT_IPAC_CRCX) name = "CRCX"; else name = "MDCX"; /* check the kind of channel and reject */ if (lchan->type != GSM_LCHAN_TCH_F && lchan->type != GSM_LCHAN_TCH_H) return tx_ipac_XXcx_nack(lchan, 0x52, 0, dch->c.msg_type); rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (rc < 0) return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR, 0, dch->c.msg_type); /* any of these can be NULL!! */ speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE); payload_type = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD); payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2); if (TLVP_PRESENT(&tp, RSL_IE_IPAC_REMOTE_IP)) connect_ip = tlvp_val32_unal(&tp, RSL_IE_IPAC_REMOTE_IP); else LOGP(DRSL, LOGL_NOTICE, "CRCX does not specify a remote IP\n"); if (TLVP_PRESENT(&tp, RSL_IE_IPAC_REMOTE_PORT)) connect_port = tlvp_val16_unal(&tp, RSL_IE_IPAC_REMOTE_PORT); else LOGP(DRSL, LOGL_NOTICE, "CRCX does not specify a remote port\n"); if (dch->c.msg_type == RSL_MT_IPAC_CRCX && connect_ip && connect_port) inc_ip_port = 1; if (payload_type && payload_type2) { LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC %s, " "RTP_PT and RTP_PT2 in same msg !?!\n", gsm_lchan_name(lchan), name); return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR, inc_ip_port, dch->c.msg_type); } if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { char *ipstr = NULL; if (lchan->abis_ip.rtp_socket) { LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC CRCX, " "but we already have socket!\n", gsm_lchan_name(lchan)); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } /* FIXME: select default value depending on speech_mode */ //if (!payload_type) lchan->abis_ip.rtp_socket = osmo_rtp_socket_create(lchan->ts->trx, OSMO_RTP_F_POLL); if (!lchan->abis_ip.rtp_socket) { LOGP(DRSL, LOGL_ERROR, "%s IPAC Failed to create RTP/RTCP sockets\n", gsm_lchan_name(lchan)); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, OSMO_RTP_P_JITBUF, btsb->rtp_jitter_buf_ms); lchan->abis_ip.rtp_socket->priv = lchan; lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb; if (connect_ip && connect_port) { /* if CRCX specifies a remote IP, we can bind() * here to 0.0.0.0 and wait for the connect() * below, after which the kernel will have * selected the local IP address. */ ipstr = "0.0.0.0"; } else { /* if CRCX does not specify a remote IP, we will * not do any connect() below, and thus the * local socket will remain bound to 0.0.0.0 - * which however we cannot legitimately report * back to the BSC in the CRCX_ACK */ ipstr = get_rsl_local_ip(lchan->ts->trx); } rc = osmo_rtp_socket_bind(lchan->abis_ip.rtp_socket, ipstr, -1); if (rc < 0) { LOGP(DRSL, LOGL_ERROR, "%s IPAC Failed to bind RTP/RTCP sockets\n", gsm_lchan_name(lchan)); osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); lchan->abis_ip.rtp_socket = NULL; msgb_queue_flush(&lchan->dl_tch_queue); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } /* FIXME: multiplex connection, BSC proxy */ } else { /* MDCX */ if (!lchan->abis_ip.rtp_socket) { LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC MDCX, " "but we have no RTP socket!\n", gsm_lchan_name(lchan)); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } } /* Special rule: If connect_ip == 0.0.0.0, use RSL IP * address */ if (connect_ip == 0) { struct e1inp_sign_link *sign_link = lchan->ts->trx->rsl_link; ia.s_addr = htonl(get_signlink_remote_ip(sign_link)); } else ia.s_addr = connect_ip; rc = osmo_rtp_socket_connect(lchan->abis_ip.rtp_socket, inet_ntoa(ia), ntohs(connect_port)); if (rc < 0) { LOGP(DRSL, LOGL_ERROR, "%s Failed to connect RTP/RTCP sockets\n", gsm_lchan_name(lchan)); osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); lchan->abis_ip.rtp_socket = NULL; msgb_queue_flush(&lchan->dl_tch_queue); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } /* save IP address and port number */ lchan->abis_ip.connect_ip = ntohl(ia.s_addr); lchan->abis_ip.connect_port = ntohs(connect_port); rc = osmo_rtp_get_bound_ip_port(lchan->abis_ip.rtp_socket, &lchan->abis_ip.bound_ip, &port); if (rc < 0) LOGP(DRSL, LOGL_ERROR, "%s IPAC cannot obtain " "locally bound IP/port: %d\n", gsm_lchan_name(lchan), rc); lchan->abis_ip.bound_port = port; /* Everything has succeeded, we can store new values in lchan */ if (payload_type) { lchan->abis_ip.rtp_payload = *payload_type; if (lchan->abis_ip.rtp_socket) osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket, *payload_type); } if (payload_type2) { lchan->abis_ip.rtp_payload2 = *payload_type2; if (lchan->abis_ip.rtp_socket) osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket, *payload_type2); } if (speech_mode) lchan->abis_ip.speech_mode = *speech_mode; /* FIXME: CSD, jitterbuffer, compression */ return rsl_tx_ipac_XXcx_ack(lchan, payload_type2 ? 1 : 0, dch->c.msg_type); } static int rsl_rx_ipac_dlcx(struct msgb *msg) { struct tlv_parsed tp; struct gsm_lchan *lchan = msg->lchan; int rc, inc_conn_id = 0; rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); if (rc < 0) return rsl_tx_ipac_dlcx_nack(lchan, 0, RSL_ERR_MAND_IE_ERROR); if (TLVP_PRESENT(&tp, RSL_IE_IPAC_CONN_ID)) inc_conn_id = 1; rc = rsl_tx_ipac_dlcx_ack(lchan, inc_conn_id); osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); lchan->abis_ip.rtp_socket = NULL; msgb_queue_flush(&lchan->dl_tch_queue); return rc; } /* * selecting message */ static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_rll_hdr *rh = msgb_l2(msg); struct gsm_lchan *lchan; if (msgb_l2len(msg) < sizeof(*rh)) { LOGP(DRSL, LOGL_NOTICE, "RSL Radio Link Layer message too short\n"); msgb_free(msg); return -EIO; } msg->l3h = (unsigned char *)rh + sizeof(*rh); lchan = rsl_lchan_lookup(trx, rh->chan_nr); if (!lchan) { LOGP(DRLL, LOGL_NOTICE, "Rx RLL %s for unknown lchan\n", rsl_msg_name(rh->c.msg_type)); msgb_free(msg); return report_error(trx); } DEBUGP(DRLL, "%s Rx RLL %s Abis -> LAPDm\n", gsm_lchan_name(lchan), rsl_msg_name(rh->c.msg_type)); /* exception: RLL messages are _NOT_ freed as they are now * owned by LAPDm which might have queued them */ return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); } static inline int rsl_link_id_is_sacch(uint8_t link_id) { if (link_id >> 6 == 1) return 1; else return 0; } static int rslms_is_meas_rep(struct msgb *msg) { struct abis_rsl_common_hdr *rh = msgb_l2(msg); struct abis_rsl_rll_hdr *rllh; struct gsm48_hdr *gh; if ((rh->msg_discr & 0xfe) != ABIS_RSL_MDISC_RLL) return 0; if (rh->msg_type != RSL_MT_UNIT_DATA_IND) return 0; rllh = msgb_l2(msg); if (rsl_link_id_is_sacch(rllh->link_id) == 0) return 0; gh = msgb_l3(msg); if (gh->proto_discr != GSM48_PDISC_RR) return 0; switch (gh->msg_type) { case GSM48_MT_RR_MEAS_REP: case GSM48_MT_RR_EXT_MEAS_REP: return 1; default: break; } /* FIXME: this does not cover the Bter frame format and the associated * short RR protocol descriptor for ENHANCED MEASUREMENT REPORT */ return 0; } /* 8.4.8 MEASUREMENT RESult */ static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len) { struct msgb *msg; uint8_t meas_res[16]; uint8_t chan_nr = gsm_lchan2chan_nr(lchan); int res_valid = lchan->meas.flags & LC_UL_M_F_RES_VALID; LOGP(DRSL, LOGL_NOTICE, "%s Tx MEAS RES valid(%d)\n", gsm_lchan_name(lchan), res_valid); if (!res_valid) return -EINPROGRESS; msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++); int ie_len = lchan_build_rsl_ul_meas(lchan, meas_res); if (ie_len >= 3) { msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, ie_len, meas_res); lchan->meas.flags &= ~LC_UL_M_F_RES_VALID; } msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->meas.bts_tx_pwr); if (lchan->meas.flags & LC_UL_M_F_L1_VALID) { msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, 2, lchan->meas.l1_info); lchan->meas.flags &= ~LC_UL_M_F_L1_VALID; } msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3); //msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, FIXME); rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } /* call-back for LAPDm code, called when it wants to send msgs UP */ int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx) { struct gsm_lchan *lchan = ctx; struct abis_rsl_common_hdr *rh = msgb_l2(msg); if (lchan->state != LCHAN_S_ACTIVE) { LOGP(DRSL, LOGL_INFO, "%s(%s) is not active . Dropping message.\n", gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); msgb_free(msg); return 0; } msg->trx = lchan->ts->trx; /* check if this is a measurement report from SACCH which needs special * processing before forwarding */ if (rslms_is_meas_rep(msg)) { int rc; LOGP(DRSL, LOGL_INFO, "%s Handing RLL msg %s from LAPDm to MEAS REP\n", gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type)); rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg)); msgb_free(msg); return rc; } else { LOGP(DRSL, LOGL_INFO, "%s Fwd RLL msg %s from LAPDm to A-bis\n", gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type)); return abis_bts_rsl_sendmsg(msg); } } static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_cchan_hdr *cch = msgb_l2(msg); int ret = 0; if (msgb_l2len(msg) < sizeof(*cch)) { LOGP(DRSL, LOGL_NOTICE, "RSL Common Channel Management message too short\n"); msgb_free(msg); return -EIO; } msg->l3h = (unsigned char *)cch + sizeof(*cch); msg->lchan = rsl_lchan_lookup(trx, cch->chan_nr); if (!msg->lchan) { LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", rsl_msg_name(cch->c.msg_type)); msgb_free(msg); return report_error(trx); } LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan), rsl_msg_name(cch->c.msg_type)); switch (cch->c.msg_type) { case RSL_MT_BCCH_INFO: ret = rsl_rx_bcch_info(trx, msg); break; case RSL_MT_IMMEDIATE_ASSIGN_CMD: ret = rsl_rx_imm_ass(trx, msg); break; case RSL_MT_PAGING_CMD: ret = rsl_rx_paging_cmd(trx, msg); break; case RSL_MT_SMS_BC_CMD: ret = rsl_rx_sms_bcast_cmd(trx, msg); break; case RSL_MT_SMS_BC_REQ: case RSL_MT_NOT_CMD: LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL cchan msg_type %s\n", rsl_msg_name(cch->c.msg_type)); break; default: LOGP(DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n", cch->c.msg_type); ret = -EINVAL; break; } if (ret != 1) msgb_free(msg); return ret; } static int rsl_rx_dchan(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); int ret = 0; if (msgb_l2len(msg) < sizeof(*dch)) { LOGP(DRSL, LOGL_NOTICE, "RSL Dedicated Channel Management message too short\n"); msgb_free(msg); return -EIO; } msg->l3h = (unsigned char *)dch + sizeof(*dch); msg->lchan = rsl_lchan_lookup(trx, dch->chan_nr); if (!msg->lchan) { LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", rsl_msg_name(dch->c.msg_type)); msgb_free(msg); return report_error(trx); } LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan), rsl_msg_name(dch->c.msg_type)); switch (dch->c.msg_type) { case RSL_MT_CHAN_ACTIV: ret = rsl_rx_chan_activ(msg); break; case RSL_MT_RF_CHAN_REL: ret = rsl_rx_rf_chan_rel(msg->lchan, dch->chan_nr); break; case RSL_MT_SACCH_INFO_MODIFY: ret = rsl_rx_sacch_inf_mod(msg); break; case RSL_MT_DEACTIVATE_SACCH: ret = l1sap_chan_deact_sacch(trx, dch->chan_nr); break; case RSL_MT_ENCR_CMD: ret = rsl_rx_encr_cmd(msg); break; case RSL_MT_MODE_MODIFY_REQ: ret = rsl_rx_mode_modif(msg); break; case RSL_MT_MS_POWER_CONTROL: ret = rsl_rx_ms_pwr_ctrl(msg); break; case RSL_MT_PHY_CONTEXT_REQ: case RSL_MT_PREPROC_CONFIG: case RSL_MT_RTD_REP: case RSL_MT_PRE_HANDO_NOTIF: case RSL_MT_MR_CODEC_MOD_REQ: case RSL_MT_TFO_MOD_REQ: LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL dchan msg_type %s\n", rsl_msg_name(dch->c.msg_type)); break; default: LOGP(DRSL, LOGL_NOTICE, "undefined RSL dchan msg_type 0x%02x\n", dch->c.msg_type); ret = -EINVAL; } if (ret != 1) msgb_free(msg); return ret; } static int rsl_rx_trx(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_common_hdr *th = msgb_l2(msg); int ret = 0; if (msgb_l2len(msg) < sizeof(*th)) { LOGP(DRSL, LOGL_NOTICE, "RSL TRX message too short\n"); msgb_free(msg); return -EIO; } msg->l3h = (unsigned char *)th + sizeof(*th); switch (th->msg_type) { case RSL_MT_SACCH_FILL: ret = rsl_rx_sacch_fill(trx, msg); break; default: LOGP(DRSL, LOGL_NOTICE, "undefined RSL TRX msg_type 0x%02x\n", th->msg_type); ret = -EINVAL; } if (ret != 1) msgb_free(msg); return ret; } static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); int ret = 0; if (msgb_l2len(msg) < sizeof(*dch)) { LOGP(DRSL, LOGL_NOTICE, "RSL ip.access message too short\n"); msgb_free(msg); return -EIO; } msg->l3h = (unsigned char *)dch + sizeof(*dch); msg->lchan = rsl_lchan_lookup(trx, dch->chan_nr); if (!msg->lchan) { LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", rsl_msg_name(dch->c.msg_type)); msgb_free(msg); return report_error(trx); } LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan), rsl_ipac_msg_name(dch->c.msg_type)); switch (dch->c.msg_type) { case RSL_MT_IPAC_CRCX: case RSL_MT_IPAC_MDCX: ret = rsl_rx_ipac_XXcx(msg); break; case RSL_MT_IPAC_DLCX: ret = rsl_rx_ipac_dlcx(msg); break; default: LOGP(DRSL, LOGL_NOTICE, "unsupported RSL ip.access msg_type 0x%02x\n", dch->c.msg_type); ret = -EINVAL; } if (ret != 1) msgb_free(msg); return ret; } int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_common_hdr *rslh = msgb_l2(msg); int ret = 0; if (msgb_l2len(msg) < sizeof(*rslh)) { LOGP(DRSL, LOGL_NOTICE, "RSL message too short\n"); msgb_free(msg); return -EIO; } switch (rslh->msg_discr & 0xfe) { case ABIS_RSL_MDISC_RLL: ret = rsl_rx_rll(trx, msg); /* exception: RLL messages are _NOT_ freed as they are now * owned by LAPDm which might have queued them */ break; case ABIS_RSL_MDISC_COM_CHAN: ret = rsl_rx_cchan(trx, msg); break; case ABIS_RSL_MDISC_DED_CHAN: ret = rsl_rx_dchan(trx, msg); break; case ABIS_RSL_MDISC_TRX: ret = rsl_rx_trx(trx, msg); break; case ABIS_RSL_MDISC_IPACCESS: ret = rsl_rx_ipaccess(trx, msg); break; default: LOGP(DRSL, LOGL_NOTICE, "unknown RSL msg_discr 0x%02x\n", rslh->msg_discr); msgb_free(msg); ret = -EINVAL; } /* we don't free here, as rsl_rx{cchan,dchan,trx,ipaccess,rll} are * responsible for owning the msg */ return ret; } osmo-bts-0.4.0/src/common/support.c000066400000000000000000000045741260026426200172270ustar00rootroot00000000000000/* (C) 2011 by Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 struct bts_support bts_support; void bts_support_init(void) { struct bts_support *sup = &bts_support; int i; memset(sup, 0, sizeof(*sup)); /* crypto supprot */ sup->a5_1 = 0; sup->a5_2 = 0; sup->a5_3 = 0; sup->a5_4 = 0; sup->a5_5 = 0; sup->a5_6 = 0; sup->a5_7 = 0; /* set supported frequencies */ for(i = 1; i <= 124; i++) // P-GSM sup->freq_map[i >> 3] |= (1 << (i & 7)); for(i = 512; i <= 885; i++) // DCS sup->freq_map[i >> 3] |= (1 << (i & 7)); for(i = 975; i <= 1023; i++) // E-GSM extension sup->freq_map[i >> 3] |= (1 << (i & 7)); sup->freq_map[0] |= 1; // channel 0 for(i = 955; i <= 974; i++) // R-GSM extension sup->freq_map[i >> 3] |= (1 << (i & 7)); /* channel combinations */ sup->chan_comb[NM_CHANC_mainBCCH] = 1; sup->chan_comb[NM_CHANC_BCCHComb] = 1; sup->chan_comb[NM_CHANC_SDCCH] = 1; sup->chan_comb[NM_CHANC_TCHFull] = 1; sup->chan_comb[NM_CHANC_TCHHalf] = 1; /* codec */ sup->full_v1 = 1; sup->full_v2 = 1; sup->full_v3 = 1; sup->half_v1 = 1; sup->half_v3 = 1; } char *bts_support_comb_name(uint8_t chan_comb) { if (chan_comb == NM_CHANC_mainBCCH) return("BCCH"); if (chan_comb == NM_CHANC_BCCHComb) return("BCCH+SDCCH/4"); if (chan_comb == NM_CHANC_BCCH_CBCH) return("BCCH+CBCH+SDCCH/4"); if (chan_comb == NM_CHANC_SDCCH) return("SDCCH/8"); if (chan_comb == NM_CHANC_SDCCH_CBCH) return("SDCCH/8+CBCH"); if (chan_comb == NM_CHANC_TCHFull) return("TCH/F"); if (chan_comb == NM_CHANC_TCHHalf) return("TCH/H"); return "Unknown"; } osmo-bts-0.4.0/src/common/sysinfo.c000066400000000000000000000117531260026426200172020ustar00rootroot00000000000000/* (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #define BTS_HAS_SI(bts, sinum) ((bts)->si_valid & (1 << sinum)) /* Apply the rules from 05.02 6.3.1.3 Mapping of BCCH Data */ uint8_t *bts_sysinfo_get(struct gsm_bts *bts, struct gsm_time *g_time) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); unsigned int tc4_cnt = 0; unsigned int tc4_sub[4]; /* System information type 2 bis or 2 ter messages are sent if * needed, as determined by the system operator. If only one of * them is needed, it is sent when TC = 5. If both are needed, * 2bis is sent when TC = 5 and 2ter is sent at least once * within any of 4 consecutive occurrences of TC = 4. */ /* System information type 2 quater is sent if needed, as * determined by the system operator. If sent on BCCH Norm, it * shall be sent when TC = 5 if neither of 2bis and 2ter are * used, otherwise it shall be sent at least once within any of * 4 consecutive occurrences of TC = 4. If sent on BCCH Ext, it * is sent at least once within any of 4 consecutive occurrences * of TC = 5. */ /* System Information type 9 is sent in those blocks with * TC = 4 which are specified in system information type 3 as * defined in 3GPP TS 04.08. */ /* System Information Type 13 need only be sent if GPRS support * is indicated in one or more of System Information Type 3 or 4 * or 7 or 8 messages. These messages also indicate if the * message is sent on the BCCH Norm or if the message is * transmitted on the BCCH Ext. In the case that the message is * sent on the BCCH Norm, it is sent at least once within any of * 4 consecutive occurrences of TC = 4. */ /* We only implement BCCH Norm at this time */ switch (g_time->tc) { case 0: /* System Information Type 1 need only be sent if * frequency hopping is in use or when the NCH is * present in a cell. If the MS finds another message * when TC = 0, it can assume that System Information * Type 1 is not in use. */ return GSM_BTS_SI(bts, SYSINFO_TYPE_1); case 1: /* A SI 2 message will be sent at least every time TC = 1. */ return GSM_BTS_SI(bts, SYSINFO_TYPE_2); case 2: return GSM_BTS_SI(bts, SYSINFO_TYPE_3); case 3: return GSM_BTS_SI(bts, SYSINFO_TYPE_4); case 4: /* iterate over 2ter, 2quater, 9, 13 */ /* determine how many SI we need to send on TC=4, * and which of them we send when */ if (BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) { tc4_sub[tc4_cnt] = SYSINFO_TYPE_2ter; tc4_cnt += 1; /* 2bis */ } if (BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) && (BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) || BTS_HAS_SI(bts, SYSINFO_TYPE_2bis))) { tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater; tc4_cnt += 1; } if (BTS_HAS_SI(bts, SYSINFO_TYPE_13)) { tc4_sub[tc4_cnt] = SYSINFO_TYPE_13; tc4_cnt += 1; } if (BTS_HAS_SI(bts, SYSINFO_TYPE_9)) { /* FIXME: check SI3 scheduling info! */ tc4_sub[tc4_cnt] = SYSINFO_TYPE_9; tc4_cnt += 1; } /* simply send SI2 if we have nothing else to send */ if (tc4_cnt == 0) return GSM_BTS_SI(bts, SYSINFO_TYPE_2); else { /* increment static counter by one, modulo count */ btsb->si.tc4_ctr = (btsb->si.tc4_ctr + 1) % tc4_cnt; return GSM_BTS_SI(bts, tc4_sub[btsb->si.tc4_ctr]); } case 5: /* 2bis, 2ter, 2quater */ if (BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis); else if (BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && !BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) return GSM_BTS_SI(bts, SYSINFO_TYPE_2ter); else if (BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis); else if (BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) && !BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) return GSM_BTS_SI(bts, SYSINFO_TYPE_2quater); break; case 6: return GSM_BTS_SI(bts, SYSINFO_TYPE_3); case 7: return GSM_BTS_SI(bts, SYSINFO_TYPE_4); } return NULL; } uint8_t *lchan_sacch_get(struct gsm_lchan *lchan) { uint32_t tmp; for (tmp = lchan->si.last + 1; tmp != lchan->si.last; tmp = (tmp + 1) % _MAX_SYSINFO_TYPE) { if (lchan->si.valid & (1 << tmp)) { lchan->si.last = tmp; return lchan->si.buf[tmp]; } } return NULL; } osmo-bts-0.4.0/src/common/tx_power.c000066400000000000000000000235511260026426200173560ustar00rootroot00000000000000/* Transmit Power computation */ /* (C) 2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include static int get_pa_drive_level_mdBm(const struct power_amp *pa, int desired_p_out_mdBm, unsigned int arfcn) { if (arfcn >= ARRAY_SIZE(pa->calib.gain_mdB)) return INT_MIN; /* FIXME: temperature compensation */ return desired_p_out_mdBm - pa->calib.gain_mdB[arfcn]; } /* maximum output power of the system */ int get_p_max_out_mdBm(struct gsm_bts_trx *trx) { struct trx_power_params *tpp = &trx->power_params; /* Add user gain, internal and external PA gain to TRX output power */ return tpp->trx_p_max_out_mdBm + tpp->user_gain_mdB + tpp->pa.nominal_gain_mdB + tpp->user_pa.nominal_gain_mdB; } /* nominal output power, i.e. OML-reduced maximum output power */ int get_p_nominal_mdBm(struct gsm_bts_trx *trx) { /* P_max_out subtracted by OML maximum power reduction IE */ return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red); } /* calculate the target total output power required, reduced by both * OML and RSL, but ignoring the attenutation required for power ramping and * thermal management */ int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) { /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */ return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2); } int get_p_target_mdBm_lchan(struct gsm_lchan *lchan) { return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power); } /* calculate the actual total output power required, taking into account the * attenutation required for power ramping but not thermal management */ int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) { struct trx_power_params *tpp = &trx->power_params; /* P_target subtracted by ramp attenuation */ return p_target_mdBm - tpp->ramp.attenuation_mdB; } /* calculate the effective total output power required, taking into account the * attenutation required for power ramping and thermal management */ int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) { struct trx_power_params *tpp = &trx->power_params; /* P_target subtracted by ramp attenuation */ return p_target_mdBm - tpp->ramp.attenuation_mdB - tpp->thermal_attenuation_mdB; } /* calculate effect TRX output power required, taking into account the * attenuations required for power ramping and thermal management */ int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) { struct trx_power_params *tpp = &trx->power_params; int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; unsigned int arfcn = trx->arfcn; /* P_actual subtracted by any bulk gain added by the user */ p_actual_mdBm = get_p_eff_mdBm(trx, p_target_mdBm) - tpp->user_gain_mdB; /* determine input drive level required at input to user PA */ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_actual_mdBm, arfcn); /* determine input drive level required at input to internal PA */ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); /* internal PA input drive level is TRX output power */ return pa_drvlvl_mdBm; } /* calculate target TRX output power required, ignoring the * attenuations required for power ramping but not thermal management */ int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) { struct trx_power_params *tpp = &trx->power_params; int p_target_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; unsigned int arfcn = trx->arfcn; /* P_target subtracted by any bulk gain added by the user */ p_target_mdBm = get_p_target_mdBm(trx, bs_power_ie) - tpp->user_gain_mdB; /* determine input drive level required at input to user PA */ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_target_mdBm, arfcn); /* determine input drive level required at input to internal PA */ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); /* internal PA input drive level is TRX output power */ return pa_drvlvl_mdBm; } int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan) { return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power); } /* output power ramping code */ /* The idea here is to avoid a hard switch from 0 to 100, but to actually * slowly and gradually ramp up or down the power. This is needed on the * one hand side to avoid very fast dynamic load changes towards the PA power * supply, but is also needed in order to avoid a DoS by too many subscriber * attempting to register at the same time. Rather, grow the cell slowly in * radius than start with the full raduis at once. */ static int we_are_ramping_up(struct gsm_bts_trx *trx) { struct trx_power_params *tpp = &trx->power_params; if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm) return 1; else return 0; } static void power_ramp_do_step(struct gsm_bts_trx *trx, int first); /* timer call-back for the ramp tumer */ static void power_ramp_timer_cb(void *_trx) { struct gsm_bts_trx *trx = _trx; struct trx_power_params *tpp = &trx->power_params; int p_trxout_eff_mdBm; /* compute new actual total output power (= minus ramp attenuation) */ tpp->p_total_cur_mdBm = get_p_actual_mdBm(trx, tpp->p_total_tgt_mdBm); /* compute new effective (= minus ramp and thermal attenuation) TRX output required */ p_trxout_eff_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); LOGP(DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, " "ramp_att=%d, therm_att=%d, user_gain=%d)\n", tpp->p_total_cur_mdBm, tpp->p_total_tgt_mdBm, tpp->ramp.attenuation_mdB, tpp->thermal_attenuation_mdB, tpp->user_gain_mdB); LOGP(DL1C, LOGL_INFO, "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm); /* Instruct L1 to apply new effective TRX output power required */ bts_model_change_power(trx, p_trxout_eff_mdBm); } /* BTS model call-back once one a call to bts_model_change_power() * completes, indicating actual L1 transmit power */ void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm) { struct trx_power_params *tpp = &trx->power_params; int p_trxout_should_mdBm; p_trxout_should_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); /* for now we simply write an error message, but in the future * we might use the value (again) as part of our math? */ if (p_trxout_cur_mdBm != p_trxout_should_mdBm) { LOGP(DL1C, LOGL_ERROR, "bts_model notifies us of %u mdBm TRX " "output power. However, it should be %u mdBm!\n", p_trxout_cur_mdBm, p_trxout_should_mdBm); } /* and do another step... */ power_ramp_do_step(trx, 0); } static void power_ramp_do_step(struct gsm_bts_trx *trx, int first) { struct trx_power_params *tpp = &trx->power_params; /* we had finished in last loop iteration */ if (!first && tpp->ramp.attenuation_mdB == 0) return; if (we_are_ramping_up(trx)) { /* ramp up power -> ramp down attenuation */ tpp->ramp.attenuation_mdB -= tpp->ramp.step_size_mdB; if (tpp->ramp.attenuation_mdB <= 0) { /* we are done */ tpp->ramp.attenuation_mdB = 0; } } else { /* ramp down power -> ramp up attenuation */ tpp->ramp.attenuation_mdB += tpp->ramp.step_size_mdB; if (tpp->ramp.attenuation_mdB >= 0) { /* we are done */ tpp->ramp.attenuation_mdB = 0; } } /* schedule timer for the next step */ tpp->ramp.step_timer.data = trx; tpp->ramp.step_timer.cb = power_ramp_timer_cb; osmo_timer_schedule(&tpp->ramp.step_timer, tpp->ramp.step_interval_sec, 0); } int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass) { struct trx_power_params *tpp = &trx->power_params; /* The input to this function is the actual desired output power, i.e. * the maximum total system power subtracted by OML as well as RSL * reductions */ LOGP(DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d)\n", tpp->p_total_cur_mdBm, p_total_tgt_mdBm); if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) { LOGP(DL1C, LOGL_ERROR, "Asked to ramp power up to " "%d mdBm, which exceeds P_max_out (%d)\n", p_total_tgt_mdBm, get_p_nominal_mdBm(trx)); return -ERANGE; } /* Cancel any pending request */ osmo_timer_del(&tpp->ramp.step_timer); /* set the new target */ tpp->p_total_tgt_mdBm = p_total_tgt_mdBm; if (we_are_ramping_up(trx)) { if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) { LOGP(DL1C, LOGL_INFO, "target_power(%d) is below max.initial power\n", tpp->p_total_tgt_mdBm); /* new setting is below the maximum initial output * power, so we can directly jump to this level */ tpp->p_total_cur_mdBm = tpp->p_total_tgt_mdBm; tpp->ramp.attenuation_mdB = 0; power_ramp_timer_cb(trx); } else { /* We need to step it up. Start from the current value */ /* Set attenuation to cause no power change right now */ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; /* start with the firsrt step */ power_ramp_do_step(trx, 1); } } else { /* Set ramp attenuation to negative value, and increase that by * steps until it reaches 0 */ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; /* start with the firsrt step */ power_ramp_do_step(trx, 1); } return 0; } osmo-bts-0.4.0/src/common/vty.c000066400000000000000000000530321260026426200163260ustar00rootroot00000000000000/* OsmoBTS VTY interface */ /* (C) 2011-2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "btsconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum node_type bts_vty_go_parent(struct vty *vty) { switch (vty->node) { case TRX_NODE: vty->node = BTS_NODE; { struct gsm_bts_trx *trx = vty->index; vty->index = trx->bts; } break; default: vty->node = CONFIG_NODE; } return vty->node; } int bts_vty_is_config_node(struct vty *vty, int node) { switch (node) { case TRX_NODE: case BTS_NODE: return 1; default: return 0; } } gDEFUN(ournode_exit, ournode_exit_cmd, "exit", "Exit current node, go down to provious node") { switch (vty->node) { case TRX_NODE: vty->node = BTS_NODE; { struct gsm_bts_trx *trx = vty->index; vty->index = trx->bts; } break; default: break; } return CMD_SUCCESS; } gDEFUN(ournode_end, ournode_end_cmd, "end", "End current mode and change to enable mode") { switch (vty->node) { default: vty_config_unlock(vty); vty->node = ENABLE_NODE; vty->index = NULL; vty->index_sub = NULL; break; } return CMD_SUCCESS; } static const char osmobts_copyright[] = "Copyright (C) 2010, 2011 by Harald Welte, Andreas Eversberg and On-Waves\r\n" "License AGPLv3+: GNU AGPL version 3 or later \r\n" "This is free software: you are free to change and redistribute it.\r\n" "There is NO WARRANTY, to the extent permitted by law.\r\n"; struct vty_app_info bts_vty_info = { .name = "OsmoBTS", .version = PACKAGE_VERSION, .copyright = osmobts_copyright, .go_parent_cb = bts_vty_go_parent, .is_config_node = bts_vty_is_config_node, }; extern struct gsm_network bts_gsmnet; struct gsm_network *gsmnet_from_vty(struct vty *v) { return &bts_gsmnet; } static struct cmd_node bts_node = { BTS_NODE, "%s(bts)#", 1, }; static struct cmd_node trx_node = { TRX_NODE, "%s(trx)#", 1, }; DEFUN(cfg_bts_trx, cfg_bts_trx_cmd, "trx <0-254>", "Select a TRX to configure\n" "TRX number\n") { int trx_nr = atoi(argv[0]); struct gsm_bts *bts = vty->index; struct gsm_bts_trx *trx; trx = gsm_bts_trx_num(bts, trx_nr); if (!trx) { vty_out(vty, "Unknown TRX %u. Aavialable TRX are: 0..%d%s", trx_nr, bts->num_trx - 1, VTY_NEWLINE); return CMD_WARNING; } vty->index = trx; vty->index_sub = &trx->description; vty->node = TRX_NODE; return CMD_SUCCESS; } /* FIXME: move to libosmocore ? */ static char buf_casecnvt[256]; char *osmo_str_tolower(const char *in) { int len, i; if (!in) return NULL; len = strlen(in); if (len > sizeof(buf_casecnvt)) len = sizeof(buf_casecnvt); for (i = 0; i < len; i++) { buf_casecnvt[i] = tolower(in[i]); if (in[i] == '\0') break; } if (i < sizeof(buf_casecnvt)) buf_casecnvt[i] = '\0'; /* just to make sure we're always zero-terminated */ buf_casecnvt[sizeof(buf_casecnvt)-1] = '\0'; return buf_casecnvt; } static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct gsm_bts_trx *trx; int i; vty_out(vty, "bts %u%s", bts->nr, VTY_NEWLINE); if (bts->description) vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE); vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE); vty_out(vty, " ipa unit-id %u %u%s", bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); vty_out(vty, " oml remote-ip %s%s", btsb->bsc_oml_host, VTY_NEWLINE); vty_out(vty, " rtp jitter-buffer %u%s", btsb->rtp_jitter_buf_ms, VTY_NEWLINE); vty_out(vty, " paging queue-size %u%s", paging_get_queue_max(btsb->paging_state), VTY_NEWLINE); vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(btsb->paging_state), VTY_NEWLINE); vty_out(vty, " uplink-power-target %d%s", btsb->ul_power_target, VTY_NEWLINE); if (btsb->agch_queue_thresh_level != GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT || btsb->agch_queue_low_level != GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT || btsb->agch_queue_high_level != GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT) vty_out(vty, " agch-queue-mgmt threshold %d low %d high %d%s", btsb->agch_queue_thresh_level, btsb->agch_queue_low_level, btsb->agch_queue_high_level, VTY_NEWLINE); for (i = 0; i < 32; i++) { if (gsmtap_sapi_mask & (1 << i)) { const char *name = get_value_string(gsmtap_sapi_names, i); vty_out(vty, " gsmtap-sapi %s%s", osmo_str_tolower(name), VTY_NEWLINE); } } if (gsmtap_sapi_acch) { const char *name = get_value_string(gsmtap_sapi_names, GSMTAP_CHANNEL_ACCH); vty_out(vty, " gsmtap-sapi %s%s", osmo_str_tolower(name), VTY_NEWLINE); } bts_model_config_write_bts(vty, bts); llist_for_each_entry(trx, &bts->trx_list, list) { struct trx_power_params *tpp = &trx->power_params; vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); if (trx->power_params.user_gain_mdB) vty_out(vty, " user-gain %u mdB%s", tpp->user_gain_mdB, VTY_NEWLINE); vty_out(vty, " power-ramp max-initial %d mdBm%s", tpp->ramp.max_initial_pout_mdBm, VTY_NEWLINE); vty_out(vty, " power-ramp step-size %d mdB%s", tpp->ramp.step_size_mdB, VTY_NEWLINE); vty_out(vty, " power-ramp step-interval %d%s", tpp->ramp.step_interval_sec, VTY_NEWLINE); vty_out(vty, " ms-power-control %s%s", trx->ms_power_control == 0 ? "dsp" : "osmo", VTY_NEWLINE); bts_model_config_write_trx(vty, trx); } } static int config_write_bts(struct vty *vty) { struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_bts *bts; llist_for_each_entry(bts, &net->bts_list, list) config_write_bts_single(vty, bts); return CMD_SUCCESS; } static int config_write_dummy(struct vty *vty) { return CMD_SUCCESS; } /* per-BTS configuration */ DEFUN(cfg_bts, cfg_bts_cmd, "bts BTS_NR", "Select a BTS to configure\n" "BTS Number\n") { struct gsm_network *gsmnet = gsmnet_from_vty(vty); int bts_nr = atoi(argv[0]); struct gsm_bts *bts; if (bts_nr >= gsmnet->num_bts) { vty_out(vty, "%% Unknown BTS number %u (num %u)%s", bts_nr, gsmnet->num_bts, VTY_NEWLINE); return CMD_WARNING; } else bts = gsm_bts_num(gsmnet, bts_nr); vty->index = bts; vty->index_sub = &bts->description; vty->node = BTS_NODE; return CMD_SUCCESS; } #warning merge with OpenBSC? DEFUN(cfg_bts_unit_id, cfg_bts_unit_id_cmd, "ipa unit-id <0-65534> <0-255>", "ip.access RSL commands\n" "Set the Unit ID of this BTS\n" "Site ID\n" "Unit ID\n") { struct gsm_bts *bts = vty->index; int site_id = atoi(argv[0]); int bts_id = atoi(argv[1]); bts->ip_access.site_id = site_id; bts->ip_access.bts_id = bts_id; return CMD_SUCCESS; } DEFUN(cfg_bts_band, cfg_bts_band_cmd, "band (450|GSM450|480|GSM480|750|GSM750|810|GSM810|850|GSM850|900|GSM900|1800|DCS1800|1900|PCS1900)", "Set the frequency band of this BTS\n" "Alias for GSM450\n450Mhz\n" "Alias for GSM480\n480Mhz\n" "Alias for GSM750\n750Mhz\n" "Alias for GSM810\n810Mhz\n" "Alias for GSM850\n850Mhz\n" "Alias for GSM900\n900Mhz\n" "Alias for DCS1800\n1800Mhz\n" "Alias for PCS1900\n1900Mhz\n") { struct gsm_bts *bts = vty->index; int band = gsm_band_parse(argv[0]); if (band < 0) { vty_out(vty, "%% BAND %d is not a valid GSM band%s", band, VTY_NEWLINE); return CMD_WARNING; } bts->band = band; return CMD_SUCCESS; } DEFUN(cfg_bts_oml_ip, cfg_bts_oml_ip_cmd, "oml remote-ip A.B.C.D", "OML Parameters\n" "OML IP Address\n" "OML IP Address\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); if (btsb->bsc_oml_host) talloc_free(btsb->bsc_oml_host); btsb->bsc_oml_host = talloc_strdup(btsb, argv[0]); return CMD_SUCCESS; } #define RTP_STR "RTP parameters\n" DEFUN_HIDDEN(cfg_bts_rtp_bind_ip, cfg_bts_rtp_bind_ip_cmd, "rtp bind-ip A.B.C.D", RTP_STR "RTP local bind IP Address\n" "RTP local bind IP Address\n") { vty_out(vty, "%% rtp bind-ip is now deprecated%s", VTY_NEWLINE); return CMD_WARNING; } DEFUN(cfg_bts_rtp_jitbuf, cfg_bts_rtp_jitbuf_cmd, "rtp jitter-buffer <0-10000>", RTP_STR "RTP jitter buffer\n" "jitter buffer in ms\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->rtp_jitter_buf_ms = atoi(argv[0]); return CMD_SUCCESS; } #define PAG_STR "Paging related parameters\n" DEFUN(cfg_bts_paging_queue_size, cfg_bts_paging_queue_size_cmd, "paging queue-size <1-1024>", PAG_STR "Maximum length of BTS-internal paging queue\n" "Maximum length of BTS-internal paging queue\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); paging_set_queue_max(btsb->paging_state, atoi(argv[0])); return CMD_SUCCESS; } DEFUN(cfg_bts_paging_lifetime, cfg_bts_paging_lifetime_cmd, "paging lifetime <0-60>", PAG_STR "Maximum lifetime of a paging record\n" "Maximum lifetime of a paging record (secods)\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); paging_set_lifetime(btsb->paging_state, atoi(argv[0])); return CMD_SUCCESS; } #define AGCH_QUEUE_STR "AGCH queue mgmt\n" DEFUN(cfg_bts_agch_queue_mgmt_params, cfg_bts_agch_queue_mgmt_params_cmd, "agch-queue-mgmt threshold <0-100> low <0-100> high <0-100000>", AGCH_QUEUE_STR "Threshold to start cleanup\nin %% of the maximum queue length\n" "Low water mark for cleanup\nin %% of the maximum queue length\n" "High water mark for cleanup\nin %% of the maximum queue length\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->agch_queue_thresh_level = atoi(argv[0]); btsb->agch_queue_low_level = atoi(argv[1]); btsb->agch_queue_high_level = atoi(argv[2]); return CMD_SUCCESS; } DEFUN(cfg_bts_agch_queue_mgmt_default, cfg_bts_agch_queue_mgmt_default_cmd, "agch-queue-mgmt default", AGCH_QUEUE_STR "Reset clean parameters to default values\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->agch_queue_thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT; btsb->agch_queue_low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT; btsb->agch_queue_high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT; return CMD_SUCCESS; } DEFUN(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd, "uplink-power-target <-110-0>", "Set the nominal target Rx Level for uplink power control loop\n" "Target uplink Rx level in dBm\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->ul_power_target = atoi(argv[0]); return CMD_SUCCESS; } #define DB_DBM_STR \ "Unit is dB (decibels)\n" \ "Unit is mdB (milli-decibels, or rather 1/10000 bel)\n" static int parse_mdbm(const char *valstr, const char *unit) { int val = atoi(valstr); if (!strcmp(unit, "dB") || !strcmp(unit, "dBm")) return val * 1000; else return val; } DEFUN(cfg_trx_user_gain, cfg_trx_user_gain_cmd, "user-gain <-100000-100000> (dB|mdB)", "Inform BTS about additional, user-provided gain or attenuation at TRX output\n" "Value of user-provided external gain(+)/attenuation(-)\n" DB_DBM_STR) { struct gsm_bts_trx *trx = vty->index; trx->power_params.user_gain_mdB = parse_mdbm(argv[0], argv[1]); return CMD_SUCCESS; } #define PR_STR "Power-Ramp settings" DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd, "power-ramp max-initial <0-100000> (dBm|mdBm)", PR_STR "Maximum initial power\n" "Value\n" DB_DBM_STR) { struct gsm_bts_trx *trx = vty->index; trx->power_params.ramp.max_initial_pout_mdBm = parse_mdbm(argv[0], argv[1]); return CMD_SUCCESS; } DEFUN(cfg_trx_pr_step_size, cfg_trx_pr_step_size_cmd, "power-ramp step-size <1-100000> (dB|mdB)", PR_STR "Power increase by step\n" "Step size\n" DB_DBM_STR) { struct gsm_bts_trx *trx = vty->index; trx->power_params.ramp.step_size_mdB = parse_mdbm(argv[0], argv[1]); return CMD_SUCCESS; } DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd, "power-ramp step-interval <1-100>", PR_STR "Power increase by step\n" "Step time in seconds\n") { struct gsm_bts_trx *trx = vty->index; trx->power_params.ramp.step_interval_sec = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(cfg_trx_ms_power_control, cfg_trx_ms_power_control_cmd, "ms-power-control (dsp|osmo)", "Mobile Station Power Level Control (change requires restart)\n" "Handled by DSP\n" "Handled by OsmoBTS\n") { struct gsm_bts_trx *trx = vty->index; trx->ms_power_control = argv[0][0] == 'd' ? 0 : 1; return CMD_SUCCESS; } /* ====================================================================== * SHOW * ======================================================================*/ static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) { vty_out(vty,"Oper '%s', Admin %u, Avail '%s'%s", abis_nm_opstate_name(nms->operational), nms->administrative, abis_nm_avail_name(nms->availability), VTY_NEWLINE); } static unsigned int llist_length(struct llist_head *list) { unsigned int len = 0; struct llist_head *pos; llist_for_each(pos, list) len++; return len; } static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts->role; vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " "BSIC %u, TSC %u and %u TRX%s", bts->nr, "FIXME", gsm_band_name(bts->band), bts->cell_identity, bts->location_area_code, bts->bsic, bts->tsc, bts->num_trx, VTY_NEWLINE); vty_out(vty, " Description: %s%s", bts->description ? bts->description : "(null)", VTY_NEWLINE); vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s", bts->ip_access.site_id, bts->ip_access.bts_id, bts->oml_tei, VTY_NEWLINE); vty_out(vty, " NM State: "); net_dump_nmstate(vty, &bts->mo.nm_state); vty_out(vty, " Site Mgr NM State: "); net_dump_nmstate(vty, &bts->site_mgr.mo.nm_state); vty_out(vty, " Paging: Queue size %u, occupied %u, lifetime %us%s", paging_get_queue_max(btsb->paging_state), paging_queue_length(btsb->paging_state), paging_get_lifetime(btsb->paging_state), VTY_NEWLINE); vty_out(vty, " AGCH: Queue limit %u, occupied %d, " "dropped %llu, merged %llu, rejected %llu, " "ag-res %llu, non-res %llu%s", btsb->agch_max_queue_length, btsb->agch_queue_length, btsb->agch_queue_dropped_msgs, btsb->agch_queue_merged_msgs, btsb->agch_queue_rejected_msgs, btsb->agch_queue_agch_msgs, btsb->agch_queue_pch_msgs, VTY_NEWLINE); vty_out(vty, " CBCH backlog queue length: %u%s", llist_length(&btsb->smscb_state.queue), VTY_NEWLINE); #if 0 vty_out(vty, " Paging: %u pending requests, %u free slots%s", paging_pending_requests_nr(bts), bts->paging.available_slots, VTY_NEWLINE); if (is_ipaccess_bts(bts)) { vty_out(vty, " OML Link state: %s.%s", bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE); } else { vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); e1isl_dump_vty(vty, bts->oml_link); } /* FIXME: chan_desc */ memset(&pl, 0, sizeof(pl)); bts_chan_load(&pl, bts); vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); dump_pchan_load_vty(vty, " ", &pl); #endif } DEFUN(show_bts, show_bts_cmd, "show bts <0-255>", SHOW_STR "Display information about a BTS\n" "BTS number") { struct gsm_network *net = gsmnet_from_vty(vty); int bts_nr; if (argc != 0) { /* use the BTS number that the user has specified */ bts_nr = atoi(argv[0]); if (bts_nr >= net->num_bts) { vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); return CMD_SUCCESS; } /* print all BTS's */ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); return CMD_SUCCESS; } static struct gsm_lchan *resolve_lchan(struct gsm_network *net, const char **argv, int idx) { int bts_nr = atoi(argv[idx+0]); int trx_nr = atoi(argv[idx+1]); int ts_nr = atoi(argv[idx+2]); int lchan_nr = atoi(argv[idx+3]); struct gsm_bts *bts; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; bts = gsm_bts_num(net, bts_nr); if (!bts) return NULL; trx = gsm_bts_trx_num(bts, trx_nr); if (!trx) return NULL; if (ts_nr >= ARRAY_SIZE(trx->ts)) return NULL; ts = &trx->ts[ts_nr]; if (lchan_nr >= ARRAY_SIZE(ts->lchan)) return NULL; return &ts->lchan[lchan_nr]; } #define BTS_T_T_L_STR \ "BTS related commands\n" \ "BTS number\n" \ "TRX related commands\n" \ "TRX number\n" \ "timeslot related commands\n" \ "timeslot number\n" \ "logical channel commands\n" \ "logical channel number\n" DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd, "HIDDEN", "HIDDEN") { int sapi; sapi = get_string_value(gsmtap_sapi_names, argv[0]); if (sapi == GSMTAP_CHANNEL_ACCH) gsmtap_sapi_acch = 1; else gsmtap_sapi_mask |= (1 << sapi); return CMD_SUCCESS; } DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd, "HIDDEN", "HIDDEN") { int sapi; sapi = get_string_value(gsmtap_sapi_names, argv[0]); if (sapi == GSMTAP_CHANNEL_ACCH) gsmtap_sapi_acch = 0; else gsmtap_sapi_mask &= ~(1 << sapi); return CMD_SUCCESS; } DEFUN(bts_t_t_l_jitter_buf, bts_t_t_l_jitter_buf_cmd, "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> rtp jitter-buffer <0-10000>", BTS_T_T_L_STR "RTP settings\n" "Jitter buffer\n" "Size of jitter buffer in (ms)\n") { struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_lchan *lchan; int jitbuf_ms = atoi(argv[4]); lchan = resolve_lchan(net, argv, 0); if (!lchan) { vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); return CMD_WARNING; } if (!lchan->abis_ip.rtp_socket) { vty_out(vty, "%% this channel has no active RTP stream%s", VTY_NEWLINE); return CMD_WARNING; } osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, OSMO_RTP_P_JITBUF, jitbuf_ms); return CMD_SUCCESS; } DEFUN(bts_t_t_l_loopback, bts_t_t_l_loopback_cmd, "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback", BTS_T_T_L_STR "Set loopback\n") { struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_lchan *lchan; lchan = resolve_lchan(net, argv, 0); if (!lchan) { vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); return CMD_WARNING; } lchan->loopback = 1; return CMD_SUCCESS; } DEFUN(no_bts_t_t_l_loopback, no_bts_t_t_l_loopback_cmd, "no bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback", NO_STR BTS_T_T_L_STR "Set loopback\n") { struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_lchan *lchan; lchan = resolve_lchan(net, argv, 0); if (!lchan) { vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); return CMD_WARNING; } lchan->loopback = 0; return CMD_SUCCESS; } int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat) { cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, "gsmtap-sapi (", "|",")", VTY_DO_LOWER); cfg_trx_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, "GSMTAP SAPI\n", "\n", "", 0); cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, "no gsmtap-sapi (", "|",")", VTY_DO_LOWER); cfg_trx_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, NO_STR "GSMTAP SAPI\n", "\n", "", 0); install_element_ve(&show_bts_cmd); logging_vty_add_cmds(cat); install_node(&bts_node, config_write_bts); install_element(CONFIG_NODE, &cfg_bts_cmd); install_default(BTS_NODE); install_element(BTS_NODE, &cfg_bts_unit_id_cmd); install_element(BTS_NODE, &cfg_bts_oml_ip_cmd); install_element(BTS_NODE, &cfg_bts_rtp_bind_ip_cmd); install_element(BTS_NODE, &cfg_bts_rtp_jitbuf_cmd); install_element(BTS_NODE, &cfg_bts_band_cmd); install_element(BTS_NODE, &cfg_description_cmd); install_element(BTS_NODE, &cfg_no_description_cmd); install_element(BTS_NODE, &cfg_bts_paging_queue_size_cmd); install_element(BTS_NODE, &cfg_bts_paging_lifetime_cmd); install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_default_cmd); install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_params_cmd); install_element(BTS_NODE, &cfg_bts_ul_power_target_cmd); install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd); install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd); /* add and link to TRX config node */ install_element(BTS_NODE, &cfg_bts_trx_cmd); install_node(&trx_node, config_write_dummy); install_default(TRX_NODE); install_element(TRX_NODE, &cfg_trx_user_gain_cmd); install_element(TRX_NODE, &cfg_trx_pr_max_initial_cmd); install_element(TRX_NODE, &cfg_trx_pr_step_size_cmd); install_element(TRX_NODE, &cfg_trx_pr_step_interval_cmd); install_element(TRX_NODE, &cfg_trx_ms_power_control_cmd); install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd); return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/000077500000000000000000000000001260026426200167605ustar00rootroot00000000000000osmo-bts-0.4.0/src/osmo-bts-sysmo/Makefile.am000066400000000000000000000034111260026426200210130ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -lortp EXTRA_DIST = misc/sysmobts_mgr.h misc/sysmobts_misc.h misc/sysmobts_par.h \ misc/sysmobts_eeprom.h misc/sysmobts_nl.h femtobts.h hw_misc.h \ l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h bin_PROGRAMS = sysmobts sysmobts-remote l1fwd-proxy sysmobts-mgr sysmobts-util COMMON_SOURCES = main.c femtobts.c l1_if.c oml.c sysmobts_vty.c tch.c hw_misc.c calib_file.c \ eeprom.c calib_fixup.c utils.c misc/sysmobts_par.c oml_router.c sysmobts_ctrl.c sysmobts_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c sysmobts_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) sysmobts_remote_SOURCES = $(COMMON_SOURCES) l1_transp_fwd.c sysmobts_remote_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) l1fwd_proxy_SOURCES = l1_fwd_main.c l1_transp_hw.c l1fwd_proxy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) sysmobts_mgr_SOURCES = \ misc/sysmobts_mgr.c misc/sysmobts_misc.c \ misc/sysmobts_par.c misc/sysmobts_nl.c \ misc/sysmobts_mgr_2050.c \ misc/sysmobts_mgr_vty.c \ misc/sysmobts_mgr_nl.c \ misc/sysmobts_mgr_temp.c \ misc/sysmobts_mgr_calib.c \ eeprom.c sysmobts_mgr_LDADD = $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(top_builddir)/src/common/libbts.a sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS) osmo-bts-0.4.0/src/osmo-bts-sysmo/calib_file.c000066400000000000000000000257761260026426200212160ustar00rootroot00000000000000/* sysmocom femtobts L1 calibration file routines*/ /* (C) 2012 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include "l1_if.h" #include "femtobts.h" #include "eeprom.h" #include "utils.h" struct calib_file_desc { const char *fname; GsmL1_FreqBand_t band; int uplink; int rx; }; static const struct calib_file_desc calib_files[] = { { .fname = "calib_rxu_850.cfg", .band = GsmL1_FreqBand_850, .uplink = 1, .rx = 1, }, { .fname = "calib_rxu_900.cfg", .band = GsmL1_FreqBand_900, .uplink = 1, .rx = 1, }, { .fname = "calib_rxu_1800.cfg", .band = GsmL1_FreqBand_1800, .uplink = 1, .rx = 1, }, { .fname = "calib_rxu_1900.cfg", .band = GsmL1_FreqBand_1900, .uplink = 1, .rx = 1, }, { .fname = "calib_rxd_850.cfg", .band = GsmL1_FreqBand_850, .uplink = 0, .rx = 1, }, { .fname = "calib_rxd_900.cfg", .band = GsmL1_FreqBand_900, .uplink = 0, .rx = 1, }, { .fname = "calib_rxd_1800.cfg", .band = GsmL1_FreqBand_1800, .uplink = 0, .rx = 1, }, { .fname = "calib_rxd_1900.cfg", .band = GsmL1_FreqBand_1900, .uplink = 0, .rx = 1, }, { .fname = "calib_tx_850.cfg", .band = GsmL1_FreqBand_850, .uplink = 0, .rx = 0, }, { .fname = "calib_tx_900.cfg", .band = GsmL1_FreqBand_900, .uplink = 0, .rx = 0, }, { .fname = "calib_tx_1800.cfg", .band = GsmL1_FreqBand_1800, .uplink = 0, .rx = 0, }, { .fname = "calib_tx_1900.cfg", .band = GsmL1_FreqBand_1900, .uplink = 0, .rx = 0, }, }; static const unsigned int arrsize_by_band[] = { [GsmL1_FreqBand_850] = 124, [GsmL1_FreqBand_900] = 194, [GsmL1_FreqBand_1800] = 374, [GsmL1_FreqBand_1900] = 299 }; /* determine next calibration file index based on supported bands */ static int next_calib_file_idx(uint32_t band_mask, int last_idx) { int i; for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { int band = band_femto2osmo(calib_files[i].band); if (band < 0) continue; if (band_mask & band) return i; } return -1; } static float read_float(FILE *in) { int rc; float f = 0.0f; rc = fscanf(in, "%f\n", &f); if (rc != 1) LOGP(DL1C, LOGL_ERROR, "Reading a float from calib data failed.\n"); return f; } static int read_int(FILE *in) { int rc; int i = 0; rc = fscanf(in, "%d\n", &i); if (rc != 1) LOGP(DL1C, LOGL_ERROR, "Reading an int from calib data failed.\n"); return i; } /* some particular units have calibration data that is incompatible with * firmware >= 3.3, so we need to alter it as follows: */ static const float delta_by_band[Num_GsmL1_FreqBand] = { [GsmL1_FreqBand_850] = -2.5f, [GsmL1_FreqBand_900] = -2.0f, [GsmL1_FreqBand_1800] = -8.0f, [GsmL1_FreqBand_1900] = -12.0f, }; extern const uint8_t fixup_macs[95][6]; static void determine_fixup(struct femtol1_hdl *fl1h) { uint8_t macaddr[6]; int rc, i; rc = eeprom_ReadEthAddr(macaddr); if (rc != EEPROM_SUCCESS) { LOGP(DL1C, LOGL_ERROR, "Unable to read Ethenet MAC from EEPROM\n"); return; } /* assume no fixup is needed */ fl1h->fixup_needed = FIXUP_NOT_NEEDED; if (fl1h->hw_info.dsp_version[0] < 3 || (fl1h->hw_info.dsp_version[0] == 3 && fl1h->hw_info.dsp_version[1] < 3)) { LOGP(DL1C, LOGL_NOTICE, "No calibration table fix-up needed, " "firmware < 3.3\n"); return; } for (i = 0; i < sizeof(fixup_macs)/6; i++) { if (!memcmp(fixup_macs[i], macaddr, 6)) { fl1h->fixup_needed = FIXUP_NEEDED; break; } } LOGP(DL1C, LOGL_NOTICE, "MAC Address is %02x:%02x:%02x:%02x:%02x:%02x -> %s\n", macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5], fl1h->fixup_needed == FIXUP_NEEDED ? "FIXUP" : "NO FIXUP"); } static int fixup_needed(struct femtol1_hdl *fl1h) { if (fl1h->fixup_needed == FIXUP_UNITILIAZED) determine_fixup(fl1h); return fl1h->fixup_needed == FIXUP_NEEDED; } static void calib_fixup_rx(struct femtol1_hdl *fl1h, SuperFemto_Prim_t *prim) { #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq; if (fixup_needed(fl1h)) rx->fExtRxGain += delta_by_band[rx->freqBand]; #endif } static int calib_file_read(const char *path, const struct calib_file_desc *desc, SuperFemto_Prim_t *prim) { FILE *in; char fname[PATH_MAX]; int i; fname[0] = '\0'; snprintf(fname, sizeof(fname)-1, "%s/%s", path, desc->fname); fname[sizeof(fname)-1] = '\0'; in = fopen(fname, "r"); if (!in) { LOGP(DL1C, LOGL_ERROR, "Failed to open '%s' for calibration data.\n", fname); return -1; } #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) if (desc->rx) { SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq; memset(rx, 0, sizeof(*rx)); prim->id = SuperFemto_PrimId_SetRxCalibTblReq; rx->freqBand = desc->band; rx->bUplink = desc->uplink; rx->fExtRxGain = read_float(in); rx->fRxMixGainCorr = read_float(in); for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++) rx->fRxLnaGainCorr[i] = read_float(in); for (i = 0; i < arrsize_by_band[desc->band]; i++) rx->fRxRollOffCorr[i] = read_float(in); if (desc->uplink) { rx->u8IqImbalMode = read_int(in); printf("%s: u8IqImbalMode=%d\n", desc->fname, rx->u8IqImbalMode); for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++) rx->u16IqImbalCorr[i] = read_int(in); } } else { SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq; memset(tx, 0, sizeof(*tx)); prim->id = SuperFemto_PrimId_SetTxCalibTblReq; tx->freqBand = desc->band; for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++) tx->fTxGainGmsk[i] = read_float(in); tx->fTx8PskCorr = read_float(in); for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++) tx->fTxExtAttCorr[i] = read_float(in); for (i = 0; i < arrsize_by_band[desc->band]; i++) tx->fTxRollOffCorr[i] = read_float(in); } #else #warning Format of calibration tables before API version 2.4.0 not supported #endif fclose(in); return 0; } static int calib_eeprom_read(const struct calib_file_desc *desc, SuperFemto_Prim_t *prim) { eeprom_Error_t eerr; int i; #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) if (desc->rx) { SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq; eeprom_RxCal_t rx_cal; memset(rx, 0, sizeof(*rx)); prim->id = SuperFemto_PrimId_SetRxCalibTblReq; rx->freqBand = desc->band; rx->bUplink = desc->uplink; eerr = eeprom_ReadRxCal(desc->band, desc->uplink, &rx_cal); if (eerr != EEPROM_SUCCESS) { LOGP(DL1C, LOGL_ERROR, "Error reading RxCalibration " "from EEPROM, band=%d, ul=%d, err=%d\n", desc->band, desc->uplink, eerr); return -EIO; } rx->fExtRxGain = rx_cal.fExtRxGain; rx->fRxMixGainCorr = rx_cal.fRxMixGainCorr; for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++) rx->fRxLnaGainCorr[i] = rx_cal.fRxLnaGainCorr[i]; for (i = 0; i < arrsize_by_band[desc->band]; i++) rx->fRxRollOffCorr[i] = rx_cal.fRxRollOffCorr[i]; if (desc->uplink) { rx->u8IqImbalMode = rx_cal.u8IqImbalMode; for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++) rx->u16IqImbalCorr[i] = rx_cal.u16IqImbalCorr[i]; } } else { SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq; eeprom_TxCal_t tx_cal; memset(tx, 0, sizeof(*tx)); prim->id = SuperFemto_PrimId_SetTxCalibTblReq; tx->freqBand = desc->band; eerr = eeprom_ReadTxCal(desc->band, &tx_cal); if (eerr != EEPROM_SUCCESS) { LOGP(DL1C, LOGL_ERROR, "Error reading TxCalibration " "from EEPROM, band=%d, err=%d\n", desc->band, eerr); return -EIO; } for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++) tx->fTxGainGmsk[i] = tx_cal.fTxGainGmsk[i]; tx->fTx8PskCorr = tx_cal.fTx8PskCorr; for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++) tx->fTxExtAttCorr[i] = tx_cal.fTxExtAttCorr[i]; for (i = 0; i < arrsize_by_band[desc->band]; i++) tx->fTxRollOffCorr[i] = tx_cal.fTxRollOffCorr[i]; } #endif return 0; } /* iteratively download the calibration data into the L1 */ static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data); /* send the calibration table for a single specified file */ static int calib_file_send(struct femtol1_hdl *fl1h, const struct calib_file_desc *desc) { struct calib_send_state *st = &fl1h->st; struct msgb *msg; int rc; msg = sysp_msgb_alloc(); if (fl1h->calib_path) rc = calib_file_read(fl1h->calib_path, desc, msgb_sysprim(msg)); else rc = calib_eeprom_read(desc, msgb_sysprim(msg)); if (rc < 0) { msgb_free(msg); /* still, we'd like to continue trying to load * calibration for all other bands */ st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support, st->last_file_idx); if (st->last_file_idx >= 0) return calib_file_send(fl1h, &calib_files[st->last_file_idx]); else return rc; } calib_fixup_rx(fl1h, msgb_sysprim(msg)); return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL); } /* completion callback after every SetCalibTbl is confirmed */ static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct calib_send_state *st = &fl1h->st; LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded (src: %s)\n", calib_files[st->last_file_idx].fname, fl1h->calib_path ? "file" : "eeprom"); msgb_free(l1_msg); st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support, st->last_file_idx); if (st->last_file_idx >= 0) return calib_file_send(fl1h, &calib_files[st->last_file_idx]); LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); eeprom_free_resources(); return 0; } int calib_load(struct femtol1_hdl *fl1h) { #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) LOGP(DL1C, LOGL_ERROR, "L1 calibration is not supported on pre 2.4.0 firmware.\n"); return -1; #else int idx = next_calib_file_idx(fl1h->hw_info.band_support, -1); if (idx < 0) { LOGP(DL1C, LOGL_ERROR, "No band_support?!?\n"); return -1; } return calib_file_send(fl1h, &calib_files[idx]); #endif } #if 0 int main(int argc, char **argv) { SuperFemto_Prim_t p; int i; for (i = 0; i < ARRAY_SIZE(calib_files); i++) { memset(&p, 0, sizeof(p)); calib_read_file(argv[1], &calib_files[i], &p); } exit(0); } #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/calib_fixup.c000066400000000000000000000076251260026426200214230ustar00rootroot00000000000000/* AUTOGENERATED, DO NOT EDIT */ #include const uint8_t fixup_macs[95][6] = { { 0x00, 0x0D, 0xCC, 0x08, 0x02, 0x3B }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x31 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x32 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x33 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x34 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x35 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x36 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x37 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x38 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x39 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3A }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3C }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3D }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3E }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x40 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x41 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x42 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x43 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x44 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x45 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x46 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x47 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x48 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x49 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4A }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4B }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4C }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4D }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4E }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4F }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x50 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x51 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x52 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x53 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x55 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x56 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x57 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x58 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x59 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5A }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5B }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5C }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5D }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5E }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5F }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x60 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x97 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x98 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x99 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9A }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9B }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9C }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9D }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9E }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9F }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA0 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA1 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA3 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA4 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA5 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA6 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA7 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA8 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA9 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAA }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAB }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAC }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAD }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAE }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAF }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB0 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB1 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB2 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB3 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB4 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB5 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB6 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB7 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB8 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB9 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBA }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBB }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBC }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBE }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBF }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC0 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC1 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC3 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC6 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC7 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC8 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC9 }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCA }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCB }, { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCD }, }; osmo-bts-0.4.0/src/osmo-bts-sysmo/eeprom.c000066400000000000000000001406141260026426200204210ustar00rootroot00000000000000// $Id: $ /**************************************************************************** * * **** I * ****** *** * ******* **** * ******** **** **** **** ********* ******* **** *********** * ********* **** **** **** ********* ************** ************* * **** ***** **** **** **** **** ***** ****** ***** **** * **** ***** **** **** **** **** ***** **** **** **** * **** ********* **** **** **** **** **** **** **** * **** ******** **** ****I **** ***** ***** **** **** * **** ****** ***** ****** ***** ****** ******* ****** ******* * **** **** ************ ****** ************* ************* * **** *** **** **** **** ***** **** ***** **** * **** * I N N O V A T I O N T O D A Y F O R T O M M O R O W **** * *** * ************************************************************************//** * * @file eeprom.c * @brief SuperFemto EEPROM interface. * * Author : Yves Godin * Date : 2012 * $Revision: $ * * Copyright (c) Nutaq. 2012 * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * *************************************************************************** * * "$Revision: $" * "$Name: $" * "$Date: $" * ***************************************************************************/ #include #include #include #include #include #include "eeprom.h" //#define DISP_ERROR 1 #ifdef DISP_ERROR #define PERROR(x, args ...) fprintf(stderr, x, ## args) #else #define PERROR(x, args ...) do { } while (0) #endif /**************************************************************************** * Private constants * ****************************************************************************/ /** * EEPROM device file */ #define EEPROM_DEV "/sys/bus/i2c/devices/i2c-1/1-0050/eeprom" /** * EEPROM configuration start address */ #define EEPROM_CFG_START_ADDR 0x0100 /** * EEPROM configuration max size */ #define EEPROM_CFG_MAX_SIZE (0x2000 - EEPROM_CFG_START_ADDR) /** * EEPROM config magic ID */ #define EEPROM_CFG_MAGIC_ID 0x53464548 /** * EEPROM section ID */ typedef enum { EEPROM_SID_SYSINFO = 0x1000, ///< System information EEPROM_SID_RFCLOCK_CAL = 0x2000, ///< RF Clock Calibration EEPROM_SID_GSM850_TXCAL = 0x3000, ///< GSM-850 TX Calibration Table EEPROM_SID_GSM850_RXUCAL = 0x3010, ///< GSM-850 RX Uplink Calibration Table EEPROM_SID_GSM850_RXDCAL = 0x3020, ///< GSM-850 RX Downlink Calibration Table EEPROM_SID_GSM900_TXCAL = 0x3100, ///< GSM-900 TX Calibration Table EEPROM_SID_GSM900_RXUCAL = 0x3110, ///< GSM-900 RX Uplink Calibration Table EEPROM_SID_GSM900_RXDCAL = 0x3120, ///< GSM-900 RX Downlink Calibration Table EEPROM_SID_DCS1800_TXCAL = 0x3200, ///< DCS-1800 TX Calibration Table EEPROM_SID_DCS1800_RXUCAL = 0x3210, ///< DCS-1800 RX Uplink Calibration Table EEPROM_SID_DCS1800_RXDCAL = 0x3220, ///< DCS-1800 RX Downlink Calibration Table EEPROM_SID_PCS1900_TXCAL = 0x3300, ///< PCS-1900 TX Calibration Table EEPROM_SID_PCS1900_RXUCAL = 0x3310, ///< PCS-1900 RX Uplink Calibration Table EEPROM_SID_PCS1900_RXDCAL = 0x3320 ///< PCS-1900 RX Downlink Calibration Table } eeprom_SID_t; /**************************************************************************** * Private types * ****************************************************************************/ /** * TX calibration table (common part) */ typedef struct { uint16_t u16SectionID; ///< Section ID uint16_t u16Crc; ///< Parity uint32_t u32Time; ///< Epoch time int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB) int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB) int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN } __attribute__((packed)) eeprom_CfgTxCal_t; /** * RX calibration table (common part) */ typedef struct { uint16_t u16SectionID; ///< Section ID uint16_t u16Crc; ///< Parity uint32_t u32Time; ///< Epoch time uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto) uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation int16_t sfixExtRxGain; ///< [Q6.9] External RX gain int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation } __attribute__((packed)) eeprom_CfgRxCal_t; /** * EEPROM configuration area format */ typedef struct { struct { uint32_t u32MagicId; ///< Magic ID (0x53464548) uint32_t u16Version : 16; ///< Header format version (v1) uint32_t : 16; ///< unused } hdr; union { /** EEPROM Format V1 */ struct { /** System information */ struct { uint16_t u16SectionID; ///< Section ID uint16_t u16Crc; ///< Parity uint32_t u32Time; ///< Epoch time char szSn[16]; ///< Serial number uint32_t u8Rev : 8; ///< Board revision uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows) uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows) uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows) uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows) uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows) uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows) uint32_t : 12; ///< unused } __attribute__((packed)) sysInfo; /** RF Clock configuration */ struct { uint16_t u16SectionID; ///< Section ID uint16_t u16Crc; ///< Parity uint32_t u32Time; ///< Epoch time int i24ClkCor :24; ///< Clock correction value in PPB. uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge) } __attribute__((packed)) rfClk; /** GSM-850 TX Calibration Table */ eeprom_CfgTxCal_t gsm850TxCal; uint16_t __gsm850TxCalMem[124]; /** GSM-850 RX Uplink Calibration Table */ eeprom_CfgRxCal_t gsm850RxuCal; uint16_t __gsm850RxuCalMem[124]; /** GSM-850 RX Downlink Calibration Table */ eeprom_CfgRxCal_t gsm850RxdCal; uint16_t __gsm850RxdCalMem[124]; /** GSM-900 TX Calibration Table */ eeprom_CfgTxCal_t gsm900TxCal; uint16_t __gsm900TxCalMem[194]; /** GSM-900 RX Uplink Calibration Table */ eeprom_CfgRxCal_t gsm900RxuCal; uint16_t __gsm900RxuCalMem[194]; /** GSM-900 RX Downlink Calibration Table */ eeprom_CfgRxCal_t gsm900RxdCal; uint16_t __gsm900RxdCalMem[194]; /** DCS-1800 TX Calibration Table */ eeprom_CfgTxCal_t dcs1800TxCal; uint16_t __dcs1800TxCalMem[374]; /** DCS-1800 RX Uplink Calibration Table */ eeprom_CfgRxCal_t dcs1800RxuCal; uint16_t __dcs1800RxuCalMem[374]; /** DCS-1800 RX Downlink Calibration Table */ eeprom_CfgRxCal_t dcs1800RxdCal; uint16_t __dcs1800RxdCalMem[374]; /** PCS-1900 TX Calibration Table */ eeprom_CfgTxCal_t pcs1900TxCal; uint16_t __pcs1900TxCalMem[299]; /** PCS-1900 RX Uplink Calibration Table */ eeprom_CfgRxCal_t pcs1900RxuCal; uint16_t __pcs1900RxuCalMem[299]; /** PCS-1900 RX Downlink Calibration Table */ eeprom_CfgRxCal_t pcs1900RxdCal; uint16_t __pcs1900RxdCalMem[299]; } __attribute__((packed)) v1; } __attribute__((packed)) cfg; } __attribute__((packed)) eeprom_Cfg_t; /**************************************************************************** * Private routine prototypes * ****************************************************************************/ static int eeprom_read( int addr, int size, char *pBuff ); static int eeprom_write( int addr, int size, const char *pBuff ); static uint16_t eeprom_crc( uint8_t *pu8Data, int len ); static eeprom_Cfg_t *eeprom_cached_config(void); /**************************************************************************** * Public functions * ****************************************************************************/ /**************************************************************************** * Function : eeprom_ResetCfg ************************************************************************//** * * This function reset the content of the EEPROM config area. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ResetCfg( void ) { int err; eeprom_Cfg_t ee; // Clear the structure memset( &ee, 0xFF, sizeof(eeprom_Cfg_t) ); // Init the header ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; ee.hdr.u16Version = 1; // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) { return EEPROM_ERR_DEVICE; } return EEPROM_SUCCESS; } eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr ) { int err; err = eeprom_read(0, 6, (char *) ethaddr); if ( err != 6 ) { return EEPROM_ERR_DEVICE; } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_ReadSysInfo ************************************************************************//** * * This function reads the system information from the EEPROM. * * @param [inout] pTime * Pointer to a system info structure. * * @param [inout] pSysInfo * Pointer to a system info structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo ) { int err; eeprom_Cfg_t ee; // Get a copy of the EEPROM header err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); if ( err != sizeof(ee.hdr) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the header magic ID if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { PERROR( "Invalid EEPROM format\n" ); return EEPROM_ERR_INVALID; } switch ( ee.hdr.u16Version ) { case 1: { // Get a copy of the EEPROM section err = eeprom_read( EEPROM_CFG_START_ADDR + ((uint32_t)&ee.cfg.v1.sysInfo - (uint32_t)&ee), sizeof(ee.cfg.v1.sysInfo), (char *)&ee.cfg.v1.sysInfo ); if ( err != sizeof(ee.cfg.v1.sysInfo) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the ID if ( ee.cfg.v1.sysInfo.u16SectionID != EEPROM_SID_SYSINFO ) { PERROR( "Uninitialized data section\n" ); return EEPROM_ERR_UNAVAILABLE; } // Validate the CRC if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.sysInfo.u16Crc ) { PERROR( "Parity error\n" ); return EEPROM_ERR_PARITY; } // Expand the content of the section memcpy( (void *)pSysInfo->szSn, ee.cfg.v1.sysInfo.szSn, sizeof(pSysInfo->szSn) ); pSysInfo->u8Rev = ee.cfg.v1.sysInfo.u8Rev; pSysInfo->u8Tcxo = ee.cfg.v1.sysInfo.u2Tcxo; pSysInfo->u8Ocxo = ee.cfg.v1.sysInfo.u2Ocxo; pSysInfo->u8GSM850 = ee.cfg.v1.sysInfo.u2GSM850; pSysInfo->u8GSM900 = ee.cfg.v1.sysInfo.u2GSM900; pSysInfo->u8DCS1800 = ee.cfg.v1.sysInfo.u2DCS1800; pSysInfo->u8PCS1900 = ee.cfg.v1.sysInfo.u2PCS1900; break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_WriteSysInfo ************************************************************************//** * * This function writes the system information to the EEPROM. * * @param [in] pSysInfo * Pointer to the system info structure to be written. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo ) { int err; eeprom_Cfg_t ee; // Get a copy of the EEPROM header err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); if ( err != sizeof(ee.hdr) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the header magic ID if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { // Init the header ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; ee.hdr.u16Version = 1; // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } } switch ( ee.hdr.u16Version ) { case 1: { ee.cfg.v1.sysInfo.u16SectionID = EEPROM_SID_SYSINFO; ee.cfg.v1.sysInfo.u16Crc = 0; ee.cfg.v1.sysInfo.u32Time = time(NULL); // Compress the info memcpy( ee.cfg.v1.sysInfo.szSn, pSysInfo->szSn, sizeof(ee.cfg.v1.sysInfo.szSn) ); ee.cfg.v1.sysInfo.u8Rev = pSysInfo->u8Rev; ee.cfg.v1.sysInfo.u2Tcxo = pSysInfo->u8Tcxo; ee.cfg.v1.sysInfo.u2Ocxo = pSysInfo->u8Ocxo; ee.cfg.v1.sysInfo.u2GSM850 = pSysInfo->u8GSM850; ee.cfg.v1.sysInfo.u2GSM900 = pSysInfo->u8GSM900; ee.cfg.v1.sysInfo.u2DCS1800 = pSysInfo->u8DCS1800; ee.cfg.v1.sysInfo.u2PCS1900 = pSysInfo->u8PCS1900; // Add the CRC ee.cfg.v1.sysInfo.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) ); // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint32_t)&ee.cfg.v1.sysInfo - (uint32_t)&ee), sizeof(ee.cfg.v1.sysInfo), (const char *) &ee.cfg.v1.sysInfo ); if ( err != sizeof(ee.cfg.v1.sysInfo) ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_ReadRfClockCal ************************************************************************//** * * This function reads the RF clock calibration data from the EEPROM. * * @param [inout] pRfClockCal * Pointer to a RF clock calibration structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal ) { int err; eeprom_Cfg_t ee; // Get a copy of the EEPROM header err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee), (char *) &ee ); if ( err != sizeof(ee) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the header magic ID if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { PERROR( "Invalid EEPROM format\n" ); return EEPROM_ERR_INVALID; } switch ( ee.hdr.u16Version ) { case 1: { // Get a copy of the EEPROM section err = eeprom_read( EEPROM_CFG_START_ADDR + ((uint32_t)&ee.cfg.v1.rfClk - (uint32_t)&ee), sizeof(ee.cfg.v1.rfClk), (char *)&ee.cfg.v1.rfClk ); if ( err != sizeof(ee.cfg.v1.rfClk) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the ID if ( ee.cfg.v1.rfClk.u16SectionID != EEPROM_SID_RFCLOCK_CAL ) { PERROR( "Uninitialized data section\n" ); return EEPROM_ERR_UNAVAILABLE; } // Validate the CRC if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.rfClk.u16Crc ) { PERROR( "Parity error\n" ); return EEPROM_ERR_PARITY; } // Expand the content of the section pRfClockCal->iClkCor = ee.cfg.v1.rfClk.i24ClkCor; pRfClockCal->u8ClkSrc = ee.cfg.v1.rfClk.u8ClkSrc; break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_WriteRfClockCal ************************************************************************//** * * This function writes the RF clock calibration data to the EEPROM. * * @param [in] pSysInfo * Pointer to the system info structure to be written. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal ) { int err; eeprom_Cfg_t ee; // Get a copy of the EEPROM header err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); if ( err != sizeof(ee.hdr) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the header magic ID if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { // Init the header ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; ee.hdr.u16Version = 1; // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } } switch ( ee.hdr.u16Version ) { case 1: { ee.cfg.v1.rfClk.u16SectionID = EEPROM_SID_RFCLOCK_CAL; ee.cfg.v1.rfClk.u16Crc = 0; ee.cfg.v1.rfClk.u32Time = time(NULL); // Compress the info ee.cfg.v1.rfClk.i24ClkCor = pRfClockCal->iClkCor; ee.cfg.v1.rfClk.u8ClkSrc = pRfClockCal->u8ClkSrc; // Add the CRC ee.cfg.v1.rfClk.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) ); // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint32_t)&ee.cfg.v1.rfClk - (uint32_t)&ee), sizeof(ee.cfg.v1.rfClk), (const char *) &ee.cfg.v1.rfClk ); if ( err != sizeof(ee.cfg.v1.rfClk) ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_ReadTxCal ************************************************************************//** * * This function reads the TX calibration tables for the specified band from * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [inout] pTxCal * Pointer to a TX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal ) { int i; int size; int nArfcn; eeprom_Cfg_t *ee = eeprom_cached_config(); eeprom_SID_t sId; eeprom_CfgTxCal_t *pCfgTxCal = NULL; // Get a copy of the EEPROM header if (!ee) { PERROR( "Reading cached content failed.\n" ); return EEPROM_ERR_DEVICE; } switch ( ee->hdr.u16Version ) { case 1: { switch ( iBand ) { case 0: nArfcn = 124; sId = EEPROM_SID_GSM850_TXCAL; pCfgTxCal = &ee->cfg.v1.gsm850TxCal; size = sizeof(ee->cfg.v1.gsm850TxCal) + sizeof(ee->cfg.v1.__gsm850TxCalMem); break; case 1: nArfcn = 194; sId = EEPROM_SID_GSM900_TXCAL; pCfgTxCal = &ee->cfg.v1.gsm900TxCal; size = sizeof(ee->cfg.v1.gsm900TxCal) + sizeof(ee->cfg.v1.__gsm900TxCalMem); break; case 2: nArfcn = 374; sId = EEPROM_SID_DCS1800_TXCAL; pCfgTxCal = &ee->cfg.v1.dcs1800TxCal; size = sizeof(ee->cfg.v1.dcs1800TxCal) + sizeof(ee->cfg.v1.__dcs1800TxCalMem); break; case 3: nArfcn = 299; sId = EEPROM_SID_PCS1900_TXCAL; pCfgTxCal = &ee->cfg.v1.pcs1900TxCal; size = sizeof(ee->cfg.v1.pcs1900TxCal) + sizeof(ee->cfg.v1.__pcs1900TxCalMem); break; default: PERROR( "Invalid GSM band specified (%d)\n", iBand ); return EEPROM_ERR_INVALID; } // Validate the ID if ( pCfgTxCal->u16SectionID != sId ) { PERROR( "Uninitialized data section\n" ); return EEPROM_ERR_UNAVAILABLE; } // Validate the CRC if ( eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCal->u16Crc ) { PERROR( "Parity error\n" ); return EEPROM_ERR_PARITY; } // Expand the content of the section for ( i = 0; i < 80; i++ ) { pTxCal->fTxGainGmsk[i] = (float)pCfgTxCal->sfixTxGainGmsk[i] * 0.03125f; } pTxCal->fTx8PskCorr = (float)pCfgTxCal->sfixTx8PskCorr * 0.001953125f; for ( i = 0; i < 31; i++ ) { pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCal->sfixTxExtAttCorr[i] * 0.001953125f; } for ( i = 0; i < nArfcn; i++ ) { pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCal->sfixTxRollOffCorr[i] * 0.001953125f; } break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_WriteTxCal ************************************************************************//** * * This function writes the TX calibration tables for the specified band to * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [in] pTxCal * Pointer to a TX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal ) { int i; int err; int size; int nArfcn; eeprom_Cfg_t ee; eeprom_SID_t sId; eeprom_CfgTxCal_t *pCfgTxCal = NULL; // Get a copy of the EEPROM header err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); if ( err != sizeof(ee.hdr) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the header magic ID if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { // Init the header ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; ee.hdr.u16Version = 1; // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } } switch ( ee.hdr.u16Version ) { case 1: { int32_t fixVal; switch ( iBand ) { case 0: nArfcn = 124; sId = EEPROM_SID_GSM850_TXCAL; pCfgTxCal = &ee.cfg.v1.gsm850TxCal; size = sizeof(ee.cfg.v1.gsm850TxCal) + sizeof(ee.cfg.v1.__gsm850TxCalMem); break; case 1: nArfcn = 194; sId = EEPROM_SID_GSM900_TXCAL; pCfgTxCal = &ee.cfg.v1.gsm900TxCal; size = sizeof(ee.cfg.v1.gsm900TxCal) + sizeof(ee.cfg.v1.__gsm900TxCalMem); break; case 2: nArfcn = 374; sId = EEPROM_SID_DCS1800_TXCAL; pCfgTxCal = &ee.cfg.v1.dcs1800TxCal; size = sizeof(ee.cfg.v1.dcs1800TxCal) + sizeof(ee.cfg.v1.__dcs1800TxCalMem); break; case 3: nArfcn = 299; sId = EEPROM_SID_PCS1900_TXCAL; pCfgTxCal = &ee.cfg.v1.pcs1900TxCal; size = sizeof(ee.cfg.v1.pcs1900TxCal) + sizeof(ee.cfg.v1.__pcs1900TxCalMem); break; default: PERROR( "Invalid GSM band specified (%d)\n", iBand ); return EEPROM_ERR_INVALID; } pCfgTxCal->u16SectionID = sId; pCfgTxCal->u16Crc = 0; pCfgTxCal->u32Time = time(NULL); // Compress the calibration tables for ( i = 0; i < 80; i++ ) { fixVal = (int32_t)(pTxCal->fTxGainGmsk[i] * 32.f + (pTxCal->fTxGainGmsk[i]>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgTxCal->sfixTxGainGmsk[i] = 32767; else if ( fixVal < -32768 ) pCfgTxCal->sfixTxGainGmsk[i] = -32768; else pCfgTxCal->sfixTxGainGmsk[i] = (int16_t)fixVal; } fixVal = (int32_t)(pTxCal->fTx8PskCorr * 512.f + (pTxCal->fTx8PskCorr>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgTxCal->sfixTx8PskCorr = 32767; else if ( fixVal < -32768 ) pCfgTxCal->sfixTx8PskCorr = -32768; else pCfgTxCal->sfixTx8PskCorr = (int16_t)fixVal; for ( i = 0; i < 31; i++ ) { fixVal = (int32_t)(pTxCal->fTxExtAttCorr[i] * 512.f + (pTxCal->fTxExtAttCorr[i]>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgTxCal->sfixTxExtAttCorr[i] = 32767; else if ( fixVal < -32768 ) pCfgTxCal->sfixTxExtAttCorr[i] = -32768; else pCfgTxCal->sfixTxExtAttCorr[i] = (int16_t)fixVal; } for ( i = 0; i < nArfcn; i++ ) { fixVal = (int32_t)(pTxCal->fTxRollOffCorr[i] * 512.f + (pTxCal->fTxRollOffCorr[i]>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgTxCal->sfixTxRollOffCorr[i] = 32767; else if ( fixVal < -32768 ) pCfgTxCal->sfixTxRollOffCorr[i] = -32768; else pCfgTxCal->sfixTxRollOffCorr[i] = (int16_t)fixVal; } // Add the CRC pCfgTxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) ); // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint32_t)pCfgTxCal - (uint32_t)&ee), size, (const char *)pCfgTxCal ); if ( err != size ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_ReadRxCal ************************************************************************//** * * This function reads the RX calibration tables for the specified band from * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [in] iUplink * Uplink flag (0:downlink, X:downlink). * * @param [inout] pRxCal * Pointer to a RX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal ) { int i; int size; int nArfcn; eeprom_Cfg_t *ee = eeprom_cached_config(); eeprom_SID_t sId; eeprom_CfgRxCal_t *pCfgRxCal = NULL; if (!ee) { PERROR( "Reading cached content failed.\n" ); return EEPROM_ERR_DEVICE; } switch ( ee->hdr.u16Version ) { case 1: { switch ( iBand ) { case 0: nArfcn = 124; if ( iUplink ) { sId = EEPROM_SID_GSM850_RXUCAL; pCfgRxCal = &ee->cfg.v1.gsm850RxuCal; size = sizeof(ee->cfg.v1.gsm850RxuCal) + sizeof(ee->cfg.v1.__gsm850RxuCalMem); } else { sId = EEPROM_SID_GSM850_RXDCAL; pCfgRxCal = &ee->cfg.v1.gsm850RxdCal; size = sizeof(ee->cfg.v1.gsm850RxdCal) + sizeof(ee->cfg.v1.__gsm850RxdCalMem); } break; case 1: nArfcn = 194; if ( iUplink ) { sId = EEPROM_SID_GSM900_RXUCAL; pCfgRxCal = &ee->cfg.v1.gsm900RxuCal; size = sizeof(ee->cfg.v1.gsm900RxuCal) + sizeof(ee->cfg.v1.__gsm900RxuCalMem); } else { sId = EEPROM_SID_GSM900_RXDCAL; pCfgRxCal = &ee->cfg.v1.gsm900RxdCal; size = sizeof(ee->cfg.v1.gsm900RxdCal) + sizeof(ee->cfg.v1.__gsm900RxdCalMem); } break; case 2: nArfcn = 374; if ( iUplink ) { sId = EEPROM_SID_DCS1800_RXUCAL; pCfgRxCal = &ee->cfg.v1.dcs1800RxuCal; size = sizeof(ee->cfg.v1.dcs1800RxuCal) + sizeof(ee->cfg.v1.__dcs1800RxuCalMem); } else { sId = EEPROM_SID_DCS1800_RXDCAL; pCfgRxCal = &ee->cfg.v1.dcs1800RxdCal; size = sizeof(ee->cfg.v1.dcs1800RxdCal) + sizeof(ee->cfg.v1.__dcs1800RxdCalMem); } break; case 3: nArfcn = 299; if ( iUplink ) { sId = EEPROM_SID_PCS1900_RXUCAL; pCfgRxCal = &ee->cfg.v1.pcs1900RxuCal; size = sizeof(ee->cfg.v1.pcs1900RxuCal) + sizeof(ee->cfg.v1.__pcs1900RxuCalMem); } else { sId = EEPROM_SID_PCS1900_RXDCAL; pCfgRxCal = &ee->cfg.v1.pcs1900RxdCal; size = sizeof(ee->cfg.v1.pcs1900RxdCal) + sizeof(ee->cfg.v1.__pcs1900RxdCalMem); } break; default: PERROR( "Invalid GSM band specified (%d)\n", iBand ); return EEPROM_ERR_INVALID; } // Validate the ID if ( pCfgRxCal->u16SectionID != sId ) { PERROR( "Uninitialized data section\n" ); return EEPROM_ERR_UNAVAILABLE; } // Validate the CRC if ( eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCal->u16Crc ) { PERROR( "Parity error\n" ); return EEPROM_ERR_PARITY; } // Expand the IQ imbalance mode (0:off, 1:on, 2:auto) pRxCal->u8IqImbalMode = pCfgRxCal->u16IqImbalMode; // Expand the IQ imbalance compensation for ( i = 0; i < 4; i++ ) { pRxCal->u16IqImbalCorr[i] = pCfgRxCal->u16IqImbalCorr[i]; } // Expand the External RX gain pRxCal->fExtRxGain = (float)pCfgRxCal->sfixExtRxGain * 0.001953125f; // Expand the Mixer gain error compensation pRxCal->fRxMixGainCorr = (float)pCfgRxCal->sfixRxMixGainCorr * 0.001953125f; // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) for ( i = 0; i < 3; i++ ) { pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCal->sfixRxLnaGainCorr[i] * 0.001953125f; } // Expand the Frequency roll-off compensation for ( i = 0; i < nArfcn; i++ ) { pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCal->sfixRxRollOffCorr[i] * 0.001953125f; } break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Function : eeprom_WriteRxCal ************************************************************************//** * * This function writes the RX calibration tables for the specified band to * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [in] iUplink * Uplink flag (0:downlink, X:downlink). * * @param [in] pRxCal * Pointer to a RX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal ) { int i; int err; int size; int nArfcn; eeprom_Cfg_t ee; eeprom_SID_t sId; eeprom_CfgRxCal_t *pCfgRxCal = NULL; // Get a copy of the EEPROM header err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); if ( err != sizeof(ee.hdr) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); return EEPROM_ERR_DEVICE; } // Validate the header magic ID if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { // Init the header ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; ee.hdr.u16Version = 1; // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } } switch ( ee.hdr.u16Version ) { case 1: { int32_t fixVal; switch ( iBand ) { case 0: nArfcn = 124; if ( iUplink ) { sId = EEPROM_SID_GSM850_RXUCAL; pCfgRxCal = &ee.cfg.v1.gsm850RxuCal; size = sizeof(ee.cfg.v1.gsm850RxuCal) + sizeof(ee.cfg.v1.__gsm850RxuCalMem); } else { sId = EEPROM_SID_GSM850_RXDCAL; pCfgRxCal = &ee.cfg.v1.gsm850RxdCal; size = sizeof(ee.cfg.v1.gsm850RxdCal) + sizeof(ee.cfg.v1.__gsm850RxdCalMem); } break; case 1: nArfcn = 194; if ( iUplink ) { sId = EEPROM_SID_GSM900_RXUCAL; pCfgRxCal = &ee.cfg.v1.gsm900RxuCal; size = sizeof(ee.cfg.v1.gsm900RxuCal) + sizeof(ee.cfg.v1.__gsm900RxuCalMem); } else { sId = EEPROM_SID_GSM900_RXDCAL; pCfgRxCal = &ee.cfg.v1.gsm900RxdCal; size = sizeof(ee.cfg.v1.gsm900RxdCal) + sizeof(ee.cfg.v1.__gsm900RxdCalMem); } break; case 2: nArfcn = 374; if ( iUplink ) { sId = EEPROM_SID_DCS1800_RXUCAL; pCfgRxCal = &ee.cfg.v1.dcs1800RxuCal; size = sizeof(ee.cfg.v1.dcs1800RxuCal) + sizeof(ee.cfg.v1.__dcs1800RxuCalMem); } else { sId = EEPROM_SID_DCS1800_RXDCAL; pCfgRxCal = &ee.cfg.v1.dcs1800RxdCal; size = sizeof(ee.cfg.v1.dcs1800RxdCal) + sizeof(ee.cfg.v1.__dcs1800RxdCalMem); } break; case 3: nArfcn = 299; if ( iUplink ) { sId = EEPROM_SID_PCS1900_RXUCAL; pCfgRxCal = &ee.cfg.v1.pcs1900RxuCal; size = sizeof(ee.cfg.v1.pcs1900RxuCal) + sizeof(ee.cfg.v1.__pcs1900RxuCalMem); } else { sId = EEPROM_SID_PCS1900_RXDCAL; pCfgRxCal = &ee.cfg.v1.pcs1900RxdCal; size = sizeof(ee.cfg.v1.pcs1900RxdCal) + sizeof(ee.cfg.v1.__pcs1900RxdCalMem); } break; default: PERROR( "Invalid GSM band specified (%d)\n", iBand ); return EEPROM_ERR_INVALID; } pCfgRxCal->u16SectionID = sId; pCfgRxCal->u16Crc = 0; pCfgRxCal->u32Time = time(NULL); // Compress the IQ imbalance mode (0:off, 1:on, 2:auto) pCfgRxCal->u16IqImbalMode = pRxCal->u8IqImbalMode; // Compress the IQ imbalance compensation for ( i = 0; i < 4; i++ ) { pCfgRxCal->u16IqImbalCorr[i] = pRxCal->u16IqImbalCorr[i]; } // Compress the External RX gain fixVal = (int32_t)(pRxCal->fExtRxGain * 512.f + (pRxCal->fExtRxGain>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgRxCal->sfixExtRxGain = 32767; else if ( fixVal < -32768 ) pCfgRxCal->sfixExtRxGain = -32768; else pCfgRxCal->sfixExtRxGain = (int16_t)fixVal; // Compress the Mixer gain error compensation fixVal = (int32_t)(pRxCal->fRxMixGainCorr * 512.f + (pRxCal->fRxMixGainCorr>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgRxCal->sfixRxMixGainCorr = 32767; else if ( fixVal < -32768 ) pCfgRxCal->sfixRxMixGainCorr = -32768; else pCfgRxCal->sfixRxMixGainCorr = (int16_t)fixVal; // Compress the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) for ( i = 0; i < 3; i++ ) { fixVal = (int32_t)(pRxCal->fRxLnaGainCorr[i] * 512.f + (pRxCal->fRxLnaGainCorr[i]>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgRxCal->sfixRxLnaGainCorr[i] = 32767; else if ( fixVal < -32768 ) pCfgRxCal->sfixRxLnaGainCorr[i] = -32768; else pCfgRxCal->sfixRxLnaGainCorr[i] = (int16_t)fixVal; } // Compress the Frequency roll-off compensation for ( i = 0; i < nArfcn; i++ ) { fixVal = (int32_t)(pRxCal->fRxRollOffCorr[i] * 512.f + (pRxCal->fRxRollOffCorr[i]>0 ? 0.5f:-0.5f)); if ( fixVal > 32767 ) pCfgRxCal->sfixRxRollOffCorr[i] = 32767; else if ( fixVal < -32768 ) pCfgRxCal->sfixRxRollOffCorr[i] = -32768; else pCfgRxCal->sfixRxRollOffCorr[i] = (int16_t)fixVal; } // Add the CRC pCfgRxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) ); // Write it to the EEPROM err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint32_t)pCfgRxCal - (uint32_t)&ee), size, (const char *)pCfgRxCal ); if ( err != size ) { PERROR( "Error while writing to the EEPROM (%d)\n", err ); return EEPROM_ERR_DEVICE; } break; } default: { PERROR( "Unsupported header version\n" ); return EEPROM_ERR_UNSUPPORTED; } } return EEPROM_SUCCESS; } /**************************************************************************** * Private functions * ****************************************************************************/ /** * Dump the content of the EEPROM to the standard output */ int eeprom_dump( int addr, int size, int hex ) { FILE *f; char ch; int i; f = fopen( EEPROM_DEV, "r+" ); if ( f == NULL ) { perror( "eeprom fopen" ); return -1; } if (fseek( f, addr, SEEK_SET ) != 0) { perror( "eeprom fseek" ); fclose( f ); return -1; } for ( i = 0; i < size; ++i, ++addr ) { if ( fread( &ch, 1, 1, f ) != 1 ) { perror( "eeprom fread" ); fclose( f ); return -1; } if ( hex ) { if ( (i % 16) == 0 ) { printf( "\n %.4x| ", addr ); } else if ( (i % 8) == 0 ) { printf( " " ); } printf( "%.2x ", ch ); } else putchar( ch ); } if ( hex ) { printf( "\n\n" ); } fflush( stdout ); fclose( f ); return 0; } static FILE *g_file; static eeprom_Cfg_t *g_cached_cfg; void eeprom_free_resources(void) { if (g_file) fclose(g_file); g_file = NULL; /* release the header */ free(g_cached_cfg); g_cached_cfg = NULL; } /** * Read up to 'size' bytes of data from the EEPROM starting at offset 'addr'. */ static int eeprom_read( int addr, int size, char *pBuff ) { FILE *f = g_file; int n; if (!f) { f = fopen( EEPROM_DEV, "r+" ); if ( f == NULL ) { perror( "eeprom fopen" ); return -1; } g_file = f; } if (fseek( f, addr, SEEK_SET ) != 0) { perror( "eeprom fseek" ); return -1; } n = fread( pBuff, 1, size, f ); return n; } static void eeprom_cache_cfg(void) { int err; free(g_cached_cfg); g_cached_cfg = malloc(sizeof(*g_cached_cfg)); if (!g_cached_cfg) return; err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(*g_cached_cfg), (char *) g_cached_cfg ); if ( err != sizeof(*g_cached_cfg) ) { PERROR( "Error while reading the EEPROM content (%d)\n", err ); goto error; } if ( g_cached_cfg->hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) { PERROR( "Invalid EEPROM format\n" ); goto error; } return; error: free(g_cached_cfg); g_cached_cfg = NULL; } static eeprom_Cfg_t *eeprom_cached_config(void) { if (!g_cached_cfg) eeprom_cache_cfg(); return g_cached_cfg; } /** * Write up to 'size' bytes of data to the EEPROM starting at offset 'addr'. */ static int eeprom_write( int addr, int size, const char *pBuff ) { FILE *f = g_file; int n; if (!f) { f = fopen( EEPROM_DEV, "r+" ); if ( f == NULL ) { perror( "eeprom fopen" ); return -1; } g_file = f; } if (fseek( f, addr, SEEK_SET ) != 0) { perror( "eeprom fseek" ); n = -1; goto error; } n = fwrite( pBuff, 1, size, f ); error: fclose( f ); g_file = NULL; return n; } /** * EEPROM CRC. */ static uint16_t eeprom_crc( uint8_t *pu8Data, int len ) { int i; uint16_t crc = 0xFFFF; while (len--) { crc ^= (uint16_t)*pu8Data++; for (i=0; i<8; i++) { if (crc & 1) crc = (crc >> 1) ^ 0x8408; else crc = (crc >> 1); } } crc = ~crc; return crc; } osmo-bts-0.4.0/src/osmo-bts-sysmo/eeprom.h000066400000000000000000000275331260026426200204320ustar00rootroot00000000000000/*************************************************************************** * * **** I * ****** *** * ******* **** * ******** **** **** **** ********* ******* **** *********** * ********* **** **** **** ********* ************** ************* * **** ***** **** **** **** **** ***** ****** ***** **** * **** ***** **** **** **** **** ***** **** **** **** * **** ********* **** **** **** **** **** **** **** * **** ******** **** ****I **** ***** ***** **** **** * **** ****** ***** ****** ***** ****** ******* ****** ******* * **** **** ************ ****** ************* ************* * **** *** **** **** **** ***** **** ***** **** * **** * I N N O V A T I O N T O D A Y F O R T O M M O R O W **** * *** * *************************************************************************** * * Project : SuperFemto * File : eeprom.h * Description : EEPROM interface. * * Copyright (c) Nutaq. 2012 * *************************************************************************** * * "$Revision: 1.1 $" * "$Name: $" * "$Date: 2012/06/20 02:18:30 $" * "$Author: Yves.Godin $" * ***************************************************************************/ #ifndef EEPROM_H__ #define EEPROM_H__ #include /**************************************************************************** * Public constants * ****************************************************************************/ /** * EEPROM error code */ typedef enum { EEPROM_SUCCESS = 0, ///< Success EEPROM_ERR_DEVICE = -1, ///< Device access error EEPROM_ERR_PARITY = -2, ///< Parity error EEPROM_ERR_UNAVAILABLE = -3, ///< Information unavailable EEPROM_ERR_INVALID = -4, ///< Invalid format EEPROM_ERR_UNSUPPORTED = -5, ///< Unsupported format } eeprom_Error_t; /**************************************************************************** * Struct : eeprom_SysInfo_t ************************************************************************//** * * SuperFemto system information. * ***************************************************************************/ typedef struct eeprom_SysInfo { char szSn[16]; ///< Serial number uint8_t u8Rev; ///< Board revision uint8_t u8Tcxo; ///< TCXO present (0:absent, 1:present, X:unknown) uint8_t u8Ocxo; ///< OCXO present (0:absent, 1:present, X:unknown) uint8_t u8GSM850; ///< GSM-850 supported (0:unsupported, 1:supported, X:unknown) uint8_t u8GSM900; ///< GSM-900 supported (0:unsupported, 1:supported, X:unknown) uint8_t u8DCS1800; ///< GSM-1800 supported (0:unsupported, 1:supported, X:unknown) uint8_t u8PCS1900; ///< GSM-1900 supported (0:unsupported, 1:supported, X:unknown) } eeprom_SysInfo_t; /**************************************************************************** * Struct : eeprom_RfClockCal_t ************************************************************************//** * * SuperFemto RF clock calibration. * ***************************************************************************/ typedef struct eeprom_RfClockCal { int iClkCor; ///< Clock correction value in PPB. uint8_t u8ClkSrc; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge) } eeprom_RfClockCal_t; /**************************************************************************** * Struct : eeprom_TxCal_t ************************************************************************//** * * SuperFemto transmit calibration table. * ***************************************************************************/ typedef struct eeprom_TxCal { float fTxGainGmsk[80]; ///< Gain setting for GMSK output level from +50dBm to -29 dBm float fTx8PskCorr; ///< Gain adjustment for 8 PSK (default to +3.25 dB) float fTxExtAttCorr[31]; ///< Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB) float fTxRollOffCorr[374]; /**< Gain correction for each ARFCN for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused for DCS-1800: 0=512, 1:513, ..., 373:885 for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */ } eeprom_TxCal_t; /**************************************************************************** * Struct : eeprom_RxCal_t ************************************************************************//** * * SuperFemto receive calibration table. * ***************************************************************************/ typedef struct eeprom_RxCal { float fExtRxGain; ///< External RX gain float fRxMixGainCorr; ///< Mixer gain error compensation float fRxLnaGainCorr[3]; ///< LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) float fRxRollOffCorr[374]; /***< Frequency roll-off compensation for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused for DCS-1800: 0=512, 1:513, ..., 373:885 for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */ uint8_t u8IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto) uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation } eeprom_RxCal_t; /**************************************************************************** * Public functions * ****************************************************************************/ eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr ); /**************************************************************************** * Function : eeprom_ResetCfg ************************************************************************//** * * This function reset the content of the EEPROM config area. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ResetCfg( void ); /**************************************************************************** * Function : eeprom_ReadSysInfo ************************************************************************//** * * This function reads the system information from the EEPROM. * * @param [inout] pTime * Pointer to a system info structure. * * @param [inout] pSysInfo * Pointer to a system info structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo ); /**************************************************************************** * Function : eeprom_WriteSysInfo ************************************************************************//** * * This function writes the system information to the EEPROM. * * @param [in] pSysInfo * Pointer to the system info structure to be written. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo ); /**************************************************************************** * Function : eeprom_ReadRfClockCal ************************************************************************//** * * This function reads the RF clock calibration data from the EEPROM. * * @param [inout] pRfClockCal * Pointer to a RF clock calibration structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal ); /**************************************************************************** * Function : eeprom_WriteRfClockCal ************************************************************************//** * * This function writes the RF clock calibration data to the EEPROM. * * @param [in] pSysInfo * Pointer to the system info structure to be written. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal ); /**************************************************************************** * Function : eeprom_ReadTxCal ************************************************************************//** * * This function reads the TX calibration tables for the specified band from * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [inout] pTxCal * Pointer to a TX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal ); /**************************************************************************** * Function : eeprom_WriteTxCal ************************************************************************//** * * This function writes the TX calibration tables for the specified band to * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [in] pTxCal * Pointer to a TX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal ); /**************************************************************************** * Function : eeprom_ReadRxCal ************************************************************************//** * * This function reads the RX calibration tables for the specified band from * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [in] iUplink * Uplink flag (0:downlink, X:downlink). * * @param [inout] pRxCal * Pointer to a RX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal ); /**************************************************************************** * Function : eeprom_WriteRxCal ************************************************************************//** * * This function writes the RX calibration tables for the specified band to * the EEPROM. * * @param [in] iBand * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). * * @param [in] iUplink * Uplink flag (0:downlink, X:downlink). * * @param [in] pRxCal * Pointer to a RX calibration table structure. * * @return * 0 if or an error core. * ****************************************************************************/ eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal ); void eeprom_free_resources(void); #endif // EEPROM_H__ osmo-bts-0.4.0/src/osmo-bts-sysmo/femtobts.c000066400000000000000000000343571260026426200207630ustar00rootroot00000000000000/* sysmocom femtobts L1 API related definitions */ /* (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "femtobts.h" const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM] = { [GsmL1_PrimId_MphInitReq] = L1P_T_REQ, [GsmL1_PrimId_MphCloseReq] = L1P_T_REQ, [GsmL1_PrimId_MphConnectReq] = L1P_T_REQ, [GsmL1_PrimId_MphDisconnectReq] = L1P_T_REQ, [GsmL1_PrimId_MphActivateReq] = L1P_T_REQ, [GsmL1_PrimId_MphDeactivateReq] = L1P_T_REQ, [GsmL1_PrimId_MphConfigReq] = L1P_T_REQ, [GsmL1_PrimId_MphMeasureReq] = L1P_T_REQ, [GsmL1_PrimId_MphInitCnf] = L1P_T_CONF, [GsmL1_PrimId_MphCloseCnf] = L1P_T_CONF, [GsmL1_PrimId_MphConnectCnf] = L1P_T_CONF, [GsmL1_PrimId_MphDisconnectCnf] = L1P_T_CONF, [GsmL1_PrimId_MphActivateCnf] = L1P_T_CONF, [GsmL1_PrimId_MphDeactivateCnf] = L1P_T_CONF, [GsmL1_PrimId_MphConfigCnf] = L1P_T_CONF, [GsmL1_PrimId_MphMeasureCnf] = L1P_T_CONF, [GsmL1_PrimId_MphTimeInd] = L1P_T_IND, [GsmL1_PrimId_MphSyncInd] = L1P_T_IND, [GsmL1_PrimId_PhEmptyFrameReq] = L1P_T_REQ, [GsmL1_PrimId_PhDataReq] = L1P_T_REQ, [GsmL1_PrimId_PhConnectInd] = L1P_T_IND, [GsmL1_PrimId_PhReadyToSendInd] = L1P_T_IND, [GsmL1_PrimId_PhDataInd] = L1P_T_IND, [GsmL1_PrimId_PhRaInd] = L1P_T_IND, }; const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1] = { { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" }, { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" }, { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" }, { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" }, { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" }, { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" }, { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" }, { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" }, { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" }, { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" }, { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" }, { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" }, { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" }, { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" }, { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" }, { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" }, { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" }, { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" }, { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" }, { GsmL1_PrimId_PhDataReq, "PH-DATA.req" }, { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" }, { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" }, { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" }, { GsmL1_PrimId_PhRaInd, "PH-RA.ind" }, { 0, NULL } }; const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM] = { [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf, [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf, [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf, [GsmL1_PrimId_MphDisconnectReq] = GsmL1_PrimId_MphDisconnectCnf, [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf, [GsmL1_PrimId_MphDeactivateReq] = GsmL1_PrimId_MphDeactivateCnf, [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf, [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf, }; const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM] = { [SuperFemto_PrimId_SystemInfoReq] = L1P_T_REQ, [SuperFemto_PrimId_SystemInfoCnf] = L1P_T_CONF, [SuperFemto_PrimId_SystemFailureInd] = L1P_T_IND, [SuperFemto_PrimId_ActivateRfReq] = L1P_T_REQ, [SuperFemto_PrimId_ActivateRfCnf] = L1P_T_CONF, [SuperFemto_PrimId_DeactivateRfReq] = L1P_T_REQ, [SuperFemto_PrimId_DeactivateRfCnf] = L1P_T_CONF, [SuperFemto_PrimId_SetTraceFlagsReq] = L1P_T_REQ, [SuperFemto_PrimId_RfClockInfoReq] = L1P_T_REQ, [SuperFemto_PrimId_RfClockInfoCnf] = L1P_T_CONF, [SuperFemto_PrimId_RfClockSetupReq] = L1P_T_REQ, [SuperFemto_PrimId_RfClockSetupCnf] = L1P_T_CONF, [SuperFemto_PrimId_Layer1ResetReq] = L1P_T_REQ, [SuperFemto_PrimId_Layer1ResetCnf] = L1P_T_CONF, #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) [SuperFemto_PrimId_GetTxCalibTblReq] = L1P_T_REQ, [SuperFemto_PrimId_GetTxCalibTblCnf] = L1P_T_CONF, [SuperFemto_PrimId_SetTxCalibTblReq] = L1P_T_REQ, [SuperFemto_PrimId_SetTxCalibTblCnf] = L1P_T_CONF, [SuperFemto_PrimId_GetRxCalibTblReq] = L1P_T_REQ, [SuperFemto_PrimId_GetRxCalibTblCnf] = L1P_T_CONF, [SuperFemto_PrimId_SetRxCalibTblReq] = L1P_T_REQ, [SuperFemto_PrimId_SetRxCalibTblCnf] = L1P_T_CONF, #endif #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) [SuperFemto_PrimId_MuteRfReq] = L1P_T_REQ, [SuperFemto_PrimId_MuteRfCnf] = L1P_T_CONF, #endif }; const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1] = { { SuperFemto_PrimId_SystemInfoReq, "SYSTEM-INFO.req" }, { SuperFemto_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" }, { SuperFemto_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" }, { SuperFemto_PrimId_ActivateRfReq, "ACTIVATE-RF.req" }, { SuperFemto_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" }, { SuperFemto_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" }, { SuperFemto_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" }, { SuperFemto_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" }, { SuperFemto_PrimId_RfClockInfoReq, "RF-CLOCK-INFO.req" }, { SuperFemto_PrimId_RfClockInfoCnf, "RF-CLOCK-INFO.conf" }, { SuperFemto_PrimId_RfClockSetupReq, "RF-CLOCK-SETUP.req" }, { SuperFemto_PrimId_RfClockSetupCnf, "RF-CLOCK-SETUP.conf" }, { SuperFemto_PrimId_Layer1ResetReq, "LAYER1-RESET.req" }, { SuperFemto_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" }, #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) { SuperFemto_PrimId_GetTxCalibTblReq, "GET-TX-CALIB.req" }, { SuperFemto_PrimId_GetTxCalibTblCnf, "GET-TX-CALIB.cnf" }, { SuperFemto_PrimId_SetTxCalibTblReq, "SET-TX-CALIB.req" }, { SuperFemto_PrimId_SetTxCalibTblCnf, "SET-TX-CALIB.cnf" }, { SuperFemto_PrimId_GetRxCalibTblReq, "GET-RX-CALIB.req" }, { SuperFemto_PrimId_GetRxCalibTblCnf, "GET-RX-CALIB.cnf" }, { SuperFemto_PrimId_SetRxCalibTblReq, "SET-RX-CALIB.req" }, { SuperFemto_PrimId_SetRxCalibTblCnf, "SET-RX-CALIB.cnf" }, #endif #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) { SuperFemto_PrimId_MuteRfReq, "MUTE-RF.req" }, { SuperFemto_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, #endif { 0, NULL } }; const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM] = { [SuperFemto_PrimId_SystemInfoReq] = SuperFemto_PrimId_SystemInfoCnf, [SuperFemto_PrimId_ActivateRfReq] = SuperFemto_PrimId_ActivateRfCnf, [SuperFemto_PrimId_DeactivateRfReq] = SuperFemto_PrimId_DeactivateRfCnf, [SuperFemto_PrimId_RfClockInfoReq] = SuperFemto_PrimId_RfClockInfoCnf, [SuperFemto_PrimId_RfClockSetupReq] = SuperFemto_PrimId_RfClockSetupCnf, [SuperFemto_PrimId_Layer1ResetReq] = SuperFemto_PrimId_Layer1ResetCnf, #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) [SuperFemto_PrimId_GetTxCalibTblReq] = SuperFemto_PrimId_GetTxCalibTblCnf, [SuperFemto_PrimId_SetTxCalibTblReq] = SuperFemto_PrimId_SetTxCalibTblCnf, [SuperFemto_PrimId_GetRxCalibTblReq] = SuperFemto_PrimId_GetRxCalibTblCnf, [SuperFemto_PrimId_SetRxCalibTblReq] = SuperFemto_PrimId_SetRxCalibTblCnf, #endif #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) [SuperFemto_PrimId_MuteRfReq] = SuperFemto_PrimId_MuteRfCnf, #endif }; const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1] = { { GsmL1_Sapi_Idle, "IDLE" }, { GsmL1_Sapi_Fcch, "FCCH" }, { GsmL1_Sapi_Sch, "SCH" }, { GsmL1_Sapi_Sacch, "SACCH" }, { GsmL1_Sapi_Sdcch, "SDCCH" }, { GsmL1_Sapi_Bcch, "BCCH" }, { GsmL1_Sapi_Pch, "PCH" }, { GsmL1_Sapi_Agch, "AGCH" }, { GsmL1_Sapi_Cbch, "CBCH" }, { GsmL1_Sapi_Rach, "RACH" }, { GsmL1_Sapi_TchF, "TCH/F" }, { GsmL1_Sapi_FacchF, "FACCH/F" }, { GsmL1_Sapi_TchH, "TCH/H" }, { GsmL1_Sapi_FacchH, "FACCH/H" }, { GsmL1_Sapi_Nch, "NCH" }, { GsmL1_Sapi_Pdtch, "PDTCH" }, { GsmL1_Sapi_Pacch, "PACCH" }, { GsmL1_Sapi_Pbcch, "PBCCH" }, { GsmL1_Sapi_Pagch, "PAGCH" }, { GsmL1_Sapi_Ppch, "PPCH" }, { GsmL1_Sapi_Pnch, "PNCH" }, { GsmL1_Sapi_Ptcch, "PTCCH" }, { GsmL1_Sapi_Prach, "PRACH" }, { 0, NULL } }; const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1] = { { GsmL1_Status_Success, "Success" }, { GsmL1_Status_Generic, "Generic error" }, { GsmL1_Status_NoMemory, "Not enough memory" }, { GsmL1_Status_Timeout, "Timeout" }, { GsmL1_Status_InvalidParam, "Invalid parameter" }, { GsmL1_Status_Busy, "Resource busy" }, { GsmL1_Status_NoRessource, "No more resources" }, { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" }, { GsmL1_Status_NullInterface, "Trying to call a NULL interface" }, { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" }, { GsmL1_Status_BadCrc, "Bad CRC" }, { GsmL1_Status_BadUsf, "Bad USF" }, { GsmL1_Status_InvalidCPS, "Invalid CPS field" }, { GsmL1_Status_UnexpectedBurst, "Unexpected burst" }, { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" }, { GsmL1_Status_CriticalError, "Critical error" }, { GsmL1_Status_OverheatError, "Overheat error" }, { GsmL1_Status_DeviceError, "Device error" }, { GsmL1_Status_FacchError, "FACCH / TCH order error" }, { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" }, { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" }, { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" }, { GsmL1_Status_NotSynchronized, "Not synchronized" }, { GsmL1_Status_Unsupported, "Unsupported feature" }, { 0, NULL } }; const struct value_string femtobts_tracef_names[29] = { { DBG_DEBUG, "DEBUG" }, { DBG_L1WARNING, "L1_WARNING" }, { DBG_ERROR, "ERROR" }, { DBG_L1RXMSG, "L1_RX_MSG" }, { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" }, { DBG_L1TXMSG, "L1_TX_MSG" }, { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" }, { DBG_MPHCNF, "MPH_CNF" }, { DBG_MPHIND, "MPH_IND" }, { DBG_MPHREQ, "MPH_REQ" }, { DBG_PHIND, "PH_IND" }, { DBG_PHREQ, "PH_REQ" }, { DBG_PHYRF, "PHY_RF" }, { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" }, { DBG_MODE, "MODE" }, { DBG_TDMAINFO, "TDMA_INFO" }, { DBG_BADCRC, "BAD_CRC" }, { DBG_PHINDBYTE, "PH_IND_BYTE" }, { DBG_PHREQBYTE, "PH_REQ_BYTE" }, { DBG_DEVICEMSG, "DEVICE_MSG" }, { DBG_RACHINFO, "RACH_INFO" }, { DBG_LOGCHINFO, "LOG_CH_INFO" }, { DBG_MEMORY, "MEMORY" }, { DBG_PROFILING, "PROFILING" }, { DBG_TESTCOMMENT, "TEST_COMMENT" }, { DBG_TEST, "TEST" }, { DBG_STATUS, "STATUS" }, { 0, NULL } }; const struct value_string femtobts_tracef_docs[29] = { { DBG_DEBUG, "Debug Region" }, { DBG_L1WARNING, "L1 Warning Region" }, { DBG_ERROR, "Error Region" }, { DBG_L1RXMSG, "L1_RX_MSG Region" }, { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" }, { DBG_L1TXMSG, "L1_TX_MSG Region" }, { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" }, { DBG_MPHCNF, "MphConfirmation Region" }, { DBG_MPHIND, "MphIndication Region" }, { DBG_MPHREQ, "MphRequest Region" }, { DBG_PHIND, "PhIndication Region" }, { DBG_PHREQ, "PhRequest Region" }, { DBG_PHYRF, "PhyRF Region" }, { DBG_PHYRFMSGBYTE, "PhyRF Message Region" }, { DBG_MODE, "Mode Region" }, { DBG_TDMAINFO, "TDMA Info Region" }, { DBG_BADCRC, "Bad CRC Region" }, { DBG_PHINDBYTE, "PH_IND_BYTE" }, { DBG_PHREQBYTE, "PH_REQ_BYTE" }, { DBG_DEVICEMSG, "Device Message Region" }, { DBG_RACHINFO, "RACH Info" }, { DBG_LOGCHINFO, "LOG_CH_INFO" }, { DBG_MEMORY, "Memory Region" }, { DBG_PROFILING, "Profiling Region" }, { DBG_TESTCOMMENT, "Test Comments" }, { DBG_TEST, "Test Region" }, { DBG_STATUS, "Status Region" }, { 0, NULL } }; const struct value_string femtobts_tch_pl_names[] = { { GsmL1_TchPlType_NA, "N/A" }, { GsmL1_TchPlType_Fr, "FR" }, { GsmL1_TchPlType_Hr, "HR" }, { GsmL1_TchPlType_Efr, "EFR" }, { GsmL1_TchPlType_Amr, "AMR(IF2)" }, { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" }, { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" }, { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" }, { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" }, { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" }, { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" }, { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" }, { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" }, { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" }, { 0, NULL } }; const struct value_string femtobts_clksrc_names[] = { #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) { SuperFemto_ClkSrcId_None, "None" }, { SuperFemto_ClkSrcId_Ocxo, "ocxo" }, { SuperFemto_ClkSrcId_Tcxo, "tcxo" }, { SuperFemto_ClkSrcId_External, "ext" }, { SuperFemto_ClkSrcId_GpsPps, "gps" }, { SuperFemto_ClkSrcId_Trx, "trx" }, { SuperFemto_ClkSrcId_Rx, "rx" }, { SuperFemto_ClkSrcId_Edge, "edge" }, { SuperFemto_ClkSrcId_NetList, "nwl" }, #else { SF_CLKSRC_NONE, "None" }, { SF_CLKSRC_OCXO, "ocxo" }, { SF_CLKSRC_TCXO, "tcxo" }, { SF_CLKSRC_EXT, "ext" }, { SF_CLKSRC_GPS, "gps" }, { SF_CLKSRC_TRX, "trx" }, { SF_CLKSRC_RX, "rx" }, #endif { 0, NULL } }; const struct value_string femtobts_dir_names[] = { { GsmL1_Dir_TxDownlink, "TxDL" }, { GsmL1_Dir_TxUplink, "TxUL" }, { GsmL1_Dir_RxUplink, "RxUL" }, { GsmL1_Dir_RxDownlink, "RxDL" }, { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" }, { 0, NULL } }; const struct value_string femtobts_chcomb_names[] = { { GsmL1_LogChComb_0, "dummy" }, { GsmL1_LogChComb_I, "tch_f" }, { GsmL1_LogChComb_II, "tch_h" }, { GsmL1_LogChComb_IV, "ccch" }, { GsmL1_LogChComb_V, "ccch_sdcch4" }, { GsmL1_LogChComb_VII, "sdcch8" }, { GsmL1_LogChComb_XIII, "pdtch" }, { 0, NULL } }; const uint8_t pdch_msu_size[_NUM_PDCH_CS] = { [PDCH_CS_1] = 23, [PDCH_CS_2] = 34, [PDCH_CS_3] = 40, [PDCH_CS_4] = 54, [PDCH_MCS_1] = 27, [PDCH_MCS_2] = 33, [PDCH_MCS_3] = 42, [PDCH_MCS_4] = 49, [PDCH_MCS_5] = 60, [PDCH_MCS_6] = 78, [PDCH_MCS_7] = 118, [PDCH_MCS_8] = 142, [PDCH_MCS_9] = 154 }; osmo-bts-0.4.0/src/osmo-bts-sysmo/femtobts.h000066400000000000000000000067341260026426200207660ustar00rootroot00000000000000#ifndef FEMTOBTS_H #define FEMTOBTS_H #include #include #include #include #ifdef FEMTOBTS_API_VERSION #define SuperFemto_PrimId_t FemtoBts_PrimId_t #define SuperFemto_Prim_t FemtoBts_Prim_t #define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq #define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf #define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t #define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd #define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq #define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf #define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq #define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf #define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq #define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq #define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf #define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq #define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf #define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq #define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf #define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM #define HW_SYSMOBTS_V1 1 #define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z) #endif #ifdef L1_HAS_RTP_MODE /* * The bit ordering has been fixed on >= 3.10 but I am verifying * this on 3.11. */ #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 11, 0) #define USE_L1_RTP_MODE /* Tell L1 to use RTP mode */ #endif #endif /* * Depending on the firmware version either GsmL1_Prim_t or SuperFemto_Prim_t * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the * bigger struct. */ #define SYSMOBTS_PRIM_SIZE \ (OSMO_MAX(sizeof(SuperFemto_Prim_t), sizeof(GsmL1_Prim_t)) + 128) enum l1prim_type { L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */ L1P_T_REQ, L1P_T_CONF, L1P_T_IND, }; #if !defined(SUPERFEMTO_API_VERSION) || SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) enum uperfemto_clk_src { SF_CLKSRC_NONE = 0, SF_CLKSRC_OCXO = 1, SF_CLKSRC_TCXO = 2, SF_CLKSRC_EXT = 3, SF_CLKSRC_GPS = 4, SF_CLKSRC_TRX = 5, SF_CLKSRC_RX = 6, SF_CLKSRC_NL = 7, }; #endif const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM]; const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1]; const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM]; const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM]; const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1]; const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM]; const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1]; const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1]; const struct value_string femtobts_tracef_names[29]; const struct value_string femtobts_tracef_docs[29]; const struct value_string femtobts_tch_pl_names[15]; const struct value_string femtobts_clksrc_names[10]; const struct value_string femtobts_dir_names[6]; enum pdch_cs { PDCH_CS_1, PDCH_CS_2, PDCH_CS_3, PDCH_CS_4, PDCH_MCS_1, PDCH_MCS_2, PDCH_MCS_3, PDCH_MCS_4, PDCH_MCS_5, PDCH_MCS_6, PDCH_MCS_7, PDCH_MCS_8, PDCH_MCS_9, _NUM_PDCH_CS }; const uint8_t pdch_msu_size[_NUM_PDCH_CS]; #endif /* FEMTOBTS_H */ osmo-bts-0.4.0/src/osmo-bts-sysmo/hw_misc.c000066400000000000000000000043171260026426200205620ustar00rootroot00000000000000/* Misc HW routines for Sysmocom BTS */ /* (C) 2012 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include "hw_misc.h" static const struct value_string sysmobts_led_names[] = { { LED_RF_ACTIVE, "activity_led" }, { LED_ONLINE, "online_led" }, { 0, NULL } }; int sysmobts_led_set(enum sysmobts_led nr, int on) { char tmp[PATH_MAX+1]; const char *filename; int fd; uint8_t byte; if (on) byte = '1'; else byte = '0'; filename = get_value_string(sysmobts_led_names, nr); if (!filename) return -EINVAL; snprintf(tmp, sizeof(tmp)-1, "/sys/class/leds/%s/brightness", filename); tmp[sizeof(tmp)-1] = '\0'; fd = open(tmp, O_WRONLY); if (fd < 0) return -ENODEV; write(fd, &byte, 1); close(fd); return 0; } #if 0 #define HWMON_PREFIX "/sys/class/hwmon/hwmon0/device" static FILE *temperature_f[NUM_TEMP]; int sysmobts_temp_init() { char tmp[PATH_MAX+1]; FILE *in; int rc = 0; for (i = 0; i < NUM_TEMP; i++) { snprintf(tmp, sizeof(tmp)-1, HWMON_PREFIX "/temp%u_input", i+1), tmp[sizeof(tmp)-1] = '\0'; temperature_f[i] = fopen(tmp, "r"); if (!temperature_f[i]) rc = -ENODEV; } return 0; } int sysmobts_temp_get(uint8_t num) { if (num >= NUM_TEMP) return -EINVAL; if (!temperature_f[num]) return -ENODEV; in = fopen(tmp, "r"); if (!in) return -ENODEV; fclose(tmp); return 0; } #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/hw_misc.h000066400000000000000000000002651260026426200205650ustar00rootroot00000000000000#ifndef _SYSMOBTS_HW_MISC_H #define _SYSMOBTS_HW_MISC_H enum sysmobts_led { LED_NONE, LED_RF_ACTIVE, LED_ONLINE, }; int sysmobts_led_set(enum sysmobts_led nr, int on); #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_fwd.h000066400000000000000000000001621260026426200203040ustar00rootroot00000000000000#define L1FWD_L1_PORT 9999 #define L1FWD_SYS_PORT 9998 #define L1FWD_TCH_PORT 9997 #define L1FWD_PDTCH_PORT 9996 osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_fwd_main.c000066400000000000000000000136131260026426200213100ustar00rootroot00000000000000/* Sysmocom femtobts L1 proxy */ /* (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" #include "l1_transp.h" #include "l1_fwd.h" static const uint16_t fwd_udp_ports[_NUM_MQ_WRITE] = { [MQ_SYS_READ] = L1FWD_SYS_PORT, [MQ_L1_READ] = L1FWD_L1_PORT, #ifndef HW_SYSMOBTS_V1 [MQ_TCH_READ] = L1FWD_TCH_PORT, [MQ_PDTCH_READ] = L1FWD_PDTCH_PORT, #endif }; struct l1fwd_hdl { struct sockaddr_storage remote_sa[_NUM_MQ_WRITE]; socklen_t remote_sa_len[_NUM_MQ_WRITE]; struct osmo_wqueue udp_wq[_NUM_MQ_WRITE]; struct femtol1_hdl *fl1h; }; /* callback when there's a new L1 primitive coming in from the HW */ int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg) { struct l1fwd_hdl *l1fh = fl1h->priv; /* Enqueue message to UDP socket */ if (osmo_wqueue_enqueue(&l1fh->udp_wq[wq], msg) != 0) { LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n", wq); msgb_free(msg); return -EAGAIN; } return 0; } /* callback when there's a new SYS primitive coming in from the HW */ int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg) { struct l1fwd_hdl *l1fh = fl1h->priv; /* Enqueue message to UDP socket */ if (osmo_wqueue_enqueue(&l1fh->udp_wq[MQ_SYS_WRITE], msg) != 0) { LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE ful. dropping msg\n"); msgb_free(msg); return -EAGAIN; } return 0; } /* data has arrived on the udp socket */ static int udp_read_cb(struct osmo_fd *ofd) { struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx"); struct l1fwd_hdl *l1fh = ofd->data; struct femtol1_hdl *fl1h = l1fh->fl1h; int rc; if (!msg) return -ENOMEM; msg->l1h = msg->data; l1fh->remote_sa_len[ofd->priv_nr] = sizeof(l1fh->remote_sa[ofd->priv_nr]); rc = recvfrom(ofd->fd, msg->l1h, msgb_tailroom(msg), 0, (struct sockaddr *) &l1fh->remote_sa[ofd->priv_nr], &l1fh->remote_sa_len[ofd->priv_nr]); if (rc < 0) { perror("read from udp"); msgb_free(msg); return rc; } else if (rc == 0) { perror("len=0 read from udp"); msgb_free(msg); return rc; } msgb_put(msg, rc); DEBUGP(DL1C, "UDP: Received %u bytes for queue %d\n", rc, ofd->priv_nr); /* put the message into the right queue */ if (osmo_wqueue_enqueue(&fl1h->write_q[ofd->priv_nr], msg) != 0) { LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n", ofd->priv_nr); msgb_free(msg); return -EAGAIN; } return 0; } /* callback when we can write to the UDP socket */ static int udp_write_cb(struct osmo_fd *ofd, struct msgb *msg) { int rc; struct l1fwd_hdl *l1fh = ofd->data; DEBUGP(DL1C, "UDP: Writing %u bytes for queue %d\n", msgb_l1len(msg), ofd->priv_nr); rc = sendto(ofd->fd, msg->l1h, msgb_l1len(msg), 0, (const struct sockaddr *)&l1fh->remote_sa[ofd->priv_nr], l1fh->remote_sa_len[ofd->priv_nr]); if (rc < 0) { LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", strerror(errno)); return rc; } else if (rc < msgb_l1len(msg)) { LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " "%u < %u\n", rc, msgb_l1len(msg)); return -EIO; } return 0; } int main(int argc, char **argv) { struct l1fwd_hdl *l1fh; struct femtol1_hdl *fl1h; int rc, i; printf("sizeof(GsmL1_Prim_t) = %zu\n", sizeof(GsmL1_Prim_t)); printf("sizeof(SuperFemto_Prim_t) = %zu\n", sizeof(SuperFemto_Prim_t)); bts_log_init(NULL); /* * hack and prevent that two l1fwd-proxy/sysmobts run at the same * time. This is done by binding to the same VTY port. */ rc = osmo_sock_init(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "127.0.0.1", 4241, OSMO_SOCK_F_BIND); if (rc < 0) { fprintf(stderr, "Failed to bind to the BTS VTY port.\n"); return EXIT_FAILURE; } /* allocate new femtol1_handle */ fl1h = talloc_zero(NULL, struct femtol1_hdl); INIT_LLIST_HEAD(&fl1h->wlc_list); /* open the actual hardware transport */ for (i = 0; i < ARRAY_SIZE(fl1h->write_q); i++) { rc = l1if_transport_open(i, fl1h); if (rc < 0) exit(1); } /* create our fwd handle */ l1fh = talloc_zero(NULL, struct l1fwd_hdl); l1fh->fl1h = fl1h; fl1h->priv = l1fh; /* Open UDP */ for (i = 0; i < ARRAY_SIZE(l1fh->udp_wq); i++) { struct osmo_wqueue *wq = &l1fh->udp_wq[i]; osmo_wqueue_init(wq, 10); wq->write_cb = udp_write_cb; wq->read_cb = udp_read_cb; wq->bfd.when |= BSC_FD_READ; wq->bfd.data = l1fh; wq->bfd.priv_nr = i; rc = osmo_sock_init_ofd(&wq->bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, NULL, fwd_udp_ports[i], OSMO_SOCK_F_BIND); if (rc < 0) { perror("sock_init"); exit(1); } } while (1) { rc = osmo_select_main(0); if (rc < 0) { perror("select"); exit(1); } } exit(0); } osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_if.c000066400000000000000000001314231260026426200201220ustar00rootroot00000000000000/* Interface handler for Sysmocom L1 */ /* (C) 2011-2014 by Harald Welte * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" #include "l1_transp.h" #include "hw_misc.h" #include "misc/sysmobts_par.h" #include "eeprom.h" #include "utils.h" extern int pcu_direct; #define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */ #define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */ struct wait_l1_conf { struct llist_head list; /* internal linked list */ struct osmo_timer_list timer; /* timer for L1 timeout */ unsigned int conf_prim_id; /* primitive we expect in response */ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */ l1if_compl_cb *cb; void *cb_data; }; static void release_wlc(struct wait_l1_conf *wlc) { osmo_timer_del(&wlc->timer); talloc_free(wlc); } static void l1if_req_timeout(void *data) { struct wait_l1_conf *wlc = data; if (wlc->is_sys_prim) LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n", get_value_string(femtobts_sysprim_names, wlc->conf_prim_id)); else LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", get_value_string(femtobts_l1prim_names, wlc->conf_prim_id)); exit(23); } static int _l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, int is_system_prim, l1if_compl_cb *cb, void *data) { struct wait_l1_conf *wlc; struct osmo_wqueue *wqueue; unsigned int timeout_secs; /* allocate new wsc and store reference to mutex and conf_id */ wlc = talloc_zero(fl1h, struct wait_l1_conf); wlc->cb = cb; wlc->cb_data = data; /* Make sure we actually have received a REQUEST type primitive */ if (is_system_prim == 0) { GsmL1_Prim_t *l1p = msgb_l1prim(msg); LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n", get_value_string(femtobts_l1prim_names, l1p->id)); if (femtobts_l1prim_type[l1p->id] != L1P_T_REQ) { LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", get_value_string(femtobts_l1prim_names, l1p->id)); talloc_free(wlc); return -EINVAL; } wlc->is_sys_prim = 0; wlc->conf_prim_id = femtobts_l1prim_req2conf[l1p->id]; wqueue = &fl1h->write_q[MQ_L1_WRITE]; timeout_secs = 30; } else { SuperFemto_Prim_t *sysp = msgb_sysprim(msg); LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n", get_value_string(femtobts_sysprim_names, sysp->id)); if (femtobts_sysprim_type[sysp->id] != L1P_T_REQ) { LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", get_value_string(femtobts_sysprim_names, sysp->id)); talloc_free(wlc); return -EINVAL; } wlc->is_sys_prim = 1; wlc->conf_prim_id = femtobts_sysprim_req2conf[sysp->id]; wqueue = &fl1h->write_q[MQ_SYS_WRITE]; timeout_secs = 30; } /* enqueue the message in the queue and add wsc to list */ if (osmo_wqueue_enqueue(wqueue, msg) != 0) { /* So we will get a timeout but the log message might help */ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n", is_system_prim ? "system primitive" : "gsm"); msgb_free(msg); } llist_add(&wlc->list, &fl1h->wlc_list); /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */ wlc->timer.data = wlc; wlc->timer.cb = l1if_req_timeout; osmo_timer_schedule(&wlc->timer, timeout_secs, 0); return 0; } /* send a request primitive to the L1 and schedule completion call-back */ int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, l1if_compl_cb *cb, void *data) { return _l1if_req_compl(fl1h, msg, 1, cb, data); } int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, l1if_compl_cb *cb, void *data) { return _l1if_req_compl(fl1h, msg, 0, cb, data); } /* allocate a msgb containing a GsmL1_Prim_t */ struct msgb *l1p_msgb_alloc(void) { struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim"); if (msg) msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t)); return msg; } /* allocate a msgb containing a SuperFemto_Prim_t */ struct msgb *sysp_msgb_alloc(void) { struct msgb *msg = msgb_alloc(sizeof(SuperFemto_Prim_t), "sys_prim"); if (msg) msg->l1h = msgb_put(msg, sizeof(SuperFemto_Prim_t)); return msg; } static GsmL1_PhDataReq_t * data_req_from_rts_ind(GsmL1_Prim_t *l1p, const GsmL1_PhReadyToSendInd_t *rts_ind) { GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; l1p->id = GsmL1_PrimId_PhDataReq; /* copy fields from PH-RSS.ind */ data_req->hLayer1 = rts_ind->hLayer1; data_req->u8Tn = rts_ind->u8Tn; data_req->u32Fn = rts_ind->u32Fn; data_req->sapi = rts_ind->sapi; data_req->subCh = rts_ind->subCh; data_req->u8BlockNbr = rts_ind->u8BlockNbr; return data_req; } static GsmL1_PhEmptyFrameReq_t * empty_req_from_rts_ind(GsmL1_Prim_t *l1p, const GsmL1_PhReadyToSendInd_t *rts_ind) { GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; l1p->id = GsmL1_PrimId_PhEmptyFrameReq; empty_req->hLayer1 = rts_ind->hLayer1; empty_req->u8Tn = rts_ind->u8Tn; empty_req->u32Fn = rts_ind->u32Fn; empty_req->sapi = rts_ind->sapi; empty_req->subCh = rts_ind->subCh; empty_req->u8BlockNbr = rts_ind->u8BlockNbr; return empty_req; } /* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable * uni-directional de-cryption on the uplink. We need this ugly layering * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD) * to this point in L1 */ static int check_for_ciph_cmd(struct femtol1_hdl *fl1h, struct msgb *msg, struct gsm_lchan *lchan) { uint8_t n_s; /* only do this if we are in the right state */ switch (lchan->ciph_state) { case LCHAN_CIPH_NONE: case LCHAN_CIPH_RX_REQ: break; default: return 0; } /* First byte (Address Field) of LAPDm header) */ if (msg->data[0] != 0x03) return 0; /* First byte (protocol discriminator) of RR */ if ((msg->data[3] & 0xF) != GSM48_PDISC_RR) return 0; /* 2nd byte (msg type) of RR */ if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD) return 0; /* Remember N(S) + 1 to find the first ciphered frame */ n_s = (msg->data[1] >> 1) & 0x7; lchan->ciph_ns = (n_s + 1) % 8; lchan->ciph_state = LCHAN_CIPH_RX_REQ; l1if_set_ciphering(fl1h, lchan, 0); return 1; } /* public helpers for the test */ int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h, struct msgb *msg, struct gsm_lchan *lchan) { return check_for_ciph_cmd(fl1h, msg, lchan); } static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = { 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B }; /* fill PH-DATA.req from l1sap primitive */ static GsmL1_PhDataReq_t * data_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1, uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, uint8_t block_nr, uint8_t len) { GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; l1p->id = GsmL1_PrimId_PhDataReq; /* copy fields from PH-RSS.ind */ data_req->hLayer1 = fl1->hLayer1; data_req->u8Tn = tn; data_req->u32Fn = fn; data_req->sapi = sapi; data_req->subCh = sub_ch; data_req->u8BlockNbr = block_nr; data_req->msgUnitParam.u8Size = len; return data_req; } /* fill PH-EMPTY_FRAME.req from l1sap primitive */ static GsmL1_PhEmptyFrameReq_t * empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1, uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t subch, uint8_t block_nr) { GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; l1p->id = GsmL1_PrimId_PhEmptyFrameReq; empty_req->hLayer1 = fl1->hLayer1; empty_req->u8Tn = tn; empty_req->u32Fn = fn; empty_req->sapi = sapi; empty_req->subCh = subch; empty_req->u8BlockNbr = block_nr; return empty_req; } static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, struct osmo_phsap_prim *l1sap) { struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); uint32_t u32Fn; uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; uint8_t chan_nr, link_id; GsmL1_Prim_t *l1p; int len; if (!msg) { LOGP(DL1C, LOGL_FATAL, "PH-DATA.req without msg. " "Please fix!\n"); abort(); } chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; u32Fn = l1sap->u.data.fn; u8Tn = L1SAP_CHAN2TS(chan_nr); subCh = 0x1f; if (L1SAP_IS_LINK_SACCH(link_id)) { sapi = GsmL1_Sapi_Sacch; if (!L1SAP_IS_CHAN_TCHF(chan_nr)) subCh = l1sap_chan2ss(chan_nr); } else if (L1SAP_IS_CHAN_TCHF(chan_nr)) { if (trx->ts[u8Tn].pchan == GSM_PCHAN_PDCH) { if (L1SAP_IS_PTCCH(u32Fn)) { sapi = GsmL1_Sapi_Ptcch; u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn); } else { sapi = GsmL1_Sapi_Pdtch; u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn); } } else { sapi = GsmL1_Sapi_FacchF; u8BlockNbr = (u32Fn % 13) >> 2; } } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { subCh = L1SAP_CHAN2SS_TCHH(chan_nr); sapi = GsmL1_Sapi_FacchH; u8BlockNbr = (u32Fn % 26) >> 3; } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); sapi = GsmL1_Sapi_Sdcch; } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); sapi = GsmL1_Sapi_Sdcch; } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { sapi = GsmL1_Sapi_Bcch; } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { /* The sapi depends on DSP configuration, not * on the actual SYSTEM INFORMATION 3. */ u8BlockNbr = L1SAP_FN2CCCHBLOCK(u32Fn); if (u8BlockNbr >= 1) sapi = GsmL1_Sapi_Pch; else sapi = GsmL1_Sapi_Agch; } else { LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d " "chan_nr %d link_id %d\n", l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); return -EINVAL; } /* pull and trim msg to start of payload */ msgb_pull(msg, sizeof(*l1sap)); len = msg->len; msgb_trim(msg, 0); /* convert l1sap message to GsmL1 primitive, keep payload */ if (len) { /* data request */ /* wrap zeroed l1p structure arrount payload * this must be done in three steps, since the actual * payload is not at the end but inside the l1p structure. */ msgb_push(msg, offsetof(GsmL1_Prim_t, u.phDataReq.msgUnitParam.u8Buffer)); memset(msg->data, 0, msg->len); msgb_put(msg, len); memset(msg->tail, 0, sizeof(*l1p) - msg->len); msgb_put(msg, sizeof(*l1p) - msg->len); msg->l1h = msg->data; l1p = msgb_l1prim(msg); data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); } else { /* empty frame */ /* put l1p structure */ msgb_put(msg, sizeof(*l1p)); memset(msg->data, 0, msg->len); msg->l1h = msg->data; l1p = msgb_l1prim(msg); empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); } /* send message to DSP's queue */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], msg) != 0) { LOGP(DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(msg); } return 0; } static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, struct osmo_phsap_prim *l1sap) { struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); struct gsm_lchan *lchan; uint32_t u32Fn; uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi, ss; uint8_t chan_nr; GsmL1_Prim_t *l1p; struct msgb *nmsg = NULL; chan_nr = l1sap->u.tch.chan_nr; u32Fn = l1sap->u.tch.fn; u8Tn = L1SAP_CHAN2TS(chan_nr); u8BlockNbr = (u32Fn % 13) >> 2; if (L1SAP_IS_CHAN_TCHH(chan_nr)) { ss = subCh = L1SAP_CHAN2SS_TCHH(chan_nr); sapi = GsmL1_Sapi_TchH; } else { subCh = 0x1f; ss = 0; sapi = GsmL1_Sapi_TchF; } lchan = &trx->ts[u8Tn].lchan[ss]; /* create new message and fill data */ if (msg) { msgb_pull(msg, sizeof(*l1sap)); /* create new message */ nmsg = l1p_msgb_alloc(); if (!nmsg) return -ENOMEM; l1p = msgb_l1prim(nmsg); l1if_tch_encode(lchan, l1p->u.phDataReq.msgUnitParam.u8Buffer, &l1p->u.phDataReq.msgUnitParam.u8Size, msg->data, msg->len); } /* no message/data, we generate an empty traffic msg */ if (!nmsg) nmsg = gen_empty_tch_msg(lchan); /* no traffic message, we generate an empty msg */ if (!nmsg) { nmsg = l1p_msgb_alloc(); if (!nmsg) return -ENOMEM; } l1p = msgb_l1prim(nmsg); /* if we provide data, or if data is already in nmsg */ if (l1p->u.phDataReq.msgUnitParam.u8Size) { /* data request */ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, l1p->u.phDataReq.msgUnitParam.u8Size); } else { /* empty frame */ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); } /* send message to DSP's queue */ osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); msgb_free(msg); return 0; } static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, struct osmo_phsap_prim *l1sap) { struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); uint8_t u8Tn, ss; uint8_t chan_nr; struct gsm_lchan *lchan; int rc = 0; switch (l1sap->u.info.type) { case PRIM_INFO_ACT_CIPH: chan_nr = l1sap->u.info.u.ciph_req.chan_nr; u8Tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); lchan = &trx->ts[u8Tn].lchan[ss]; if (l1sap->u.info.u.ciph_req.uplink) { l1if_set_ciphering(fl1, lchan, 0); lchan->ciph_state = LCHAN_CIPH_RX_REQ; } if (l1sap->u.info.u.ciph_req.downlink) { l1if_set_ciphering(fl1, lchan, 1); lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; } if (l1sap->u.info.u.ciph_req.downlink && l1sap->u.info.u.ciph_req.uplink) lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; break; case PRIM_INFO_ACTIVATE: case PRIM_INFO_DEACTIVATE: case PRIM_INFO_MODIFY: chan_nr = l1sap->u.info.u.act_req.chan_nr; u8Tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); lchan = &trx->ts[u8Tn].lchan[ss]; if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) l1if_rsl_chan_act(lchan); else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { if (lchan->ho.active == HANDOVER_WAIT_FRAME) l1if_rsl_chan_mod(lchan); else l1if_rsl_mode_modify(lchan); } else if (l1sap->u.info.u.act_req.sacch_only) l1if_rsl_deact_sacch(lchan); else l1if_rsl_chan_rel(lchan); msgb_free(msg); break; default: LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", l1sap->u.info.type); rc = -EINVAL; } return rc; } /* primitive from common part */ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { struct msgb *msg = l1sap->oph.msg; int rc = 0; switch (OSMO_PRIM_HDR(&l1sap->oph)) { case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): rc = ph_data_req(trx, msg, l1sap); break; case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): rc = ph_tch_req(trx, msg, l1sap); break; case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): rc = mph_info_req(trx, msg, l1sap); break; default: LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", l1sap->oph.primitive, l1sap->oph.operation); rc = -EINVAL; } if (rc) msgb_free(msg); return rc; } static int handle_mph_time_ind(struct femtol1_hdl *fl1, GsmL1_MphTimeInd_t *time_ind) { struct gsm_bts_trx *trx = fl1->priv; struct gsm_bts *bts = trx->bts; struct osmo_phsap_prim l1sap; uint32_t fn; /* increment the primitive count for the alive timer */ fl1->alive_prim_cnt++; /* ignore every time indication, except for c0 */ if (trx != bts->c0) { return 0; } fn = time_ind->u32Fn; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_INDICATION, NULL); l1sap.u.info.type = PRIM_INFO_TIME; l1sap.u.info.u.time_ind.fn = fn; return l1sap_up(trx, &l1sap); } static uint8_t chan_nr_by_sapi(enum gsm_phys_chan_config pchan, GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, uint8_t u8Tn, uint32_t u32Fn) { uint8_t cbits = 0; switch (sapi) { case GsmL1_Sapi_Bcch: cbits = 0x10; break; case GsmL1_Sapi_Sacch: switch(pchan) { case GSM_PCHAN_TCH_F: cbits = 0x01; break; case GSM_PCHAN_TCH_H: cbits = 0x02 + subCh; break; case GSM_PCHAN_CCCH_SDCCH4: cbits = 0x04 + subCh; break; case GSM_PCHAN_SDCCH8_SACCH8C: cbits = 0x08 + subCh; break; default: LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", pchan); return 0; } break; case GsmL1_Sapi_Sdcch: switch(pchan) { case GSM_PCHAN_CCCH_SDCCH4: cbits = 0x04 + subCh; break; case GSM_PCHAN_SDCCH8_SACCH8C: cbits = 0x08 + subCh; break; default: LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", pchan); return 0; } break; case GsmL1_Sapi_Agch: case GsmL1_Sapi_Pch: cbits = 0x12; break; case GsmL1_Sapi_Pdtch: case GsmL1_Sapi_Pacch: switch(pchan) { case GSM_PCHAN_PDCH: cbits = 0x01; break; default: LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", pchan); return 0; } break; case GsmL1_Sapi_TchF: cbits = 0x01; break; case GsmL1_Sapi_TchH: cbits = 0x02 + subCh; break; case GsmL1_Sapi_FacchF: cbits = 0x01; break; case GsmL1_Sapi_FacchH: cbits = 0x02 + subCh; break; case GsmL1_Sapi_Ptcch: if (!L1SAP_IS_PTCCH(u32Fn)) { LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame " "number other than 12, got it at %u (%u). " "Please fix!\n", u32Fn % 52, u32Fn); abort(); } switch(pchan) { case GSM_PCHAN_PDCH: cbits = 0x01; break; default: LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", pchan); return 0; } break; default: return 0; } /* not reached due to default case above */ return (cbits << 3) | u8Tn; } static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1, GsmL1_PhReadyToSendInd_t *rts_ind, struct msgb *l1p_msg) { struct gsm_bts_trx *trx = fl1->priv; struct gsm_bts *bts = trx->bts; struct msgb *resp_msg; GsmL1_PhDataReq_t *data_req; GsmL1_MsgUnitParam_t *msu_param; struct gsm_time g_time; uint32_t t3p; int rc; struct osmo_phsap_prim *l1sap; uint8_t chan_nr, link_id; uint32_t fn; /* check if primitive should be handled by common part */ chan_nr = chan_nr_by_sapi(trx->ts[rts_ind->u8Tn].pchan, rts_ind->sapi, rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); if (chan_nr) { fn = rts_ind->u32Fn; if (rts_ind->sapi == GsmL1_Sapi_Sacch) link_id = 0x40; else link_id = 0; rc = msgb_trim(l1p_msg, sizeof(*l1sap)); if (rc < 0) MSGB_ABORT(l1p_msg, "No room for primitive\n"); l1sap = msgb_l1sap_prim(l1p_msg); if (rts_ind->sapi == GsmL1_Sapi_TchF || rts_ind->sapi == GsmL1_Sapi_TchH) { osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, PRIM_OP_INDICATION, l1p_msg); l1sap->u.tch.chan_nr = chan_nr; l1sap->u.tch.fn = fn; } else { osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, PRIM_OP_INDICATION, l1p_msg); l1sap->u.data.link_id = link_id; l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.fn = fn; } return l1sap_up(trx, l1sap); } gsm_fn2gsmtime(&g_time, rts_ind->u32Fn); DEBUGP(DL1P, "Rx PH-RTS.ind %02u/%02u/%02u SAPI=%s\n", g_time.t1, g_time.t2, g_time.t3, get_value_string(femtobts_l1sapi_names, rts_ind->sapi)); /* in all other cases, we need to allocate a new PH-DATA.ind * primitive msgb and start to fill it */ resp_msg = l1p_msgb_alloc(); data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); msu_param = &data_req->msgUnitParam; /* set default size */ msu_param->u8Size = GSM_MACBLOCK_LEN; switch (rts_ind->sapi) { case GsmL1_Sapi_Sch: /* compute T3prime */ t3p = (g_time.t3 - 1) / 10; /* fill SCH burst with data */ msu_param->u8Size = 4; msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9); msu_param->u8Buffer[1] = (g_time.t1 >> 1); msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); msu_param->u8Buffer[3] = (t3p & 1); break; case GsmL1_Sapi_Prach: goto empty_frame; break; case GsmL1_Sapi_Cbch: /* get them from bts->si_buf[] */ bts_cbch_get(bts, msu_param->u8Buffer, &g_time); break; default: memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); break; } tx: /* transmit */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) { LOGP(DL1C, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(resp_msg); } msgb_free(l1p_msg); return 0; empty_frame: /* in case we decide to send an empty frame... */ empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); goto tx; } static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) { LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, m->fBer, m->i16BurstTiming); } static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, GsmL1_MeasParam_t *m) { struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_INDICATION, NULL); l1sap.u.info.type = PRIM_INFO_MEAS; l1sap.u.info.u.meas_ind.chan_nr = chan_nr; l1sap.u.info.u.meas_ind.ta_offs_qbits = m->i16BurstTiming; l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 100); l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); return l1sap_up(trx, &l1sap); } static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, struct msgb *l1p_msg) { struct gsm_bts_trx *trx = fl1->priv; uint8_t chan_nr, link_id; struct osmo_phsap_prim *l1sap; uint32_t fn; uint8_t *data, len; int rc = 0; int8_t rssi; chan_nr = chan_nr_by_sapi(trx->ts[data_ind->u8Tn].pchan, data_ind->sapi, data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); if (!chan_nr) { LOGP(DL1C, LOGL_ERROR, "PH-DATA-INDICATION for unknown sapi " "%d\n", data_ind->sapi); msgb_free(l1p_msg); return ENOTSUP; } fn = data_ind->u32Fn; link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? 0x40 : 0x00; process_meas_res(trx, chan_nr, &data_ind->measParam); if (data_ind->measParam.fLinkQuality < fl1->min_qual_norm && data_ind->msgUnitParam.u8Size != 0) { msgb_free(l1p_msg); return 0; } DEBUGP(DL1C, "Rx PH-DATA.ind %s (hL2 %08x): %s", get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->hLayer2, osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); dump_meas_res(LOGL_DEBUG, &data_ind->measParam); /* check for TCH */ if (data_ind->sapi == GsmL1_Sapi_TchF || data_ind->sapi == GsmL1_Sapi_TchH) { /* TCH speech frame handling */ rc = l1if_tch_rx(trx, chan_nr, l1p_msg); msgb_free(l1p_msg); return rc; } /* get rssi */ rssi = (int8_t) (data_ind->measParam.fRssi); /* get data pointer and length */ data = data_ind->msgUnitParam.u8Buffer; len = data_ind->msgUnitParam.u8Size; /* pull lower header part before data */ msgb_pull(l1p_msg, data - l1p_msg->data); /* trim remaining data to it's size, to get rid of upper header part */ rc = msgb_trim(l1p_msg, len); if (rc < 0) MSGB_ABORT(l1p_msg, "No room for primitive data\n"); l1p_msg->l2h = l1p_msg->data; /* push new l1 header */ l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap)); /* fill header */ l1sap = msgb_l1sap_prim(l1p_msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_INDICATION, l1p_msg); l1sap->u.data.link_id = link_id; l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.fn = fn; l1sap->u.data.rssi = rssi; return l1sap_up(trx, l1sap); } static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, struct msgb *l1p_msg) { struct gsm_bts_trx *trx = fl1->priv; struct gsm_bts *bts = trx->bts; struct gsm_bts_role_bts *btsb = bts->role; struct gsm_lchan *lchan; struct osmo_phsap_prim *l1sap; uint32_t fn; uint8_t ra, acc_delay = 0; int rc; /* increment number of busy RACH slots, if required */ if (trx == bts->c0 && ra_ind->measParam.fRssi >= btsb->load.rach.busy_thresh) btsb->load.rach.busy++; if (ra_ind->measParam.fLinkQuality < fl1->min_qual_rach) { msgb_free(l1p_msg); return 0; } if (ra_ind->measParam.i16BurstTiming > 0) acc_delay = ra_ind->measParam.i16BurstTiming >> 2; /* increment number of RACH slots with valid non-handover RACH burst */ lchan = l1if_hLayer_to_lchan(trx, ra_ind->hLayer2); if (trx == bts->c0 && !(lchan && lchan->ho.active == HANDOVER_ENABLED)) btsb->load.rach.access++; dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); if (ra_ind->msgUnitParam.u8Size != 1) { LOGP(DL1C, LOGL_ERROR, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi); msgb_free(l1p_msg); return 0; } fn = ra_ind->u32Fn; ra = ra_ind->msgUnitParam.u8Buffer[0]; rc = msgb_trim(l1p_msg, sizeof(*l1sap)); if (rc < 0) MSGB_ABORT(l1p_msg, "No room for primitive data\n"); l1sap = msgb_l1sap_prim(l1p_msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, l1p_msg); l1sap->u.rach_ind.ra = ra; l1sap->u.rach_ind.acc_delay = acc_delay; l1sap->u.rach_ind.fn = fn; if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4) l1sap->u.rach_ind.chan_nr = 0x88; else l1sap->u.rach_ind.chan_nr = gsm_lchan2chan_nr(lchan); return l1sap_up(trx, l1sap); } /* handle any random indication from the L1 */ static int l1if_handle_ind(struct femtol1_hdl *fl1, struct msgb *msg) { GsmL1_Prim_t *l1p = msgb_l1prim(msg); int rc = 0; switch (l1p->id) { case GsmL1_PrimId_MphTimeInd: rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd); break; case GsmL1_PrimId_MphSyncInd: break; case GsmL1_PrimId_PhConnectInd: break; case GsmL1_PrimId_PhReadyToSendInd: return handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, msg); case GsmL1_PrimId_PhDataInd: return handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); case GsmL1_PrimId_PhRaInd: return handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); break; default: break; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(msg); return rc; } static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) { /* the limitation here is that we cannot have multiple callers * sending the same primitive */ if (wlc->is_sys_prim != 0) return 0; if (l1p->id != wlc->conf_prim_id) return 0; return 1; } int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg) { GsmL1_Prim_t *l1p = msgb_l1prim(msg); struct wait_l1_conf *wlc; int rc; switch (l1p->id) { case GsmL1_PrimId_MphTimeInd: /* silent, don't clog the log file */ break; default: LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n", get_value_string(femtobts_l1prim_names, l1p->id), wq); } /* check if this is a resposne to a sync-waiting request */ llist_for_each_entry(wlc, &fl1h->wlc_list, list) { if (is_prim_compat(l1p, wlc)) { llist_del(&wlc->list); if (wlc->cb) rc = wlc->cb(fl1h->priv, msg, wlc->cb_data); else { rc = 0; msgb_free(msg); } release_wlc(wlc); return rc; } } /* if we reach here, it is not a Conf for a pending Req */ return l1if_handle_ind(fl1h, msg); } int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg) { SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct wait_l1_conf *wlc; int rc; LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n", get_value_string(femtobts_sysprim_names, sysp->id)); /* check if this is a resposne to a sync-waiting request */ llist_for_each_entry(wlc, &fl1h->wlc_list, list) { /* the limitation here is that we cannot have multiple callers * sending the same primitive */ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) { llist_del(&wlc->list); if (wlc->cb) rc = wlc->cb(fl1h->priv, msg, wlc->cb_data); else { rc = 0; msgb_free(msg); } release_wlc(wlc); return rc; } } /* if we reach here, it is not a Conf for a pending Req */ return l1if_handle_ind(fl1h, msg); } #if 0 /* called by RSL if the BCCH SI has been modified */ int sysinfo_has_changed(struct gsm_bts *bts, int si) { /* FIXME: Determine BS_AG_BLKS_RES and * * set cfgParams.u.agch.u8NbrOfAgch * * determine implications on paging */ /* FIXME: Check for Extended BCCH presence */ /* FIXME: Check for CCCH_CONF */ /* FIXME: Check for BS_PA_MFRMS: update paging */ return 0; } #endif static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; int on = 0; unsigned int i; if (sysp->id == SuperFemto_PrimId_ActivateRfCnf) on = 1; if (on) status = sysp->u.activateRfCnf.status; else status = sysp->u.deactivateRfCnf.status; LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", get_value_string(femtobts_l1status_names, status)); if (on) { if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); bts_shutdown(trx->bts, "RF-ACT failure"); } else bts_update_status(BTS_STATUS_RF_ACTIVE, 1); /* signal availability */ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); oml_mo_tx_sw_act_rep(&trx->mo); oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); for (i = 0; i < ARRAY_SIZE(trx->ts); i++) oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); } else { bts_update_status(BTS_STATUS_RF_ACTIVE, 0); oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); } msgb_free(resp); return 0; } static int get_clk_cal(struct femtol1_hdl *hdl) { #ifdef FEMTOBTS_API_VERSION return hdl->clk_cal; #else switch (hdl->clk_src) { case SuperFemto_ClkSrcId_Ocxo: case SuperFemto_ClkSrcId_Tcxo: /* only for those on-board clocks it makes sense to use * the calibration value */ return hdl->clk_cal; default: /* external clocks like GPS are taken 1:1 without any * modification by a local calibration value */ LOGP(DL1C, LOGL_INFO, "Ignoring Clock Calibration for " "selected %s clock\n", get_value_string(femtobts_clksrc_names, hdl->clk_src)); return 0; } #endif } /* * RevC was the last HW revision without an external * attenuator. Check for that. */ static int has_external_atten(struct femtol1_hdl *hdl) { /* older version doesn't have an attenuator */ return hdl->hw_info.ver_major > 2; } /* activate or de-activate the entire RF-Frontend */ int l1if_activate_rf(struct femtol1_hdl *hdl, int on) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct gsm_bts_trx *trx = hdl->priv; if (on) { sysp->id = SuperFemto_PrimId_ActivateRfReq; #ifdef HW_SYSMOBTS_V1 sysp->u.activateRfReq.u12ClkVc = get_clk_cal(hdl); #else #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(0,2,0) sysp->u.activateRfReq.timing.u8TimSrc = 1; /* Master */ #endif /* 0.2.0 */ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; /* Use clock from OCXO or whatever source is configured */ #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) sysp->u.activateRfReq.rfTrx.u8ClkSrc = hdl->clk_src; #else sysp->u.activateRfReq.rfTrx.clkSrc = hdl->clk_src; #endif /* 2.1.0 */ sysp->u.activateRfReq.rfTrx.iClkCor = get_clk_cal(hdl); #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) sysp->u.activateRfReq.rfRx.u8ClkSrc = hdl->clk_src; #else sysp->u.activateRfReq.rfRx.clkSrc = hdl->clk_src; #endif /* 2.1.0 */ sysp->u.activateRfReq.rfRx.iClkCor = get_clk_cal(hdl); #endif /* API 2.4.0 */ #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0) if (has_external_atten(hdl)) { LOGP(DL1C, LOGL_INFO, "Using external attenuator.\n"); sysp->u.activateRfReq.rfTrx.u8UseExtAtten = 1; sysp->u.activateRfReq.rfTrx.fMaxTxPower = sysmobts_get_nominal_power(trx); } #endif /* 2.2.0 */ #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,8,1) /* maximum cell size in quarter-bits, 90 == 12.456 km */ sysp->u.activateRfReq.u8MaxCellSize = 90; #endif #endif /* !HW_SYSMOBTS_V1 */ } else { sysp->id = SuperFemto_PrimId_DeactivateRfReq; } return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); } #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) { int i; for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { struct gsm_lchan *lchan = &ts->lchan[i]; if (!is_muted) continue; if (lchan->state != LCHAN_S_ACTIVE) continue; /* skip channels that might be active for another reason */ if (lchan->type == GSM_LCHAN_CCCH) continue; if (lchan->type == GSM_LCHAN_PDTCH) continue; if (lchan->s <= 0) continue; lchan->s = 0; rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); } } static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; status = sysp->u.muteRfCnf.status; if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); } else { int i; LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", get_value_string(femtobts_l1status_names, status)); bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); osmo_static_assert( ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), ts_array_size); for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); } msgb_free(resp); return 0; } #endif /* mute/unmute RF time slots */ int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", mute[0], mute[1], mute[2], mute[3], mute[4], mute[5], mute[6], mute[7] ); #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3,6,0) LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n"); msgb_free(msg); return -ENOTSUP; #else sysp->id = SuperFemto_PrimId_MuteRfReq; memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); /* save for later use */ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); #endif /* < 3.6.0 */ } /* call-back on arrival of DSP+FPGA version + band capability */ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); SuperFemto_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); int rc; fl1h->hw_info.dsp_version[0] = sic->dspVersion.major; fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor; fl1h->hw_info.dsp_version[2] = sic->dspVersion.build; fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major; fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor; fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build; #ifndef HW_SYSMOBTS_V1 fl1h->hw_info.ver_major = sic->boardVersion.rev; fl1h->hw_info.ver_minor = sic->boardVersion.option; #endif LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn", sic->dspVersion.major, sic->dspVersion.minor, sic->dspVersion.build, sic->fpgaVersion.major, sic->fpgaVersion.minor, sic->fpgaVersion.build); #ifdef HW_SYSMOBTS_V1 if (sic->rfBand.gsm850) fl1h->hw_info.band_support |= GSM_BAND_850; if (sic->rfBand.gsm900) fl1h->hw_info.band_support |= GSM_BAND_900; if (sic->rfBand.dcs1800) fl1h->hw_info.band_support |= GSM_BAND_1800; if (sic->rfBand.pcs1900) fl1h->hw_info.band_support |= GSM_BAND_1900; #endif if (!(fl1h->hw_info.band_support & trx->bts->band)) LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", gsm_band_name(trx->bts->band)); /* Request the activation */ l1if_activate_rf(fl1h, 1); #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) /* load calibration tables (if we know their path) */ rc = calib_load(fl1h); if (rc < 0) LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " "unable to load tables!\n"); #else LOGP(DL1C, LOGL_NOTICE, "Operating without calibration " "as software was compiled against old header files\n"); #endif msgb_free(resp); /* FIXME: clock related */ return 0; } /* request DSP+FPGA code versions + band capability */ static int l1if_get_info(struct femtol1_hdl *hdl) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_SystemInfoReq; return l1if_req_compl(hdl, msg, info_compl_cb, NULL); } static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status = sysp->u.layer1ResetCnf.status; LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n", get_value_string(femtobts_l1status_names, status)); msgb_free(resp); /* If we're coming out of reset .. */ if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); bts_shutdown(trx->bts, "L1-RESET failure"); } /* as we cannot get the current DSP trace flags, we simply * set them to zero (or whatever dsp_trace_f has been initialized to */ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f); /* obtain version information on DSP/FPGA and band capabilities */ l1if_get_info(fl1h); return 0; } int l1if_reset(struct femtol1_hdl *hdl) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_Layer1ResetReq; return l1if_req_compl(hdl, msg, reset_compl_cb, NULL); } /* set the trace flags within the DSP */ int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", flags); sysp->id = SuperFemto_PrimId_SetTraceFlagsReq; sysp->u.setTraceFlagsReq.u32Tf = flags; hdl->dsp_trace_f = flags; /* There is no confirmation we could wait for */ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) { LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n"); msgb_free(msg); return -EAGAIN; } return 0; } /* get those femtol1_hdl.hw_info elements that sre in EEPROM */ static int get_hwinfo_eeprom(struct femtol1_hdl *fl1h) { eeprom_SysInfo_t sysinfo; int val, rc; rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, &val); if (rc < 0) return rc; fl1h->hw_info.model_nr = val; rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_FLAGS, &val); if (rc < 0) return rc; fl1h->hw_info.model_flags = val; rc = sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, &val); if (rc < 0) return rc; fl1h->hw_info.trx_nr = val; rc = eeprom_ReadSysInfo(&sysinfo); if (rc != EEPROM_SUCCESS) { /* some early units don't yet have the EEPROM * information structure */ LOGP(DL1C, LOGL_ERROR, "Unable to read band support " "from EEPROM, assuming all bands\n"); fl1h->hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | GSM_BAND_1800 | GSM_BAND_1900; return 0; } if (sysinfo.u8GSM850) fl1h->hw_info.band_support |= GSM_BAND_850; if (sysinfo.u8GSM900) fl1h->hw_info.band_support |= GSM_BAND_900; if (sysinfo.u8DCS1800) fl1h->hw_info.band_support |= GSM_BAND_1800; if (sysinfo.u8PCS1900) fl1h->hw_info.band_support |= GSM_BAND_1900; return 0; } struct femtol1_hdl *l1if_open(void *priv) { struct femtol1_hdl *fl1h; int rc; #ifndef HW_SYSMOBTS_V1 LOGP(DL1C, LOGL_INFO, "sysmoBTSv2 L1IF compiled against API headers " "v%u.%u.%u\n", SUPERFEMTO_API_VERSION >> 16, (SUPERFEMTO_API_VERSION >> 8) & 0xff, SUPERFEMTO_API_VERSION & 0xff); #else LOGP(DL1C, LOGL_INFO, "sysmoBTSv1 L1IF compiled against API headers " "v%u.%u.%u\n", FEMTOBTS_API_VERSION >> 16, (FEMTOBTS_API_VERSION >> 8) & 0xff, FEMTOBTS_API_VERSION & 0xff); #endif fl1h = talloc_zero(priv, struct femtol1_hdl); if (!fl1h) return NULL; INIT_LLIST_HEAD(&fl1h->wlc_list); fl1h->priv = priv; fl1h->clk_cal = 0; fl1h->clk_use_eeprom = 1; fl1h->min_qual_rach = MIN_QUAL_RACH; fl1h->min_qual_norm = MIN_QUAL_NORM; get_hwinfo_eeprom(fl1h); #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) if (fl1h->hw_info.model_nr == 2050) { /* On the sysmoBTS 2050, we don't have an OCXO but * start with the TCXO and will sync it with the PPS * of the GPS in case there is a fix. */ fl1h->clk_src = SuperFemto_ClkSrcId_Tcxo; LOGP(DL1C, LOGL_INFO, "Clock source defaulting to GPS 1PPS " "on sysmoBTS 2050\n"); } else { /* default clock source: OCXO */ fl1h->clk_src = SuperFemto_ClkSrcId_Ocxo; } #else fl1h->clk_src = SF_CLKSRC_OCXO; #endif rc = l1if_transport_open(MQ_SYS_WRITE, fl1h); if (rc < 0) { talloc_free(fl1h); return NULL; } rc = l1if_transport_open(MQ_L1_WRITE, fl1h); if (rc < 0) { l1if_transport_close(MQ_SYS_WRITE, fl1h); talloc_free(fl1h); return NULL; } return fl1h; } int l1if_close(struct femtol1_hdl *fl1h) { l1if_transport_close(MQ_L1_WRITE, fl1h); l1if_transport_close(MQ_SYS_WRITE, fl1h); return 0; } #ifdef HW_SYSMOBTS_V1 int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h) { LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n"); return -ENOTSUP; } int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h) { LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n"); return -ENOTSUP; } #else static int clock_reset_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { msgb_free(resp); return 0; } static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) LOGP(DL1C, LOGL_ERROR, "Rx RfClockSetupConf failed with: %d\n", sysp->u.rfClockSetupCnf.status); msgb_free(resp); return 0; } static int clock_correct_info_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); SuperFemto_Prim_t *sysp = msgb_sysprim(resp); LOGP(DL1C, LOGL_NOTICE, "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n", sysp->u.rfClockInfoCnf.rfTrx.iClkCor, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrx.clkSrc), sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); if (sysp->u.rfClockInfoCnf.rfTrx.clkSrc == SuperFemto_ClkSrcId_GpsPps) { LOGP(DL1C, LOGL_ERROR, "Calibrating GPS against GPS doesn not make sense.\n"); msgb_free(resp); return -1; } if (sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc == SuperFemto_ClkSrcId_None) { LOGP(DL1C, LOGL_ERROR, "No reference clock set. Please reset first.\n"); msgb_free(resp); return -1; } if (sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes == 0) { LOGP(DL1C, LOGL_ERROR, "Couldn't determine the clock difference.\n"); msgb_free(resp); return -1; } fl1h->clk_cal = sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr; fl1h->clk_use_eeprom = 0; msgb_free(resp); /* * Let's reset the counter and this will lead to applying the * new calibration. */ l1if_rf_clock_info_reset(fl1h); return 0; } int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); /* Set GPS/PPS as reference */ sysp->id = SuperFemto_PrimId_RfClockSetupReq; sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h); sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; l1if_req_compl(fl1h, msg, clock_setup_cb, NULL); /* Reset the error counters */ msg = sysp_msgb_alloc(); sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 1; return l1if_req_compl(fl1h, msg, clock_reset_cb, NULL); } int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 0; return l1if_req_compl(fl1h, msg, clock_correct_info_cb, NULL); } #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_if.h000066400000000000000000000073441260026426200201330ustar00rootroot00000000000000#ifndef _FEMTO_L1_H #define _FEMTO_L1_H #include #include #include #include #include #include enum { MQ_SYS_READ, MQ_L1_READ, #ifndef HW_SYSMOBTS_V1 MQ_TCH_READ, MQ_PDTCH_READ, #endif _NUM_MQ_READ }; enum { MQ_SYS_WRITE, MQ_L1_WRITE, #ifndef HW_SYSMOBTS_V1 MQ_TCH_WRITE, MQ_PDTCH_WRITE, #endif _NUM_MQ_WRITE }; struct calib_send_state { const char *path; int last_file_idx; }; enum { FIXUP_UNITILIAZED, FIXUP_NEEDED, FIXUP_NOT_NEEDED, }; struct femtol1_hdl { struct gsm_time gsm_time; uint32_t hLayer1; /* handle to the L1 instance in the DSP */ uint32_t dsp_trace_f; uint8_t clk_use_eeprom; int clk_cal; uint8_t clk_src; float min_qual_rach; float min_qual_norm; char *calib_path; struct llist_head wlc_list; void *priv; /* user reference */ struct osmo_timer_list alive_timer; unsigned int alive_prim_cnt; struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */ struct osmo_wqueue write_q[_NUM_MQ_WRITE]; struct { /* from DSP/FPGA after L1 Init */ uint8_t dsp_version[3]; uint8_t fpga_version[3]; uint32_t band_support; /* bitmask of GSM_BAND_* */ uint8_t ver_major; uint8_t ver_minor; /* from EEPROM */ uint16_t model_nr; uint16_t model_flags; uint8_t trx_nr; } hw_info; int fixup_needed; struct calib_send_state st; uint8_t last_rf_mute[8]; }; #define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) #define msgb_sysprim(msg) ((SuperFemto_Prim_t *)(msg)->l1h) typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data); /* send a request primitive to the L1 and schedule completion call-back */ int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, l1if_compl_cb *cb, void *cb_data); int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, l1if_compl_cb *cb, void *cb_data); struct femtol1_hdl *l1if_open(void *priv); int l1if_close(struct femtol1_hdl *hdl); int l1if_reset(struct femtol1_hdl *hdl); int l1if_activate_rf(struct femtol1_hdl *hdl, int on); int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags); int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power); int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb); struct msgb *l1p_msgb_alloc(void); struct msgb *sysp_msgb_alloc(void); uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan); struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer); /* tch.c */ void l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, const uint8_t *rtp_pl, unsigned int rtp_pl_len); int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg); int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer); struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan); /* ciphering */ int l1if_set_ciphering(struct femtol1_hdl *fl1h, struct gsm_lchan *lchan, int dir_downlink); /* channel control */ int l1if_rsl_chan_act(struct gsm_lchan *lchan); int l1if_rsl_chan_rel(struct gsm_lchan *lchan); int l1if_rsl_chan_mod(struct gsm_lchan *lchan); int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); int l1if_rsl_mode_modify(struct gsm_lchan *lchan); /* calibration loading */ int calib_load(struct femtol1_hdl *fl1h); /* on-line re-calibration */ int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h); int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h); /* public helpers for test */ int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h, struct msgb *msg, struct gsm_lchan *lchan); inline int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, const uint8_t ms_power, const float rxLevel); #endif /* _FEMTO_L1_H */ osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_transp.h000066400000000000000000000007471260026426200210440ustar00rootroot00000000000000#ifndef _FEMTOL1_TRANSPH_ #define _FEMTOL1_TRANSPH_ #include /* functions a transport calls on arrival of primitive from BTS */ int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg); int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg); /* functions exported by a transport */ int l1if_transport_open(int q, struct femtol1_hdl *fl1h); int l1if_transport_close(int q, struct femtol1_hdl *fl1h); #endif /* _FEMTOL1_TRANSP_H */ osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_transp_fwd.c000066400000000000000000000071171260026426200216750ustar00rootroot00000000000000/* Interface handler for Sysmocom L1 (forwarding) */ /* (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" #include "l1_transp.h" #include "l1_fwd.h" static const uint16_t fwd_udp_ports[] = { [MQ_SYS_WRITE] = L1FWD_SYS_PORT, [MQ_L1_WRITE] = L1FWD_L1_PORT, #ifndef HW_SYSMOBTS_V1 [MQ_TCH_WRITE] = L1FWD_TCH_PORT, [MQ_PDTCH_WRITE]= L1FWD_PDTCH_PORT, #endif }; static int fwd_read_cb(struct osmo_fd *ofd) { struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx"); struct femtol1_hdl *fl1h = ofd->data; int rc; if (!msg) return -ENOMEM; msg->l1h = msg->data; rc = read(ofd->fd, msg->l1h, msgb_tailroom(msg)); if (rc < 0) { LOGP(DL1C, LOGL_ERROR, "Short read from UDP\n"); msgb_free(msg); return rc; } else if (rc == 0) { LOGP(DL1C, LOGL_ERROR, "Len=0 from UDP\n"); msgb_free(msg); return rc; } msgb_put(msg, rc); if (ofd->priv_nr == MQ_SYS_WRITE) rc = l1if_handle_sysprim(fl1h, msg); else rc = l1if_handle_l1prim(ofd->priv_nr, fl1h, msg); return rc; } static int prim_write_cb(struct osmo_fd *ofd, struct msgb *msg) { /* write to the fd */ return write(ofd->fd, msg->l1h, msgb_l1len(msg)); } int l1if_transport_open(int q, struct femtol1_hdl *fl1h) { int rc; char *bts_host = getenv("L1FWD_BTS_HOST"); switch (q) { case MQ_L1_WRITE: LOGP(DL1C, LOGL_INFO, "sizeof(GsmL1_Prim_t) = %zu\n", sizeof(GsmL1_Prim_t)); break; case MQ_SYS_WRITE: LOGP(DL1C, LOGL_INFO, "sizeof(SuperFemto_Prim_t) = %zu\n", sizeof(SuperFemto_Prim_t)); break; } if (!bts_host) { fprintf(stderr, "You have to set the L1FWD_BTS_HOST environment variable\n"); exit(2); } struct osmo_wqueue *wq = &fl1h->write_q[q]; struct osmo_fd *ofd = &wq->bfd; osmo_wqueue_init(wq, 10); wq->write_cb = prim_write_cb; wq->read_cb = fwd_read_cb; ofd->data = fl1h; ofd->priv_nr = q; ofd->when |= BSC_FD_READ; rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, bts_host, fwd_udp_ports[q], OSMO_SOCK_F_CONNECT); if (rc < 0) return rc; return 0; } int l1if_transport_close(int q, struct femtol1_hdl *fl1h) { struct osmo_wqueue *wq = &fl1h->write_q[q]; struct osmo_fd *ofd = &wq->bfd; osmo_wqueue_clear(wq); osmo_fd_unregister(ofd); close(ofd->fd); return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/l1_transp_hw.c000066400000000000000000000173371260026426200215400ustar00rootroot00000000000000/* Interface handler for Sysmocom L1 (real hardware) */ /* (C) 2011 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" #include "l1_transp.h" #ifdef HW_SYSMOBTS_V1 #define DEV_SYS_DSP2ARM_NAME "/dev/msgq/femtobts_dsp2arm" #define DEV_SYS_ARM2DSP_NAME "/dev/msgq/femtobts_arm2dsp" #define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_dsp2arm" #define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_arm2dsp" #else #define DEV_SYS_DSP2ARM_NAME "/dev/msgq/superfemto_dsp2arm" #define DEV_SYS_ARM2DSP_NAME "/dev/msgq/superfemto_arm2dsp" #define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm" #define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp" #define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm" #define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp" #define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm" #define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp" #endif static const char *rd_devnames[] = { [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME, [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME, #ifndef HW_SYSMOBTS_V1 [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME, [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME, #endif }; static const char *wr_devnames[] = { [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME, [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME, #ifndef HW_SYSMOBTS_V1 [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME, [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME, #endif }; /* * Make sure that all structs we read fit into the SYSMOBTS_PRIM_SIZE */ osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, l1_prim) osmo_static_assert(sizeof(SuperFemto_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, super_prim) static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) { struct osmo_wqueue *queue; queue = container_of(fd, struct osmo_wqueue, bfd); if (what & BSC_FD_READ) queue->read_cb(fd); if (what & BSC_FD_EXCEPT) queue->except_cb(fd); if (what & BSC_FD_WRITE) { struct iovec iov[5]; struct msgb *msg, *tmp; int written, count = 0; fd->when &= ~BSC_FD_WRITE; llist_for_each_entry(msg, &queue->msg_queue, list) { /* more writes than we have */ if (count >= ARRAY_SIZE(iov)) break; iov[count].iov_base = msg->l1h; iov[count].iov_len = msgb_l1len(msg); count += 1; } /* TODO: check if all lengths are the same. */ /* Nothing scheduled? This should not happen. */ if (count == 0) { if (!llist_empty(&queue->msg_queue)) fd->when |= BSC_FD_WRITE; return 0; } written = writev(fd->fd, iov, count); if (written < 0) { /* nothing written?! */ if (!llist_empty(&queue->msg_queue)) fd->when |= BSC_FD_WRITE; return 0; } /* now delete the written entries */ written = written / iov[0].iov_len; count = 0; llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) { queue->current_length -= 1; llist_del(&msg->list); msgb_free(msg); count += 1; if (count >= written) break; } if (!llist_empty(&queue->msg_queue)) fd->when |= BSC_FD_WRITE; } return 0; } static int prim_size_for_queue(int queue) { switch (queue) { case MQ_SYS_WRITE: return sizeof(SuperFemto_Prim_t); case MQ_L1_WRITE: #ifndef HW_SYSMOBTS_V1 case MQ_TCH_WRITE: case MQ_PDTCH_WRITE: #endif return sizeof(GsmL1_Prim_t); default: /* The compiler can't know that priv_nr is an enum. Assist. */ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", queue); assert(false); break; } } /* callback when there's something to read from the l1 msg_queue */ static int read_dispatch_one(struct femtol1_hdl *fl1h, struct msgb *msg, int queue) { switch (queue) { case MQ_SYS_WRITE: return l1if_handle_sysprim(fl1h, msg); case MQ_L1_WRITE: #ifndef HW_SYSMOBTS_V1 case MQ_TCH_WRITE: case MQ_PDTCH_WRITE: #endif return l1if_handle_l1prim(queue, fl1h, msg); default: /* The compiler can't know that priv_nr is an enum. Assist. */ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", queue); assert(false); break; } }; static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what) { int i, rc; const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr); uint32_t count; struct iovec iov[3]; struct msgb *msg[ARRAY_SIZE(iov)]; for (i = 0; i < ARRAY_SIZE(iov); ++i) { msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd"); msg[i]->l1h = msg[i]->data; iov[i].iov_base = msg[i]->l1h; iov[i].iov_len = msgb_tailroom(msg[i]); } rc = readv(ofd->fd, iov, ARRAY_SIZE(iov)); count = rc / prim_size; for (i = 0; i < count; ++i) { msgb_put(msg[i], prim_size); read_dispatch_one(ofd->data, msg[i], ofd->priv_nr); } for (i = count; i < ARRAY_SIZE(iov); ++i) msgb_free(msg[i]); return 1; } /* callback when we can write to one of the l1 msg_queue devices */ static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg) { int rc; rc = write(ofd->fd, msg->l1h, msgb_l1len(msg)); if (rc < 0) { LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", strerror(errno)); return rc; } else if (rc < msg->len) { LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " "%u < %u\n", rc, msg->len); return -EIO; } return 0; } int l1if_transport_open(int q, struct femtol1_hdl *hdl) { int rc; /* Step 1: Open all msg_queue file descriptors */ struct osmo_fd *read_ofd = &hdl->read_ofd[q]; struct osmo_wqueue *wq = &hdl->write_q[q]; struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; rc = open(rd_devnames[q], O_RDONLY); if (rc < 0) { LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue: %s\n", strerror(errno)); return rc; } read_ofd->fd = rc; read_ofd->priv_nr = q; read_ofd->data = hdl; read_ofd->cb = l1if_fd_cb; read_ofd->when = BSC_FD_READ; rc = osmo_fd_register(read_ofd); if (rc < 0) { close(read_ofd->fd); read_ofd->fd = -1; return rc; } rc = open(wr_devnames[q], O_WRONLY); if (rc < 0) { LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue: %s\n", strerror(errno)); goto out_read; } osmo_wqueue_init(wq, 10); wq->write_cb = l1fd_write_cb; write_ofd->cb = wqueue_vector_cb; write_ofd->fd = rc; write_ofd->priv_nr = q; write_ofd->data = hdl; write_ofd->when = BSC_FD_WRITE; rc = osmo_fd_register(write_ofd); if (rc < 0) { close(write_ofd->fd); write_ofd->fd = -1; goto out_read; } return 0; out_read: close(hdl->read_ofd[q].fd); osmo_fd_unregister(&hdl->read_ofd[q]); return rc; } int l1if_transport_close(int q, struct femtol1_hdl *hdl) { struct osmo_fd *read_ofd = &hdl->read_ofd[q]; struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; osmo_fd_unregister(read_ofd); close(read_ofd->fd); read_ofd->fd = -1; osmo_fd_unregister(write_ofd); close(write_ofd->fd); write_ofd->fd = -1; return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/main.c000066400000000000000000000231741260026426200200570ustar00rootroot00000000000000/* Main program for Sysmocom BTS */ /* (C) 2011-2013 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYSMOBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" #include "utils.h" #include "eeprom.h" #include "l1_if.h" #include "hw_misc.h" #include "oml_router.h" int pcu_direct = 0; static const char *config_file = "osmo-bts.cfg"; static int daemonize = 0; static unsigned int dsp_trace = 0x71c00020; static int rt_prio = -1; static char *gsmtap_ip = 0; int bts_model_init(struct gsm_bts *bts) { struct femtol1_hdl *fl1h; int rc; fl1h = l1if_open(bts->c0); if (!fl1h) { LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n"); return -EIO; } fl1h->dsp_trace_f = dsp_trace; bts->c0->role_bts.l1h = fl1h; rc = sysmobts_get_nominal_power(bts->c0); if (rc < 0) { LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal " "transmit power. Assuming 23dBm.\n"); rc = 23; } bts->c0->nominal_power = rc; bts->c0->power_params.trx_p_max_out_mdBm = to_mdB(rc); bts_model_vty_init(bts); return 0; } int bts_model_oml_estab(struct gsm_bts *bts) { struct femtol1_hdl *fl1h = bts->c0->role_bts.l1h; l1if_reset(fl1h); return 0; } /* Set the clock calibration to the value * read from the eeprom. */ void clk_cal_use_eeprom(struct gsm_bts *bts) { int rc; struct femtol1_hdl *hdl; eeprom_RfClockCal_t rf_clk; hdl = bts->c0->role_bts.l1h; if (!hdl || !hdl->clk_use_eeprom) return; rc = eeprom_ReadRfClockCal(&rf_clk); if (rc != EEPROM_SUCCESS) { LOGP(DL1C, LOGL_ERROR, "Failed to read from EEPROM.\n"); return; } hdl->clk_cal = rf_clk.iClkCor; LOGP(DL1C, LOGL_NOTICE, "Read clock calibration(%d) from EEPROM.\n", hdl->clk_cal); } void bts_update_status(enum bts_global_status which, int on) { static uint64_t states = 0; uint64_t old_states = states; int led_rf_active_on; if (on) states |= (1ULL << which); else states &= ~(1ULL << which); led_rf_active_on = (states & (1ULL << BTS_STATUS_RF_ACTIVE)) && !(states & (1ULL << BTS_STATUS_RF_MUTE)); LOGP(DL1C, LOGL_INFO, "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n", which, on, (long long)old_states, (long long)states, led_rf_active_on); sysmobts_led_set(LED_RF_ACTIVE, led_rf_active_on); } static void print_help() { printf( "Some useful options:\n" " -h --help this text\n" " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" " -D --daemonize For the process into a background daemon\n" " -c --config-file Specify the filename of the config file\n" " -s --disable-color Don't use colors in stderr log output\n" " -T --timestamp Prefix every log line with a timestamp\n" " -V --version Print version information and exit\n" " -e --log-level Set a global log-level\n" " -p --dsp-trace Set DSP trace flags\n" " -r --realtime PRIO Use SCHED_RR with the specified priority\n" " -w --hw-version Print the targeted HW Version\n" " -M --pcu-direct Force PCU to access message queue for " "PDCH dchannel directly\n" " -i --gsmtap-ip The destination IP used for GSMTAP.\n" ); } static void print_hwversion() { #ifdef HW_SYSMOBTS_V1 printf("sysmobts was compiled for hw version 1.\n"); #else printf("sysmobts was compiled for hw version 2.\n"); #endif } /* FIXME: finally get some option parsing code into libosmocore */ static void handle_options(int argc, char **argv) { while (1) { int option_idx = 0, c; static const struct option long_options[] = { /* FIXME: all those are generic Osmocom app options */ { "help", 0, 0, 'h' }, { "debug", 1, 0, 'd' }, { "daemonize", 0, 0, 'D' }, { "config-file", 1, 0, 'c' }, { "disable-color", 0, 0, 's' }, { "timestamp", 0, 0, 'T' }, { "version", 0, 0, 'V' }, { "log-level", 1, 0, 'e' }, { "dsp-trace", 1, 0, 'p' }, { "hw-version", 0, 0, 'w' }, { "pcu-direct", 0, 0, 'M' }, { "realtime", 1, 0, 'r' }, { "gsmtap-ip", 1, 0, 'i' }, { 0, 0, 0, 0 } }; c = getopt_long(argc, argv, "hc:d:Dc:sTVe:p:w:Mr:i:", long_options, &option_idx); if (c == -1) break; switch (c) { case 'h': print_help(); exit(0); break; case 's': log_set_use_color(osmo_stderr_target, 0); break; case 'd': log_parse_category_mask(osmo_stderr_target, optarg); break; case 'D': daemonize = 1; break; case 'c': config_file = optarg; break; case 'T': log_set_print_timestamp(osmo_stderr_target, 1); break; case 'M': pcu_direct = 1; break; case 'V': print_version(1); exit(0); break; case 'e': log_set_log_level(osmo_stderr_target, atoi(optarg)); break; case 'p': dsp_trace = strtoul(optarg, NULL, 16); break; case 'w': print_hwversion(); exit(0); break; case 'r': rt_prio = atoi(optarg); break; case 'i': gsmtap_ip = optarg; break; default: break; } } } static struct gsm_bts *bts; static void signal_handler(int signal) { fprintf(stderr, "signal %u received\n", signal); switch (signal) { case SIGINT: //osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); bts_shutdown(bts, "SIGINT"); break; case SIGABRT: case SIGUSR1: case SIGUSR2: talloc_report_full(tall_bts_ctx, stderr); break; default: break; } } static int write_pid_file(char *procname) { FILE *outf; char tmp[PATH_MAX+1]; snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname); tmp[PATH_MAX-1] = '\0'; outf = fopen(tmp, "w"); if (!outf) return -1; fprintf(outf, "%d\n", getpid()); fclose(outf); return 0; } extern int sysmobts_ctrlif_inst_cmds(void); int main(int argc, char **argv) { struct stat st; struct sched_param param; struct gsm_bts_role_bts *btsb; struct e1inp_line *line; void *tall_msgb_ctx; struct osmo_fd accept_fd, read_fd; int rc; tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); bts_log_init(NULL); bts = gsm_bts_alloc(tall_bts_ctx); vty_init(&bts_vty_info); e1inp_vty_init(); bts_vty_init(bts, &bts_log_info); handle_options(argc, argv); /* enable realtime priority for us */ if (rt_prio != -1) { memset(¶m, 0, sizeof(param)); param.sched_priority = rt_prio; rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); if (rc != 0) { fprintf(stderr, "Setting SCHED_RR priority(%d) failed: %s\n", param.sched_priority, strerror(errno)); exit(1); } } if (gsmtap_ip) { gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); if (!gsmtap) { fprintf(stderr, "Failed during gsmtap_init()\n"); exit(1); } gsmtap_source_add_sink(gsmtap); } if (bts_init(bts) < 0) { fprintf(stderr, "unable to open bts\n"); exit(1); } btsb = bts_role_bts(bts); btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); abis_init(bts); rc = vty_read_config_file(config_file, NULL); if (rc < 0) { fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); exit(1); } clk_cal_use_eeprom(bts); if (stat(SYSMOBTS_RF_LOCK_PATH, &st) == 0) { LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); exit(23); } write_pid_file("osmo-bts"); bts_controlif_setup(bts); sysmobts_ctrlif_inst_cmds(); rc = telnet_init(tall_bts_ctx, NULL, OSMO_VTY_PORT_BTS); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); } if (pcu_sock_init()) { fprintf(stderr, "PCU L1 socket failed\n"); exit(1); } signal(SIGINT, &signal_handler); //signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); osmo_init_ignore_signals(); rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); if (rc < 0) { fprintf(stderr, "Error creating the OML router: %s rc=%d\n", OML_ROUTER_PATH, rc); exit(1); } if (!btsb->bsc_oml_host) { fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n"); exit(1); } line = abis_open(bts, btsb->bsc_oml_host, "sysmoBTS"); if (!line) { fprintf(stderr, "unable to connect to BSC\n"); exit(2); } if (daemonize) { rc = osmo_daemonize(); if (rc < 0) { perror("Error during daemonize"); exit(1); } } while (1) { log_reset_context(); osmo_select_main(0); } } void bts_model_abis_close(struct gsm_bts *bts) { /* for now, we simply terminate the program and re-spawn */ bts_shutdown(bts, "Abis close"); } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/000077500000000000000000000000001260026426200177135ustar00rootroot00000000000000osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h000066400000000000000000000012441260026426200233170ustar00rootroot00000000000000#ifndef _SYSMOBTS_EEPROM_H #define _SYSMOBTS_EEPROM_H #include struct sysmobts_eeprom { /* offset */ uint8_t eth_mac[6]; /* 0-5 */ uint8_t _pad0[10]; /* 6-15 */ uint16_t unused1; /* 16-17 */ uint8_t temp1_max; /* 18 */ uint8_t temp2_max; /* 19 */ uint32_t serial_nr; /* 20-23 */ uint32_t operational_hours; /* 24-27 */ uint32_t boot_count; /* 28-31 */ uint16_t model_nr; /* 32-33 */ uint16_t model_flags; /* 34-35 */ uint8_t trx_nr; /* 36 */ uint8_t _pad1[84]; /* 37-120 */ uint8_t gpg_key[128]; /* 121-249 */ } __attribute__((packed)); enum sysmobts_model_number { MODEL_SYSMOBTS_1020 = 1002, MODEL_SYSMOBTS_2050 = 2050, }; #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr.c000066400000000000000000000147551260026426200226230ustar00rootroot00000000000000/* Main program for SysmoBTS management daemon */ /* (C) 2012 by Harald Welte * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include "misc/sysmobts_misc.h" #include "misc/sysmobts_mgr.h" #include "misc/sysmobts_par.h" static int bts_type; static int trx_number; static int no_eeprom_write = 0; static int daemonize = 0; void *tall_mgr_ctx; /* every 6 hours means 365*4 = 1460 EEprom writes per year (max) */ #define TEMP_TIMER_SECS (6 * 3600) /* every 1 hours means 365*24 = 8760 EEprom writes per year (max) */ #define HOURS_TIMER_SECS (1 * 3600) /* the initial state */ static struct sysmobts_mgr_instance manager = { .config_file = "sysmobts-mgr.cfg", .rf_limit = { .thresh_warn = 60, .thresh_crit = 78, }, .digital_limit = { .thresh_warn = 60, .thresh_crit = 78, }, .board_limit = { .thresh_warn = 60, .thresh_crit = 78, }, .pa_limit = { .thresh_warn = 60, .thresh_crit = 100, }, .action_warn = 0, .action_crit = TEMP_ACT_PA_OFF, .state = STATE_NORMAL, }; static int classify_bts(void) { int rc; rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, &bts_type); if (rc < 0) { fprintf(stderr, "Failed to get model number.\n"); return -1; } rc = sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, &trx_number); if (rc < 0) { fprintf(stderr, "Failed to get the trx number.\n"); return -1; } return 0; } int sysmobts_bts_type(void) { return bts_type; } int sysmobts_trx_number(void) { return trx_number; } int is_sbts2050(void) { return bts_type == 2050; } int is_sbts2050_trx(int trx) { return trx_number == trx; } int is_sbts2050_master(void) { if (!is_sbts2050()) return 0; if (!is_sbts2050_trx(0)) return 0; return 1; } static struct osmo_timer_list temp_timer; static void check_temp_timer_cb(void *unused) { sysmobts_check_temp(no_eeprom_write); osmo_timer_schedule(&temp_timer, TEMP_TIMER_SECS, 0); } static struct osmo_timer_list hours_timer; static void hours_timer_cb(void *unused) { sysmobts_update_hours(no_eeprom_write); osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0); } static void print_help(void) { printf("sysmobts-mgr [-nsD] [-d cat]\n"); printf(" -n Do not write to EEPROM\n"); printf(" -s Disable color\n"); printf(" -d CAT enable debugging\n"); printf(" -D daemonize\n"); printf(" -c Specify the filename of the config file\n"); } static int parse_options(int argc, char **argv) { int opt; while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) { switch (opt) { case 'n': no_eeprom_write = 1; break; case 'h': print_help(); return -1; case 's': log_set_use_color(osmo_stderr_target, 0); break; case 'd': log_parse_category_mask(osmo_stderr_target, optarg); break; case 'D': daemonize = 1; break; case 'c': manager.config_file = optarg; break; default: return -1; } } return 0; } static void signal_handler(int signal) { fprintf(stderr, "signal %u received\n", signal); switch (signal) { case SIGINT: sysmobts_check_temp(no_eeprom_write); sysmobts_update_hours(no_eeprom_write); exit(0); break; case SIGABRT: case SIGUSR1: case SIGUSR2: talloc_report_full(tall_mgr_ctx, stderr); break; default: break; } } static struct log_info_cat mgr_log_info_cat[] = { [DTEMP] = { .name = "DTEMP", .description = "Temperature monitoring", .color = "\033[1;35m", .enabled = 1, .loglevel = LOGL_INFO, }, [DFW] = { .name = "DFW", .description = "DSP/FPGA firmware management", .color = "\033[1;36m", .enabled = 1, .loglevel = LOGL_INFO, }, [DFIND] = { .name = "DFIND", .description = "ipaccess-find handling", .color = "\033[1;37m", .enabled = 1, .loglevel = LOGL_INFO, }, [DCALIB] = { .name = "DCALIB", .description = "Calibration handling", .color = "\033[1;37m", .enabled = 1, .loglevel = LOGL_INFO, }, }; static const struct log_info mgr_log_info = { .cat = mgr_log_info_cat, .num_cat = ARRAY_SIZE(mgr_log_info_cat), }; static int mgr_log_init(void) { osmo_init_logging(&mgr_log_info); return 0; } int main(int argc, char **argv) { void *tall_msgb_ctx; int rc; tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); mgr_log_init(); if (classify_bts() != 0) exit(2); osmo_init_ignore_signals(); signal(SIGINT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); rc = parse_options(argc, argv); if (rc < 0) exit(2); sysmobts_mgr_vty_init(); logging_vty_add_cmds(&mgr_log_info); rc = sysmobts_mgr_parse_config(&manager); if (rc < 0) { LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); exit(1); } rc = telnet_init(tall_msgb_ctx, NULL, OSMO_VTY_PORT_BTSMGR); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); } /* start temperature check timer */ temp_timer.cb = check_temp_timer_cb; check_temp_timer_cb(NULL); /* start operational hours timer */ hours_timer.cb = hours_timer_cb; hours_timer_cb(NULL); /* start uc temperature check timer */ sbts2050_uc_initialize(); /* handle broadcast messages for ipaccess-find */ if (sysmobts_mgr_nl_init() != 0) exit(3); /* Initialize the temperature control */ sysmobts_mgr_temp_init(&manager); if (sysmobts_mgr_calib_init(&manager) != 0) exit(3); if (daemonize) { rc = osmo_daemonize(); if (rc < 0) { perror("Error during daemonize"); exit(1); } } while (1) { log_reset_context(); osmo_select_main(0); } } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr.h000066400000000000000000000046361260026426200226250ustar00rootroot00000000000000#ifndef _SYSMOBTS_MGR_H #define _SYSMOBTS_MGR_H #include #include #include #include #include #include enum { DTEMP, DFW, DFIND, DCALIB, }; enum { #if 0 TEMP_ACT_PWR_CONTRL = 0x1, #endif TEMP_ACT_SLAVE_OFF = 0x4, TEMP_ACT_PA_OFF = 0x8, TEMP_ACT_BTS_SRV_OFF = 0x10, }; /* actions only for normal state */ enum { #if 0 TEMP_ACT_NORM_PW_CONTRL = 0x1, #endif TEMP_ACT_NORM_SLAVE_ON = 0x4, TEMP_ACT_NORM_PA_ON = 0x8, TEMP_ACT_NORM_BTS_SRV_ON= 0x10, }; enum sysmobts_temp_state { STATE_NORMAL, /* Everything is fine */ STATE_WARNING_HYST, /* Go back to normal next? */ STATE_WARNING, /* We are above the warning threshold */ STATE_CRITICAL, /* We have an issue. Wait for below warning */ }; /** * Temperature Limits. We separate from a threshold * that will generate a warning and one that is so * severe that an action will be taken. */ struct sysmobts_temp_limit { int thresh_warn; int thresh_crit; }; enum mgr_vty_node { MGR_NODE = _LAST_OSMOVTY_NODE + 1, ACT_NORM_NODE, ACT_WARN_NODE, ACT_CRIT_NODE, LIMIT_RF_NODE, LIMIT_DIGITAL_NODE, LIMIT_BOARD_NODE, LIMIT_PA_NODE, }; struct sysmobts_mgr_instance { const char *config_file; struct sysmobts_temp_limit rf_limit; struct sysmobts_temp_limit digital_limit; /* Only available on sysmobts 2050 */ struct sysmobts_temp_limit board_limit; struct sysmobts_temp_limit pa_limit; int action_norm; int action_warn; int action_crit; enum sysmobts_temp_state state; struct { int initial_calib_started; int is_up; struct osmo_timer_list recon_timer; struct ipa_client_conn *bts_conn; int state; struct osmo_timer_list timer; uint32_t last_seqno; /* gps structure to see if there is a fix */ int gps_open; struct osmo_fd gpsfd; struct gps_data_t gpsdata; struct osmo_timer_list fix_timeout; /* Loop/Re-try control */ int calib_from_loop; struct osmo_timer_list calib_timeout; } calib; }; int sysmobts_mgr_vty_init(void); int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *mgr); int sysmobts_mgr_nl_init(void); int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr); const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state); int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr); int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr); extern void *tall_mgr_ctx; #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c000066400000000000000000000217311260026426200232610ustar00rootroot00000000000000/* (C) 2014 by s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 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 "sysmobts_misc.h" #include "sysmobts_par.h" #include "sysmobts_mgr.h" #include "btsconfig.h" #include #include #include #include #include #include #include #ifdef BUILD_SBTS2050 #include #define SERIAL_ALLOC_SIZE 300 #define SIZE_HEADER_RSP 5 #define SIZE_HEADER_CMD 4 struct uc { int id; int fd; const char *path; }; struct ucinfo { uint16_t id; int master; int slave; int pa; }; static struct uc ucontrol0 = { .id = 0, .path = "/dev/ttyS0", .fd = -1, }; /********************************************************************** * Functions read/write from serial interface *********************************************************************/ static int hand_serial_read(int fd, struct msgb *msg, int numbytes) { int rc, bread = 0; if (numbytes > msgb_tailroom(msg)) return -ENOSPC; while (bread < numbytes) { rc = read(fd, msg->tail, numbytes - bread); if (rc < 0) return -1; if (rc == 0) break; bread += rc; msgb_put(msg, rc); } return bread; } static int hand_serial_write(int fd, struct msgb *msg) { int rc, bwritten = 0; while (msg->len > 0) { rc = write(fd, msg->data, msg->len); if (rc <= 0) return -1; msgb_pull(msg, rc); bwritten += rc; } return bwritten; } /********************************************************************** * Functions request information to Microcontroller *********************************************************************/ static void add_parity(cmdpkt_t *command) { int n; uint8_t parity = 0x00; for (n = 0; n < SIZE_HEADER_CMD+command->u8Len; n++) parity ^= ((uint8_t *)command)[n]; command->cmd.raw[command->u8Len] = parity; } static struct msgb *sbts2050_ucinfo_sndrcv(struct uc *ucontrol, const struct ucinfo *info) { int num, rc; cmdpkt_t *command; rsppkt_t *response; struct msgb *msg; fd_set fdread; struct timeval tout = { .tv_sec = 10, }; switch (info->id) { case SBTS2050_TEMP_RQT: num = sizeof(command->cmd.tempGet); break; case SBTS2050_PWR_RQT: num = sizeof(command->cmd.pwrSetState); break; case SBTS2050_PWR_STATUS: num = sizeof(command->cmd.pwrGetStatus); break; default: return NULL; } num = num + SIZE_HEADER_CMD+1; msg = msgb_alloc(SERIAL_ALLOC_SIZE, "Message Microcontroller"); if (msg == NULL) { LOGP(DTEMP, LOGL_ERROR, "Error creating msg\n"); return NULL; } command = (cmdpkt_t *) msgb_put(msg, num); command->u16Magic = 0xCAFE; switch (info->id) { case SBTS2050_TEMP_RQT: command->u8Id = info->id; command->u8Len = sizeof(command->cmd.tempGet); break; case SBTS2050_PWR_RQT: command->u8Id = info->id; command->u8Len = sizeof(command->cmd.pwrSetState); command->cmd.pwrSetState.u1MasterEn = !!info->master; command->cmd.pwrSetState.u1SlaveEn = !!info->slave; command->cmd.pwrSetState.u1PwrAmpEn = !!info->pa; break; case SBTS2050_PWR_STATUS: command->u8Id = info->id; command->u8Len = sizeof(command->cmd.pwrGetStatus); break; default: goto err; } add_parity(command); if (hand_serial_write(ucontrol->fd, msg) < 0) goto err; msgb_reset(msg); FD_ZERO(&fdread); FD_SET(ucontrol->fd, &fdread); num = SIZE_HEADER_RSP; while (1) { rc = select(ucontrol->fd+1, &fdread, NULL, NULL, &tout); if (rc > 0) { if (hand_serial_read(ucontrol->fd, msg, num) < 0) goto err; response = (rsppkt_t *)msg->data; if (response->u8Id != info->id || msg->len <= 0 || response->i8Error != RQT_SUCCESS) goto err; if (msg->len == SIZE_HEADER_RSP + response->u8Len + 1) break; num = response->u8Len + 1; } else goto err; } return msg; err: msgb_free(msg); return NULL; } /********************************************************************** * Get power status function *********************************************************************/ int sbts2050_uc_get_status(struct sbts2050_power_status *status) { struct msgb *msg; const struct ucinfo info = { .id = SBTS2050_PWR_STATUS, }; rsppkt_t *response; memset(status, 0, sizeof(*status)); msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info); if (msg == NULL) { LOGP(DTEMP, LOGL_ERROR, "Error requesting power status.\n"); return -1; } response = (rsppkt_t *)msg->data; status->main_supply_current = response->rsp.pwrGetStatus.u8MainSupplyA / 64.f; status->master_enabled = response->rsp.pwrGetStatus.u1MasterEn; status->master_voltage = response->rsp.pwrGetStatus.u8MasterV / 32.f; status->master_current = response->rsp.pwrGetStatus.u8MasterA / 64.f;; status->slave_enabled = response->rsp.pwrGetStatus.u1SlaveEn; status->slave_voltage = response->rsp.pwrGetStatus.u8SlaveV / 32.f; status->slave_current = response->rsp.pwrGetStatus.u8SlaveA / 64.f; status->pa_enabled = response->rsp.pwrGetStatus.u1PwrAmpEn; status->pa_voltage = response->rsp.pwrGetStatus.u8PwrAmpV / 4.f; status->pa_current = response->rsp.pwrGetStatus.u8PwrAmpA / 64.f; status->pa_bias_voltage = response->rsp.pwrGetStatus.u8PwrAmpBiasV / 16.f; msgb_free(msg); return 0; } /********************************************************************** * Uc Power Switching handling *********************************************************************/ int sbts2050_uc_set_power(int pmaster, int pslave, int ppa) { struct msgb *msg; const struct ucinfo info = { .id = SBTS2050_PWR_RQT, .master = pmaster, .slave = pslave, .pa = ppa }; msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info); if (msg == NULL) { LOGP(DTEMP, LOGL_ERROR, "Error switching off some unit.\n"); return -1; } LOGP(DTEMP, LOGL_DEBUG, "Switch off/on success:\n" "MASTER %s\n" "SLAVE %s\n" "PA %s\n", pmaster ? "ON" : "OFF", pslave ? "ON" : "OFF", ppa ? "ON" : "OFF"); msgb_free(msg); return 0; } /********************************************************************** * Uc temperature handling *********************************************************************/ int sbts2050_uc_check_temp(int *temp_pa, int *temp_board) { rsppkt_t *response; struct msgb *msg; const struct ucinfo info = { .id = SBTS2050_TEMP_RQT, }; msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info); if (msg == NULL) { LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n"); return -1; } response = (rsppkt_t *)msg->data; *temp_board = response->rsp.tempGet.i8BrdTemp; *temp_pa = response->rsp.tempGet.i8PaTemp; LOGP(DTEMP, LOGL_DEBUG, "Temperature Board: %+3d C, " "Tempeture PA: %+3d C\n", response->rsp.tempGet.i8BrdTemp, response->rsp.tempGet.i8PaTemp); msgb_free(msg); return 0; } void sbts2050_uc_initialize(void) { if (!is_sbts2050()) return; ucontrol0.fd = osmo_serial_init(ucontrol0.path, 115200); if (ucontrol0.fd < 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to open the serial interface\n"); return; } if (is_sbts2050_master()) { LOGP(DTEMP, LOGL_NOTICE, "Going to enable the PA.\n"); sbts2050_uc_set_pa_power(1); } } int sbts2050_uc_set_pa_power(int on_off) { struct sbts2050_power_status status; if (sbts2050_uc_get_status(&status) != 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n"); return -1; } return sbts2050_uc_set_power(status.master_enabled, status.slave_enabled, on_off); } int sbts2050_uc_set_slave_power(int on_off) { struct sbts2050_power_status status; if (sbts2050_uc_get_status(&status) != 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n"); return -1; } return sbts2050_uc_set_power( status.master_enabled, on_off, status.pa_enabled); } #else void sbts2050_uc_initialize(void) { LOGP(DTEMP, LOGL_NOTICE, "sysmoBTS2050 was not enabled at compile time.\n"); } int sbts2050_uc_check_temp(int *temp_pa, int *temp_board) { LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without temp support.\n"); *temp_pa = *temp_board = 99999; return -1; } int sbts2050_uc_get_status(struct sbts2050_power_status *status) { memset(status, 0, sizeof(*status)); LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without status support.\n"); return -1; } int sbts2050_uc_set_pa_power(int on_off) { LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without PA support.\n"); return -1; } int sbts2050_uc_set_slave_power(int on_off) { LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without UC support.\n"); return -1; } #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c000066400000000000000000000330531260026426200237450ustar00rootroot00000000000000/* OCXO/TCXO calibration control for SysmoBTS management daemon */ /* * (C) 2014,2015 by Holger Hans Peter Freyther * (C) 2014 by Harald Welte for the IPA code from the oml router * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "misc/sysmobts_mgr.h" #include "misc/sysmobts_misc.h" #include "osmo-bts/msg_utils.h" #include #include #include #include #include #include #include #include static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop); static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int reason); static void request_clock_reset(struct sysmobts_mgr_instance *mgr); static void bts_updown_cb(struct ipa_client_conn *link, int up); enum calib_state { CALIB_INITIAL, CALIB_GPS_WAIT_FOR_FIX, CALIB_CTR_RESET, CALIB_CTR_WAIT, CALIB_COR_SET, }; enum calib_result { CALIB_FAIL_START, CALIB_FAIL_GPS, CALIB_FAIL_CTRL, CALIB_SUCESS, }; static void calib_loop_run(void *_data) { int rc; struct sysmobts_mgr_instance *mgr = _data; LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n"); rc = calib_run(mgr, 1); if (rc != 0) calib_state_reset(mgr, CALIB_FAIL_START); } static void mgr_gps_close(struct sysmobts_mgr_instance *mgr) { if (!mgr->calib.gps_open) return; osmo_timer_del(&mgr->calib.fix_timeout); osmo_fd_unregister(&mgr->calib.gpsfd); gps_close(&mgr->calib.gpsdata); memset(&mgr->calib.gpsdata, 0, sizeof(mgr->calib.gpsdata)); mgr->calib.gps_open = 0; } static void mgr_gps_checkfix(struct sysmobts_mgr_instance *mgr) { struct gps_data_t *data = &mgr->calib.gpsdata; /* No 2D fix yet */ if (data->fix.mode < MODE_2D) { LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n", data->fix.mode); return; } /* The trimble driver is broken...add some sanity checking */ if (data->satellites_used < 1) { LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n", data->satellites_used); return; } LOGP(DCALIB, LOGL_NOTICE, "Got a GPS fix continuing.\n"); osmo_timer_del(&mgr->calib.fix_timeout); mgr_gps_close(mgr); request_clock_reset(mgr); } static int mgr_gps_read(struct osmo_fd *fd, unsigned int what) { int rc; struct sysmobts_mgr_instance *mgr = fd->data; rc = gps_read(&mgr->calib.gpsdata); if (rc == -1) { LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n"); calib_state_reset(mgr, CALIB_FAIL_GPS); return -1; } if (rc > 0) mgr_gps_checkfix(mgr); return 0; } static void mgr_gps_fix_timeout(void *_data) { struct sysmobts_mgr_instance *mgr = _data; LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPRS fix.\n"); calib_state_reset(mgr, CALIB_FAIL_GPS); } static void mgr_gps_open(struct sysmobts_mgr_instance *mgr) { int rc; rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->calib.gpsdata); if (rc != 0) { LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc); calib_state_reset(mgr, CALIB_FAIL_GPS); return; } mgr->calib.gps_open = 1; gps_stream(&mgr->calib.gpsdata, WATCH_ENABLE, NULL); mgr->calib.gpsfd.data = mgr; mgr->calib.gpsfd.cb = mgr_gps_read; mgr->calib.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT; mgr->calib.gpsfd.fd = mgr->calib.gpsdata.gps_fd; if (osmo_fd_register(&mgr->calib.gpsfd) < 0) { LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n"); calib_state_reset(mgr, CALIB_FAIL_GPS); } mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX; mgr->calib.fix_timeout.data = mgr; mgr->calib.fix_timeout.cb = mgr_gps_fix_timeout; osmo_timer_schedule(&mgr->calib.fix_timeout, 60, 0); LOGP(DCALIB, LOGL_NOTICE, "Opened the GPSD connection waiting for fix: %d\n", mgr->calib.gpsfd.fd); } static void send_ctrl_cmd(struct sysmobts_mgr_instance *mgr, struct msgb *msg) { ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); ipa_prepend_header(msg, IPAC_PROTO_OSMO); ipa_client_conn_send(mgr->calib.bts_conn, msg); } static void send_set_ctrl_cmd_int(struct sysmobts_mgr_instance *mgr, const char *key, const int val) { struct msgb *msg; int ret; msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); ret = snprintf((char *) msg->data, 4096, "SET %u %s %d", mgr->calib.last_seqno++, key, val); msg->l2h = msgb_put(msg, ret); return send_ctrl_cmd(mgr, msg); } static void send_set_ctrl_cmd(struct sysmobts_mgr_instance *mgr, const char *key, const char *val) { struct msgb *msg; int ret; msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); ret = snprintf((char *) msg->data, 4096, "SET %u %s %s", mgr->calib.last_seqno++, key, val); msg->l2h = msgb_put(msg, ret); return send_ctrl_cmd(mgr, msg); } static void send_get_ctrl_cmd(struct sysmobts_mgr_instance *mgr, const char *key) { struct msgb *msg; int ret; msg = msgb_alloc_headroom(1024, 128, "CTRL GET"); ret = snprintf((char *) msg->data, 4096, "GET %u %s", mgr->calib.last_seqno++, key); msg->l2h = msgb_put(msg, ret); return send_ctrl_cmd(mgr, msg); } static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop) { if (!mgr->calib.is_up) { LOGP(DCALIB, LOGL_ERROR, "Control interface not connected.\n"); return -1; } if (mgr->calib.state != CALIB_INITIAL) { LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); return -2; } mgr->calib.calib_from_loop = from_loop; /* From now on everything will be handled from the failure */ mgr->calib.initial_calib_started = 1; mgr_gps_open(mgr); return 0; } int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr) { return calib_run(mgr, 0); } static void request_clock_reset(struct sysmobts_mgr_instance *mgr) { send_set_ctrl_cmd(mgr, "trx.0.clock-info", "1"); mgr->calib.state = CALIB_CTR_RESET; } static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int outcome) { if (mgr->calib.calib_from_loop) { /* * In case of success calibrate in two hours again * and in case of a failure in some minutes. */ int timeout = 2 * 60 * 60; if (outcome != CALIB_SUCESS) timeout = 5 * 60; mgr->calib.calib_timeout.data = mgr; mgr->calib.calib_timeout.cb = calib_loop_run; osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0); } mgr->calib.state = CALIB_INITIAL; osmo_timer_del(&mgr->calib.timer); mgr_gps_close(mgr); } static void calib_get_clock_err_cb(void *_data) { struct sysmobts_mgr_instance *mgr = _data; LOGP(DCALIB, LOGL_DEBUG, "Requesting current clock-info.\n"); send_get_ctrl_cmd(mgr, "trx.0.clock-info"); } static void handle_ctrl_reset_resp( struct sysmobts_mgr_instance *mgr, struct ctrl_cmd *cmd) { if (strcmp(cmd->variable, "trx.0.clock-info") != 0) { LOGP(DCALIB, LOGL_ERROR, "Unexpected variable: %s\n", cmd->variable); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } if (strcmp(cmd->reply, "success") != 0) { LOGP(DCALIB, LOGL_ERROR, "Unexpected reply: %s\n", cmd->variable); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } mgr->calib.state = CALIB_CTR_WAIT; mgr->calib.timer.cb = calib_get_clock_err_cb; mgr->calib.timer.data = mgr; osmo_timer_schedule(&mgr->calib.timer, 60, 0); LOGP(DCALIB, LOGL_DEBUG, "Reset the calibration counter. Waiting 60 seconds.\n"); } static void handle_ctrl_get_resp( struct sysmobts_mgr_instance *mgr, struct ctrl_cmd *cmd) { char *saveptr = NULL; char *clk_cur; char *clk_src; char *cal_err; char *cal_res; char *cal_src; int cal_err_int; if (strcmp(cmd->variable, "trx.0.clock-info") != 0) { LOGP(DCALIB, LOGL_ERROR, "Unexpected variable: %s\n", cmd->variable); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } clk_cur = strtok_r(cmd->reply, ",", &saveptr); clk_src = strtok_r(NULL, ",", &saveptr); cal_err = strtok_r(NULL, ",", &saveptr); cal_res = strtok_r(NULL, ",", &saveptr); cal_src = strtok_r(NULL, ",", &saveptr); if (!clk_cur || !clk_src || !cal_err || !cal_res || !cal_src) { LOGP(DCALIB, LOGL_ERROR, "Parse error on clock-info reply\n"); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } cal_err_int = atoi(cal_err); LOGP(DCALIB, LOGL_NOTICE, "Calibration CUR(%s) SRC(%s) ERR(%s/%d) RES(%s) SRC(%s)\n", clk_cur, clk_src, cal_err, cal_err_int, cal_res, cal_src); if (strcmp(cal_res, "0") == 0) { LOGP(DCALIB, LOGL_ERROR, "Invalid clock resolution. Giving up\n"); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } /* Now we can finally set the new value */ LOGP(DCALIB, LOGL_NOTICE, "Going to apply %d as new clock correction.\n", -cal_err_int); send_set_ctrl_cmd_int(mgr, "trx.0.clock-correction", -cal_err_int); mgr->calib.state = CALIB_COR_SET; } static void handle_ctrl_set_cor( struct sysmobts_mgr_instance *mgr, struct ctrl_cmd *cmd) { if (strcmp(cmd->variable, "trx.0.clock-correction") != 0) { LOGP(DCALIB, LOGL_ERROR, "Unexpected variable: %s\n", cmd->variable); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } if (strcmp(cmd->reply, "success") != 0) { LOGP(DCALIB, LOGL_ERROR, "Unexpected reply: %s\n", cmd->variable); calib_state_reset(mgr, CALIB_FAIL_CTRL); return; } LOGP(DCALIB, LOGL_NOTICE, "Calibration process completed\n"); calib_state_reset(mgr, CALIB_SUCESS); } static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg) { struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg); if (!cmd) { LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n"); return; } switch (cmd->type) { case CTRL_TYPE_GET_REPLY: switch (mgr->calib.state) { case CALIB_CTR_WAIT: handle_ctrl_get_resp(mgr, cmd); break; default: LOGP(DCALIB, LOGL_ERROR, "Unhandled response in state: %d %s/%s\n", mgr->calib.state, cmd->variable, cmd->reply); calib_state_reset(mgr, CALIB_FAIL_CTRL); break; }; break; case CTRL_TYPE_SET_REPLY: switch (mgr->calib.state) { case CALIB_CTR_RESET: handle_ctrl_reset_resp(mgr, cmd); break; case CALIB_COR_SET: handle_ctrl_set_cor(mgr, cmd); break; default: LOGP(DCALIB, LOGL_ERROR, "Unhandled response in state: %d %s/%s\n", mgr->calib.state, cmd->variable, cmd->reply); calib_state_reset(mgr, CALIB_FAIL_CTRL); break; }; break; case CTRL_TYPE_TRAP: /* ignore any form of trap */ break; default: LOGP(DCALIB, LOGL_ERROR, "Unhandled CTRL response: %d. Resetting state\n", cmd->type); calib_state_reset(mgr, CALIB_FAIL_CTRL); break; } talloc_free(cmd); } /* Schedule a connect towards the BTS */ static void schedule_bts_connect(struct sysmobts_mgr_instance *mgr) { DEBUGP(DLCTRL, "Scheduling BTS connect\n"); osmo_timer_schedule(&mgr->calib.recon_timer, 1, 0); } /* BTS re-connect timer call-back */ static void bts_recon_timer_cb(void *data) { int rc; struct sysmobts_mgr_instance *mgr = data; /* The connection failures are to be expected during boot */ mgr->calib.bts_conn->ofd->when |= BSC_FD_WRITE; rc = ipa_client_conn_open(mgr->calib.bts_conn); if (rc < 0) { LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n"); schedule_bts_connect(mgr); } } static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg) { int rc; struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg); struct ipaccess_head_ext *hh_ext; DEBUGP(DCALIB, "Received data from BTS: %s\n", osmo_hexdump(msgb_data(msg), msgb_length(msg))); /* regular message handling */ rc = msg_verify_ipa_structure(msg); if (rc < 0) { LOGP(DCALIB, LOGL_ERROR, "Invalid IPA message from BTS (rc=%d)\n", rc); goto err; } switch (hh->proto) { case IPAC_PROTO_IPACCESS: /* handle the core IPA CCM messages in libosmoabis */ ipa_ccm_rcvmsg_bts_base(msg, link->ofd); msgb_free(msg); break; case IPAC_PROTO_OSMO: hh_ext = (struct ipaccess_head_ext *) hh->data; switch (hh_ext->proto) { case IPAC_PROTO_EXT_CTRL: handle_ctrl(link->data, msg); break; default: LOGP(DCALIB, LOGL_NOTICE, "Unhandled osmo ID %u from BTS\n", hh_ext->proto); }; msgb_free(msg); break; default: LOGP(DCALIB, LOGL_NOTICE, "Unhandled stream ID %u from BTS\n", hh->proto); msgb_free(msg); break; } return 0; err: msgb_free(msg); return -1; } /* link to BSC has gone up or down */ static void bts_updown_cb(struct ipa_client_conn *link, int up) { struct sysmobts_mgr_instance *mgr = link->data; LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down"); if (up) { mgr->calib.is_up = 1; mgr->calib.last_seqno = 0; if (!mgr->calib.initial_calib_started) calib_run(mgr, 1); } else { mgr->calib.is_up = 0; schedule_bts_connect(mgr); calib_state_reset(mgr, CALIB_FAIL_CTRL); } } int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr) { if (!is_sbts2050_master()) { LOGP(DCALIB, LOGL_NOTICE, "Calib is only possible on the sysmoBTS2050 master\n"); return 0; } mgr->calib.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0, "localhost", 4238, bts_updown_cb, bts_read_cb, NULL, mgr); if (!mgr->calib.bts_conn) { LOGP(DCALIB, LOGL_ERROR, "Failed to create IPA connection\n"); return -1; } mgr->calib.recon_timer.cb = bts_recon_timer_cb; mgr->calib.recon_timer.data = mgr; schedule_bts_connect(mgr); return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c000066400000000000000000000123451260026426200233050ustar00rootroot00000000000000/* NetworkListen for SysmoBTS management daemon */ /* * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "misc/sysmobts_mgr.h" #include "misc/sysmobts_misc.h" #include "misc/sysmobts_nl.h" #include "misc/sysmobts_par.h" #include #include #include #include #include #include #include #include #include #include static struct osmo_fd nl_fd; /* * The TLV structure in IPA messages in UDP packages is a bit * weird. First the header appears to have an extra NULL byte * and second the L16 of the L16TV needs to include +1 for the * tag. The default msgb/tlv and libosmo-abis routines do not * provide this. */ static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto) { struct ipaccess_head *hh; /* prepend the ip.access header */ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1); hh->len = htons(msg->len - sizeof(*hh) - 1); hh->proto = proto; } static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag, const uint8_t *val) { uint8_t *buf = msgb_put(msg, len + 2 + 1); *buf++ = (len + 1) >> 8; *buf++ = (len + 1) & 0xff; *buf++ = tag; memcpy(buf, val, len); } /* * We don't look at the content of the request yet and lie * about most of the responses. */ static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd, uint8_t *data, size_t len) { static int fetched_info = 0; static char mac_str[20] = {0, }; static char *model_name; static char ser_str[20] = {0, }; struct sockaddr_in loc_addr; int rc; char loc_ip[INET_ADDRSTRLEN]; struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response"); if (!msg) { LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n"); return; } if (!fetched_info) { uint8_t mac[6]; int serno; /* fetch the MAC */ sysmobts_par_get_buf(SYSMOBTS_PAR_MAC, mac, sizeof(mac)); snprintf(mac_str, sizeof(mac_str), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); /* fetch the serial number */ sysmobts_par_get_int(SYSMOBTS_PAR_SERNR, &serno); snprintf(ser_str, sizeof(ser_str), "%d", serno); /* fetch the model and trx number */ switch(sysmobts_bts_type()) { case 0: case 0xffff: case 1002: model_name = "sysmoBTS 1002"; break; case 2050: if (sysmobts_trx_number() == 0) model_name = "sysmoBTS 2050 (master)"; else if (sysmobts_trx_number() == 1) model_name = "sysmoBTS 2050 (slave)"; else model_name = "sysmoBTS 2050 (unknown)"; break; default: model_name = "Unknown"; break; } fetched_info = 1; } if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) { LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n"); return; } msgb_put_u8(msg, IPAC_MSGT_ID_RESP); /* append MAC addr */ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str); /* append ip address */ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip)); quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip); /* append the serial number */ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str); /* abuse some flags */ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name); /* ip.access nanoBTS would reply to port==3006 */ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS); rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src)); if (rc != msg->len) LOGP(DFIND, LOGL_ERROR, "Failed to send with rc(%d) errno(%d)\n", rc, errno); } static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what) { uint8_t data[2048]; char src[INET_ADDRSTRLEN]; struct sockaddr_in addr = {}; socklen_t len = sizeof(addr); int rc; rc = recvfrom(fd->fd, data, sizeof(data), 0, (struct sockaddr *) &addr, &len); if (rc <= 0) { LOGP(DFIND, LOGL_ERROR, "Failed to read from socket errno(%d)\n", errno); return -1; } LOGP(DFIND, LOGL_DEBUG, "Received request from: %s size %d\n", inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc); if (rc < 6) return 0; if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET) return 0; respond_to(&addr, fd, data + 6, rc - 6); return 0; } int sysmobts_mgr_nl_init(void) { int rc; nl_fd.cb = ipaccess_bcast; rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, "0.0.0.0", 3006, OSMO_SOCK_F_BIND); if (rc < 0) { perror("Socket creation"); return -1; } return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c000066400000000000000000000200001260026426200236240ustar00rootroot00000000000000/* Temperature control for SysmoBTS management daemon */ /* * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "misc/sysmobts_mgr.h" #include "misc/sysmobts_misc.h" #include #include #include static struct sysmobts_mgr_instance *s_mgr; static struct osmo_timer_list temp_ctrl_timer; static const struct value_string state_names[] = { { STATE_NORMAL, "NORMAL" }, { STATE_WARNING_HYST, "WARNING (HYST)" }, { STATE_WARNING, "WARNING" }, { STATE_CRITICAL, "CRITICAL" }, { 0, NULL } }; const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state) { return get_value_string(state_names, state); } static int next_state(enum sysmobts_temp_state current_state, int critical, int warning) { int next_state = -1; switch (current_state) { case STATE_NORMAL: if (critical) next_state = STATE_CRITICAL; else if (warning) next_state = STATE_WARNING; break; case STATE_WARNING_HYST: if (critical) next_state = STATE_CRITICAL; else if (warning) next_state = STATE_WARNING; else next_state = STATE_NORMAL; break; case STATE_WARNING: if (critical) next_state = STATE_CRITICAL; else if (!warning) next_state = STATE_WARNING_HYST; break; case STATE_CRITICAL: if (!critical && !warning) next_state = STATE_WARNING; break; }; return next_state; } static void handle_normal_actions(int actions) { /* switch off the PA */ if (actions & TEMP_ACT_NORM_PA_ON) { if (!is_sbts2050()) { LOGP(DTEMP, LOGL_NOTICE, "PA can only be switched-on on the master\n"); } else if (sbts2050_uc_set_pa_power(1) != 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n"); } else { LOGP(DTEMP, LOGL_NOTICE, "Switched on the PA as normal action.\n"); } } if (actions & TEMP_ACT_NORM_SLAVE_ON) { if (!is_sbts2050()) { LOGP(DTEMP, LOGL_NOTICE, "Slave on only possible on the sysmoBTS2050\n"); } else if (sbts2050_uc_set_slave_power(1) != 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the slave BTS\n"); } else { LOGP(DTEMP, LOGL_NOTICE, "Switched on the slave as normal action.\n"); } } if (actions & TEMP_ACT_NORM_BTS_SRV_ON) { LOGP(DTEMP, LOGL_NOTICE, "Going to switch on the BTS service\n"); /* * TODO: use/create something like nspawn that serializes * and used SIGCHLD/waitpid to pick up the dead processes * without invoking shell. */ system("/bin/systemctl start sysmobts.service"); } } static void handle_actions(int actions) { /* switch off the PA */ if (actions & TEMP_ACT_PA_OFF) { if (!is_sbts2050()) { LOGP(DTEMP, LOGL_NOTICE, "PA can only be switched-off on the master\n"); } else if (sbts2050_uc_set_pa_power(0) != 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA. Stop BTS?\n"); } else { LOGP(DTEMP, LOGL_NOTICE, "Switched off the PA due temperature.\n"); } } if (actions & TEMP_ACT_SLAVE_OFF) { if (!is_sbts2050()) { LOGP(DTEMP, LOGL_NOTICE, "Slave off only possible on the sysmoBTS2050\n"); } else if (sbts2050_uc_set_slave_power(0) != 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the slave BTS\n"); } else { LOGP(DTEMP, LOGL_NOTICE, "Switched off the slave due temperature\n"); } } if (actions & TEMP_ACT_BTS_SRV_OFF) { LOGP(DTEMP, LOGL_NOTICE, "Going to switch off the BTS service\n"); /* * TODO: use/create something like nspawn that serializes * and used SIGCHLD/waitpid to pick up the dead processes * without invoking shell. */ system("/bin/systemctl stop sysmobts.service"); } } /** * Go back to normal! Depending on the configuration execute the normal * actions that could (start to) undo everything we did in the other * states. What is still missing is the power increase/decrease depending * on the state. E.g. starting from WARNING_HYST we might want to slowly * ramp up the output power again. */ static void execute_normal_act(struct sysmobts_mgr_instance *manager) { LOGP(DTEMP, LOGL_NOTICE, "System is back to normal temperature.\n"); handle_normal_actions(manager->action_norm); } static void execute_warning_act(struct sysmobts_mgr_instance *manager) { LOGP(DTEMP, LOGL_NOTICE, "System has reached temperature warning.\n"); handle_actions(manager->action_warn); } static void execute_critical_act(struct sysmobts_mgr_instance *manager) { LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n"); handle_actions(manager->action_crit); } static void sysmobts_mgr_temp_handle(struct sysmobts_mgr_instance *manager, int critical, int warning) { int new_state = next_state(manager->state, critical, warning); /* Nothing changed */ if (new_state < 0) return; LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n", get_value_string(state_names, manager->state), get_value_string(state_names, new_state)); manager->state = new_state; switch (manager->state) { case STATE_NORMAL: execute_normal_act(manager); break; case STATE_WARNING_HYST: /* do nothing? Maybe start to increase transmit power? */ break; case STATE_WARNING: execute_warning_act(manager); break; case STATE_CRITICAL: execute_critical_act(manager); break; }; } static void temp_ctrl_check() { int rc; int warn_thresh_passed = 0; int crit_thresh_passed = 0; LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n"); /* Read the current digital temperature */ rc = sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, SYSMOBTS_TEMP_INPUT); if (rc < 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to read the digital temperature. rc=%d\n", rc); warn_thresh_passed = crit_thresh_passed = 1; } else { int temp = rc / 1000; if (temp > s_mgr->digital_limit.thresh_warn) warn_thresh_passed = 1; if (temp > s_mgr->digital_limit.thresh_crit) crit_thresh_passed = 1; LOGP(DTEMP, LOGL_DEBUG, "Digital temperature is: %d\n", temp); } /* Read the current RF temperature */ rc = sysmobts_temp_get(SYSMOBTS_TEMP_RF, SYSMOBTS_TEMP_INPUT); if (rc < 0) { LOGP(DTEMP, LOGL_ERROR, "Failed to read the RF temperature. rc=%d\n", rc); warn_thresh_passed = crit_thresh_passed = 1; } else { int temp = rc / 1000; if (temp > s_mgr->rf_limit.thresh_warn) warn_thresh_passed = 1; if (temp > s_mgr->rf_limit.thresh_crit) crit_thresh_passed = 1; LOGP(DTEMP, LOGL_DEBUG, "RF temperature is: %d\n", temp); } if (is_sbts2050()) { int temp_pa, temp_board; rc = sbts2050_uc_check_temp(&temp_pa, &temp_board); if (rc != 0) { /* XXX what do here? */ LOGP(DTEMP, LOGL_ERROR, "Failed to read the temperature! Reboot?!\n"); warn_thresh_passed = 1; crit_thresh_passed = 1; } else { LOGP(DTEMP, LOGL_DEBUG, "SBTS2050 board(%d) PA(%d)\n", temp_board, temp_pa); if (temp_pa > s_mgr->pa_limit.thresh_warn) warn_thresh_passed = 1; if (temp_pa > s_mgr->pa_limit.thresh_crit) crit_thresh_passed = 1; if (temp_board > s_mgr->board_limit.thresh_warn) warn_thresh_passed = 1; if (temp_board > s_mgr->board_limit.thresh_crit) crit_thresh_passed = 1; } } sysmobts_mgr_temp_handle(s_mgr, crit_thresh_passed, warn_thresh_passed); } static void temp_ctrl_check_cb(void *unused) { temp_ctrl_check(); /* Check every two minutes? XXX make it configurable! */ osmo_timer_schedule(&temp_ctrl_timer, 2 * 60, 0); } int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr) { s_mgr = mgr; temp_ctrl_timer.cb = temp_ctrl_check_cb; temp_ctrl_check_cb(NULL); return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c000066400000000000000000000342211260026426200235130ustar00rootroot00000000000000/* (C) 2014 by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * * Author: Alvaro Neira Ayuso * * 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 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 #include #include #include #include #include #include #include #include "sysmobts_misc.h" #include "sysmobts_mgr.h" #include "btsconfig.h" static struct sysmobts_mgr_instance *s_mgr; static const char copyright[] = "(C) 2012 by Harald Welte \r\n" "(C) 2014 by Holger Hans Peter Freyther\r\n" "License AGPLv3+: GNU AGPL version 2 or later \r\n" "This is free software: you are free to change and redistribute it.\r\n" "There is NO WARRANTY, to the extent permitted by law.\r\n"; static enum node_type go_to_parent(struct vty *vty) { switch (vty->node) { case MGR_NODE: vty->node = CONFIG_NODE; break; case ACT_NORM_NODE: case ACT_WARN_NODE: case ACT_CRIT_NODE: case LIMIT_RF_NODE: case LIMIT_DIGITAL_NODE: case LIMIT_BOARD_NODE: case LIMIT_PA_NODE: vty->node = MGR_NODE; break; default: vty->node = CONFIG_NODE; } return vty->node; } static int is_config_node(struct vty *vty, int node) { switch (node) { case MGR_NODE: case ACT_NORM_NODE: case ACT_WARN_NODE: case ACT_CRIT_NODE: case LIMIT_RF_NODE: case LIMIT_DIGITAL_NODE: case LIMIT_BOARD_NODE: case LIMIT_PA_NODE: return 1; default: return 0; } } static struct vty_app_info vty_info = { .name = "sysmobts-mgr", .version = PACKAGE_VERSION, .go_parent_cb = go_to_parent, .is_config_node = is_config_node, .copyright = copyright, }; #define MGR_STR "Configure sysmobts-mgr\n" static struct cmd_node mgr_node = { MGR_NODE, "%s(sysmobts-mgr)# ", 1, }; static struct cmd_node act_norm_node = { ACT_NORM_NODE, "%s(action-normal)# ", 1, }; static struct cmd_node act_warn_node = { ACT_WARN_NODE, "%s(action-warn)# ", 1, }; static struct cmd_node act_crit_node = { ACT_CRIT_NODE, "%s(action-critical)# ", 1, }; static struct cmd_node limit_rf_node = { LIMIT_RF_NODE, "%s(limit-rf)# ", 1, }; static struct cmd_node limit_digital_node = { LIMIT_DIGITAL_NODE, "%s(limit-digital)# ", 1, }; static struct cmd_node limit_board_node = { LIMIT_BOARD_NODE, "%s(limit-board)# ", 1, }; static struct cmd_node limit_pa_node = { LIMIT_PA_NODE, "%s(limit-pa)# ", 1, }; DEFUN(cfg_mgr, cfg_mgr_cmd, "sysmobts-mgr", MGR_STR) { vty->node = MGR_NODE; return CMD_SUCCESS; } static void write_temp_limit(struct vty *vty, const char *name, struct sysmobts_temp_limit *limit) { vty_out(vty, " %s%s", name, VTY_NEWLINE); vty_out(vty, " threshold warning %d%s", limit->thresh_warn, VTY_NEWLINE); vty_out(vty, " threshold critical %d%s", limit->thresh_crit, VTY_NEWLINE); } static void write_norm_action(struct vty *vty, const char *name, int actions) { vty_out(vty, " %s%s", name, VTY_NEWLINE); vty_out(vty, " %spa-on%s", (actions & TEMP_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE); vty_out(vty, " %sbts-service-on%s", (actions & TEMP_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); vty_out(vty, " %sslave-on%s", (actions & TEMP_ACT_NORM_SLAVE_ON) ? "" : "no ", VTY_NEWLINE); } static void write_action(struct vty *vty, const char *name, int actions) { vty_out(vty, " %s%s", name, VTY_NEWLINE); #if 0 vty_out(vty, " %spower-control%s", (actions & TEMP_ACT_PWR_CONTRL) ? "" : "no ", VTY_NEWLINE); /* only on the sysmobts 2050 */ vty_out(vty, " %smaster-off%s", (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE); vty_out(vty, " %sslave-off%s", (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE); #endif vty_out(vty, " %spa-off%s", (actions & TEMP_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE); vty_out(vty, " %sbts-service-off%s", (actions & TEMP_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); vty_out(vty, " %sslave-off%s", (actions & TEMP_ACT_SLAVE_OFF) ? "" : "no ", VTY_NEWLINE); } static int config_write_mgr(struct vty *vty) { vty_out(vty, "sysmobts-mgr%s", VTY_NEWLINE); write_temp_limit(vty, "limits rf", &s_mgr->rf_limit); write_temp_limit(vty, "limits digital", &s_mgr->digital_limit); write_temp_limit(vty, "limits board", &s_mgr->board_limit); write_temp_limit(vty, "limits pa", &s_mgr->pa_limit); write_norm_action(vty, "actions normal", s_mgr->action_norm); write_action(vty, "actions warn", s_mgr->action_warn); write_action(vty, "actions critical", s_mgr->action_crit); return CMD_SUCCESS; } static int config_write_dummy(struct vty *vty) { return CMD_SUCCESS; } #define CFG_LIMIT(name, expl, switch_to, variable) \ DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ "limits " #name, \ "Configure Limits\n" expl) \ { \ vty->node = switch_to; \ vty->index = &s_mgr->variable; \ return CMD_SUCCESS; \ } CFG_LIMIT(rf, "RF\n", LIMIT_RF_NODE, rf_limit) CFG_LIMIT(digital, "Digital\n", LIMIT_DIGITAL_NODE, digital_limit) CFG_LIMIT(board, "Board\n", LIMIT_BOARD_NODE, board_limit) CFG_LIMIT(pa, "Power Amplifier\n", LIMIT_PA_NODE, pa_limit) #undef CFG_LIMIT DEFUN(cfg_limit_warning, cfg_thresh_warning_cmd, "threshold warning <0-200>", "Threshold to reach\n" "Warning level\n" "Range\n") { struct sysmobts_temp_limit *limit = vty->index; limit->thresh_warn = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(cfg_limit_crit, cfg_thresh_crit_cmd, "threshold critical <0-200>", "Threshold to reach\n" "Severe level\n" "Range\n") { struct sysmobts_temp_limit *limit = vty->index; limit->thresh_crit = atoi(argv[0]); return CMD_SUCCESS; } #define CFG_ACTION(name, expl, switch_to, variable) \ DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \ "actions " #name, \ "Configure Actions\n" expl) \ { \ vty->node = switch_to; \ vty->index = &s_mgr->variable; \ return CMD_SUCCESS; \ } CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm) CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn) CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit) #undef CFG_ACTION DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd, "pa-on", "Switch the Power Amplifier on\n") { int *action = vty->index; *action |= TEMP_ACT_NORM_PA_ON; return CMD_SUCCESS; } DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd, "no pa-on", NO_STR "Switch the Power Amplifier on\n") { int *action = vty->index; *action &= ~TEMP_ACT_NORM_PA_ON; return CMD_SUCCESS; } DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd, "bts-service-on", "Start the systemd sysmobts.service\n") { int *action = vty->index; *action |= TEMP_ACT_NORM_BTS_SRV_ON; return CMD_SUCCESS; } DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd, "no bts-service-on", NO_STR "Start the systemd sysmobts.service\n") { int *action = vty->index; *action &= ~TEMP_ACT_NORM_BTS_SRV_ON; return CMD_SUCCESS; } DEFUN(cfg_action_slave_on, cfg_action_slave_on_cmd, "slave-on", "Power-on secondary device on sysmoBTS2050\n") { int *action = vty->index; *action |= TEMP_ACT_NORM_SLAVE_ON; return CMD_SUCCESS; } DEFUN(cfg_no_action_slave_on, cfg_no_action_slave_on_cmd, "no slave-on", NO_STR "Power-on secondary device on sysmoBTS2050\n") { int *action = vty->index; *action &= ~TEMP_ACT_NORM_SLAVE_ON; return CMD_SUCCESS; } DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd, "pa-off", "Switch the Power Amplifier off\n") { int *action = vty->index; *action |= TEMP_ACT_PA_OFF; return CMD_SUCCESS; } DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd, "no pa-off", NO_STR "Do not switch off the Power Amplifier\n") { int *action = vty->index; *action &= ~TEMP_ACT_PA_OFF; return CMD_SUCCESS; } DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd, "bts-service-off", "Stop the systemd sysmobts.service\n") { int *action = vty->index; *action |= TEMP_ACT_BTS_SRV_OFF; return CMD_SUCCESS; } DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd, "no bts-service-off", NO_STR "Stop the systemd sysmobts.service\n") { int *action = vty->index; *action &= ~TEMP_ACT_BTS_SRV_OFF; return CMD_SUCCESS; } DEFUN(cfg_action_slave_off, cfg_action_slave_off_cmd, "slave-off", "Power-off secondary device on sysmoBTS2050\n") { int *action = vty->index; *action |= TEMP_ACT_SLAVE_OFF; return CMD_SUCCESS; } DEFUN(cfg_no_action_slave_off, cfg_no_action_slave_off_cmd, "no slave-off", NO_STR "Power-off secondary device on sysmoBTS2050\n") { int *action = vty->index; *action &= ~TEMP_ACT_SLAVE_OFF; return CMD_SUCCESS; } DEFUN(show_mgr, show_mgr_cmd, "show manager", SHOW_STR "Display information about the manager") { vty_out(vty, "BTS Control Interface: %s%s", s_mgr->calib.is_up ? "connected" : "disconnected", VTY_NEWLINE); vty_out(vty, "Temperature control state: %s%s", sysmobts_mgr_temp_get_state(s_mgr->state), VTY_NEWLINE); vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); vty_out(vty, " Digital: %f Celcius%s", sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, SYSMOBTS_TEMP_INPUT) / 1000.0f, VTY_NEWLINE); vty_out(vty, " RF: %f Celcius%s", sysmobts_temp_get(SYSMOBTS_TEMP_RF, SYSMOBTS_TEMP_INPUT) / 1000.0f, VTY_NEWLINE); if (is_sbts2050()) { int temp_pa, temp_board; struct sbts2050_power_status status; vty_out(vty, " sysmoBTS 2050 is %s%s", is_sbts2050_master() ? "master" : "slave", VTY_NEWLINE); sbts2050_uc_check_temp(&temp_pa, &temp_board); vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_pa, VTY_NEWLINE); vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_board, VTY_NEWLINE); sbts2050_uc_get_status(&status); vty_out(vty, "Power Status%s", VTY_NEWLINE); vty_out(vty, " Main Supply :(ON) [(24.00)Vdc, %4.2f A]%s", status.main_supply_current, VTY_NEWLINE); vty_out(vty, " Master SF : %s [%6.2f Vdc, %4.2f A]%s", status.master_enabled ? "ON " : "OFF", status.master_voltage, status.master_current, VTY_NEWLINE); vty_out(vty, " Slave SF : %s [%6.2f Vdc, %4.2f A]%s", status.slave_enabled ? "ON" : "OFF", status.slave_voltage, status.slave_current, VTY_NEWLINE); vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A]%s", status.pa_enabled ? "ON" : "OFF", status.pa_voltage, status.pa_current, VTY_NEWLINE); vty_out(vty, " PA Bias : %s [%6.2f Vdc, ---- A]%s", status.pa_enabled ? "ON" : "OFF", status.pa_bias_voltage, VTY_NEWLINE); } return CMD_SUCCESS; } DEFUN(calibrate_trx, calibrate_trx_cmd, "trx 0 calibrate-clock", "Transceiver commands\n" "Transceiver 0\n" "Calibrate clock against GPS PPS\n") { if (sysmobts_mgr_calib_run(s_mgr) < 0) { vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); return CMD_WARNING; } return CMD_SUCCESS; } static void register_limit(int limit) { install_element(limit, &cfg_thresh_warning_cmd); install_element(limit, &cfg_thresh_crit_cmd); } static void register_normal_action(int act) { install_element(act, &cfg_action_pa_on_cmd); install_element(act, &cfg_no_action_pa_on_cmd); install_element(act, &cfg_action_bts_srv_on_cmd); install_element(act, &cfg_no_action_bts_srv_on_cmd); /* these only work on the sysmobts 2050 */ install_element(act, &cfg_action_slave_on_cmd); install_element(act, &cfg_no_action_slave_on_cmd); } static void register_action(int act) { #if 0 install_element(act, &cfg_action_pwr_contrl_cmd); install_element(act, &cfg_no_action_pwr_contrl_cmd); #endif install_element(act, &cfg_action_pa_off_cmd); install_element(act, &cfg_no_action_pa_off_cmd); install_element(act, &cfg_action_bts_srv_off_cmd); install_element(act, &cfg_no_action_bts_srv_off_cmd); /* these only work on the sysmobts 2050 */ install_element(act, &cfg_action_slave_off_cmd); install_element(act, &cfg_no_action_slave_off_cmd); } int sysmobts_mgr_vty_init(void) { vty_init(&vty_info); install_element_ve(&show_mgr_cmd); install_element(ENABLE_NODE, &calibrate_trx_cmd); install_node(&mgr_node, config_write_mgr); install_element(CONFIG_NODE, &cfg_mgr_cmd); vty_install_default(MGR_NODE); /* install the limit nodes */ install_node(&limit_rf_node, config_write_dummy); install_element(MGR_NODE, &cfg_limit_rf_cmd); register_limit(LIMIT_RF_NODE); vty_install_default(LIMIT_RF_NODE); install_node(&limit_digital_node, config_write_dummy); install_element(MGR_NODE, &cfg_limit_digital_cmd); register_limit(LIMIT_DIGITAL_NODE); vty_install_default(LIMIT_DIGITAL_NODE); install_node(&limit_board_node, config_write_dummy); install_element(MGR_NODE, &cfg_limit_board_cmd); register_limit(LIMIT_BOARD_NODE); vty_install_default(LIMIT_BOARD_NODE); install_node(&limit_pa_node, config_write_dummy); install_element(MGR_NODE, &cfg_limit_pa_cmd); register_limit(LIMIT_PA_NODE); vty_install_default(LIMIT_PA_NODE); /* install the normal node */ install_node(&act_norm_node, config_write_dummy); install_element(MGR_NODE, &cfg_action_normal_cmd); register_normal_action(ACT_NORM_NODE); /* install the warning and critical node */ install_node(&act_warn_node, config_write_dummy); install_element(MGR_NODE, &cfg_action_warn_cmd); register_action(ACT_WARN_NODE); vty_install_default(ACT_WARN_NODE); install_node(&act_crit_node, config_write_dummy); install_element(MGR_NODE, &cfg_action_critical_cmd); register_action(ACT_CRIT_NODE); vty_install_default(ACT_CRIT_NODE); return 0; } int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *manager) { int rc; s_mgr = manager; rc = vty_read_config_file(s_mgr->config_file, NULL); if (rc < 0) { fprintf(stderr, "Failed to parse the config file: '%s'\n", s_mgr->config_file); return rc; } return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_misc.c000066400000000000000000000151371260026426200227640ustar00rootroot00000000000000/* (C) 2012 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "btsconfig.h" #include "sysmobts_misc.h" #include "sysmobts_par.h" #include "sysmobts_mgr.h" /********************************************************************* * Temperature handling *********************************************************************/ #define TEMP_PATH "/sys/class/hwmon/hwmon0/device/temp%u_%s" static const char *temp_type_str[_NUM_TEMP_TYPES] = { [SYSMOBTS_TEMP_INPUT] = "input", [SYSMOBTS_TEMP_LOWEST] = "lowest", [SYSMOBTS_TEMP_HIGHEST] = "highest", }; int sysmobts_temp_get(enum sysmobts_temp_sensor sensor, enum sysmobts_temp_type type) { char buf[PATH_MAX]; char tempstr[8]; int fd, rc; if (sensor < SYSMOBTS_TEMP_DIGITAL || sensor > SYSMOBTS_TEMP_RF) return -EINVAL; if (type >= ARRAY_SIZE(temp_type_str)) return -EINVAL; snprintf(buf, sizeof(buf)-1, TEMP_PATH, sensor, temp_type_str[type]); buf[sizeof(buf)-1] = '\0'; fd = open(buf, O_RDONLY); if (fd < 0) return fd; rc = read(fd, tempstr, sizeof(tempstr)); tempstr[sizeof(tempstr)-1] = '\0'; if (rc < 0) { close(fd); return rc; } if (rc == 0) { close(fd); return -EIO; } close(fd); return atoi(tempstr); } static const struct { const char *name; enum sysmobts_temp_sensor sensor; enum sysmobts_par ee_par; } temp_data[] = { { .name = "digital", .sensor = SYSMOBTS_TEMP_DIGITAL, .ee_par = SYSMOBTS_PAR_TEMP_DIG_MAX, }, { .name = "rf", .sensor = SYSMOBTS_TEMP_RF, .ee_par = SYSMOBTS_PAR_TEMP_RF_MAX, } }; void sysmobts_check_temp(int no_eeprom_write) { int temp_old[ARRAY_SIZE(temp_data)]; int temp_hi[ARRAY_SIZE(temp_data)]; int temp_cur[ARRAY_SIZE(temp_data)]; int i, rc; for (i = 0; i < ARRAY_SIZE(temp_data); i++) { int ret; rc = sysmobts_par_get_int(temp_data[i].ee_par, &ret); temp_old[i] = ret * 1000; temp_hi[i] = sysmobts_temp_get(temp_data[i].sensor, SYSMOBTS_TEMP_HIGHEST); temp_cur[i] = sysmobts_temp_get(temp_data[i].sensor, SYSMOBTS_TEMP_INPUT); if ((temp_cur[i] < 0 && temp_cur[i] > -1000) || (temp_hi[i] < 0 && temp_hi[i] > -1000)) { LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n"); return; } LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n", temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000); if (temp_hi[i] > temp_old[i]) { LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " "temperature: %d.%d C\n", temp_data[i].name, temp_hi[i]/1000, temp_hi[i]%1000); if (!no_eeprom_write) { rc = sysmobts_par_set_int(SYSMOBTS_PAR_TEMP_DIG_MAX, temp_hi[0]/1000); if (rc < 0) LOGP(DTEMP, LOGL_ERROR, "error writing new %s " "max temp %d (%s)\n", temp_data[i].name, rc, strerror(errno)); } } } } /********************************************************************* * Hours handling *********************************************************************/ static time_t last_update; int sysmobts_update_hours(int no_eeprom_write) { time_t now = time(NULL); int rc, op_hrs; /* first time after start of manager program */ if (last_update == 0) { last_update = now; rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs); if (rc < 0) { LOGP(DTEMP, LOGL_ERROR, "Unable to read " "operational hours: %d (%s)\n", rc, strerror(errno)); return rc; } LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", op_hrs); return 0; } if (now >= last_update + 3600) { rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs); if (rc < 0) { LOGP(DTEMP, LOGL_ERROR, "Unable to read " "operational hours: %d (%s)\n", rc, strerror(errno)); return rc; } /* number of hours to increase */ op_hrs += (now-last_update)/3600; LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", op_hrs); if (!no_eeprom_write) { rc = sysmobts_par_set_int(SYSMOBTS_PAR_HOURS, op_hrs); if (rc < 0) return rc; } last_update = now; } return 0; } /********************************************************************* * Firmware reloading *********************************************************************/ #define SYSMOBTS_FW_PATH "/lib/firmware" static const char *fw_names[_NUM_FW] = { [SYSMOBTS_FW_FPGA] = "sysmobts-v2.bit", [SYSMOBTS_FW_DSP] = "sysmobts-v2.out", }; static const char *fw_devs[_NUM_FW] = { [SYSMOBTS_FW_FPGA] = "/dev/fpgadl_par0", [SYSMOBTS_FW_DSP] = "/dev/dspdl_dm644x_0", }; int sysmobts_firmware_reload(enum sysmobts_firmware_type type) { char name[PATH_MAX]; uint8_t buf[1024]; int fd_in, fd_out, rc; if (type >= _NUM_FW) return -EINVAL; snprintf(name, sizeof(name)-1, "%s/%s", SYSMOBTS_FW_PATH, fw_names[type]); name[sizeof(name)-1] = '\0'; fd_in = open(name, O_RDONLY); if (fd_in < 0) { LOGP(DFW, LOGL_ERROR, "unable ot open firmware file %s: %s\n", name, strerror(errno)); return fd_in; } fd_out = open(fw_devs[type], O_WRONLY); if (fd_out < 0) { LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", fw_devs[type], strerror(errno)); close(fd_in); return fd_out; } while ((rc = read(fd_in, buf, sizeof(buf)))) { int written; if (rc < 0) { LOGP(DFW, LOGL_ERROR, "error %d during read " "from %s: %s\n", rc, name, strerror(errno)); close(fd_in); close(fd_out); return -EIO; } written = write(fd_out, buf, rc); if (written < rc) { LOGP(DFW, LOGL_ERROR, "short write during " "fw write to %s\n", fw_devs[type]); close(fd_in); close(fd_out); return -EIO; } } close(fd_in); close(fd_out); return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_misc.h000066400000000000000000000024631260026426200227670ustar00rootroot00000000000000#ifndef _SYSMOBTS_MISC_H #define _SYSMOBTS_MISC_H #include enum sysmobts_temp_sensor { SYSMOBTS_TEMP_DIGITAL = 1, SYSMOBTS_TEMP_RF = 2, }; enum sysmobts_temp_type { SYSMOBTS_TEMP_INPUT, SYSMOBTS_TEMP_LOWEST, SYSMOBTS_TEMP_HIGHEST, _NUM_TEMP_TYPES }; int sysmobts_temp_get(enum sysmobts_temp_sensor sensor, enum sysmobts_temp_type type); void sysmobts_check_temp(int no_eeprom_write); int sysmobts_update_hours(int no_epprom_write); enum sysmobts_firmware_type { SYSMOBTS_FW_FPGA, SYSMOBTS_FW_DSP, _NUM_FW }; int sysmobts_firmware_reload(enum sysmobts_firmware_type type); int sysmobts_bts_type(); int sysmobts_trx_number(); int is_sbts2050(void); int is_sbts2050_trx(int); int is_sbts2050_master(void); struct sbts2050_power_status { float main_supply_current; int master_enabled; float master_voltage; float master_current; int slave_enabled; float slave_voltage; float slave_current; int pa_enabled; float pa_voltage; float pa_current; float pa_bias_voltage; }; int sbts2050_uc_check_temp(int *temp_pa, int *temp_board); int sbts2050_uc_set_power(int pmaster, int pslave, int ppa); int sbts2050_uc_get_status(struct sbts2050_power_status *status); int sbts2050_uc_set_pa_power(int on_off); int sbts2050_uc_set_slave_power(int on_off); void sbts2050_uc_initialize(); #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_nl.c000066400000000000000000000057111260026426200224370ustar00rootroot00000000000000/* Helper for netlink */ /* * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #define NLMSG_TAIL(nmsg) \ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) /** * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source * address will be used when sending a message this function can be used. * It will ask the routing code of the kernel for the PREFSRC */ int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source) { int fd, rc; struct rtmsg *r; struct rtattr *rta; struct { struct nlmsghdr n; struct rtmsg r; char buf[1024]; } req; memset(&req, 0, sizeof(req)); fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); if (fd < 0) { perror("nl socket"); return -1; } /* Send a rtmsg and ask for a response */ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.n.nlmsg_type = RTM_GETROUTE; req.n.nlmsg_seq = 1; /* Prepare the routing request */ req.r.rtm_family = AF_INET; /* set the dest */ rta = NLMSG_TAIL(&req.n); rta->rta_type = RTA_DST; rta->rta_len = RTA_LENGTH(sizeof(*dest)); memcpy(RTA_DATA(rta), dest, sizeof(*dest)); /* update sizes for dest */ req.r.rtm_dst_len = sizeof(*dest) * 8; req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len); rc = send(fd, &req, req.n.nlmsg_len, 0); if (rc != req.n.nlmsg_len) { perror("short write"); close(fd); return -2; } /* now receive a response and parse it */ rc = recv(fd, &req, sizeof(req), 0); if (rc <= 0) { perror("short read"); close(fd); return -3; } if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) { close(fd); return -4; } r = NLMSG_DATA(&req.n); rc -= NLMSG_LENGTH(sizeof(*r)); rta = RTM_RTA(r); while (RTA_OK(rta, rc)) { if (rta->rta_type != RTA_PREFSRC) { rta = RTA_NEXT(rta, rc); continue; } /* we are done */ memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta)); close(fd); return 0; } close(fd); return -5; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_nl.h000066400000000000000000000015211260026426200224370ustar00rootroot00000000000000/* * (C) 2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 . * */ #pragma once struct in_addr; int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_par.c000066400000000000000000000132641260026426200226120ustar00rootroot00000000000000/* sysmobts - access to hardware related parameters */ /* (C) 2012 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include "sysmobts_eeprom.h" #include "sysmobts_par.h" #include "eeprom.h" #define EEPROM_PATH "/sys/devices/platform/i2c_davinci.1/i2c-1/1-0050/eeprom" const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1] = { { SYSMOBTS_PAR_MAC, "ethaddr" }, { SYSMOBTS_PAR_CLK_FACTORY, "clk-factory" }, { SYSMOBTS_PAR_TEMP_DIG_MAX, "temp-dig-max" }, { SYSMOBTS_PAR_TEMP_RF_MAX, "temp-rf-max" }, { SYSMOBTS_PAR_SERNR, "serial-nr" }, { SYSMOBTS_PAR_HOURS, "hours-running" }, { SYSMOBTS_PAR_BOOTS, "boot-count" }, { SYSMOBTS_PAR_KEY, "key" }, { SYSMOBTS_PAR_MODEL_NR, "model-nr" }, { SYSMOBTS_PAR_MODEL_FLAGS, "model-flags" }, { SYSMOBTS_PAR_TRX_NR, "trx-nr" }, { 0, NULL } }; static struct { int read; struct sysmobts_eeprom ee; } g_ee; static struct sysmobts_eeprom *get_eeprom(int update_rqd) { if (update_rqd || g_ee.read == 0) { int fd, rc; fd = open(EEPROM_PATH, O_RDONLY); if (fd < 0) return NULL; rc = read(fd, &g_ee.ee, sizeof(g_ee.ee)); close(fd); if (rc < sizeof(g_ee.ee)) return NULL; g_ee.read = 1; } return &g_ee.ee; } static int set_eeprom(struct sysmobts_eeprom *ee) { int fd, rc; memcpy(&g_ee.ee, ee, sizeof(*ee)); fd = open(EEPROM_PATH, O_WRONLY); if (fd < 0) return fd; rc = write(fd, ee, sizeof(*ee)); if (rc < sizeof(*ee)) { close(fd); return -EIO; } close(fd); return 0; } int sysmobts_par_is_int(enum sysmobts_par par) { switch (par) { case SYSMOBTS_PAR_CLK_FACTORY: case SYSMOBTS_PAR_TEMP_DIG_MAX: case SYSMOBTS_PAR_TEMP_RF_MAX: case SYSMOBTS_PAR_SERNR: case SYSMOBTS_PAR_HOURS: case SYSMOBTS_PAR_BOOTS: case SYSMOBTS_PAR_MODEL_NR: case SYSMOBTS_PAR_MODEL_FLAGS: case SYSMOBTS_PAR_TRX_NR: return 1; default: return 0; } } int sysmobts_par_get_int(enum sysmobts_par par, int *ret) { eeprom_RfClockCal_t rf_clk; eeprom_Error_t err; struct sysmobts_eeprom *ee = get_eeprom(0); if (!ee) return -EIO; if (par >= _NUM_SYSMOBTS_PAR) return -ENODEV; switch (par) { case SYSMOBTS_PAR_CLK_FACTORY: err = eeprom_ReadRfClockCal(&rf_clk); if (err != EEPROM_SUCCESS) return -EIO; *ret = rf_clk.iClkCor; break; case SYSMOBTS_PAR_TEMP_DIG_MAX: *ret = ee->temp1_max; break; case SYSMOBTS_PAR_TEMP_RF_MAX: *ret = ee->temp2_max; break; case SYSMOBTS_PAR_SERNR: *ret = ee->serial_nr; break; case SYSMOBTS_PAR_HOURS: *ret = ee->operational_hours; break; case SYSMOBTS_PAR_BOOTS: *ret = ee->boot_count; break; case SYSMOBTS_PAR_MODEL_NR: *ret = ee->model_nr; break; case SYSMOBTS_PAR_MODEL_FLAGS: *ret = ee->model_flags; break; case SYSMOBTS_PAR_TRX_NR: *ret = ee->trx_nr; break; default: return -EINVAL; } return 0; } int sysmobts_par_set_int(enum sysmobts_par par, int val) { eeprom_RfClockCal_t rf_clk; eeprom_Error_t err; struct sysmobts_eeprom *ee = get_eeprom(1); if (!ee) return -EIO; if (par >= _NUM_SYSMOBTS_PAR) return -ENODEV; switch (par) { case SYSMOBTS_PAR_CLK_FACTORY: err = eeprom_ReadRfClockCal(&rf_clk); if (err != EEPROM_SUCCESS) return -EIO; rf_clk.iClkCor = val; err = eeprom_WriteRfClockCal(&rf_clk); if (err != EEPROM_SUCCESS) return -EIO; break; case SYSMOBTS_PAR_TEMP_DIG_MAX: ee->temp1_max = val; break; case SYSMOBTS_PAR_TEMP_RF_MAX: ee->temp2_max = val; break; case SYSMOBTS_PAR_SERNR: ee->serial_nr = val; break; case SYSMOBTS_PAR_HOURS: ee->operational_hours = val; break; case SYSMOBTS_PAR_BOOTS: ee->boot_count = val; break; case SYSMOBTS_PAR_MODEL_NR: ee->model_nr = val; break; case SYSMOBTS_PAR_MODEL_FLAGS: ee->model_flags = val; break; case SYSMOBTS_PAR_TRX_NR: ee->trx_nr = val; break; default: return -EINVAL; } set_eeprom(ee); return 0; } int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf, unsigned int size) { uint8_t *ptr; unsigned int len; struct sysmobts_eeprom *ee = get_eeprom(0); if (!ee) return -EIO; if (par >= _NUM_SYSMOBTS_PAR) return -ENODEV; switch (par) { case SYSMOBTS_PAR_MAC: ptr = ee->eth_mac; len = sizeof(ee->eth_mac); break; case SYSMOBTS_PAR_KEY: ptr = ee->gpg_key; len = sizeof(ee->gpg_key); break; default: return -EINVAL; } if (size < len) len = size; memcpy(buf, ptr, len); return len; } int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf, unsigned int size) { uint8_t *ptr; unsigned int len; struct sysmobts_eeprom *ee = get_eeprom(0); if (!ee) return -EIO; if (par >= _NUM_SYSMOBTS_PAR) return -ENODEV; switch (par) { case SYSMOBTS_PAR_MAC: ptr = ee->eth_mac; len = sizeof(ee->eth_mac); break; case SYSMOBTS_PAR_KEY: ptr = ee->gpg_key; len = sizeof(ee->gpg_key); break; default: return -EINVAL; } if (len < size) size = len; memcpy(ptr, buf, size); return len; } osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_par.h000066400000000000000000000014501260026426200226110ustar00rootroot00000000000000#ifndef _SYSMOBTS_PAR_H #define _SYSMOBTS_PAR_H #include enum sysmobts_par { SYSMOBTS_PAR_MAC, SYSMOBTS_PAR_CLK_FACTORY, SYSMOBTS_PAR_TEMP_DIG_MAX, SYSMOBTS_PAR_TEMP_RF_MAX, SYSMOBTS_PAR_SERNR, SYSMOBTS_PAR_HOURS, SYSMOBTS_PAR_BOOTS, SYSMOBTS_PAR_KEY, SYSMOBTS_PAR_MODEL_NR, SYSMOBTS_PAR_MODEL_FLAGS, SYSMOBTS_PAR_TRX_NR, _NUM_SYSMOBTS_PAR }; extern const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1]; int sysmobts_par_get_int(enum sysmobts_par par, int *ret); int sysmobts_par_set_int(enum sysmobts_par par, int val); int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf, unsigned int size); int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf, unsigned int size); int sysmobts_par_is_int(enum sysmobts_par par); #endif osmo-bts-0.4.0/src/osmo-bts-sysmo/misc/sysmobts_util.c000066400000000000000000000062651260026426200230100ustar00rootroot00000000000000/* sysmobts-util - access to hardware related parameters */ /* (C) 2012-2013 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include "sysmobts_par.h" enum act { ACT_GET, ACT_SET, }; static enum act action; static char *write_arg; static int void_warranty; static void print_help() { const struct value_string *par = sysmobts_par_names; printf("sysmobts-util [--void-warranty -r | -w value] param_name\n"); printf("Possible param names:\n"); for (; par->str != NULL; par += 1) { if (!sysmobts_par_is_int(par->value)) continue; printf(" %s\n", par->str); } } static int parse_options(int argc, char **argv) { while (1) { int option_idx = 0, c; static const struct option long_options[] = { { "help", 0, 0, 'h' }, { "read", 0, 0, 'r' }, { "void-warranty", 0, 0, 1000}, { "write", 1, 0, 'w' }, { 0, 0, 0, 0 } }; c = getopt_long(argc, argv, "rw:h", long_options, &option_idx); if (c == -1) break; switch (c) { case 'r': action = ACT_GET; break; case 'w': action = ACT_SET; write_arg = optarg; break; case 'h': print_help(); return -1; break; case 1000: printf("Will void warranty on write.\n"); void_warranty = 1; break; default: return -1; } } return 0; } int main(int argc, char **argv) { const char *parname; enum sysmobts_par par; int rc, val; rc = parse_options(argc, argv); if (rc < 0) exit(2); if (optind >= argc) { fprintf(stderr, "You must specify the parameter name\n"); exit(2); } parname = argv[optind]; rc = get_string_value(sysmobts_par_names, parname); if (rc < 0) { fprintf(stderr, "`%s' is not a valid parameter\n", parname); exit(2); } else par = rc; switch (action) { case ACT_GET: rc = sysmobts_par_get_int(par, &val); if (rc < 0) { fprintf(stderr, "Error %d\n", rc); goto err; } printf("%d\n", val); break; case ACT_SET: rc = sysmobts_par_get_int(par, &val); if (rc < 0) { fprintf(stderr, "Error %d\n", rc); goto err; } if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) { fprintf(stderr, "Parameter is already set!\r\n"); goto err; } rc = sysmobts_par_set_int(par, atoi(write_arg)); if (rc < 0) { fprintf(stderr, "Error %d\n", rc); goto err; } printf("Success setting %s=%d\n", parname, atoi(write_arg)); break; default: fprintf(stderr, "Unsupported action\n"); goto err; } exit(0); err: exit(1); } osmo-bts-0.4.0/src/osmo-bts-sysmo/oml.c000066400000000000000000001403131260026426200177150ustar00rootroot00000000000000/* (C) 2011 by Harald Welte * (C) 2013-2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include "l1_if.h" #include "femtobts.h" #include "utils.h" static int mph_info_chan_confirm(struct gsm_lchan *lchan, enum osmo_mph_info_type type, uint8_t cause) { struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, NULL); l1sap.u.info.type = type; l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan); l1sap.u.info.u.act_cnf.cause = cause; return l1sap_up(lchan->ts->trx, &l1sap); } enum sapi_cmd_type { SAPI_CMD_ACTIVATE, SAPI_CMD_CONFIG_CIPHERING, SAPI_CMD_CONFIG_LOGCH_PARAM, SAPI_CMD_SACCH_REL_MARKER, SAPI_CMD_REL_MARKER, SAPI_CMD_DEACTIVATE, }; struct sapi_cmd { struct llist_head entry; GsmL1_Sapi_t sapi; GsmL1_Dir_t dir; enum sapi_cmd_type type; int (*callback)(struct gsm_lchan *lchan, int status); }; static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { [GSM_PCHAN_NONE] = GsmL1_LogChComb_0, [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV, [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V, [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V, [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I, [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II, [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII, [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII, [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, //[GSM_PCHAN_TCH_F_PDCH] = FIXME, [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, }; static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct femtol1_hdl *gl1) { prim->id = id; /* for some reason the hLayer1 field is not always at the same position * in the GsmL1_Prim_t, so we have to have this ugly case statement here... */ switch (id) { case GsmL1_PrimId_MphInitReq: //prim->u.mphInitReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphCloseReq: prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphConnectReq: prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphDisconnectReq: prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphActivateReq: prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphDeactivateReq: prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphConfigReq: prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphMeasureReq: prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_MphInitCnf: case GsmL1_PrimId_MphCloseCnf: case GsmL1_PrimId_MphConnectCnf: case GsmL1_PrimId_MphDisconnectCnf: case GsmL1_PrimId_MphActivateCnf: case GsmL1_PrimId_MphDeactivateCnf: case GsmL1_PrimId_MphConfigCnf: case GsmL1_PrimId_MphMeasureCnf: break; case GsmL1_PrimId_MphTimeInd: break; case GsmL1_PrimId_MphSyncInd: break; case GsmL1_PrimId_PhEmptyFrameReq: prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_PhDataReq: prim->u.phDataReq.hLayer1 = gl1->hLayer1; break; case GsmL1_PrimId_PhConnectInd: break; case GsmL1_PrimId_PhReadyToSendInd: break; case GsmL1_PrimId_PhDataInd: break; case GsmL1_PrimId_PhRaInd: break; default: LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id); break; } return &prim->u; } GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) { /* for some reason the Status field is not always at the same position * in the GsmL1_Prim_t, so we have to have this ugly case statement here... */ switch (prim->id) { case GsmL1_PrimId_MphInitCnf: return prim->u.mphInitCnf.status; case GsmL1_PrimId_MphCloseCnf: return prim->u.mphCloseCnf.status; case GsmL1_PrimId_MphConnectCnf: return prim->u.mphConnectCnf.status; case GsmL1_PrimId_MphDisconnectCnf: return prim->u.mphDisconnectCnf.status; case GsmL1_PrimId_MphActivateCnf: return prim->u.mphActivateCnf.status; case GsmL1_PrimId_MphDeactivateCnf: return prim->u.mphDeactivateCnf.status; case GsmL1_PrimId_MphConfigCnf: return prim->u.mphConfigCnf.status; case GsmL1_PrimId_MphMeasureCnf: return prim->u.mphMeasureCnf.status; default: break; } return GsmL1_Status_Success; } #if 0 static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data) { struct msgb *resp_msg = data; GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); if (prim_status(l1p) != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", get_value_string(femtobts_l1prim_names, l1p->id), get_value_string(femtobts_l1status_names, cc->status)); return 0; } msgb_free(l1_msg); return abis_nm_sendmsg(msg); } #endif int lchan_activate(struct gsm_lchan *lchan); static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_Status_t status = prim_status(l1p); if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", get_value_string(femtobts_l1prim_names, l1p->id), get_value_string(femtobts_l1status_names, status)); msgb_free(l1_msg); return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); } msgb_free(l1_msg); /* Set to Operational State: Enabled */ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && mo->obj_inst.ts_nr == 0) { struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); mo->bts->c0->ts[0].lchan[4].rel_act_kind = LCHAN_REL_ACT_OML; lchan_activate(&mo->bts->c0->ts[0].lchan[4]); if (cbch) { cbch->rel_act_kind = LCHAN_REL_ACT_OML; lchan_activate(cbch); } } /* Send OPSTART ack */ return oml_mo_opstart_ack(mo); } static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { struct gsm_abis_mo *mo; GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; mo = &trx->ts[cnf->u8Tn].mo; return opstart_compl(mo, l1_msg); } #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; status = sysp->u.muteRfCnf.status; if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n", get_value_string(femtobts_l1status_names, status)); bts_shutdown(trx->bts, "RF-MUTE failure"); } msgb_free(resp); return 0; } #endif static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf; LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n", get_value_string(femtobts_l1status_names, ic->status)); /* store layer1 handle */ if (ic->status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n", get_value_string(femtobts_l1status_names, ic->status)); bts_shutdown(trx->bts, "MPH-INIT failure"); } fl1h->hLayer1 = ic->hLayer1; #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) /* If the TRX was already locked the MphInit would have undone it */ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) trx_rf_lock(trx, 1, trx_mute_on_init_cb); #endif /* Begin to ramp up the power */ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); return opstart_compl(&trx->mo, l1_msg); } int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids, unsigned int num_attr_ids) { unsigned int i; if (!mo->nm_attr) return 0; for (i = 0; i < num_attr_ids; i++) { if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i])) return 0; } return 1; } static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; /* initialize the layer1 */ static int trx_init(struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct gsm_bts_role_bts *btsb = bts_role_bts(trx->bts); struct msgb *msg; GsmL1_MphInitReq_t *mi_req; GsmL1_DeviceParam_t *dev_par; int femto_band; if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, ARRAY_SIZE(trx_rqd_attr))) { /* HACK: spec says we need to decline, but openbsc * doesn't deal with this very well */ return oml_mo_opstart_ack(&trx->mo); //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); } femto_band = sysmobts_select_femto_band(trx, trx->arfcn); if (femto_band < 0) { LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n", gsm_band_name(trx->bts->band)); } msg = l1p_msgb_alloc(); mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h); dev_par = &mi_req->deviceParam; dev_par->devType = GsmL1_DevType_TxdRxu; dev_par->freqBand = femto_band; dev_par->u16Arfcn = trx->arfcn; dev_par->u16BcchArfcn = trx->bts->c0->arfcn; dev_par->u8NbTsc = trx->bts->bsic & 7; dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) ? 0.0 : btsb->ul_power_target; dev_par->fTxPowerLevel = 0.0; LOGP(DL1C, LOGL_NOTICE, "Init TRX (ARFCN %u, TSC %u, RxPower % 2f dBm, " "TxPower % 2.2f dBm\n", dev_par->u16Arfcn, dev_par->u8NbTsc, dev_par->fRxPowerLevel, dev_par->fTxPowerLevel); /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */ return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); } uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); return fl1h->hLayer1; } static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { msgb_free(l1_msg); return 0; } int bts_model_trx_close(struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg; msg = l1p_msgb_alloc(); prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h); LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); } static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); uint8_t mute[8]; int i; for (i = 0; i < ARRAY_SIZE(mute); ++i) mute[i] = locked ? 1 : 0; return l1if_mute_rf(fl1h, mute, cb); } int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], int success) { if (success) { int i; int is_locked = 1; for (i = 0; i < 8; ++i) if (!mute_state[i]) is_locked = 0; mo->nm_state.administrative = is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; mo->procedure_pending = 0; return oml_mo_statechg_ack(mo); } else { mo->procedure_pending = 0; return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); } } static int ts_connect(struct gsm_bts_trx_ts *ts) { struct msgb *msg = l1p_msgb_alloc(); struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx); GsmL1_MphConnectReq_t *cr; cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h); cr->u8Tn = ts->nr; cr->logChComb = pchan_to_logChComb[ts->pchan]; return l1if_gsm_req_compl(fl1h, msg, opstart_compl_cb, NULL); } GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) { switch (lchan->type) { case GSM_LCHAN_TCH_F: return GsmL1_Sapi_TchF; case GSM_LCHAN_TCH_H: return GsmL1_Sapi_TchH; default: LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", gsm_lchan_name(lchan)); break; } return GsmL1_Sapi_Idle; } GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) { switch (lchan->ts->pchan) { case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: if (lchan->type == GSM_LCHAN_CCCH) return GsmL1_SubCh_NA; /* fall-through */ case GSM_PCHAN_TCH_H: case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: return lchan->nr; case GSM_PCHAN_NONE: case GSM_PCHAN_CCCH: case GSM_PCHAN_TCH_F: case GSM_PCHAN_PDCH: case GSM_PCHAN_UNKNOWN: default: return GsmL1_SubCh_NA; } return GsmL1_SubCh_NA; } struct sapi_dir { GsmL1_Sapi_t sapi; GsmL1_Dir_t dir; }; static const struct sapi_dir ccch_sapis[] = { { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, }; static const struct sapi_dir tchf_sapis[] = { { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, }; static const struct sapi_dir tchh_sapis[] = { { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, }; static const struct sapi_dir sdcch_sapis[] = { { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, }; static const struct sapi_dir cbch_sapis[] = { { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink }, /* Does the CBCH really have a SACCH in Downlink? */ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, }; static const struct sapi_dir pdtch_sapis[] = { { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink }, { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink }, #if 0 { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink }, { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink }, #endif }; static const struct sapi_dir ho_sapis[] = { { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, }; struct lchan_sapis { const struct sapi_dir *sapis; unsigned int num_sapis; }; static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { [GSM_LCHAN_SDCCH] = { .sapis = sdcch_sapis, .num_sapis = ARRAY_SIZE(sdcch_sapis), }, [GSM_LCHAN_TCH_F] = { .sapis = tchf_sapis, .num_sapis = ARRAY_SIZE(tchf_sapis), }, [GSM_LCHAN_TCH_H] = { .sapis = tchh_sapis, .num_sapis = ARRAY_SIZE(tchh_sapis), }, [GSM_LCHAN_CCCH] = { .sapis = ccch_sapis, .num_sapis = ARRAY_SIZE(ccch_sapis), }, [GSM_LCHAN_PDTCH] = { .sapis = pdtch_sapis, .num_sapis = ARRAY_SIZE(pdtch_sapis), }, [GSM_LCHAN_CBCH] = { .sapis = cbch_sapis, .num_sapis = ARRAY_SIZE(cbch_sapis), }, }; static const struct lchan_sapis sapis_for_ho = { .sapis = ho_sapis, .num_sapis = ARRAY_SIZE(ho_sapis), }; static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir); static int lchan_deactivate_sapis(struct gsm_lchan *lchan); /** * Execute the first SAPI command of the queue. In case of the markers * this method is re-entrant so we need to make sure to remove a command * from the list before calling a function that will queue a command. * * \return 0 in case no Gsm Request was sent, 1 otherwise */ static int sapi_queue_exeute(struct gsm_lchan *lchan) { int res; struct sapi_cmd *cmd; cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); switch (cmd->type) { case SAPI_CMD_ACTIVATE: mph_send_activate_req(lchan, cmd); res = 1; break; case SAPI_CMD_CONFIG_CIPHERING: mph_send_config_ciphering(lchan, cmd); res = 1; break; case SAPI_CMD_CONFIG_LOGCH_PARAM: mph_send_config_logchpar(lchan, cmd); res = 1; break; case SAPI_CMD_SACCH_REL_MARKER: llist_del(&cmd->entry); talloc_free(cmd); res = check_sapi_release(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink); res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink); break; case SAPI_CMD_REL_MARKER: llist_del(&cmd->entry); talloc_free(cmd); res = lchan_deactivate_sapis(lchan); break; case SAPI_CMD_DEACTIVATE: mph_send_deactivate_req(lchan, cmd); res = 1; break; default: LOGP(DL1C, LOGL_NOTICE, "Unimplemented command type %d\n", cmd->type); llist_del(&cmd->entry); talloc_free(cmd); res = 0; abort(); break; } return res; } static void sapi_queue_send(struct gsm_lchan *lchan) { int res; do { res = sapi_queue_exeute(lchan); } while (res == 0 && !llist_empty(&lchan->sapi_cmds)); } static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) { int end; struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); llist_del(&cmd->entry); end = llist_empty(&lchan->sapi_cmds); if (cmd->callback) cmd->callback(lchan, status); talloc_free(cmd); if (end || llist_empty(&lchan->sapi_cmds)) { LOGP(DL1C, LOGL_NOTICE, "%s End of queue encountered. Now empty? %d\n", gsm_lchan_name(lchan), llist_empty(&lchan->sapi_cmds)); return; } sapi_queue_send(lchan); } /** * Queue and possible execute a SAPI command. Return 1 in case the command was * already executed and 0 in case if it was only put into the queue */ static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd) { int start = llist_empty(&lchan->sapi_cmds); llist_add_tail(&cmd->entry, &lchan->sapi_cmds); if (!start) return 0; sapi_queue_send(lchan); return 1; } static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { enum lchan_sapi_state status; struct sapi_cmd *cmd; struct gsm_lchan *lchan; GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf; /* get the lchan from the information we supplied */ lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3); if (!lchan) { LOGP(DL1C, LOGL_ERROR, "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3); goto err; } LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", gsm_lchan_name(lchan), get_value_string(femtobts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, ic->dir)); if (ic->status == GsmL1_Status_Success) { DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n", get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn); status = LCHAN_SAPI_S_ASSIGNED; } else { LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n", get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn, get_value_string(femtobts_l1status_names, ic->status)); status = LCHAN_SAPI_S_ERROR; } if (ic->dir & GsmL1_Dir_TxDownlink) lchan->sapis_dl[ic->sapi] = status; if (ic->dir & GsmL1_Dir_RxUplink) lchan->sapis_ul[ic->sapi] = status; if (llist_empty(&lchan->sapi_cmds)) { LOGP(DL1C, LOGL_ERROR, "%s Got activation confirmation with empty queue\n", gsm_lchan_name(lchan)); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_ACTIVATE) { LOGP(DL1C, LOGL_ERROR, "%s Confirmation mismatch (%d, %d) (%d, %d)\n", gsm_lchan_name(lchan), cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } sapi_queue_dispatch(lchan, ic->status); err: msgb_free(l1_msg); return 0; } uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan) { return 0xBB | (lchan->nr << 8) | (lchan->ts->nr << 16) | (lchan->ts->trx->nr << 24); } /* obtain a ptr to the lapdm_channel for a given hLayer */ struct gsm_lchan * l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2) { uint8_t magic = hLayer2 & 0xff; uint8_t ts_nr = (hLayer2 >> 16) & 0xff; uint8_t lchan_nr = (hLayer2 >> 8)& 0xff; struct gsm_bts_trx_ts *ts; if (magic != 0xBB) return NULL; /* FIXME: if we actually run on the BTS, the 32bit field is large * enough to simply put a pointer inside. */ if (ts_nr >= ARRAY_SIZE(trx->ts)) return NULL; ts = &trx->ts[ts_nr]; if (lchan_nr >= ARRAY_SIZE(ts->lchan)) return NULL; return &ts->lchan[lchan_nr]; } /* we regularly check if the DSP L1 is still sending us primitives. * if not, we simply stop the BTS program (and be re-spawned) */ static void alive_timer_cb(void *data) { struct femtol1_hdl *fl1h = data; if (fl1h->alive_prim_cnt == 0) { LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n"); exit(23); } fl1h->alive_prim_cnt = 0; osmo_timer_schedule(&fl1h->alive_timer, 5, 0); } static void clear_amr_params(GsmL1_LogChParam_t *lch_par) { int j; /* common for the SIGN, V1 and EFR: */ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA; lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset; for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; } static void set_payload_format(GsmL1_LogChParam_t *lch_par) { #ifdef L1_HAS_RTP_MODE #ifdef USE_L1_RTP_MODE lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; #else lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_If2; #endif /* USE_L1_RTP_MODE */ #endif /* L1_HAS_RTP_MODE */ } static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; struct gsm48_multi_rate_conf *mr_conf = (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; int j; LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: /* we have to set some TCH payload type even if we don't * know yet what codec we will use later on */ if (lchan->type == GSM_LCHAN_TCH_F) lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; else lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; clear_amr_params(lch_par); break; case GSM48_CMODE_SPEECH_V1: if (lchan->type == GSM_LCHAN_TCH_F) lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; else lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; set_payload_format(lch_par); clear_amr_params(lch_par); break; case GSM48_CMODE_SPEECH_EFR: lch_par->tch.tchPlType = GsmL1_TchPlType_Efr; set_payload_format(lch_par); clear_amr_params(lch_par); break; case GSM48_CMODE_SPEECH_AMR: lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; set_payload_format(lch_par); lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); /* initialize to clean state */ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; j = 0; if (mr_conf->m4_75) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m5_15) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m5_90) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m6_70) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m7_40) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m7_95) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m10_2) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2; if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) break; if (mr_conf->m12_2) lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2; break; case GSM48_CMODE_DATA_14k5: case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: case GSM48_CMODE_DATA_3k6: LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", gsm_lchan_name(lchan)); break; } } static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); struct msgb *msg = l1p_msgb_alloc(); int sapi = cmd->sapi; int dir = cmd->dir; GsmL1_MphActivateReq_t *act_req; GsmL1_LogChParam_t *lch_par; act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq, fl1h); lch_par = &act_req->logChPrm; act_req->u8Tn = lchan->ts->nr; act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); act_req->dir = dir; act_req->sapi = sapi; act_req->hLayer2 = l1if_lchan_to_hLayer(lchan); act_req->hLayer3 = act_req->hLayer2; switch (act_req->sapi) { case GsmL1_Sapi_Rach: lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic; break; case GsmL1_Sapi_Agch: #warning Set BS_AG_BLKS_RES lch_par->agch.u8NbrOfAgch = 1; break; case GsmL1_Sapi_TchH: case GsmL1_Sapi_TchF: lchan2lch_par(lch_par, lchan); break; case GsmL1_Sapi_Ptcch: lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic; break; case GsmL1_Sapi_Prach: lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic; break; case GsmL1_Sapi_Sacch: /* * For the SACCH we need to set the u8MsPowerLevel when * doing manual MS power control. */ if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; /* fall through */ case GsmL1_Sapi_Pdtch: case GsmL1_Sapi_Pacch: /* * Be sure that every packet is received, even if it * fails. In this case the length might be lower or 0. */ act_req->fBFILevel = -200.0f; break; default: break; } LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", gsm_lchan_name(lchan), act_req->hLayer2, get_value_string(femtobts_l1sapi_names, act_req->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, act_req->dir)); /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); } static void sapi_clear_queue(struct llist_head *queue) { struct sapi_cmd *next, *tmp; llist_for_each_entry_safe(next, tmp, queue, entry) { llist_del(&next->entry); talloc_free(next); } } static int sapi_activate_cb(struct gsm_lchan *lchan, int status) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); /* FIXME: Error handling */ if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "%s act failed mark broken due status: %d\n", gsm_lchan_name(lchan), status); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); return -1; } if (!llist_empty(&lchan->sapi_cmds)) return 0; if (lchan->state != LCHAN_S_ACT_REQ) return 0; lchan_set_state(lchan, LCHAN_S_ACTIVE); mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0); /* set the initial ciphering parameters for both directions */ l1if_set_ciphering(fl1h, lchan, 1); l1if_set_ciphering(fl1h, lchan, 0); if (lchan->encr.alg_id) lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; else lchan->ciph_state = LCHAN_CIPH_NONE; return 0; } static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir) { struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); cmd->sapi = sapi; cmd->dir = dir; cmd->type = SAPI_CMD_ACTIVATE; cmd->callback = sapi_activate_cb; queue_sapi_command(lchan, cmd); } int lchan_activate(struct gsm_lchan *lchan) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; unsigned int i; lchan_set_state(lchan, LCHAN_S_ACT_REQ); if (!llist_empty(&lchan->sapi_cmds)) LOGP(DL1C, LOGL_ERROR, "%s Trying to activate lchan, but commands in queue\n", gsm_lchan_name(lchan)); /* override the regular SAPIs if this is the first hand-over * related activation of the LCHAN */ if (lchan->ho.active == HANDOVER_ENABLED) s4l = &sapis_for_ho; for (i = 0; i < s4l->num_sapis; i++) { int sapi = s4l->sapis[i].sapi; int dir = s4l->sapis[i].dir; if (sapi == GsmL1_Sapi_Sch) { /* once we activate the SCH, we should get MPH-TIME.ind */ fl1h->alive_timer.cb = alive_timer_cb; fl1h->alive_timer.data = fl1h; fl1h->alive_prim_cnt = 0; osmo_timer_schedule(&fl1h->alive_timer, 5, 0); } enqueue_sapi_act_cmd(lchan, sapi, dir); } #warning "FIXME: Should this be in sapi_activate_cb?" lchan_init_lapdm(lchan); return 0; } const struct value_string femtobts_l1cfgt_names[] = { { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" }, { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" }, { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" }, { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" }, { 0, NULL } }; static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi) { int i; switch (sapi) { case GsmL1_Sapi_Rach: LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic); break; case GsmL1_Sapi_Agch: LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ", lch_par->agch.u8NbrOfAgch); break; case GsmL1_Sapi_Sacch: LOGPC(DL1C, logl, "MS Power Level 0x%02x", lch_par->sacch.u8MsPowerLevel); break; case GsmL1_Sapi_TchF: case GsmL1_Sapi_TchH: LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (", lch_par->tch.amrCmiPhase, lch_par->tch.amrInitCodecMode); for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) { LOGPC(DL1C, logl, "%x ", lch_par->tch.amrActiveCodecSet[i]); } break; /* FIXME: PRACH / PTCCH */ default: break; } LOGPC(DL1C, logl, ")\n"); } static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", gsm_trx_name(trx), get_value_string(femtobts_l1cfgt_names, cc->cfgParamId)); LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n", cc->cfgParams.setTxPowerLevel.fTxPowerLevel); power_trx_change_compl(trx, (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000)); msgb_free(l1_msg); return 0; } static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { struct gsm_lchan *lchan; GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; /* get the lchan from the information we supplied */ lchan = l1if_hLayer_to_lchan(trx, cc->hLayer3); if (!lchan) { LOGP(DL1C, LOGL_ERROR, "Failed to find lchan for hLayer3=0x%x\n", cc->hLayer3); goto err; } LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", gsm_lchan_name(lchan), get_value_string(femtobts_l1cfgt_names, cc->cfgParamId)); switch (cc->cfgParamId) { case GsmL1_ConfigParamId_SetLogChParams: dump_lch_par(LOGL_INFO, &cc->cfgParams.setLogChParams.logChParams, cc->cfgParams.setLogChParams.sapi); sapi_queue_dispatch(lchan, cc->status); break; case GsmL1_ConfigParamId_SetCipheringParams: switch (lchan->ciph_state) { case LCHAN_CIPH_RX_REQ: LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n"); lchan->ciph_state = LCHAN_CIPH_RX_CONF; break; case LCHAN_CIPH_RX_CONF_TX_REQ: LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n"); lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; break; case LCHAN_CIPH_RXTX_REQ: LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n"); lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; break; case LCHAN_CIPH_NONE: LOGPC(DL1C, LOGL_INFO, "\n"); break; default: LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); break; } if (llist_empty(&lchan->sapi_cmds)) { LOGP(DL1C, LOGL_ERROR, "%s Got ciphering conf with empty queue\n", gsm_lchan_name(lchan)); goto err; } sapi_queue_dispatch(lchan, cc->status); break; case GsmL1_ConfigParamId_SetNbTsc: default: LOGPC(DL1C, LOGL_INFO, "\n"); break; } err: msgb_free(l1_msg); return 0; } static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd) { struct gsm_bts_trx *trx = lchan->ts->trx; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg = l1p_msgb_alloc(); GsmL1_MphConfigReq_t *conf_req; GsmL1_LogChParam_t *lch_par; /* channel mode, encryption and/or multirate have changed */ /* update multi-rate config */ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h); conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams; conf_req->cfgParams.setLogChParams.sapi = cmd->sapi; conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr; conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); conf_req->cfgParams.setLogChParams.dir = cmd->dir; conf_req->hLayer3 = l1if_lchan_to_hLayer(lchan); lch_par = &conf_req->cfgParams.setLogChParams.logChParams; lchan2lch_par(lch_par, lchan); /* Update the MS Power Level */ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; /* FIXME: update encryption */ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", gsm_lchan_name(lchan), get_value_string(femtobts_l1sapi_names, conf_req->cfgParams.setLogChParams.sapi)); LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", conf_req->cfgParams.setLogChParams.u8Tn, conf_req->cfgParams.setLogChParams.subCh, conf_req->cfgParams.setLogChParams.dir); dump_lch_par(LOGL_INFO, &conf_req->cfgParams.setLogChParams.logChParams, conf_req->cfgParams.setLogChParams.sapi); return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); } static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi) { struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); cmd->dir = dir; cmd->sapi = sapi; cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM; queue_sapi_command(lchan, cmd); } static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) { enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan)); return 0; } int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power) { struct msgb *msg = l1p_msgb_alloc(); GsmL1_MphConfigReq_t *conf_req; conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h); conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel; conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power; return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL); } const enum GsmL1_CipherId_t rsl2l1_ciph[] = { [0] = GsmL1_CipherId_A50, [1] = GsmL1_CipherId_A50, [2] = GsmL1_CipherId_A51, [3] = GsmL1_CipherId_A52, [4] = GsmL1_CipherId_A53, }; static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); struct msgb *msg = l1p_msgb_alloc(); struct GsmL1_MphConfigReq_t *cfgr; cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h); cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams; cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr; cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); cfgr->cfgParams.setCipheringParams.dir = cmd->dir; cfgr->hLayer3 = l1if_lchan_to_hLayer(lchan); if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph)) return -EINVAL; cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", gsm_lchan_name(lchan), cfgr->cfgParams.setCipheringParams.cipherId, get_value_string(femtobts_dir_names, cfgr->cfgParams.setCipheringParams.dir)); memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, lchan->encr.key, lchan->encr.key_len); return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); } static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir) { struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); cmd->dir = dir; cmd->type = SAPI_CMD_CONFIG_CIPHERING; queue_sapi_command(lchan, cmd); } int l1if_set_ciphering(struct femtol1_hdl *fl1h, struct gsm_lchan *lchan, int dir_downlink) { int dir; /* ignore the request when the channel is not active */ if (lchan->state != LCHAN_S_ACTIVE) return -1; if (dir_downlink) dir = GsmL1_Dir_TxDownlink; else dir = GsmL1_Dir_RxUplink; enqueue_sapi_ciphering_cmd(lchan, dir); return 0; } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { if (lchan->state != LCHAN_S_ACTIVE) return -1; enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch); return 0; } int l1if_rsl_mode_modify(struct gsm_lchan *lchan) { if (lchan->state != LCHAN_S_ACTIVE) return -1; /* channel mode, encryption and/or multirate have changed */ /* update multi-rate config */ tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink); tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink); /* FIXME: update encryption */ return 0; } static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { enum lchan_sapi_state status; struct sapi_cmd *cmd; struct gsm_lchan *lchan; GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf; lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3); if (!lchan) { LOGP(DL1C, LOGL_ERROR, "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3); goto err; } LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", gsm_lchan_name(lchan), get_value_string(femtobts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, ic->dir)); if (ic->status == GsmL1_Status_Success) { DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn); status = LCHAN_SAPI_S_NONE; } else { LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n", get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn, get_value_string(femtobts_l1status_names, ic->status)); status = LCHAN_SAPI_S_ERROR; } if (ic->dir & GsmL1_Dir_TxDownlink) lchan->sapis_dl[ic->sapi] = status; if (ic->dir & GsmL1_Dir_RxUplink) lchan->sapis_ul[ic->sapi] = status; if (llist_empty(&lchan->sapi_cmds)) { LOGP(DL1C, LOGL_ERROR, "%s Got de-activation confirmation with empty queue\n", gsm_lchan_name(lchan)); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_DEACTIVATE) { LOGP(DL1C, LOGL_ERROR, "%s Confirmation mismatch (%d, %d) (%d, %d)\n", gsm_lchan_name(lchan), cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } sapi_queue_dispatch(lchan, ic->status); err: msgb_free(l1_msg); return 0; } static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); struct msgb *msg = l1p_msgb_alloc(); GsmL1_MphDeactivateReq_t *deact_req; deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq, fl1h); deact_req->u8Tn = lchan->ts->nr; deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); deact_req->dir = cmd->dir; deact_req->sapi = cmd->sapi; deact_req->hLayer3 = l1if_lchan_to_hLayer(lchan); LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", gsm_lchan_name(lchan), get_value_string(femtobts_l1sapi_names, deact_req->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, deact_req->dir)); /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); } static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) { /* FIXME: Error handling. There is no NACK... */ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", gsm_lchan_name(lchan)); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); return -1; } if (!llist_empty(&lchan->sapi_cmds)) return 0; /* Don't send an REL ACK on SACCH deactivate */ if (lchan->state != LCHAN_S_REL_REQ) return 0; lchan_set_state(lchan, LCHAN_S_NONE); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); return 0; } static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir) { struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); cmd->sapi = sapi; cmd->dir = dir; cmd->type = SAPI_CMD_DEACTIVATE; cmd->callback = sapi_deactivate_cb; return queue_sapi_command(lchan, cmd); } /* * Release the SAPI if it was allocated. E.g. the SACCH might be already * deactivated or during a hand-over the TCH was not allocated yet. */ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) { /* check if we should schedule a release */ if (dir & GsmL1_Dir_TxDownlink) { if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) return 0; lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; } else if (dir & GsmL1_Dir_RxUplink) { if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED) return 0; lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL; } /* now schedule the command and maybe dispatch it */ return enqueue_sapi_deact_cmd(lchan, sapi, dir); } static int release_sapis_for_ho(struct gsm_lchan *lchan) { int res = 0; int i; const struct lchan_sapis *s4l = &sapis_for_ho; for (i = s4l->num_sapis-1; i >= 0; i--) res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); return res; } static int lchan_deactivate_sapis(struct gsm_lchan *lchan) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; int i, res; res = 0; /* The order matters.. the Facch needs to be released first */ for (i = s4l->num_sapis-1; i >= 0; i--) { /* Stop the alive timer once we deactivate the SCH */ if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch) osmo_timer_del(&fl1h->alive_timer); /* Release if it was allocated */ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); } /* always attempt to disable the RACH burst */ res |= release_sapis_for_ho(lchan); /* nothing was queued */ if (res == 0) { LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", gsm_lchan_name(lchan)); lchan_set_state(lchan, LCHAN_S_BROKEN); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); } return res; } static void enqueue_rel_marker(struct gsm_lchan *lchan) { struct sapi_cmd *cmd; /* remember we need to release all active SAPIs */ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); cmd->type = SAPI_CMD_REL_MARKER; queue_sapi_command(lchan, cmd); } int lchan_deactivate(struct gsm_lchan *lchan) { lchan_set_state(lchan, LCHAN_S_REL_REQ); lchan->ciph_state = 0; /* FIXME: do this in common/\*.c */ enqueue_rel_marker(lchan); return 0; } static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan) { struct sapi_cmd *cmd; /* remember we need to check if the SACCH is allocated */ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); cmd->type = SAPI_CMD_SACCH_REL_MARKER; queue_sapi_command(lchan, cmd); } static int lchan_deactivate_sacch(struct gsm_lchan *lchan) { enqueue_sacch_rel_marker(lchan); return 0; } /* callback from OML */ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj) { /* FIXME: more checks if the attributes are valid */ switch (msg_type) { case NM_MT_SET_CHAN_ATTR: /* our L1 only supports one global TSC for all channels * one one TRX, so we need to make sure not to activate * channels with a different TSC!! */ if (TLVP_PRESENT(new_attr, NM_ATT_TSC) && TLVP_LEN(new_attr, NM_ATT_TSC) >= 1 && *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); return -NM_NACK_PARAM_RANGE; } break; } return 0; } /* callback from OML */ int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int kind, void *obj) { if (kind == NM_OC_RADIO_CARRIER) { struct gsm_bts_trx *trx = obj; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); /* Did we go through MphInit yet? If yes fire and forget */ if (fl1h->hLayer1) power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); } /* FIXME: we actaully need to send a ACK or NACK for the OML message */ return oml_fom_ack_nack(msg, 0); } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { int rc; switch (mo->obj_class) { case NM_OC_RADIO_CARRIER: rc = trx_init(obj); break; case NM_OC_CHANNEL: rc = ts_connect(obj); break; case NM_OC_BTS: case NM_OC_SITE_MANAGER: case NM_OC_BASEB_TRANSC: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); rc = oml_mo_opstart_ack(mo); if (mo->obj_class == NM_OC_BTS) { oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); } break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); } return rc; } int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { int rc = -EINVAL; int granted = 0; switch (mo->obj_class) { case NM_OC_RADIO_CARRIER: if (mo->procedure_pending) { LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " "pending procedure on RC %d\n", ((struct gsm_bts_trx *)obj)->nr); return 0; } mo->procedure_pending = 1; switch (adm_state) { case NM_STATE_LOCKED: rc = trx_rf_lock(obj, 1, NULL); break; case NM_STATE_UNLOCKED: rc = trx_rf_lock(obj, 0, NULL); break; default: granted = 1; break; } if (!granted && rc == 0) /* in progress, will send ack/nack after completion */ return 0; mo->procedure_pending = 0; break; default: /* blindly accept all state changes */ granted = 1; break; } if (granted) { mo->nm_state.administrative = adm_state; return oml_mo_statechg_ack(mo); } else return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); } int l1if_rsl_chan_act(struct gsm_lchan *lchan) { //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE); //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE); lchan_activate(lchan); return 0; } /** * Modify the given lchan in the handover scenario. This is a lot like * second channel activation but with some additional activation. */ int l1if_rsl_chan_mod(struct gsm_lchan *lchan) { const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; unsigned int i; if (lchan->ho.active == HANDOVER_NONE) return -1; LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", gsm_lchan_name(lchan)); /* Give up listening to RACH bursts */ release_sapis_for_ho(lchan); /* Activate the normal SAPIs */ for (i = 0; i < s4l->num_sapis; i++) { int sapi = s4l->sapis[i].sapi; int dir = s4l->sapis[i].dir; enqueue_sapi_act_cmd(lchan, sapi, dir); } return 0; } int l1if_rsl_chan_rel(struct gsm_lchan *lchan) { /* A duplicate RF Release Request, ignore it */ if (lchan->state == LCHAN_S_REL_REQ) { LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", gsm_lchan_name(lchan)); return 0; } lchan_deactivate(lchan); return 0; } int l1if_rsl_deact_sacch(struct gsm_lchan *lchan) { /* Only de-activate the SACCH if the lchan is active */ if (lchan->state != LCHAN_S_ACTIVE) return 0; return lchan_deactivate_sacch(lchan); } int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); return l1if_activate_rf(fl1, 0); } int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) { return l1if_set_txpower(trx_femtol1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); } osmo-bts-0.4.0/src/osmo-bts-sysmo/oml_router.c000066400000000000000000000057271260026426200213260ustar00rootroot00000000000000/* Beginnings of an OML router */ /* (C) 2014 by sysmocom s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 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 "oml_router.h" #include #include #include #include #include #include #include #include #include static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) { struct msgb *msg; int rc; msg = oml_msgb_alloc(); if (!msg) { LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); return -1; } rc = recv(fd->fd, msg->tail, msg->data_len, 0); if (rc <= 0) { close(fd->fd); osmo_fd_unregister(fd); fd->fd = -1; goto err; } msg->l1h = msgb_put(msg, rc); rc = msg_verify_ipa_structure(msg); if (rc < 0) { LOGP(DL1C, LOGL_ERROR, "OML Router: Invalid IPA message rc(%d)\n", rc); goto err; } rc = msg_verify_oml_structure(msg); if (rc < 0) { LOGP(DL1C, LOGL_ERROR, "OML Router: Invalid OML message rc(%d)\n", rc); goto err; } /* todo dispatch message */ err: msgb_free(msg); return -1; } static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) { int fd; struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; /* Accept only one connection at a time. De-register it */ if (read_fd->fd > -1) { LOGP(DL1C, LOGL_NOTICE, "New OML router connection. Closing old one.\n"); close(read_fd->fd); osmo_fd_unregister(read_fd); read_fd->fd = -1; } fd = accept(accept_fd->fd, NULL, NULL); if (fd < 0) { LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", strerror(errno)); return -1; } read_fd->fd = fd; if (osmo_fd_register(read_fd) != 0) { LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); close(fd); read_fd->fd = -1; return -1; } return 0; } int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept_fd, struct osmo_fd *read_fd) { int rc; memset(accept_fd, 0, sizeof(*accept_fd)); memset(read_fd, 0, sizeof(*read_fd)); accept_fd->cb = oml_router_accept_cb; accept_fd->data = read_fd; read_fd->cb = oml_router_read_cb; read_fd->data = bts; read_fd->when = BSC_FD_READ; read_fd->fd = -1; rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); return rc; } osmo-bts-0.4.0/src/osmo-bts-sysmo/oml_router.h000066400000000000000000000004761260026426200213270ustar00rootroot00000000000000#pragma once struct gsm_bts; struct osmo_fd; /** * The default path sysmobts will listen for incoming * registrations for OML routing and sending. */ #define OML_ROUTER_PATH "/var/run/sysmobts_oml_router" int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); osmo-bts-0.4.0/src/osmo-bts-sysmo/sysmobts_ctrl.c000066400000000000000000000162131260026426200220360ustar00rootroot00000000000000/* Control Interface for sysmoBTS */ /* (C) 2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" /* for control interface */ #ifndef HW_SYSMOBTS_V1 CTRL_CMD_DEFINE(clock_info, "clock-info"); static int ctrl_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); struct ctrl_cmd_def *cd = data; struct ctrl_cmd *cmd = cd->cmd; LOGP(DL1C, LOGL_NOTICE, "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n", sysp->u.rfClockInfoCnf.rfTrx.iClkCor, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrx.clkSrc), sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); if (ctrl_cmd_def_is_zombie(cd)) { msgb_free(resp); return 0; } cmd->reply = talloc_asprintf(cmd, "%d,%s,%d,%d,%s", sysp->u.rfClockInfoCnf.rfTrx.iClkCor, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrx.clkSrc), sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); ctrl_cmd_def_send(cd); msgb_free(resp); return 0; } static int get_clock_info(struct ctrl_cmd *cmd, void *data) { struct gsm_bts_trx *trx = cmd->node; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct ctrl_cmd_def *cd; /* geneate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 0; return l1if_req_compl(fl1h, msg, ctrl_clkinfo_cb, cd); } static int ctrl_set_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct ctrl_cmd_def *cd = data; struct ctrl_cmd *cmd = cd->cmd; if (ctrl_cmd_def_is_zombie(cd)) { msgb_free(resp); return 0; } cmd->reply = "success"; ctrl_cmd_def_send(cd); msgb_free(resp); return 0; } static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { msgb_free(resp); return 0; } static int set_clock_info(struct ctrl_cmd *cmd, void *data) { struct gsm_bts_trx *trx = cmd->node; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct ctrl_cmd_def *cd; /* geneate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); /* Set GPS/PPS as reference */ sysp->id = SuperFemto_PrimId_RfClockSetupReq; sysp->u.rfClockSetupReq.rfTrx.iClkCor = fl1h->clk_cal; /* !!! use get_clk_cal */ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; l1if_req_compl(fl1h, msg, clock_setup_cb, NULL); /* Reset the error counters */ msg = sysp_msgb_alloc(); sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 1; l1if_req_compl(fl1h, msg, ctrl_set_clkinfo_cb, cd); return CTRL_CMD_HANDLED; } static int verify_clock_info(struct ctrl_cmd *cmd, const char *value, void *data) { return 0; } CTRL_CMD_DEFINE(clock_corr, "clock-correction"); static int ctrl_get_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); struct ctrl_cmd_def *cd = data; struct ctrl_cmd *cmd = cd->cmd; if (ctrl_cmd_def_is_zombie(cd)) { msgb_free(resp); return 0; } cmd->reply = talloc_asprintf(cmd, "%d", sysp->u.rfClockInfoCnf.rfTrx.iClkCor); ctrl_cmd_def_send(cd); msgb_free(resp); return 0; } static int get_clock_corr(struct ctrl_cmd *cmd, void *data) { struct gsm_bts_trx *trx = cmd->node; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct ctrl_cmd_def *cd; /* we could theoretically simply respond with a cached value, but I * prefer to to ask the actual L1 about the currently used value to * avoid any mistakes */ /* geneate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 0; l1if_req_compl(fl1h, msg, ctrl_get_clkcorr_cb, cd); return CTRL_CMD_HANDLED; } static int ctrl_set_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); struct ctrl_cmd_def *cd = data; struct ctrl_cmd *cmd = cd->cmd; if (ctrl_cmd_def_is_zombie(cd)) { msgb_free(resp); return 0; } if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) { cmd->type = CTRL_CMD_ERROR; cmd->reply = "Error setting new correction value."; } else cmd->reply = "success"; ctrl_cmd_def_send(cd); msgb_free(resp); return 0; } static int set_clock_corr(struct ctrl_cmd *cmd, void *data) { struct gsm_bts_trx *trx = cmd->node; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct ctrl_cmd_def *cd; fl1h->clk_cal = atoi(cmd->value); /* geneate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); sysp->id = SuperFemto_PrimId_RfClockSetupReq; sysp->u.rfClockSetupReq.rfTrx.iClkCor = fl1h->clk_cal; sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; l1if_req_compl(fl1h, msg, ctrl_set_clkcorr_cb, cd); return CTRL_CMD_HANDLED; } static int verify_clock_corr(struct ctrl_cmd *cmd, const char *value, void *data) { /* FIXME: check the range */ return 0; } #endif /* HW_SYSMOBTS_V1 */ int sysmobts_ctrlif_inst_cmds(void) { int rc = 0; #ifndef HW_SYSMOBTS_V1 rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_info); rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_corr); #endif /* HW_SYSMOBTS_V1 */ return rc; } osmo-bts-0.4.0/src/osmo-bts-sysmo/sysmobts_vty.c000066400000000000000000000335031260026426200217150ustar00rootroot00000000000000/* VTY interface for sysmoBTS */ /* (C) 2011 by Harald Welte * (C) 2012,2013 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" #include "utils.h" extern int lchan_activate(struct gsm_lchan *lchan); extern int lchan_deactivate(struct gsm_lchan *lchan); #define TRX_STR "Transceiver related commands\n" "TRX number\n" #define SHOW_TRX_STR \ SHOW_STR \ TRX_STR #define DSP_TRACE_F_STR "DSP Trace Flag\n" static struct gsm_bts *vty_bts; /* configuration */ DEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd, "auto-band", "Automatically select band for ARFCN based on configured band\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->auto_band = 1; return CMD_SUCCESS; } DEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd, "no auto-band", NO_STR "Automatically select band for ARFCN based on configured band\n") { struct gsm_bts *bts = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); btsb->auto_band = 0; return CMD_SUCCESS; } DEFUN(cfg_trx_clkcal_eeprom, cfg_trx_clkcal_eeprom_cmd, "clock-calibration eeprom", "Use the eeprom clock calibration value\n") { struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); fl1h->clk_use_eeprom = 1; return CMD_SUCCESS; } DEFUN(cfg_trx_clkcal_def, cfg_trx_clkcal_def_cmd, "clock-calibration default", "Set the clock calibration value\n" "Default Clock DAC value\n") { struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); fl1h->clk_use_eeprom = 0; fl1h->clk_cal = 0xffff; return CMD_SUCCESS; } #ifdef HW_SYSMOBTS_V1 DEFUN(cfg_trx_clkcal, cfg_trx_clkcal_cmd, "clock-calibration <0-4095>", "Set the clock calibration value\n" "Clock DAC value\n") { unsigned int clkcal = atoi(argv[0]); struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); fl1h->clk_use_eeprom = 0; fl1h->clk_cal = clkcal & 0xfff; return CMD_SUCCESS; } #else DEFUN(cfg_trx_clkcal, cfg_trx_clkcal_cmd, "clock-calibration <-4095-4095>", "Set the clock calibration value\n" "Offset in PPB\n") { int clkcal = atoi(argv[0]); struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); fl1h->clk_use_eeprom = 0; fl1h->clk_cal = clkcal; return CMD_SUCCESS; } #endif DEFUN(cfg_trx_clksrc, cfg_trx_clksrc_cmd, "clock-source (tcxo|ocxo|ext|gps)", "Set the clock source value\n" "Use the TCXO\n" "Use the OCXO\n" "Use an external clock\n" "Use the GPS pps\n") { struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); int rc; rc = get_string_value(femtobts_clksrc_names, argv[0]); if (rc < 0) return CMD_WARNING; fl1h->clk_src = rc; return CMD_SUCCESS; } DEFUN(cfg_trx_cal_path, cfg_trx_cal_path_cmd, "trx-calibration-path PATH", "Set the path name to TRX calibration data\n" "Path name\n") { struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); if (fl1h->calib_path) talloc_free(fl1h->calib_path); fl1h->calib_path = talloc_strdup(fl1h, argv[0]); return CMD_SUCCESS; } DEFUN_DEPRECATED(cfg_trx_ul_power_target, cfg_trx_ul_power_target_cmd, "uplink-power-target <-110-0>", "Obsolete alias for bts uplink-power-target\n" "Target uplink Rx level in dBm\n") { struct gsm_bts_trx *trx = vty->index; struct gsm_bts_role_bts *btsb = bts_role_bts(trx->bts); btsb->ul_power_target = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(cfg_trx_min_qual_rach, cfg_trx_min_qual_rach_cmd, "min-qual-rach <-100-100>", "Set the minimum quality level of RACH burst to be accpeted\n" "C/I level in tenth of dB\n") { struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); fl1h->min_qual_rach = strtof(argv[0], NULL) / 10.0f; return CMD_SUCCESS; } DEFUN(cfg_trx_min_qual_norm, cfg_trx_min_qual_norm_cmd, "min-qual-norm <-100-100>", "Set the minimum quality level of normal burst to be accpeted\n" "C/I level in tenth of dB\n") { struct gsm_bts_trx *trx = vty->index; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); fl1h->min_qual_norm = strtof(argv[0], NULL) / 10.0f; return CMD_SUCCESS; } DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, "nominal-tx-power <0-100>", "Set the nominal transmit output power in dBm\n" "Nominal transmit output power level in dBm\n") { struct gsm_bts_trx *trx = vty->index; trx->nominal_power = atoi(argv[0]); return CMD_SUCCESS; } /* runtime */ DEFUN(show_trx_clksrc, show_trx_clksrc_cmd, "show trx <0-0> clock-source", SHOW_TRX_STR "Display the clock source for this TRX") { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h; if (!trx) return CMD_WARNING; fl1h = trx_femtol1_hdl(trx); vty_out(vty, "TRX Clock Source: %s%s", get_value_string(femtobts_clksrc_names, fl1h->clk_src), VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, "show trx <0-0> dsp-trace-flags", SHOW_TRX_STR "Display the current setting of the DSP trace flags") { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h; int i; if (!trx) return CMD_WARNING; fl1h = trx_femtol1_hdl(trx); vty_out(vty, "Femto L1 DSP trace flags:%s", VTY_NEWLINE); for (i = 0; i < ARRAY_SIZE(femtobts_tracef_names); i++) { const char *endis; if (femtobts_tracef_names[i].value == 0 && femtobts_tracef_names[i].str == NULL) break; if (fl1h->dsp_trace_f & femtobts_tracef_names[i].value) endis = "enabled"; else endis = "disabled"; vty_out(vty, "DSP Trace %-15s %s%s", femtobts_tracef_names[i].str, endis, VTY_NEWLINE); } return CMD_SUCCESS; } DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR) { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h; unsigned int flag ; if (!trx) { vty_out(vty, "Cannot find TRX number %u%s", trx_nr, VTY_NEWLINE); return CMD_WARNING; } fl1h = trx_femtol1_hdl(trx); flag = get_string_value(femtobts_tracef_names, argv[1]); l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag); return CMD_SUCCESS; } DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR) { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h; unsigned int flag ; if (!trx) { vty_out(vty, "Cannot find TRX number %u%s", trx_nr, VTY_NEWLINE); return CMD_WARNING; } fl1h = trx_femtol1_hdl(trx); flag = get_string_value(femtobts_tracef_names, argv[1]); l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag); return CMD_SUCCESS; } DEFUN(show_sys_info, show_sys_info_cmd, "show trx <0-0> system-information", SHOW_TRX_STR "Display information about system\n") { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h; int i; if (!trx) { vty_out(vty, "Cannot find TRX number %u%s", trx_nr, VTY_NEWLINE); return CMD_WARNING; } fl1h = trx_femtol1_hdl(trx); vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s", fl1h->hw_info.dsp_version[0], fl1h->hw_info.dsp_version[1], fl1h->hw_info.dsp_version[2], fl1h->hw_info.fpga_version[0], fl1h->hw_info.fpga_version[1], fl1h->hw_info.fpga_version[2], VTY_NEWLINE); vty_out(vty, "GSM Band Support: "); for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) { if (fl1h->hw_info.band_support & (1 << i)) vty_out(vty, "%s ", gsm_band_name(1 << i)); } vty_out(vty, "%s", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(activate_lchan, activate_lchan_cmd, "trx <0-0> <0-7> (activate|deactivate) <0-7>", TRX_STR "Timeslot number\n" "Activate Logical Channel\n" "Deactivate Logical Channel\n" "Logical Channel Number\n" ) { int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[3]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; if (!strcmp(argv[2], "activate")) lchan_activate(lchan); else lchan_deactivate(lchan); return CMD_SUCCESS; } DEFUN(set_tx_power, set_tx_power_cmd, "trx <0-0> tx-power <-110-100>", TRX_STR "Set transmit power (override BSC)\n" "Transmit power in dBm\n") { int trx_nr = atoi(argv[0]); int power = atoi(argv[1]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); power_ramp_start(trx, to_mdB(power), 1); return CMD_SUCCESS; } DEFUN(reset_rf_clock_ctr, reset_rf_clock_ctr_cmd, "trx <0-0> rf-clock-info reset", TRX_STR "RF Clock Information\n" "Reset the counter\n") { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); l1if_rf_clock_info_reset(fl1h); return CMD_SUCCESS; } DEFUN(correct_rf_clock_ctr, correct_rf_clock_ctr_cmd, "trx <0-0> rf-clock-info correct", TRX_STR "RF Clock Information\n" "Apply\n") { int trx_nr = atoi(argv[0]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); l1if_rf_clock_info_correct(fl1h); return CMD_SUCCESS; } DEFUN(loopback, loopback_cmd, "trx <0-0> <0-7> loopback <0-1>", TRX_STR "Timeslot number\n" "Set TCH loopback\n" "Logical Channel Number\n") { int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; lchan->loopback = 1; return CMD_SUCCESS; } DEFUN(no_loopback, no_loopback_cmd, "no trx <0-0> <0-7> loopback <0-1>", NO_STR TRX_STR "Timeslot number\n" "Set TCH loopback\n" "Logical Channel Number\n") { int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; lchan->loopback = 0; return CMD_SUCCESS; } void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) { struct gsm_bts_role_bts *btsb = bts_role_bts(bts); if (btsb->auto_band) vty_out(vty, " auto-band%s", VTY_NEWLINE); } void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); if (fl1h->clk_use_eeprom) vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE); else vty_out(vty, " clock-calibration %d%s", fl1h->clk_cal, VTY_NEWLINE); if (fl1h->calib_path) vty_out(vty, " trx-calibration-path %s%s", fl1h->calib_path, VTY_NEWLINE); vty_out(vty, " clock-source %s%s", get_value_string(femtobts_clksrc_names, fl1h->clk_src), VTY_NEWLINE); vty_out(vty, " min-qual-rach %.0f%s", fl1h->min_qual_rach * 10.0f, VTY_NEWLINE); vty_out(vty, " min-qual-norm %.0f%s", fl1h->min_qual_norm * 10.0f, VTY_NEWLINE); if (trx->nominal_power != sysmobts_get_nominal_power(trx)) vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power, VTY_NEWLINE); } int bts_model_vty_init(struct gsm_bts *bts) { vty_bts = bts; /* runtime-patch the command strings with debug levels */ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names, "trx <0-0> dsp-trace-flag (", "|",")", VTY_DO_LOWER); dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, TRX_STR DSP_TRACE_F_STR, "\n", "", 0); no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names, "no trx <0-0> dsp-trace-flag (", "|",")", VTY_DO_LOWER); no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, NO_STR TRX_STR DSP_TRACE_F_STR, "\n", "", 0); install_element_ve(&show_dsp_trace_f_cmd); install_element_ve(&show_sys_info_cmd); install_element_ve(&show_trx_clksrc_cmd); install_element_ve(&dsp_trace_f_cmd); install_element_ve(&no_dsp_trace_f_cmd); install_element(ENABLE_NODE, &activate_lchan_cmd); install_element(ENABLE_NODE, &set_tx_power_cmd); install_element(ENABLE_NODE, &reset_rf_clock_ctr_cmd); install_element(ENABLE_NODE, &correct_rf_clock_ctr_cmd); install_element(ENABLE_NODE, &loopback_cmd); install_element(ENABLE_NODE, &no_loopback_cmd); install_element(BTS_NODE, &cfg_bts_auto_band_cmd); install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd); install_element(TRX_NODE, &cfg_trx_clkcal_cmd); install_element(TRX_NODE, &cfg_trx_clkcal_eeprom_cmd); install_element(TRX_NODE, &cfg_trx_clkcal_def_cmd); install_element(TRX_NODE, &cfg_trx_clksrc_cmd); install_element(TRX_NODE, &cfg_trx_cal_path_cmd); install_element(TRX_NODE, &cfg_trx_min_qual_rach_cmd); install_element(TRX_NODE, &cfg_trx_min_qual_norm_cmd); install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); return 0; } osmo-bts-0.4.0/src/osmo-bts-sysmo/tch.c000066400000000000000000000417011260026426200177050ustar00rootroot00000000000000/* Traffic channel support for Sysmocom BTS L1 */ /* (C) 2011-2012 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" /* input octet-aligned, output not octet-aligned */ void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in, unsigned int num_nibbles) { unsigned int i; unsigned int num_whole_bytes = num_nibbles / 2; /* first byte: upper nibble empty, lower nibble from src */ out[0] = (in[0] >> 4); /* bytes 1.. */ for (i = 1; i < num_whole_bytes; i++) out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4); /* shift the last nibble, in case there's an odd count */ i = num_whole_bytes; if (num_nibbles & 1) out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4); else out[i] = (in[i-1] & 0xF) << 4; } /* input unaligned, output octet-aligned */ void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in, unsigned int num_nibbles) { unsigned int i; unsigned int num_whole_bytes = num_nibbles / 2; for (i = 0; i < num_whole_bytes; i++) out[i] = ((in[i] & 0xF) << 4) | (in[i+1] >> 4); /* shift the last nibble, in case there's an odd count */ i = num_whole_bytes; if (num_nibbles & 1) out[i] = (in[i] & 0xF) << 4; } #define GSM_FR_BITS 260 #define GSM_EFR_BITS 244 #define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */ #define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */ #define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len) { struct msgb *msg; uint8_t *cur; msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP"); if (!msg) return NULL; #ifdef USE_L1_RTP_MODE /* new L1 can deliver bits like we need them */ cur = msgb_put(msg, GSM_FR_BYTES); memcpy(cur, l1_payload, GSM_FR_BYTES); #else /* step1: reverse the bit-order of each payload byte */ osmo_revbytebits_buf(l1_payload, payload_len); cur = msgb_put(msg, GSM_FR_BYTES); /* step2: we need to shift the entire L1 payload by 4 bits right */ osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS/4); cur[0] |= 0xD0; #endif /* USE_L1_RTP_MODE */ return msg; } /*! \brief convert GSM-FR from RTP payload to L1 format * \param[out] l1_payload payload part of L1 buffer * \param[in] rtp_payload pointer to RTP payload data * \param[in] payload_len length of \a rtp_payload * \returns number of \a l1_payload bytes filled */ static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, unsigned int payload_len) { #ifdef USE_L1_RTP_MODE /* new L1 can deliver bits like we need them */ memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); #else /* step2: we need to shift the RTP payload left by one nibble*/ osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS/4); /* step1: reverse the bit-order of each payload byte */ osmo_revbytebits_buf(l1_payload, payload_len); #endif /* USE_L1_RTP_MODE */ return GSM_FR_BYTES; } #if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, uint8_t payload_len) { struct msgb *msg; uint8_t *cur; msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP"); if (!msg) return NULL; #ifdef USE_L1_RTP_MODE /* new L1 can deliver bits like we need them */ cur = msgb_put(msg, GSM_EFR_BYTES); memcpy(cur, l1_payload, GSM_EFR_BYTES); #else /* step1: reverse the bit-order of each payload byte */ osmo_revbytebits_buf(l1_payload, payload_len); cur = msgb_put(msg, GSM_EFR_BYTES); /* step 2: we need to shift the entire L1 payload by 4 bits right */ osmo_nibble_shift_right(cur, l1_payload, GSM_EFR_BITS/4); cur[0] |= 0xC0; #endif /* USE_L1_RTP_MODE */ return msg; } static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, unsigned int payload_len) { #ifndef USE_L1_RTP_MODE #error We don't support EFR with L1 that doesn't support RTP mode! #else memcpy(l1_payload, rtp_payload, payload_len); return payload_len; #endif } #else #warning No EFR support in L1 #endif /* L1_HAS_EFR */ static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len) { struct msgb *msg; uint8_t *cur; msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP"); if (!msg) return NULL; if (payload_len != GSM_HR_BYTES) { LOGP(DL1C, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", payload_len, GSM_HR_BYTES); return NULL; } cur = msgb_put(msg, GSM_HR_BYTES); memcpy(cur, l1_payload, GSM_HR_BYTES); #ifndef USE_L1_RTP_MODE /* reverse the bit-order of each payload byte */ osmo_revbytebits_buf(cur, GSM_HR_BYTES); #endif /* USE_L1_RTP_MODE */ return msg; } /*! \brief convert GSM-FR from RTP payload to L1 format * \param[out] l1_payload payload part of L1 buffer * \param[in] rtp_payload pointer to RTP payload data * \param[in] payload_len length of \a rtp_payload * \returns number of \a l1_payload bytes filled */ static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload, unsigned int payload_len) { if (payload_len != GSM_HR_BYTES) { LOGP(DL1C, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", payload_len, GSM_HR_BYTES); return 0; } memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); #ifndef USE_L1_RTP_MODE /* reverse the bit-order of each payload byte */ osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES); #endif /* USE_L1_RTP_MODE */ return GSM_HR_BYTES; } static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, struct gsm_lchan *lchan) { #ifndef USE_L1_RTP_MODE struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; #endif struct msgb *msg; uint8_t amr_if2_len = payload_len - 2; uint8_t *cur; msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP"); if (!msg) return NULL; #ifdef USE_L1_RTP_MODE cur = msgb_put(msg, amr_if2_len); memcpy(cur, l1_payload+2, amr_if2_len); /* * Audiocode's MGW doesn't like receiving CMRs that are not * the same as the previous one. This means we need to patch * the content here. */ if ((cur[0] & 0xF0) == 0xF0) cur[0]= lchan->tch.last_cmr << 4; else lchan->tch.last_cmr = cur[0] >> 4; #else u_int8_t cmr; uint8_t ft = l1_payload[2] & 0xF; uint8_t cmr_idx = l1_payload[1]; /* CMR == Unset means CMR was not transmitted at this TDMA */ if (cmr_idx == GsmL1_AmrCodecMode_Unset) cmr = lchan->tch.last_cmr; else if (cmr_idx >= amr_mrc->num_modes || cmr_idx > GsmL1_AmrCodecMode_Unset) { /* Make sure the CMR of the phone is in the active codec set */ LOGP(DL1C, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx); cmr = AMR_CMR_NONE; } else { cmr = amr_mrc->mode[cmr_idx].mode; lchan->tch.last_cmr = cmr; } /* RFC 3267 4.4.1 Payload Header */ msgb_put_u8(msg, (cmr << 4)); /* RFC 3267 AMR TOC */ msgb_put_u8(msg, AMR_TOC_QBIT | (ft << 3)); cur = msgb_put(msg, amr_if2_len-1); /* step1: reverse the bit-order within every byte */ osmo_revbytebits_buf(l1_payload+2, amr_if2_len); /* step2: shift everything left by one nibble */ osmo_nibble_shift_left_unal(cur, l1_payload+2, amr_if2_len*2 -1); #endif /* USE_L1_RTP_MODE */ return msg; } enum amr_frame_type { AMR_FT_SID_AMR = 8, }; int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc, uint8_t cmi) { unsigned int i; for (i = 0; i < amr_mrc->num_modes; i++) { if (amr_mrc->mode[i].mode == cmi) return i; } return -EINVAL; } /*! \brief convert AMR from RTP payload to L1 format * \param[out] l1_payload payload part of L1 buffer * \param[in] rtp_payload pointer to RTP payload data * \param[in] payload_len length of \a rtp_payload * \returns number of \a l1_payload bytes filled */ static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload, uint8_t payload_len, struct gsm_lchan *lchan) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; uint8_t ft = (rtp_payload[1] >> 3) & 0xf; uint8_t cmr = rtp_payload[0] >> 4; uint8_t cmi, sti; uint8_t *l1_cmi_idx = l1_payload; uint8_t *l1_cmr_idx = l1_payload+1; int rc; #ifdef USE_L1_RTP_MODE memcpy(l1_payload+2, rtp_payload, payload_len); #else uint8_t amr_if2_core_len = payload_len - 2; /* step1: shift everything right one nibble; make space for FT */ osmo_nibble_shift_right(l1_payload+2, rtp_payload+2, amr_if2_core_len*2); /* step2: reverse the bit-order within every byte of the IF2 * core frame contained in the RTP payload */ osmo_revbytebits_buf(l1_payload+2, amr_if2_core_len+1); /* lower 4 bit of first FR2 byte contains FT */ l1_payload[2] |= ft; #endif /* USE_L1_RTP_MODE */ /* CMI in downlink tells the L1 encoder which encoding function * it will use, so we have to use the frame type */ switch (ft) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: cmi = ft; LOGP(DRTP, LOGL_DEBUG, "SPEECH frame with CMI %u\n", cmi); break; case AMR_FT_SID_AMR: /* extract the mode indiciation from last bits of * 39 bit SID frame (Table 6 / 26.101) */ cmi = (rtp_payload[2+4] >> 1) & 0x7; sti = rtp_payload[2+4] & 0x10; LOGP(DRTP, LOGL_DEBUG, "SID %s frame with CMI %u\n", sti ? "UPDATE" : "FIRST", cmi); break; default: LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft); return -EINVAL; break; } rc = get_amr_mode_idx(amr_mrc, cmi); if (rc < 0) { LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n", cmi); *l1_cmi_idx = 0; } else *l1_cmi_idx = rc; /* Codec Mode Request is in upper 4 bits of RTP payload header, * and we simply copy the CMR into the CMC */ if (cmr == 0xF) { /* FIXME: we need some state about the last codec mode */ *l1_cmr_idx = 0; } else { rc = get_amr_mode_idx(amr_mrc, cmr); if (rc < 0) { /* FIXME: we need some state about the last codec mode */ LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr); *l1_cmr_idx = 0; } else *l1_cmr_idx = rc; } #if 0 /* check for bad quality indication */ if (rtp_payload[1] & AMR_TOC_QBIT) { /* obtain frame type from AMR FT */ l1_payload[2] = ft; } else { /* bad quality, we should indicate that... */ if (ft == AMR_FT_SID_AMR) { /* FIXME: Should we do GsmL1_TchPlType_Amr_SidBad? */ l1_payload[2] = ft; } else { l1_payload[2] = ft; } } #endif if (ft == AMR_FT_SID_AMR) { /* store the last SID frame in lchan context */ unsigned int copy_len; copy_len = OSMO_MIN(payload_len+1, ARRAY_SIZE(lchan->tch.last_sid.buf)); lchan->tch.last_sid.len = copy_len; memcpy(lchan->tch.last_sid.buf, l1_payload, copy_len); } return payload_len+1; } #define RTP_MSGB_ALLOC_SIZE 512 /*! \brief function for incoming RTP via TCH.req * \param rs RTP Socket * \param[in] rtp_pl buffer containing RTP payload * \param[in] rtp_pl_len length of \a rtp_pl * * This function prepares a msgb with a L1 PH-DATA.req primitive and * queues it into lchan->dl_tch_queue. * * Note that the actual L1 primitive header is not fully initialized * yet, as things like the frame number, etc. are unknown at the time we * pre-fill the primtive. */ void l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, const uint8_t *rtp_pl, unsigned int rtp_pl_len) { uint8_t *payload_type; uint8_t *l1_payload; int rc; DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), osmo_hexdump(rtp_pl, rtp_pl_len)); payload_type = &data[0]; l1_payload = &data[1]; switch (lchan->tch_mode) { case GSM48_CMODE_SPEECH_V1: if (lchan->type == GSM_LCHAN_TCH_F) { *payload_type = GsmL1_TchPlType_Fr; rc = rtppayload_to_l1_fr(l1_payload, rtp_pl, rtp_pl_len); } else{ *payload_type = GsmL1_TchPlType_Hr; rc = rtppayload_to_l1_hr(l1_payload, rtp_pl, rtp_pl_len); } break; #if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) case GSM48_CMODE_SPEECH_EFR: *payload_type = GsmL1_TchPlType_Efr; rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, rtp_pl_len); break; #endif case GSM48_CMODE_SPEECH_AMR: *payload_type = GsmL1_TchPlType_Amr; rc = rtppayload_to_l1_amr(l1_payload, rtp_pl, rtp_pl_len, lchan); break; default: /* we don't support CSD modes */ rc = -1; break; } if (rc < 0) { LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", gsm_lchan_name(lchan)); return; } *len = rc + 1; DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), osmo_hexdump(data, *len)); } static int is_recv_only(uint8_t speech_mode) { return (speech_mode & 0xF0) == (1 << 4); } /*! \brief receive a traffic L1 primitive for a given lchan */ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; uint8_t payload_type = data_ind->msgUnitParam.u8Buffer[0]; uint8_t *payload = data_ind->msgUnitParam.u8Buffer + 1; uint8_t payload_len; struct msgb *rmsg = NULL; struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; if (is_recv_only(lchan->abis_ip.speech_mode)) return -EAGAIN; if (data_ind->msgUnitParam.u8Size < 1) { LOGP(DL1C, LOGL_ERROR, "chan_nr %d Rx Payload size 0\n", chan_nr); return -EINVAL; } payload_len = data_ind->msgUnitParam.u8Size - 1; switch (payload_type) { case GsmL1_TchPlType_Fr: #if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) case GsmL1_TchPlType_Efr: #endif if (lchan->type != GSM_LCHAN_TCH_F) goto err_payload_match; break; case GsmL1_TchPlType_Hr: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; break; case GsmL1_TchPlType_Amr: if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F) goto err_payload_match; break; default: LOGP(DL1C, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n", gsm_lchan_name(lchan), get_value_string(femtobts_tch_pl_names, payload_type)); break; } switch (payload_type) { case GsmL1_TchPlType_Fr: rmsg = l1_to_rtppayload_fr(payload, payload_len); break; case GsmL1_TchPlType_Hr: rmsg = l1_to_rtppayload_hr(payload, payload_len); break; #if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) case GsmL1_TchPlType_Efr: rmsg = l1_to_rtppayload_efr(payload, payload_len); break; #endif case GsmL1_TchPlType_Amr: rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); break; } if (rmsg) { struct osmo_phsap_prim *l1sap; LOGP(DL1C, LOGL_DEBUG, "%s Rx -> RTP: %s\n", gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len)); /* add l1sap header */ rmsg->l2h = rmsg->data; msgb_push(rmsg, sizeof(*l1sap)); rmsg->l1h = rmsg->data; l1sap = msgb_l1sap_prim(rmsg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, rmsg); l1sap->u.tch.chan_nr = chan_nr; return l1sap_up(trx, l1sap); } return 0; err_payload_match: LOGP(DL1C, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n", gsm_lchan_name(lchan), get_value_string(femtobts_tch_pl_names, payload_type)); return -EINVAL; } struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan) { struct msgb *msg; GsmL1_Prim_t *l1p; GsmL1_PhDataReq_t *data_req; GsmL1_MsgUnitParam_t *msu_param; uint8_t *payload_type; uint8_t *l1_payload; msg = l1p_msgb_alloc(); if (!msg) return NULL; l1p = msgb_l1prim(msg); data_req = &l1p->u.phDataReq; msu_param = &data_req->msgUnitParam; payload_type = &msu_param->u8Buffer[0]; l1_payload = &msu_param->u8Buffer[1]; switch (lchan->tch_mode) { case GSM48_CMODE_SPEECH_AMR: *payload_type = GsmL1_TchPlType_Amr; if (lchan->tch.last_sid.len) { memcpy(l1_payload, lchan->tch.last_sid.buf, lchan->tch.last_sid.len); msu_param->u8Size = lchan->tch.last_sid.len+1; } else { /* FIXME: decide if we should send SPEECH_BAD or * SID_BAD */ #if 0 *payload_type = GsmL1_TchPlType_Amr_SidBad; memset(l1_payload, 0xFF, 5); msu_param->u8Size = 5 + 3; #else /* send an all-zero SID */ msu_param->u8Size = 8; #endif } break; default: msgb_free(msg); msg = NULL; break; } return msg; } osmo-bts-0.4.0/src/osmo-bts-sysmo/utils.c000066400000000000000000000073641260026426200202760ustar00rootroot00000000000000/* * Helper utilities that are used in OML * * (C) 2011-2013 by Harald Welte * (C) 2013 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "utils.h" #include #include #include #include "femtobts.h" #include "l1_if.h" int band_femto2osmo(GsmL1_FreqBand_t band) { switch (band) { case GsmL1_FreqBand_850: return GSM_BAND_850; case GsmL1_FreqBand_900: return GSM_BAND_900; case GsmL1_FreqBand_1800: return GSM_BAND_1800; case GsmL1_FreqBand_1900: return GSM_BAND_1900; default: return -1; } } static int band_osmo2femto(struct gsm_bts_trx *trx, enum gsm_band osmo_band) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); /* check if the TRX hardware actually supports the given band */ if (!(fl1h->hw_info.band_support & osmo_band)) return -1; /* if yes, convert from osmcoom style band definition to L1 band */ switch (osmo_band) { case GSM_BAND_850: return GsmL1_FreqBand_850; case GSM_BAND_900: return GsmL1_FreqBand_900; case GSM_BAND_1800: return GsmL1_FreqBand_1800; case GSM_BAND_1900: return GsmL1_FreqBand_1900; default: return -1; } } /** * Select the band that matches the ARFCN. In general the ARFCNs * for GSM1800 and GSM1900 overlap and one needs to specify the * rightband. When moving between GSM900/GSM1800 and GSM850/1900 * modifying the BTS configuration is a bit annoying. The auto-band * configuration allows to ease with this transition. */ int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn) { enum gsm_band band; struct gsm_bts *bts = trx->bts; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); if (!btsb->auto_band) return band_osmo2femto(trx, bts->band); /* * We need to check what will happen now. */ band = gsm_arfcn2band(arfcn); /* if we are already on the right band return */ if (band == bts->band) return band_osmo2femto(trx, bts->band); /* Check if it is GSM1800/GSM1900 */ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) return band_osmo2femto(trx, bts->band); /* * Now to the actual autobauding. We just want DCS/DCS and * PCS/PCS for PCS we check for 850/1800 though */ if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800) || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900) || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900)) return band_osmo2femto(trx, band); if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850) return band_osmo2femto(trx, GSM_BAND_1900); /* give up */ return -1; } int sysmobts_get_nominal_power(struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); switch (fl1h->hw_info.model_nr) { case 0: case 0xffff: /* old units have empty flash where the model number is * stored in later units */ case 1002: /* 200mW (23 dBm) nominal power */ return 23; case 2050: /* 5W(37dBm) per TRX. This could be raiesd to 10W(40dBm) * if the second TRX is not used. */ return 37; default: LOGP(DL1C, LOGL_ERROR, "Model number %u/0x%x not known.\n", fl1h->hw_info.model_nr, fl1h->hw_info.model_nr); break; } return -1; } osmo-bts-0.4.0/src/osmo-bts-sysmo/utils.h000066400000000000000000000004521260026426200202720ustar00rootroot00000000000000#ifndef SYSMOBTS_UTILS_H #define SYSMOBTS_UTILS_H #include #include "femtobts.h" struct gsm_bts_trx; int band_femto2osmo(GsmL1_FreqBand_t band); int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn); int sysmobts_get_nominal_power(struct gsm_bts_trx *trx); #endif osmo-bts-0.4.0/src/osmo-bts-trx/000077500000000000000000000000001260026426200164235ustar00rootroot00000000000000osmo-bts-0.4.0/src/osmo-bts-trx/Makefile.am000066400000000000000000000014721260026426200204630ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) -lortp EXTRA_DIST = trx_if.h l1_if.h scheduler.h gsm0503_parity.h gsm0503_conv.h gsm0503_interleaving.h gsm0503_mapping.h gsm0503_coding.h gsm0503_tables.h loops.h amr.h bin_PROGRAMS = osmobts-trx osmobts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler.c trx_vty.c gsm0503_parity.c gsm0503_conv.c gsm0503_interleaving.c gsm0503_mapping.c gsm0503_coding.c gsm0503_tables.c loops.c amr.c osmobts_trx_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) osmo-bts-0.4.0/src/osmo-bts-trx/amr.c000066400000000000000000000034231260026426200173500ustar00rootroot00000000000000/* AMR support for OsmoBTS-TRX */ /* (C) 2013 by Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 static int amr_len_by_ft[16] = { 12, 13, 15, 17, 19, 20, 26, 31, 0, 0, 0, 0, 0, 0, 0, 0 }; int amr_decompose_payload(uint8_t *payload, int payload_len, uint8_t *_cmr, uint8_t *_ft, uint8_t *_bfi) { uint8_t cmr, f, ft, q; if (payload_len < 2) return -EINVAL; cmr = payload[0] >> 4; if (_cmr) *_cmr = cmr; f = payload[1] >> 7; ft = (payload[1] >> 3) & 0xf; if (_ft) *_ft = ft; q = (payload[1] >> 2) & 0x1; if (_bfi) *_bfi = !q; if (f) { fprintf(stderr, "%s: multiple payloads not supported\n", __func__); return -ENOTSUP; } if (payload_len - 2 < amr_len_by_ft[ft]) return -EINVAL; return 2 + amr_len_by_ft[ft]; } int amr_compose_payload(uint8_t *payload, uint8_t cmr, uint8_t ft, uint8_t bfi) { if (cmr >= 16) return -EINVAL; if (ft >= 16) return -EINVAL; payload[0] = cmr << 4; payload[1] = (ft << 3) | ((!bfi) << 2); /* F = 0 */ return 2 + amr_len_by_ft[ft]; } osmo-bts-0.4.0/src/osmo-bts-trx/amr.h000066400000000000000000000003741260026426200173570ustar00rootroot00000000000000#ifndef _TRX_AMR_H #define _TRX_AMR_H int amr_decompose_payload(uint8_t *payload, int payload_len, uint8_t *_cmr, uint8_t *_ft, uint8_t *_bfi); int amr_compose_payload(uint8_t *payload, uint8_t cmr, uint8_t ft, uint8_t bfi); #endif /* _TRX_AMR_H */ osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_coding.c000066400000000000000000001053511260026426200212150ustar00rootroot00000000000000/* (C) 2013 by Andreas Eversberg * (C) 2015 by Alexander Chemeris * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include "gsm0503_conv.h" #include "gsm0503_parity.h" #include "gsm0503_mapping.h" #include "gsm0503_interleaving.h" #include "gsm0503_tables.h" #include "gsm0503_coding.h" int osmo_conv_decode_ber(const struct osmo_conv_code *code, const sbit_t *input, ubit_t *output, int *n_errors, int *n_bits_total) { int res, i; ubit_t recoded[1024]; /* TODO: We can do smaller, I guess */ res = osmo_conv_decode(code, input, output); *n_bits_total = osmo_conv_encode(code, output, recoded); OSMO_ASSERT(sizeof(recoded)/sizeof(recoded[0]) >= *n_bits_total); /* Count bit errors */ *n_errors = 0; for (i=0; i< *n_bits_total; i++) { if (! ((recoded[i] && input[i]<0) || (!recoded[i] && input[i]>0)) ) *n_errors += 1; } return res; } static int _xcch_decode_cB(uint8_t *l2_data, sbit_t *cB, int *n_errors, int *n_bits_total) { ubit_t conv[224]; int rv; osmo_conv_decode_ber(&gsm0503_conv_xcch, cB, conv, n_errors, n_bits_total); rv = osmo_crc64gen_check_bits(&gsm0503_fire_crc40, conv, 184, conv+184); if (rv) return -1; osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 184, 1); return 0; } static int _xcch_encode_cB(ubit_t *cB, uint8_t *l2_data) { ubit_t conv[224]; osmo_pbit2ubit_ext(conv, 0, l2_data, 0, 184, 1); osmo_crc64gen_set_bits(&gsm0503_fire_crc40, conv, 184, conv+184); osmo_conv_encode(&gsm0503_conv_xcch, conv, cB); return 0; } /* * GSM xCCH block transcoding */ int xcch_decode(uint8_t *l2_data, sbit_t *bursts, int *n_errors, int *n_bits_total) { sbit_t iB[456], cB[456]; int i; for (i=0; i<4; i++) gsm0503_xcch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, NULL); gsm0503_xcch_deinterleave(cB, iB); return _xcch_decode_cB(l2_data, cB, n_errors, n_bits_total); } int xcch_encode(ubit_t *bursts, uint8_t *l2_data) { ubit_t iB[456], cB[456], hl = 1, hn = 1; int i; _xcch_encode_cB(cB, l2_data); gsm0503_xcch_interleave(cB, iB); for (i=0; i<4; i++) gsm0503_xcch_burst_map(&iB[i * 114], &bursts[i * 116], &hl, &hn); return 0; } /* * GSM PDTCH block transcoding */ int pdtch_decode(uint8_t *l2_data, sbit_t *bursts, uint8_t *usf_p, int *n_errors, int *n_bits_total) { sbit_t iB[456], cB[676], hl_hn[8]; ubit_t conv[456]; int i, j, k, rv, best = 0, cs = 0, usf = 0; /* make GCC happy */ for (i=0; i<4; i++) gsm0503_xcch_burst_unmap(&iB[i * 114], &bursts[i * 116], hl_hn + i*2, hl_hn + i*2 + 1); for (i=0; i<4; i++) { for (j=0, k=0; j<8; j++) k += abs(((int)gsm0503_pdtch_hl_hn_sbit[i][j]) - ((int)hl_hn[j])); if (i == 0 || k < best) { best = k; cs = i+1; } } gsm0503_xcch_deinterleave(cB, iB); switch (cs) { case 1: osmo_conv_decode_ber(&gsm0503_conv_xcch, cB, conv, n_errors, n_bits_total); rv = osmo_crc64gen_check_bits(&gsm0503_fire_crc40, conv, 184, conv+184); if (rv) return -1; osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 184, 1); return 23; case 2: for (i=587, j=455; i>=0; i--) if (!gsm0503_puncture_cs2[i]) cB[i] = cB[j--]; else cB[i] = 0; osmo_conv_decode_ber(&gsm0503_conv_cs2, cB, conv, n_errors, n_bits_total); for (i=0; i<8; i++) { for (j=0, k=0; j<6; j++) k += abs(((int)gsm0503_usf2six[i][j]) - ((int)conv[j])); if (i == 0 || k < best) { best = k; usf = i; } } conv[3] = usf & 1; conv[4] = (usf >> 1) & 1; conv[5] = (usf >> 2) & 1; if (usf_p) *usf_p = usf; rv = osmo_crc16gen_check_bits(&gsm0503_cs234_crc16, conv+3, 271, conv+3+271); if (rv) return -1; osmo_ubit2pbit_ext(l2_data, 0, conv, 3, 271, 1); return 34; case 3: for (i=675, j=455; i>=0; i--) if (!gsm0503_puncture_cs3[i]) cB[i] = cB[j--]; else cB[i] = 0; osmo_conv_decode_ber(&gsm0503_conv_cs3, cB, conv, n_errors, n_bits_total); for (i=0; i<8; i++) { for (j=0, k=0; j<6; j++) k += abs(((int)gsm0503_usf2six[i][j]) - ((int)conv[j])); if (i == 0 || k < best) { best = k; usf = i; } } conv[3] = usf & 1; conv[4] = (usf >> 1) & 1; conv[5] = (usf >> 2) & 1; if (usf_p) *usf_p = usf; rv = osmo_crc16gen_check_bits(&gsm0503_cs234_crc16, conv+3, 315, conv+3+315); if (rv) return -1; osmo_ubit2pbit_ext(l2_data, 0, conv, 3, 315, 1); return 40; case 4: for (i=12; i<456;i++) conv[i] = (cB[i] < 0) ? 1:0; for (i=0; i<8; i++) { for (j=0, k=0; j<12; j++) k += abs(((int)gsm0503_usf2twelve_sbit[i][j]) - ((int)cB[j])); if (i == 0 || k < best) { best = k; usf = i; } } conv[9] = usf & 1; conv[10] = (usf >> 1) & 1; conv[11] = (usf >> 2) & 1; if (usf_p) *usf_p = usf; rv = osmo_crc16gen_check_bits(&gsm0503_cs234_crc16, conv+9, 431, conv+9+431); if (rv) { *n_bits_total = 456-12; *n_errors = *n_bits_total; return -1; } *n_bits_total = 456-12; *n_errors = 0; osmo_ubit2pbit_ext(l2_data, 0, conv, 9, 431, 1); return 54; default: *n_bits_total = 0; *n_errors = 0; break; } return -1; } int pdtch_encode(ubit_t *bursts, uint8_t *l2_data, uint8_t l2_len) { ubit_t iB[456], cB[676]; const ubit_t *hl_hn; ubit_t conv[334]; int i, j, usf; switch (l2_len) { case 23: osmo_pbit2ubit_ext(conv, 0, l2_data, 0, 184, 1); osmo_crc64gen_set_bits(&gsm0503_fire_crc40, conv, 184, conv+184); osmo_conv_encode(&gsm0503_conv_xcch, conv, cB); hl_hn = gsm0503_pdtch_hl_hn_ubit[0]; break; case 34: osmo_pbit2ubit_ext(conv, 3, l2_data, 0, 271, 1); usf = l2_data[0] & 0x7; osmo_crc16gen_set_bits(&gsm0503_cs234_crc16, conv+3, 271, conv+3+271); memcpy(conv, gsm0503_usf2six[usf], 6); osmo_conv_encode(&gsm0503_conv_cs2, conv, cB); for (i=0, j=0; i<588; i++) if (!gsm0503_puncture_cs2[i]) cB[j++] = cB[i]; hl_hn = gsm0503_pdtch_hl_hn_ubit[1]; break; case 40: osmo_pbit2ubit_ext(conv, 3, l2_data, 0, 315, 1); usf = l2_data[0] & 0x7; osmo_crc16gen_set_bits(&gsm0503_cs234_crc16, conv+3, 315, conv+3+315); memcpy(conv, gsm0503_usf2six[usf], 6); osmo_conv_encode(&gsm0503_conv_cs3, conv, cB); for (i=0, j=0; i<676; i++) if (!gsm0503_puncture_cs3[i]) cB[j++] = cB[i]; hl_hn = gsm0503_pdtch_hl_hn_ubit[2]; break; case 54: osmo_pbit2ubit_ext(cB, 9, l2_data, 0, 431, 1); usf = l2_data[0] & 0x7; osmo_crc16gen_set_bits(&gsm0503_cs234_crc16, cB+9, 431, cB+9+431); memcpy(cB, gsm0503_usf2twelve_ubit[usf], 12); hl_hn = gsm0503_pdtch_hl_hn_ubit[3]; break; default: return -1; } gsm0503_xcch_interleave(cB, iB); for (i=0; i<4; i++) gsm0503_xcch_burst_map(&iB[i * 114], &bursts[i * 116], hl_hn + i*2, hl_hn + i*2 + 1); return 0; } /* * GSM TCH/F FR/EFR transcoding */ static void tch_fr_reassemble(uint8_t *tch_data, ubit_t *b_bits, int net_order) { int i, j, k, l, o; tch_data[0] = 0xd << 4; memset(tch_data + 1, 0, 32); if (net_order) { i = 0; /* counts bits */ j = 4; /* counts output bits */ while (i < 260) { tch_data[j>>3] |= (b_bits[i] << (7-(j&7))); i++; j++; } return; } /* reassemble d-bits */ i = 0; /* counts bits */ j = 4; /* counts output bits */ k = gsm0503_gsm_fr_map[0]-1; /* current number bit in element */ l = 0; /* counts element bits */ o = 0; /* offset input bits */ while (i < 260) { tch_data[j>>3] |= (b_bits[k+o] << (7-(j&7))); if (--k < 0) { o += gsm0503_gsm_fr_map[l]; k = gsm0503_gsm_fr_map[++l]-1; } i++; j++; } } static void tch_fr_disassemble(ubit_t *b_bits, uint8_t *tch_data, int net_order) { int i, j, k, l, o; if (net_order) { i = 0; /* counts bits */ j = 4; /* counts output bits */ while (i < 260) { b_bits[i] = (tch_data[j>>3] >> (7-(j&7))) & 1; i++; j++; } return; } i = 0; /* counts bits */ j = 4; /* counts input bits */ k = gsm0503_gsm_fr_map[0]-1; /* current number bit in element */ l = 0; /* counts element bits */ o = 0; /* offset output bits */ while (i < 260) { b_bits[k+o] = (tch_data[j>>3] >> (7-(j&7))) & 1; if (--k < 0) { o += gsm0503_gsm_fr_map[l]; k = gsm0503_gsm_fr_map[++l]-1; } i++; j++; } } static void tch_hr_reassemble(uint8_t *tch_data, ubit_t *b_bits) { int i, j; tch_data[0] = 0x00; /* F = 0, FT = 000 */ memset(tch_data + 1, 0, 14); i = 0; /* counts bits */ j = 8; /* counts output bits */ while (i < 112) { tch_data[j>>3] |= (b_bits[i] << (7-(j&7))); i++; j++; } } static void tch_hr_disassemble(ubit_t *b_bits, uint8_t *tch_data) { int i, j; i = 0; /* counts bits */ j = 8; /* counts output bits */ while (i < 112) { b_bits[i] = (tch_data[j>>3] >> (7-(j&7))) & 1; i++; j++; } } static void tch_efr_reassemble(uint8_t *tch_data, ubit_t *b_bits) { int i, j; tch_data[0] = 0xc << 4; memset(tch_data + 1, 0, 30); i = 0; /* counts bits */ j = 4; /* counts output bits */ while (i < 244) { tch_data[j>>3] |= (b_bits[i] << (7-(j&7))); i++; j++; } } static void tch_efr_disassemble(ubit_t *b_bits, uint8_t *tch_data) { int i, j; i = 0; /* counts bits */ j = 4; /* counts output bits */ while (i < 244) { b_bits[i] = (tch_data[j>>3] >> (7-(j&7))) & 1; i++; j++; } } static void tch_amr_reassemble(uint8_t *tch_data, ubit_t *d_bits, int len) { int i, j; memset(tch_data, 0, (len + 7) >> 3); i = 0; /* counts bits */ j = 0; /* counts output bits */ while (i < len) { tch_data[j>>3] |= (d_bits[i] << (7-(j&7))); i++; j++; } } static void tch_amr_disassemble(ubit_t *d_bits, uint8_t *tch_data, int len) { int i, j; i = 0; /* counts bits */ j = 0; /* counts output bits */ while (i < len) { d_bits[i] = (tch_data[j>>3] >> (7-(j&7))) & 1; i++; j++; } } static void tch_fr_d_to_b(ubit_t *b_bits, ubit_t *d_bits) { int i; for (i = 0; i < 260; i++) b_bits[gsm610_bitorder[i]] = d_bits[i]; } static void tch_fr_b_to_d(ubit_t *d_bits, ubit_t *b_bits) { int i; for (i = 0; i < 260; i++) d_bits[i] = b_bits[gsm610_bitorder[i]]; } static void tch_hr_d_to_b(ubit_t *b_bits, ubit_t *d_bits) { int i; const uint16_t *map; if (!d_bits[93] && !d_bits[94]) map = gsm620_unvoiced_bitorder; else map = gsm620_voiced_bitorder; for (i = 0; i < 112; i++) b_bits[map[i]] = d_bits[i]; } static void tch_hr_b_to_d(ubit_t *d_bits, ubit_t *b_bits) { int i; const uint16_t *map; if (!b_bits[34] && !b_bits[35]) map = gsm620_unvoiced_bitorder; else map = gsm620_voiced_bitorder; for (i = 0; i < 112; i++) d_bits[i] = b_bits[map[i]]; } static void tch_efr_d_to_w(ubit_t *b_bits, ubit_t *d_bits) { int i; for (i = 0; i < 260; i++) b_bits[gsm660_bitorder[i]] = d_bits[i]; } static void tch_efr_w_to_d(ubit_t *d_bits, ubit_t *b_bits) { int i; for (i = 0; i < 260; i++) d_bits[i] = b_bits[gsm660_bitorder[i]]; } static void tch_efr_protected(ubit_t *s_bits, ubit_t *b_bits) { int i; for (i = 0; i < 65; i++) b_bits[i] = s_bits[gsm0503_gsm_efr_protected_bits[i]-1]; } static void tch_fr_unreorder(ubit_t *d, ubit_t *p, ubit_t *u) { int i; for (i=0; i<91; i++) { d[i<<1] = u[i]; d[(i<<1)+1] = u[184-i]; } for (i=0; i<3; i++) p[i] = u[91+i]; } static void tch_fr_reorder(ubit_t *u, ubit_t *d, ubit_t *p) { int i; for (i=0; i<91; i++) { u[i] = d[i<<1]; u[184-i] = d[(i<<1)+1]; } for (i=0; i<3; i++) u[91+i] = p[i]; } static void tch_hr_unreorder(ubit_t *d, ubit_t *p, ubit_t *u) { memcpy(d, u, 95); memcpy(p, u+95, 3); } static void tch_hr_reorder(ubit_t *u, ubit_t *d, ubit_t *p) { memcpy(u, d, 95); memcpy(u+95, p, 3); } static void tch_efr_reorder(ubit_t *w, ubit_t *s, ubit_t *p) { memcpy(w, s, 71); w[71] = w[72] = s[69]; memcpy(w+73, s+71, 50); w[123] = w[124] = s[119]; memcpy(w+125, s+121, 53); w[178] = w[179] = s[172]; memcpy(w+180, s+174, 50); w[230] = w[231] = s[222]; memcpy(w+232, s+224, 20); memcpy(w+252, p, 8); } static void tch_efr_unreorder(ubit_t *s, ubit_t *p, ubit_t *w) { int sum; memcpy(s, w, 71); sum = s[69] + w[71] + w[72]; s[69] = (sum > 2); memcpy(s+71, w+73, 50); sum = s[119] + w[123] + w[124]; s[119] = (sum > 2); memcpy(s+121, w+125, 53); sum = s[172] + w[178] + w[179]; s[172] = (sum > 2); memcpy(s+174, w+180, 50); sum = s[220] + w[230] + w[231]; s[222] = (sum > 2); memcpy(s+224, w+232, 20); memcpy(p, w+252, 8); } static void tch_amr_merge(ubit_t *u, ubit_t *d, ubit_t *p, int len, int prot) { memcpy(u, d, prot); memcpy(u+prot, p, 6); memcpy(u+prot+6, d+prot, len-prot); } static void tch_amr_unmerge(ubit_t *d, ubit_t *p, ubit_t *u, int len, int prot) { memcpy(d, u, prot); memcpy(p, u+prot, 6); memcpy(d+prot, u+prot+6, len-prot); } int tch_fr_decode(uint8_t *tch_data, sbit_t *bursts, int net_order, int efr, int *n_errors, int *n_bits_total) { sbit_t iB[912], cB[456], h; ubit_t conv[185], s[244], w[260], b[65], d[260], p[8]; int i, rv, len, steal = 0; for (i=0; i<8; i++) { gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], &h, i>>2); steal -= h; } gsm0503_tch_fr_deinterleave(cB, iB); if (steal > 0) { rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_fr_decode(): error decoding FACCH frame (%d/%d bits)\n", *n_errors, *n_bits_total); return -1; } return 23; } osmo_conv_decode_ber(&gsm0503_conv_tch_fr, cB, conv, n_errors, n_bits_total); tch_fr_unreorder(d, p, conv); for (i=0; i<78; i++) d[i+182] = (cB[i+378] < 0) ? 1:0; rv = osmo_crc8gen_check_bits(&gsm0503_tch_fr_crc3, d, 50, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_fr_decode(): error checking CRC8 for the FR part of an %s frame\n", efr?"EFR":"FR"); return -1; } if (efr) { tch_efr_d_to_w(w, d); tch_efr_unreorder(s, p, w); tch_efr_protected(s, b); rv = osmo_crc8gen_check_bits(&gsm0503_tch_efr_crc8, b, 65, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_fr_decode(): error checking CRC8 for the EFR part of an EFR frame\n"); return -1; } tch_efr_reassemble(tch_data, s); len = 31; } else { tch_fr_d_to_b(w, d); tch_fr_reassemble(tch_data, w, net_order); len = 33; } return len; } int tch_fr_encode(ubit_t *bursts, uint8_t *tch_data, int len, int net_order) { ubit_t iB[912], cB[456], h; ubit_t conv[185], w[260], b[65], s[244], d[260], p[8]; int i; switch (len) { case 31: /* TCH EFR */ tch_efr_disassemble(s, tch_data); tch_efr_protected(s, b); osmo_crc8gen_set_bits(&gsm0503_tch_efr_crc8, b, 65, p); tch_efr_reorder(w, s, p); tch_efr_w_to_d(d, w); goto coding_efr_fr; case 33: /* TCH FR */ tch_fr_disassemble(w, tch_data, net_order); tch_fr_b_to_d(d, w); coding_efr_fr: osmo_crc8gen_set_bits(&gsm0503_tch_fr_crc3, d, 50, p); tch_fr_reorder(conv, d, p); memcpy(cB+378, d+182, 78); osmo_conv_encode(&gsm0503_conv_tch_fr, conv, cB); h = 0; break; case 23: /* FACCH */ _xcch_encode_cB(cB, tch_data); h = 1; break; default: return -1; } gsm0503_tch_fr_interleave(cB, iB); for (i=0; i<8; i++) gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>2); return 0; } int tch_hr_decode(uint8_t *tch_data, sbit_t *bursts, int odd, int *n_errors, int *n_bits_total) { sbit_t iB[912], cB[456], h; ubit_t conv[98], b[112], d[112], p[3]; int i, rv, steal = 0; /* only unmap the stealing bits */ if (!odd) { for (i=0; i<4; i++) { gsm0503_tch_burst_unmap(NULL, &bursts[i * 116], &h, 0); steal -= h; } for (i=2; i<5; i++) { gsm0503_tch_burst_unmap(NULL, &bursts[i * 116], &h, 1); steal -= h; } } /* if we found a stole FACCH, but only at correct alignment */ if (steal > 0) { for (i=0; i<6; i++) gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i>>2); for (i=2; i<4; i++) gsm0503_tch_burst_unmap(&iB[i * 114 + 456], &bursts[i * 116], NULL, 1); gsm0503_tch_fr_deinterleave(cB, iB); rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_hr_decode(): error decoding FACCH frame (%d/%d bits)\n", *n_errors, *n_bits_total); return -1; } return 23; } for (i=0; i<4; i++) gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i>>1); gsm0503_tch_hr_deinterleave(cB, iB); osmo_conv_decode_ber(&gsm0503_conv_tch_hr, cB, conv, n_errors, n_bits_total); tch_hr_unreorder(d, p, conv); for (i=0; i<17; i++) d[i+95] = (cB[i+211] < 0) ? 1:0; rv = osmo_crc8gen_check_bits(&gsm0503_tch_fr_crc3, d + 73, 22, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_fr_decode(): error checking CRC8 for an HR frame\n"); return -1; } tch_hr_d_to_b(b, d); tch_hr_reassemble(tch_data, b); return 15; } int tch_hr_encode(ubit_t *bursts, uint8_t *tch_data, int len) { ubit_t iB[912], cB[456], h; ubit_t conv[98], b[112], d[112], p[3]; int i; switch (len) { case 15: /* TCH HR */ tch_hr_disassemble(b, tch_data); tch_hr_b_to_d(d, b); osmo_crc8gen_set_bits(&gsm0503_tch_fr_crc3, d + 73, 22, p); tch_hr_reorder(conv, d, p); osmo_conv_encode(&gsm0503_conv_tch_hr, conv, cB); memcpy(cB+211, d+95, 17); h = 0; gsm0503_tch_hr_interleave(cB, iB); for (i=0; i<4; i++) gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>1); break; case 23: /* FACCH */ _xcch_encode_cB(cB, tch_data); h = 1; gsm0503_tch_fr_interleave(cB, iB); for (i=0; i<6; i++) gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>2); for (i=2; i<4; i++) gsm0503_tch_burst_map(&iB[i * 114 + 456], &bursts[i * 116], &h, 1); break; default: return -1; } return 0; } int tch_afs_decode(uint8_t *tch_data, sbit_t *bursts, int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft, uint8_t *cmr, int *n_errors, int *n_bits_total) { sbit_t iB[912], cB[456], h; ubit_t d[244], p[6], conv[250]; int i, j, k, best = 0, rv, len, steal = 0, id = 0; *n_errors = 0; *n_bits_total = 0; for (i=0; i<8; i++) { gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], &h, i>>2); steal -= h; } gsm0503_tch_fr_deinterleave(cB, iB); if (steal > 0) { rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error decoding FACCH frame (%d/%d bits)\n", *n_errors, *n_bits_total); return -1; } return 23; } for (i=0; i<4; i++) { for (j=0, k=0; j<8; j++) k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j])); if (i == 0 || k < best) { best = k; id = i; } } /* check if indicated codec fits into range of codecs */ if (id >= codecs) { /* codec mode out of range, return id */ return id; } switch ((codec_mode_req) ? codec[*ft] : codec[id]) { case 7: /* TCH/AFS12.2 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_12_2, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 244, 81); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 81, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 12.2 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 244); len = 31; break; case 6: /* TCH/AFS10.2 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_10_2, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 204, 65); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 65, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 10.2 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 204); len = 26; break; case 5: /* TCH/AFS7.95 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_7_95, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 159, 75); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 75, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 7.95 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 159); len = 20; break; case 4: /* TCH/AFS7.4 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_7_4, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 148, 61); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 61, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 7.4 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 148); len = 19; break; case 3: /* TCH/AFS6.7 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_6_7, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 134, 55); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 55, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 6.7 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 134); len = 17; break; case 2: /* TCH/AFS5.9 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_5_9, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 118, 55); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 55, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 5.9 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 118); len = 15; break; case 1: /* TCH/AFS5.15 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_5_15, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 103, 49); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 49, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 5.15 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 103); len = 13; break; case 0: /* TCH/AFS4.75 */ osmo_conv_decode_ber(&gsm0503_conv_tch_afs_4_75, cB+8, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 95, 39); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 39, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_afs_decode(): error checking CRC8 for an AMR 4.75 frame\n"); return -1; } tch_amr_reassemble(tch_data, d, 95); len = 12; break; default: LOGP(DL1C, LOGL_ERROR, "tch_afs_decode(): Unknown frame type\n"); fprintf(stderr, "FIXME: FT %d not supported!\n", *ft); *n_bits_total = 448; *n_errors = *n_bits_total; return -1; } /* change codec request / indication, if frame is valid */ if (codec_mode_req) *cmr = id; else *ft = id; return len; } int tch_afs_encode(ubit_t *bursts, uint8_t *tch_data, int len, int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft, uint8_t cmr) { ubit_t iB[912], cB[456], h; ubit_t d[244], p[6], conv[250]; int i; uint8_t id; if (len == 23) { /* FACCH */ _xcch_encode_cB(cB, tch_data); h = 1; goto facch; } h = 0; if (codec_mode_req) { if (cmr >= codecs) { fprintf(stderr, "FIXME: CMR ID %d not in codec list!\n", cmr); return -1; } id = cmr; } else { if (ft >= codecs) { fprintf(stderr, "FIXME: FT ID %d not in codec list!\n", ft); return -1; } id = ft; } switch (codec[ft]) { case 7: /* TCH/AFS12.2 */ if (len != 31) { invalid_length: fprintf(stderr, "FIXME: payload length %d does not " "comply with codec type %d!\n", len, ft); return -1; } tch_amr_disassemble(d, tch_data, 244); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 81, p); tch_amr_merge(conv, d, p, 244, 81); osmo_conv_encode(&gsm0503_conv_tch_afs_12_2, conv, cB+8); break; case 6: /* TCH/AFS10.2 */ if (len != 26) goto invalid_length; tch_amr_disassemble(d, tch_data, 204); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 65, p); tch_amr_merge(conv, d, p, 204, 65); osmo_conv_encode(&gsm0503_conv_tch_afs_10_2, conv, cB+8); break; case 5: /* TCH/AFS7.95 */ if (len != 20) goto invalid_length; tch_amr_disassemble(d, tch_data, 159); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 75, p); tch_amr_merge(conv, d, p, 159, 75); osmo_conv_encode(&gsm0503_conv_tch_afs_7_95, conv, cB+8); break; case 4: /* TCH/AFS7.4 */ if (len != 19) goto invalid_length; tch_amr_disassemble(d, tch_data, 148); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p); tch_amr_merge(conv, d, p, 148, 61); osmo_conv_encode(&gsm0503_conv_tch_afs_7_4, conv, cB+8); break; case 3: /* TCH/AFS6.7 */ if (len != 17) goto invalid_length; tch_amr_disassemble(d, tch_data, 134); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); tch_amr_merge(conv, d, p, 134, 55); osmo_conv_encode(&gsm0503_conv_tch_afs_6_7, conv, cB+8); break; case 2: /* TCH/AFS5.9 */ if (len != 15) goto invalid_length; tch_amr_disassemble(d, tch_data, 118); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); tch_amr_merge(conv, d, p, 118, 55); osmo_conv_encode(&gsm0503_conv_tch_afs_5_9, conv, cB+8); break; case 1: /* TCH/AFS5.15 */ if (len != 13) goto invalid_length; tch_amr_disassemble(d, tch_data, 103); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p); tch_amr_merge(conv, d, p, 103, 49); osmo_conv_encode(&gsm0503_conv_tch_afs_5_15, conv, cB+8); break; case 0: /* TCH/AFS4.75 */ if (len != 12) goto invalid_length; tch_amr_disassemble(d, tch_data, 95); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p); tch_amr_merge(conv, d, p, 95, 39); osmo_conv_encode(&gsm0503_conv_tch_afs_4_75, conv, cB+8); break; default: fprintf(stderr, "FIXME: FT %d not supported!\n", ft); return -1; } memcpy(cB, gsm0503_afs_ic_ubit[id], 8); facch: gsm0503_tch_fr_interleave(cB, iB); for (i=0; i<8; i++) gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>2); return 0; } int tch_ahs_decode(uint8_t *tch_data, sbit_t *bursts, int odd, int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft, uint8_t *cmr, int *n_errors, int *n_bits_total) { sbit_t iB[912], cB[456], h; ubit_t d[244], p[6], conv[135]; int i, j, k, best = 0, rv, len, steal = 0, id = 0; /* only unmap the stealing bits */ if (!odd) { for (i=0; i<4; i++) { gsm0503_tch_burst_unmap(NULL, &bursts[i * 116], &h, 0); steal -= h; } for (i=2; i<5; i++) { gsm0503_tch_burst_unmap(NULL, &bursts[i * 116], &h, 1); steal -= h; } } /* if we found a stole FACCH, but only at correct alignment */ if (steal > 0) { for (i=0; i<6; i++) gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i>>2); for (i=2; i<4; i++) gsm0503_tch_burst_unmap(&iB[i * 114 + 456], &bursts[i * 116], NULL, 1); gsm0503_tch_fr_deinterleave(cB, iB); rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error decoding FACCH frame (%d/%d bits)\n", *n_errors, *n_bits_total); return -1; } return 23; } for (i=0; i<4; i++) gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i>>1); gsm0503_tch_hr_deinterleave(cB, iB); for (i=0; i<4; i++) { for (j=0, k=0; j<4; j++) k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j])); if (i == 0 || k < best) { best = k; id = i; } } /* check if indicated codec fits into range of codecs */ if (id >= codecs) { /* codec mode out of range, return id */ return id; } switch ((codec_mode_req) ? codec[*ft] : codec[id]) { case 5: /* TCH/AHS7.95 */ osmo_conv_decode_ber(&gsm0503_conv_tch_ahs_7_95, cB+4, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 123, 67); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 67, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error checking CRC8 for an AMR 7.95 frame\n"); return -1; } for (i=0; i<36;i++) d[i+123] = (cB[i+192] < 0) ? 1:0; tch_amr_reassemble(tch_data, d, 159); len = 20; break; case 4: /* TCH/AHS7.4 */ osmo_conv_decode_ber(&gsm0503_conv_tch_ahs_7_4, cB+4, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 120, 61); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 61, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error checking CRC8 for an AMR 7.4 frame\n"); return -1; } for (i=0; i<28;i++) d[i+120] = (cB[i+200] < 0) ? 1:0; tch_amr_reassemble(tch_data, d, 148); len = 19; break; case 3: /* TCH/AHS6.7 */ osmo_conv_decode_ber(&gsm0503_conv_tch_ahs_6_7, cB+4, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 110, 55); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 55, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error checking CRC8 for an AMR 6.7 frame\n"); return -1; } for (i=0; i<24;i++) d[i+110] = (cB[i+204] < 0) ? 1:0; tch_amr_reassemble(tch_data, d, 134); len = 17; break; case 2: /* TCH/AHS5.9 */ osmo_conv_decode_ber(&gsm0503_conv_tch_ahs_5_9, cB+4, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 102, 55); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 55, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error checking CRC8 for an AMR 5.9 frame\n"); return -1; } for (i=0; i<16;i++) d[i+102] = (cB[i+212] < 0) ? 1:0; tch_amr_reassemble(tch_data, d, 118); len = 15; break; case 1: /* TCH/AHS5.15 */ osmo_conv_decode_ber(&gsm0503_conv_tch_ahs_5_15, cB+4, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 91, 49); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 49, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error checking CRC8 for an AMR 5.15 frame\n"); return -1; } for (i=0; i<12;i++) d[i+91] = (cB[i+216] < 0) ? 1:0; tch_amr_reassemble(tch_data, d, 103); len = 13; break; case 0: /* TCH/AHS4.75 */ osmo_conv_decode_ber(&gsm0503_conv_tch_ahs_4_75, cB+4, conv, n_errors, n_bits_total); tch_amr_unmerge(d, p, conv, 83, 39); rv = osmo_crc8gen_check_bits(&gsm0503_amr_crc6, d, 39, p); if (rv) { LOGP(DL1C, LOGL_NOTICE, "tch_ahs_decode(): error checking CRC8 for an AMR 4.75 frame\n"); return -1; } for (i=0; i<12;i++) d[i+83] = (cB[i+216] < 0) ? 1:0; tch_amr_reassemble(tch_data, d, 95); len = 12; break; default: LOGP(DL1C, LOGL_ERROR, "tch_afs_decode(): Unknown frame type\n"); fprintf(stderr, "FIXME: FT %d not supported!\n", *ft); *n_bits_total = 159; *n_errors = *n_bits_total; return -1; } /* change codec request / indication, if frame is valid */ if (codec_mode_req) *cmr = id; else *ft = id; return len; } int tch_ahs_encode(ubit_t *bursts, uint8_t *tch_data, int len, int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft, uint8_t cmr) { ubit_t iB[912], cB[456], h; ubit_t d[244], p[6], conv[135]; int i; uint8_t id; if (len == 23) { /* FACCH */ _xcch_encode_cB(cB, tch_data); h = 1; gsm0503_tch_fr_interleave(cB, iB); for (i=0; i<6; i++) gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>2); for (i=2; i<4; i++) gsm0503_tch_burst_map(&iB[i * 114 + 456], &bursts[i * 116], &h, 1); return 0; } h = 0; if (codec_mode_req) { if (cmr >= codecs) { fprintf(stderr, "FIXME: CMR ID %d not in codec list!\n", cmr); return -1; } id = cmr; } else { if (ft >= codecs) { fprintf(stderr, "FIXME: FT ID %d not in codec list!\n", ft); return -1; } id = ft; } switch (codec[ft]) { case 5: /* TCH/AHS7.95 */ if (len != 20) { invalid_length: fprintf(stderr, "FIXME: payload length %d does not " "comply with codec type %d!\n", len, ft); return -1; } tch_amr_disassemble(d, tch_data, 159); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 67, p); tch_amr_merge(conv, d, p, 123, 67); osmo_conv_encode(&gsm0503_conv_tch_ahs_7_95, conv, cB+4); memcpy(cB+192, d+123, 36); break; case 4: /* TCH/AHS7.4 */ if (len != 19) goto invalid_length; tch_amr_disassemble(d, tch_data, 148); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p); tch_amr_merge(conv, d, p, 120, 61); osmo_conv_encode(&gsm0503_conv_tch_ahs_7_4, conv, cB+4); memcpy(cB+200, d+120, 28); break; case 3: /* TCH/AHS6.7 */ if (len != 17) goto invalid_length; tch_amr_disassemble(d, tch_data, 134); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); tch_amr_merge(conv, d, p, 110, 55); osmo_conv_encode(&gsm0503_conv_tch_ahs_6_7, conv, cB+4); memcpy(cB+204, d+110, 24); break; case 2: /* TCH/AHS5.9 */ if (len != 15) goto invalid_length; tch_amr_disassemble(d, tch_data, 118); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); tch_amr_merge(conv, d, p, 102, 55); osmo_conv_encode(&gsm0503_conv_tch_ahs_5_9, conv, cB+4); memcpy(cB+212, d+102, 16); break; case 1: /* TCH/AHS5.15 */ if (len != 13) goto invalid_length; tch_amr_disassemble(d, tch_data, 103); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p); tch_amr_merge(conv, d, p, 91, 49); osmo_conv_encode(&gsm0503_conv_tch_ahs_5_15, conv, cB+4); memcpy(cB+216, d+91, 12); break; case 0: /* TCH/AHS4.75 */ if (len != 12) goto invalid_length; tch_amr_disassemble(d, tch_data, 95); osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p); tch_amr_merge(conv, d, p, 83, 39); osmo_conv_encode(&gsm0503_conv_tch_ahs_4_75, conv, cB+4); memcpy(cB+216, d+83, 12); break; default: fprintf(stderr, "FIXME: FT %d not supported!\n", ft); return -1; } memcpy(cB, gsm0503_afs_ic_ubit[id], 4); gsm0503_tch_hr_interleave(cB, iB); for (i=0; i<4; i++) gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>1); return 0; } /* * GSM RACH transcoding */ /* * GSM RACH apply BSIC to parity * * p(j) = p(j) xor b(j) j = 0, ..., 5 * b(0) = MSB of PLMN colour code * b(5) = LSB of BS colour code */ static int rach_apply_bsic(ubit_t *d, uint8_t bsic) { int i; /* Apply it */ for (i=0; i<6; i++) d[8+i] ^= ((bsic >> (5-i)) & 1); return 0; } int rach_decode(uint8_t *ra, sbit_t *burst, uint8_t bsic) { ubit_t conv[14]; int rv; osmo_conv_decode(&gsm0503_conv_rach, burst, conv); rach_apply_bsic(conv, bsic); rv = osmo_crc8gen_check_bits(&gsm0503_rach_crc6, conv, 8, conv+8); if (rv) return -1; osmo_ubit2pbit_ext(ra, 0, conv, 0, 8, 1); return 0; } int rach_encode(ubit_t *burst, uint8_t *ra, uint8_t bsic) { ubit_t conv[14]; osmo_pbit2ubit_ext(conv, 0, ra, 0, 8, 1); osmo_crc8gen_set_bits(&gsm0503_rach_crc6, conv, 8, conv+8); rach_apply_bsic(conv, bsic); osmo_conv_encode(&gsm0503_conv_rach, conv, burst); return 0; } /* * GSM SCH transcoding */ int sch_decode(uint8_t *sb_info, sbit_t *burst) { ubit_t conv[35]; int rv; osmo_conv_decode(&gsm0503_conv_sch, burst, conv); rv = osmo_crc16gen_check_bits(&gsm0503_sch_crc10, conv, 25, conv+25); if (rv) return -1; osmo_ubit2pbit_ext(sb_info, 0, conv, 0, 25, 1); return 0; } int sch_encode(ubit_t *burst, uint8_t *sb_info) { ubit_t conv[35]; osmo_pbit2ubit_ext(conv, 0, sb_info, 0, 25, 1); osmo_crc16gen_set_bits(&gsm0503_sch_crc10, conv, 25, conv+25); osmo_conv_encode(&gsm0503_conv_sch, conv, burst); return 0; } osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_coding.h000066400000000000000000000045271260026426200212250ustar00rootroot00000000000000/* (C) 2013 by Andreas Eversberg * (C) 2015 by Alexander Chemeris * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 _0503_CODING_H #define _0503_CODING_H int xcch_decode(uint8_t *l2_data, sbit_t *bursts, int *n_errors, int *n_bits_total); int xcch_encode(ubit_t *bursts, uint8_t *l2_data); int pdtch_decode(uint8_t *l2_data, sbit_t *bursts, uint8_t *usf_p, int *n_errors, int *n_bits_total); int pdtch_encode(ubit_t *bursts, uint8_t *l2_data, uint8_t l2_len); int tch_fr_decode(uint8_t *tch_data, sbit_t *bursts, int net_order, int efr, int *n_errors, int *n_bits_total); int tch_fr_encode(ubit_t *bursts, uint8_t *tch_data, int len, int net_order); int tch_hr_decode(uint8_t *tch_data, sbit_t *bursts, int odd, int *n_errors, int *n_bits_total); int tch_hr_encode(ubit_t *bursts, uint8_t *tch_data, int len); int tch_afs_decode(uint8_t *tch_data, sbit_t *bursts, int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft, uint8_t *cmr, int *n_errors, int *n_bits_total); int tch_afs_encode(ubit_t *bursts, uint8_t *tch_data, int len, int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft, uint8_t cmr); int tch_ahs_decode(uint8_t *tch_data, sbit_t *bursts, int odd, int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft, uint8_t *cmr, int *n_errors, int *n_bits_total); int tch_ahs_encode(ubit_t *bursts, uint8_t *tch_data, int len, int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft, uint8_t cmr); int rach_decode(uint8_t *ra, sbit_t *burst, uint8_t bsic); int rach_encode(ubit_t *burst, uint8_t *ra, uint8_t bsic); int sch_decode(uint8_t *sb_info, sbit_t *burst); int sch_encode(ubit_t *burst, uint8_t *sb_info); #endif /* _0503_CODING_H */ osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_conv.c000066400000000000000000000777001260026426200207250ustar00rootroot00000000000000 #include #include #include "gsm0503_conv.h" /* * GSM convolutional coding * * G_0 = 1 + x^3 + x^4 * G_1 = 1 + x + x^3 + x^4 */ static const uint8_t conv_xcch_next_output[][2] = { { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, }; static const uint8_t conv_xcch_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, }; const struct osmo_conv_code gsm0503_conv_xcch = { .N = 2, .K = 5, .len = 224, .next_output = conv_xcch_next_output, .next_state = conv_xcch_next_state, }; const struct osmo_conv_code gsm0503_conv_cs2 = { .N = 2, .K = 5, .len = 290, .next_output = conv_xcch_next_output, .next_state = conv_xcch_next_state, }; const struct osmo_conv_code gsm0503_conv_cs3 = { .N = 2, .K = 5, .len = 334, .next_output = conv_xcch_next_output, .next_state = conv_xcch_next_state, }; const struct osmo_conv_code gsm0503_conv_rach = { .N = 2, .K = 5, .len = 14, .next_output = conv_xcch_next_output, .next_state = conv_xcch_next_state, }; const struct osmo_conv_code gsm0503_conv_sch = { .N = 2, .K = 5, .len = 35, .next_output = conv_xcch_next_output, .next_state = conv_xcch_next_state, }; const struct osmo_conv_code gsm0503_conv_tch_fr = { .N = 2, .K = 5, .len = 185, .next_output = conv_xcch_next_output, .next_state = conv_xcch_next_state, }; /* * GSM HR convolutional coding * * G_0 = 1 + x^2 + x^3 + x^5 + x^6 * G_1 = 1 + x + x^4 + x^6 * G_3 = 1 + x + x^2 + x^3 + x^4 + x^6 */ static const uint8_t conv_tch_hr_next_output[][2] = { { 0, 7 }, { 3, 4 }, { 5, 2 }, { 6, 1 }, { 5, 2 }, { 6, 1 }, { 0, 7 }, { 3, 4 }, { 3, 4 }, { 0, 7 }, { 6, 1 }, { 5, 2 }, { 6, 1 }, { 5, 2 }, { 3, 4 }, { 0, 7 }, { 4, 3 }, { 7, 0 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 4, 3 }, { 7, 0 }, { 7, 0 }, { 4, 3 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 7, 0 }, { 4, 3 }, { 7, 0 }, { 4, 3 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 7, 0 }, { 4, 3 }, { 4, 3 }, { 7, 0 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 4, 3 }, { 7, 0 }, { 3, 4 }, { 0, 7 }, { 6, 1 }, { 5, 2 }, { 6, 1 }, { 5, 2 }, { 3, 4 }, { 0, 7 }, { 0, 7 }, { 3, 4 }, { 5, 2 }, { 6, 1 }, { 5, 2 }, { 6, 1 }, { 0, 7 }, { 3, 4 }, }; static const uint8_t conv_tch_hr_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, { 16, 17 }, { 18, 19 }, { 20, 21 }, { 22, 23 }, { 24, 25 }, { 26, 27 }, { 28, 29 }, { 30, 31 }, { 32, 33 }, { 34, 35 }, { 36, 37 }, { 38, 39 }, { 40, 41 }, { 42, 43 }, { 44, 45 }, { 46, 47 }, { 48, 49 }, { 50, 51 }, { 52, 53 }, { 54, 55 }, { 56, 57 }, { 58, 59 }, { 60, 61 }, { 62, 63 }, { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, { 16, 17 }, { 18, 19 }, { 20, 21 }, { 22, 23 }, { 24, 25 }, { 26, 27 }, { 28, 29 }, { 30, 31 }, { 32, 33 }, { 34, 35 }, { 36, 37 }, { 38, 39 }, { 40, 41 }, { 42, 43 }, { 44, 45 }, { 46, 47 }, { 48, 49 }, { 50, 51 }, { 52, 53 }, { 54, 55 }, { 56, 57 }, { 58, 59 }, { 60, 61 }, { 62, 63 }, }; static const int conv_tch_hr_puncture[] = { /* Class 1 bits */ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58, 61, 64, 67, 70, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 175, 178, 181, 184, 187, 190, 193, 196, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, 229, 232, 235, 238, 241, 244, 247, 250, 253, 256, 259, 262, 265, 268, 271, 274, 277, 280, 283, /* Tail bits */ 295, 298, 301, 304, 307, 310, /* End */ -1, }; const struct osmo_conv_code gsm0503_conv_tch_hr = { .N = 3, .K = 7, .len = 98, .next_output = conv_tch_hr_next_output, .next_state = conv_tch_hr_next_state, .puncture = conv_tch_hr_puncture, }; /* TCH/AFS12.2 */ /* ----------- */ static const uint8_t conv_tch_afs_12_2_next_output[][2] = { { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, }; static const uint8_t conv_tch_afs_12_2_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 9, 8 }, { 11, 10 }, { 13, 12 }, { 15, 14 }, { 1, 0 }, { 3, 2 }, { 5, 4 }, { 7, 6 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, }; static const uint8_t conv_tch_afs_12_2_next_term_output[] = { 0, 1, 0, 1, 3, 2, 3, 2, 3, 2, 3, 2, 0, 1, 0, 1, }; static const uint8_t conv_tch_afs_12_2_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_afs_12_2_puncture[] = { 321, 325, 329, 333, 337, 341, 345, 349, 353, 357, 361, 363, 365, 369, 373, 377, 379, 381, 385, 389, 393, 395, 397, 401, 405, 409, 411, 413, 417, 421, 425, 427, 429, 433, 437, 441, 443, 445, 449, 453, 457, 459, 461, 465, 469, 473, 475, 477, 481, 485, 489, 491, 493, 495, 497, 499, 501, 503, 505, 507, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_12_2 = { .N = 2, .K = 5, .len = 250, .next_output = conv_tch_afs_12_2_next_output, .next_state = conv_tch_afs_12_2_next_state, .next_term_output = conv_tch_afs_12_2_next_term_output, .next_term_state = conv_tch_afs_12_2_next_term_state, .puncture = conv_tch_afs_12_2_puncture, }; /* TCH/AFS10.2 */ /* ----------- */ static const uint8_t conv_tch_afs_10_2_next_output[][2] = { { 0, 7 }, { 2, 5 }, { 4, 3 }, { 6, 1 }, { 2, 5 }, { 0, 7 }, { 6, 1 }, { 4, 3 }, { 0, 7 }, { 2, 5 }, { 4, 3 }, { 6, 1 }, { 2, 5 }, { 0, 7 }, { 6, 1 }, { 4, 3 }, }; static const uint8_t conv_tch_afs_10_2_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, }; static const uint8_t conv_tch_afs_10_2_next_term_output[] = { 0, 5, 3, 6, 5, 0, 6, 3, 7, 2, 4, 1, 2, 7, 1, 4, }; static const uint8_t conv_tch_afs_10_2_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_afs_10_2_puncture[] = { 1, 4, 7, 10, 16, 19, 22, 28, 31, 34, 40, 43, 46, 52, 55, 58, 64, 67, 70, 76, 79, 82, 88, 91, 94, 100, 103, 106, 112, 115, 118, 124, 127, 130, 136, 139, 142, 148, 151, 154, 160, 163, 166, 172, 175, 178, 184, 187, 190, 196, 199, 202, 208, 211, 214, 220, 223, 226, 232, 235, 238, 244, 247, 250, 256, 259, 262, 268, 271, 274, 280, 283, 286, 292, 295, 298, 304, 307, 310, 316, 319, 322, 325, 328, 331, 334, 337, 340, 343, 346, 349, 352, 355, 358, 361, 364, 367, 370, 373, 376, 379, 382, 385, 388, 391, 394, 397, 400, 403, 406, 409, 412, 415, 418, 421, 424, 427, 430, 433, 436, 439, 442, 445, 448, 451, 454, 457, 460, 463, 466, 469, 472, 475, 478, 481, 484, 487, 490, 493, 496, 499, 502, 505, 508, 511, 514, 517, 520, 523, 526, 529, 532, 535, 538, 541, 544, 547, 550, 553, 556, 559, 562, 565, 568, 571, 574, 577, 580, 583, 586, 589, 592, 595, 598, 601, 604, 607, 609, 610, 613, 616, 619, 621, 622, 625, 627, 628, 631, 633, 634, 636, 637, 639, 640, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_10_2 = { .N = 3, .K = 5, .len = 210, .next_output = conv_tch_afs_10_2_next_output, .next_state = conv_tch_afs_10_2_next_state, .next_term_output = conv_tch_afs_10_2_next_term_output, .next_term_state = conv_tch_afs_10_2_next_term_state, .puncture = conv_tch_afs_10_2_puncture, }; /* TCH/AFS7.95 */ /* ----------- */ static const uint8_t conv_tch_afs_7_95_next_output[][2] = { { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, }; static const uint8_t conv_tch_afs_7_95_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 5, 4 }, { 7, 6 }, { 9, 8 }, { 11, 10 }, { 12, 13 }, { 14, 15 }, { 16, 17 }, { 18, 19 }, { 21, 20 }, { 23, 22 }, { 25, 24 }, { 27, 26 }, { 28, 29 }, { 30, 31 }, { 33, 32 }, { 35, 34 }, { 36, 37 }, { 38, 39 }, { 40, 41 }, { 42, 43 }, { 45, 44 }, { 47, 46 }, { 49, 48 }, { 51, 50 }, { 52, 53 }, { 54, 55 }, { 56, 57 }, { 58, 59 }, { 61, 60 }, { 63, 62 }, { 1, 0 }, { 3, 2 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 }, { 13, 12 }, { 15, 14 }, { 17, 16 }, { 19, 18 }, { 20, 21 }, { 22, 23 }, { 24, 25 }, { 26, 27 }, { 29, 28 }, { 31, 30 }, { 32, 33 }, { 34, 35 }, { 37, 36 }, { 39, 38 }, { 41, 40 }, { 43, 42 }, { 44, 45 }, { 46, 47 }, { 48, 49 }, { 50, 51 }, { 53, 52 }, { 55, 54 }, { 57, 56 }, { 59, 58 }, { 60, 61 }, { 62, 63 }, }; static const uint8_t conv_tch_afs_7_95_next_term_output[] = { 0, 3, 5, 6, 5, 6, 0, 3, 3, 0, 6, 5, 6, 5, 3, 0, 4, 7, 1, 2, 1, 2, 4, 7, 7, 4, 2, 1, 2, 1, 7, 4, 7, 4, 2, 1, 2, 1, 7, 4, 4, 7, 1, 2, 1, 2, 4, 7, 3, 0, 6, 5, 6, 5, 3, 0, 0, 3, 5, 6, 5, 6, 0, 3, }; static const uint8_t conv_tch_afs_7_95_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, }; static int conv_tch_afs_7_95_puncture[] = { 1, 2, 4, 5, 8, 22, 70, 118, 166, 214, 262, 310, 317, 319, 325, 332, 334, 341, 343, 349, 356, 358, 365, 367, 373, 380, 382, 385, 389, 391, 397, 404, 406, 409, 413, 415, 421, 428, 430, 433, 437, 439, 445, 452, 454, 457, 461, 463, 469, 476, 478, 481, 485, 487, 490, 493, 500, 502, 503, 505, 506, 508, 509, 511, 512, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_7_95 = { .N = 3, .K = 7, .len = 165, .next_output = conv_tch_afs_7_95_next_output, .next_state = conv_tch_afs_7_95_next_state, .next_term_output = conv_tch_afs_7_95_next_term_output, .next_term_state = conv_tch_afs_7_95_next_term_state, .puncture = conv_tch_afs_7_95_puncture, }; /* TCH/AFS7.4 */ /* ---------- */ static const uint8_t conv_tch_afs_7_4_next_output[][2] = { { 0, 7 }, { 2, 5 }, { 4, 3 }, { 6, 1 }, { 2, 5 }, { 0, 7 }, { 6, 1 }, { 4, 3 }, { 0, 7 }, { 2, 5 }, { 4, 3 }, { 6, 1 }, { 2, 5 }, { 0, 7 }, { 6, 1 }, { 4, 3 }, }; static const uint8_t conv_tch_afs_7_4_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, }; static const uint8_t conv_tch_afs_7_4_next_term_output[] = { 0, 5, 3, 6, 5, 0, 6, 3, 7, 2, 4, 1, 2, 7, 1, 4, }; static const uint8_t conv_tch_afs_7_4_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_afs_7_4_puncture[] = { 0, 355, 361, 367, 373, 379, 385, 391, 397, 403, 409, 415, 421, 427, 433, 439, 445, 451, 457, 460, 463, 466, 468, 469, 471, 472, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_7_4 = { .N = 3, .K = 5, .len = 154, .next_output = conv_tch_afs_7_4_next_output, .next_state = conv_tch_afs_7_4_next_state, .next_term_output = conv_tch_afs_7_4_next_term_output, .next_term_state = conv_tch_afs_7_4_next_term_state, .puncture = conv_tch_afs_7_4_puncture, }; /* TCH/AFS6.7 */ /* ---------- */ static const uint8_t conv_tch_afs_6_7_next_output[][2] = { { 0, 15 }, { 4, 11 }, { 8, 7 }, { 12, 3 }, { 4, 11 }, { 0, 15 }, { 12, 3 }, { 8, 7 }, { 0, 15 }, { 4, 11 }, { 8, 7 }, { 12, 3 }, { 4, 11 }, { 0, 15 }, { 12, 3 }, { 8, 7 }, }; static const uint8_t conv_tch_afs_6_7_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, }; static int conv_tch_afs_6_7_puncture[] = { 1, 3, 7, 11, 15, 27, 39, 55, 67, 79, 95, 107, 119, 135, 147, 159, 175, 187, 199, 215, 227, 239, 255, 267, 279, 287, 291, 295, 299, 303, 307, 311, 315, 319, 323, 327, 331, 335, 339, 343, 347, 351, 355, 359, 363, 367, 369, 371, 375, 377, 379, 383, 385, 387, 391, 393, 395, 399, 401, 403, 407, 409, 411, 415, 417, 419, 423, 425, 427, 431, 433, 435, 439, 441, 443, 447, 449, 451, 455, 457, 459, 463, 465, 467, 471, 473, 475, 479, 481, 483, 487, 489, 491, 495, 497, 499, 503, 505, 507, 511, 513, 515, 519, 521, 523, 527, 529, 531, 535, 537, 539, 543, 545, 547, 549, 551, 553, 555, 557, 559, 561, 563, 565, 567, 569, 571, 573, 575, -1, /* end */ }; static const uint8_t conv_tch_afs_6_7_next_term_output[] = { 0, 11, 7, 12, 11, 0, 12, 7, 15, 4, 8, 3, 4, 15, 3, 8, }; static const uint8_t conv_tch_afs_6_7_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; const struct osmo_conv_code gsm0503_conv_tch_afs_6_7 = { .N = 4, .K = 5, .len = 140, .next_output = conv_tch_afs_6_7_next_output, .next_state = conv_tch_afs_6_7_next_state, .next_term_output = conv_tch_afs_6_7_next_term_output, .next_term_state = conv_tch_afs_6_7_next_term_state, .puncture = conv_tch_afs_6_7_puncture, }; /* TCH/AFS5.9 */ /* ---------- */ static const uint8_t conv_tch_afs_5_9_next_output[][2] = { { 0, 15 }, { 8, 7 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 0, 15 }, { 8, 7 }, { 8, 7 }, { 0, 15 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 8, 7 }, { 0, 15 }, { 8, 7 }, { 0, 15 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 8, 7 }, { 0, 15 }, { 0, 15 }, { 8, 7 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 0, 15 }, { 8, 7 }, { 0, 15 }, { 8, 7 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 0, 15 }, { 8, 7 }, { 8, 7 }, { 0, 15 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 8, 7 }, { 0, 15 }, { 8, 7 }, { 0, 15 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 8, 7 }, { 0, 15 }, { 0, 15 }, { 8, 7 }, { 4, 11 }, { 12, 3 }, { 4, 11 }, { 12, 3 }, { 0, 15 }, { 8, 7 }, }; static const uint8_t conv_tch_afs_5_9_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 17, 16 }, { 18, 19 }, { 20, 21 }, { 23, 22 }, { 24, 25 }, { 27, 26 }, { 29, 28 }, { 30, 31 }, { 32, 33 }, { 35, 34 }, { 37, 36 }, { 38, 39 }, { 41, 40 }, { 42, 43 }, { 44, 45 }, { 47, 46 }, { 49, 48 }, { 50, 51 }, { 52, 53 }, { 55, 54 }, { 56, 57 }, { 59, 58 }, { 61, 60 }, { 62, 63 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, { 16, 17 }, { 19, 18 }, { 21, 20 }, { 22, 23 }, { 25, 24 }, { 26, 27 }, { 28, 29 }, { 31, 30 }, { 33, 32 }, { 34, 35 }, { 36, 37 }, { 39, 38 }, { 40, 41 }, { 43, 42 }, { 45, 44 }, { 46, 47 }, { 48, 49 }, { 51, 50 }, { 53, 52 }, { 54, 55 }, { 57, 56 }, { 58, 59 }, { 60, 61 }, { 63, 62 }, }; static const uint8_t conv_tch_afs_5_9_next_term_output[] = { 0, 7, 11, 12, 11, 12, 0, 7, 7, 0, 12, 11, 12, 11, 7, 0, 8, 15, 3, 4, 3, 4, 8, 15, 15, 8, 4, 3, 4, 3, 15, 8, 15, 8, 4, 3, 4, 3, 15, 8, 8, 15, 3, 4, 3, 4, 8, 15, 7, 0, 12, 11, 12, 11, 7, 0, 0, 7, 11, 12, 11, 12, 0, 7, }; static const uint8_t conv_tch_afs_5_9_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, }; static int conv_tch_afs_5_9_puncture[] = { 0, 1, 3, 5, 7, 11, 15, 31, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255, 271, 287, 303, 319, 327, 331, 335, 343, 347, 351, 359, 363, 367, 375, 379, 383, 391, 395, 399, 407, 411, 415, 423, 427, 431, 439, 443, 447, 455, 459, 463, 467, 471, 475, 479, 483, 487, 491, 495, 499, 503, 507, 509, 511, 512, 513, 515, 516, 517, 519, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_5_9 = { .N = 4, .K = 7, .len = 124, .next_output = conv_tch_afs_5_9_next_output, .next_state = conv_tch_afs_5_9_next_state, .next_term_output = conv_tch_afs_5_9_next_term_output, .next_term_state = conv_tch_afs_5_9_next_term_state, .puncture = conv_tch_afs_5_9_puncture, }; /* TCH/AFS5.15 */ /* ----------- */ static const uint8_t conv_tch_afs_5_15_next_output[][2] = { { 0, 31 }, { 4, 27 }, { 24, 7 }, { 28, 3 }, { 4, 27 }, { 0, 31 }, { 28, 3 }, { 24, 7 }, { 0, 31 }, { 4, 27 }, { 24, 7 }, { 28, 3 }, { 4, 27 }, { 0, 31 }, { 28, 3 }, { 24, 7 }, }; static const uint8_t conv_tch_afs_5_15_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, }; static const uint8_t conv_tch_afs_5_15_next_term_output[] = { 0, 27, 7, 28, 27, 0, 28, 7, 31, 4, 24, 3, 4, 31, 3, 24, }; static const uint8_t conv_tch_afs_5_15_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_afs_5_15_puncture[] = { 0, 4, 5, 9, 10, 14, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 290, 300, 310, 315, 320, 325, 330, 334, 335, 340, 344, 345, 350, 354, 355, 360, 364, 365, 370, 374, 375, 380, 384, 385, 390, 394, 395, 400, 404, 405, 410, 414, 415, 420, 424, 425, 430, 434, 435, 440, 444, 445, 450, 454, 455, 460, 464, 465, 470, 474, 475, 480, 484, 485, 490, 494, 495, 500, 504, 505, 510, 514, 515, 520, 524, 525, 529, 530, 534, 535, 539, 540, 544, 545, 549, 550, 554, 555, 559, 560, 564, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_5_15 = { .N = 5, .K = 5, .len = 109, .next_output = conv_tch_afs_5_15_next_output, .next_state = conv_tch_afs_5_15_next_state, .next_term_output = conv_tch_afs_5_15_next_term_output, .next_term_state = conv_tch_afs_5_15_next_term_state, .puncture = conv_tch_afs_5_15_puncture, }; /* TCH/AFS4.75 */ /* ----------- */ static const uint8_t conv_tch_afs_4_75_next_output[][2] = { { 0, 31 }, { 24, 7 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 0, 31 }, { 24, 7 }, { 24, 7 }, { 0, 31 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 24, 7 }, { 0, 31 }, { 24, 7 }, { 0, 31 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 24, 7 }, { 0, 31 }, { 0, 31 }, { 24, 7 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 0, 31 }, { 24, 7 }, { 0, 31 }, { 24, 7 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 0, 31 }, { 24, 7 }, { 24, 7 }, { 0, 31 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 24, 7 }, { 0, 31 }, { 24, 7 }, { 0, 31 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 24, 7 }, { 0, 31 }, { 0, 31 }, { 24, 7 }, { 4, 27 }, { 28, 3 }, { 4, 27 }, { 28, 3 }, { 0, 31 }, { 24, 7 }, }; static const uint8_t conv_tch_afs_4_75_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 17, 16 }, { 18, 19 }, { 20, 21 }, { 23, 22 }, { 24, 25 }, { 27, 26 }, { 29, 28 }, { 30, 31 }, { 32, 33 }, { 35, 34 }, { 37, 36 }, { 38, 39 }, { 41, 40 }, { 42, 43 }, { 44, 45 }, { 47, 46 }, { 49, 48 }, { 50, 51 }, { 52, 53 }, { 55, 54 }, { 56, 57 }, { 59, 58 }, { 61, 60 }, { 62, 63 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, { 16, 17 }, { 19, 18 }, { 21, 20 }, { 22, 23 }, { 25, 24 }, { 26, 27 }, { 28, 29 }, { 31, 30 }, { 33, 32 }, { 34, 35 }, { 36, 37 }, { 39, 38 }, { 40, 41 }, { 43, 42 }, { 45, 44 }, { 46, 47 }, { 48, 49 }, { 51, 50 }, { 53, 52 }, { 54, 55 }, { 57, 56 }, { 58, 59 }, { 60, 61 }, { 63, 62 }, }; static const uint8_t conv_tch_afs_4_75_next_term_output[] = { 0, 7, 27, 28, 27, 28, 0, 7, 7, 0, 28, 27, 28, 27, 7, 0, 24, 31, 3, 4, 3, 4, 24, 31, 31, 24, 4, 3, 4, 3, 31, 24, 31, 24, 4, 3, 4, 3, 31, 24, 24, 31, 3, 4, 3, 4, 24, 31, 7, 0, 28, 27, 28, 27, 7, 0, 0, 7, 27, 28, 27, 28, 0, 7, }; static const uint8_t conv_tch_afs_4_75_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, }; static int conv_tch_afs_4_75_puncture[] = { 0, 1, 2, 4, 5, 7, 9, 15, 25, 35, 45, 55, 65, 75, 85, 95, 105, 115, 125, 135, 145, 155, 165, 175, 185, 195, 205, 215, 225, 235, 245, 255, 265, 275, 285, 295, 305, 315, 325, 335, 345, 355, 365, 375, 385, 395, 400, 405, 410, 415, 420, 425, 430, 435, 440, 445, 450, 455, 459, 460, 465, 470, 475, 479, 480, 485, 490, 495, 499, 500, 505, 509, 510, 515, 517, 519, 520, 522, 524, 525, 526, 527, 529, 530, 531, 532, 534, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_afs_4_75 = { .N = 5, .K = 7, .len = 101, .next_output = conv_tch_afs_4_75_next_output, .next_state = conv_tch_afs_4_75_next_state, .next_term_output = conv_tch_afs_4_75_next_term_output, .next_term_state = conv_tch_afs_4_75_next_term_state, .puncture = conv_tch_afs_4_75_puncture, }; /* TCH/AHS7.95 */ /* ----------- */ static const uint8_t conv_tch_ahs_7_95_next_output[][2] = { { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, }; static const uint8_t conv_tch_ahs_7_95_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 9, 8 }, { 11, 10 }, { 13, 12 }, { 15, 14 }, { 1, 0 }, { 3, 2 }, { 5, 4 }, { 7, 6 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, }; static const uint8_t conv_tch_ahs_7_95_next_term_output[] = { 0, 1, 0, 1, 3, 2, 3, 2, 3, 2, 3, 2, 0, 1, 0, 1, }; static const uint8_t conv_tch_ahs_7_95_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_ahs_7_95_puncture[] = { 1, 3, 5, 7, 11, 15, 19, 23, 27, 31, 35, 43, 47, 51, 55, 59, 63, 67, 71, 79, 83, 87, 91, 95, 99, 103, 107, 115, 119, 123, 127, 131, 135, 139, 143, 151, 155, 159, 163, 167, 171, 175, 177, 179, 183, 185, 187, 191, 193, 195, 197, 199, 203, 205, 207, 211, 213, 215, 219, 221, 223, 227, 229, 231, 233, 235, 239, 241, 243, 247, 249, 251, 255, 257, 259, 261, 263, 265, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_ahs_7_95 = { .N = 2, .K = 5, .len = 129, .next_output = conv_tch_ahs_7_95_next_output, .next_state = conv_tch_ahs_7_95_next_state, .next_term_output = conv_tch_ahs_7_95_next_term_output, .next_term_state = conv_tch_ahs_7_95_next_term_state, .puncture = conv_tch_ahs_7_95_puncture, }; /* TCH/AHS7.4 */ /* ---------- */ static const uint8_t conv_tch_ahs_7_4_next_output[][2] = { { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, }; static const uint8_t conv_tch_ahs_7_4_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 9, 8 }, { 11, 10 }, { 13, 12 }, { 15, 14 }, { 1, 0 }, { 3, 2 }, { 5, 4 }, { 7, 6 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, }; static const uint8_t conv_tch_ahs_7_4_next_term_output[] = { 0, 1, 0, 1, 3, 2, 3, 2, 3, 2, 3, 2, 0, 1, 0, 1, }; static const uint8_t conv_tch_ahs_7_4_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_ahs_7_4_puncture[] = { 1, 3, 7, 11, 19, 23, 27, 35, 39, 43, 51, 55, 59, 67, 71, 75, 83, 87, 91, 99, 103, 107, 115, 119, 123, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, 183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 221, 223, 227, 229, 231, 235, 237, 239, 243, 245, 247, 251, 253, 255, 257, 259, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_ahs_7_4 = { .N = 2, .K = 5, .len = 126, .next_output = conv_tch_ahs_7_4_next_output, .next_state = conv_tch_ahs_7_4_next_state, .next_term_output = conv_tch_ahs_7_4_next_term_output, .next_term_state = conv_tch_ahs_7_4_next_term_state, .puncture = conv_tch_ahs_7_4_puncture, }; /* TCH/AHS6.7 */ /* ---------- */ static const uint8_t conv_tch_ahs_6_7_next_output[][2] = { { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, }; static const uint8_t conv_tch_ahs_6_7_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 9, 8 }, { 11, 10 }, { 13, 12 }, { 15, 14 }, { 1, 0 }, { 3, 2 }, { 5, 4 }, { 7, 6 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, }; static const uint8_t conv_tch_ahs_6_7_next_term_output[] = { 0, 1, 0, 1, 3, 2, 3, 2, 3, 2, 3, 2, 0, 1, 0, 1, }; static const uint8_t conv_tch_ahs_6_7_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_ahs_6_7_puncture[] = { 1, 3, 9, 19, 29, 39, 49, 59, 69, 79, 89, 99, 109, 119, 129, 139, 149, 159, 167, 169, 177, 179, 187, 189, 197, 199, 203, 207, 209, 213, 217, 219, 223, 227, 229, 231, 233, 235, 237, 239, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_ahs_6_7 = { .N = 2, .K = 5, .len = 116, .next_output = conv_tch_ahs_6_7_next_output, .next_state = conv_tch_ahs_6_7_next_state, .next_term_output = conv_tch_ahs_6_7_next_term_output, .next_term_state = conv_tch_ahs_6_7_next_term_state, .puncture = conv_tch_ahs_6_7_puncture, }; /* TCH/AHS5.9 */ /* ---------- */ static const uint8_t conv_tch_ahs_5_9_next_output[][2] = { { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, }; static const uint8_t conv_tch_ahs_5_9_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 9, 8 }, { 11, 10 }, { 13, 12 }, { 15, 14 }, { 1, 0 }, { 3, 2 }, { 5, 4 }, { 7, 6 }, { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, }; static const uint8_t conv_tch_ahs_5_9_next_term_output[] = { 0, 1, 0, 1, 3, 2, 3, 2, 3, 2, 3, 2, 0, 1, 0, 1, }; static const uint8_t conv_tch_ahs_5_9_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_ahs_5_9_puncture[] = { 1, 15, 71, 127, 139, 151, 163, 175, 187, 195, 203, 211, 215, 219, 221, 223, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_ahs_5_9 = { .N = 2, .K = 5, .len = 108, .next_output = conv_tch_ahs_5_9_next_output, .next_state = conv_tch_ahs_5_9_next_state, .next_term_output = conv_tch_ahs_5_9_next_term_output, .next_term_state = conv_tch_ahs_5_9_next_term_state, .puncture = conv_tch_ahs_5_9_puncture, }; /* TCH/AHS5.15 */ /* ----------- */ static const uint8_t conv_tch_ahs_5_15_next_output[][2] = { { 0, 7 }, { 2, 5 }, { 4, 3 }, { 6, 1 }, { 2, 5 }, { 0, 7 }, { 6, 1 }, { 4, 3 }, { 0, 7 }, { 2, 5 }, { 4, 3 }, { 6, 1 }, { 2, 5 }, { 0, 7 }, { 6, 1 }, { 4, 3 }, }; static const uint8_t conv_tch_ahs_5_15_next_state[][2] = { { 0, 1 }, { 3, 2 }, { 5, 4 }, { 6, 7 }, { 9, 8 }, { 10, 11 }, { 12, 13 }, { 15, 14 }, { 1, 0 }, { 2, 3 }, { 4, 5 }, { 7, 6 }, { 8, 9 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, }; static const uint8_t conv_tch_ahs_5_15_next_term_output[] = { 0, 5, 3, 6, 5, 0, 6, 3, 7, 2, 4, 1, 2, 7, 1, 4, }; static const uint8_t conv_tch_ahs_5_15_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, }; static int conv_tch_ahs_5_15_puncture[] = { 0, 1, 3, 4, 6, 9, 12, 15, 18, 21, 27, 33, 39, 45, 51, 54, 57, 63, 69, 75, 81, 87, 90, 93, 99, 105, 111, 117, 123, 126, 129, 135, 141, 147, 153, 159, 162, 165, 168, 171, 174, 177, 180, 183, 186, 189, 192, 195, 198, 201, 204, 207, 210, 213, 216, 219, 222, 225, 228, 231, 234, 237, 240, 243, 244, 246, 249, 252, 255, 256, 258, 261, 264, 267, 268, 270, 273, 276, 279, 280, 282, 285, 288, 289, 291, 294, 295, 297, 298, 300, 301, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_ahs_5_15 = { .N = 3, .K = 5, .len = 97, .next_output = conv_tch_ahs_5_15_next_output, .next_state = conv_tch_ahs_5_15_next_state, .next_term_output = conv_tch_ahs_5_15_next_term_output, .next_term_state = conv_tch_ahs_5_15_next_term_state, .puncture = conv_tch_ahs_5_15_puncture, }; /* TCH/AHS4.75 */ /* ----------- */ static const uint8_t conv_tch_ahs_4_75_next_output[][2] = { { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 }, { 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 }, { 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 }, }; static const uint8_t conv_tch_ahs_4_75_next_state[][2] = { { 0, 1 }, { 2, 3 }, { 5, 4 }, { 7, 6 }, { 9, 8 }, { 11, 10 }, { 12, 13 }, { 14, 15 }, { 16, 17 }, { 18, 19 }, { 21, 20 }, { 23, 22 }, { 25, 24 }, { 27, 26 }, { 28, 29 }, { 30, 31 }, { 33, 32 }, { 35, 34 }, { 36, 37 }, { 38, 39 }, { 40, 41 }, { 42, 43 }, { 45, 44 }, { 47, 46 }, { 49, 48 }, { 51, 50 }, { 52, 53 }, { 54, 55 }, { 56, 57 }, { 58, 59 }, { 61, 60 }, { 63, 62 }, { 1, 0 }, { 3, 2 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 }, { 13, 12 }, { 15, 14 }, { 17, 16 }, { 19, 18 }, { 20, 21 }, { 22, 23 }, { 24, 25 }, { 26, 27 }, { 29, 28 }, { 31, 30 }, { 32, 33 }, { 34, 35 }, { 37, 36 }, { 39, 38 }, { 41, 40 }, { 43, 42 }, { 44, 45 }, { 46, 47 }, { 48, 49 }, { 50, 51 }, { 53, 52 }, { 55, 54 }, { 57, 56 }, { 59, 58 }, { 60, 61 }, { 62, 63 }, }; static const uint8_t conv_tch_ahs_4_75_next_term_output[] = { 0, 3, 5, 6, 5, 6, 0, 3, 3, 0, 6, 5, 6, 5, 3, 0, 4, 7, 1, 2, 1, 2, 4, 7, 7, 4, 2, 1, 2, 1, 7, 4, 7, 4, 2, 1, 2, 1, 7, 4, 4, 7, 1, 2, 1, 2, 4, 7, 3, 0, 6, 5, 6, 5, 3, 0, 0, 3, 5, 6, 5, 6, 0, 3, }; static const uint8_t conv_tch_ahs_4_75_next_term_state[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, }; static int conv_tch_ahs_4_75_puncture[] = { 1, 2, 4, 5, 7, 8, 10, 13, 16, 22, 28, 34, 40, 46, 52, 58, 64, 70, 76, 82, 88, 94, 100, 106, 112, 118, 124, 130, 136, 142, 148, 151, 154, 160, 163, 166, 172, 175, 178, 184, 187, 190, 196, 199, 202, 208, 211, 214, 220, 223, 226, 232, 235, 238, 241, 244, 247, 250, 253, 256, 259, 262, 265, 268, 271, 274, 275, 277, 278, 280, 281, 283, 284, -1, /* end */ }; const struct osmo_conv_code gsm0503_conv_tch_ahs_4_75 = { .N = 3, .K = 7, .len = 89, .next_output = conv_tch_ahs_4_75_next_output, .next_state = conv_tch_ahs_4_75_next_state, .next_term_output = conv_tch_ahs_4_75_next_term_output, .next_term_state = conv_tch_ahs_4_75_next_term_state, .puncture = conv_tch_ahs_4_75_puncture, }; osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_conv.h000066400000000000000000000024371260026426200207250ustar00rootroot00000000000000#ifndef _0503_CONV_H #define _0503_CONV_H extern const struct osmo_conv_code gsm0503_conv_xcch; extern const struct osmo_conv_code gsm0503_conv_cs2; extern const struct osmo_conv_code gsm0503_conv_cs3; extern const struct osmo_conv_code gsm0503_conv_rach; extern const struct osmo_conv_code gsm0503_conv_sch; extern const struct osmo_conv_code gsm0503_conv_tch_fr; extern const struct osmo_conv_code gsm0503_conv_tch_hr; extern const struct osmo_conv_code gsm0503_conv_tch_afs_12_2; extern const struct osmo_conv_code gsm0503_conv_tch_afs_10_2; extern const struct osmo_conv_code gsm0503_conv_tch_afs_7_95; extern const struct osmo_conv_code gsm0503_conv_tch_afs_7_4; extern const struct osmo_conv_code gsm0503_conv_tch_afs_6_7; extern const struct osmo_conv_code gsm0503_conv_tch_afs_5_9; extern const struct osmo_conv_code gsm0503_conv_tch_afs_5_15; extern const struct osmo_conv_code gsm0503_conv_tch_afs_4_75; extern const struct osmo_conv_code gsm0503_conv_tch_ahs_7_95; extern const struct osmo_conv_code gsm0503_conv_tch_ahs_7_4; extern const struct osmo_conv_code gsm0503_conv_tch_ahs_6_7; extern const struct osmo_conv_code gsm0503_conv_tch_ahs_5_9; extern const struct osmo_conv_code gsm0503_conv_tch_ahs_5_15; extern const struct osmo_conv_code gsm0503_conv_tch_ahs_4_75; #endif /* _0503_CONV_H */ osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_interleaving.c000066400000000000000000000062441260026426200224420ustar00rootroot00000000000000 #include #include #include "gsm0503_tables.h" #include "gsm0503_interleaving.h" /* * GSM xCCH interleaving and burst mapping * * Interleaving: * * Given 456 coded input bits, form 4 blocks of 114 bits: * * i(B, j) = c(n, k) k = 0, ..., 455 * n = 0, ..., N, N + 1, ... * B = B_0 + 4n + (k mod 4) * j = 2(49k mod 57) + ((k mod 8) div 4) * * Mapping on Burst: * * e(B, j) = i(B, j) * e(B, 59 + j) = i(B, 57 + j) j = 0, ..., 56 * e(B, 57) = h_l(B) * e(B, 58) = h_n(B) * * Where hl(B) and hn(B) are bits in burst B indicating flags. */ void gsm0503_xcch_deinterleave(sbit_t *cB, sbit_t *iB) { int j, k, B; for (k=0; k<456; k++) { B = k & 3; j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); cB[k] = iB[B * 114 + j]; } } void gsm0503_xcch_interleave(ubit_t *cB, ubit_t *iB) { int j, k, B; for (k=0; k<456; k++) { B = k & 3; j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); iB[B * 114 + j] = cB[k]; } } /* * GSM TCH FR/EFR/AFS interleaving and burst mapping * * Interleaving: * * Given 456 coded input bits, form 8 blocks of 114 bits, * where even bits of the first 4 blocks and odd bits of the last 4 blocks * are used: * * i(B, j) = c(n, k) k = 0, ..., 455 * n = 0, ..., N, N + 1, ... * B = B_0 + 4n + (k mod 8) * j = 2(49k mod 57) + ((k mod 8) div 4) * * Mapping on Burst: * * e(B, j) = i(B, j) * e(B, 59 + j) = i(B, 57 + j) j = 0, ..., 56 * e(B, 57) = h_l(B) * e(B, 58) = h_n(B) * * Where hl(B) and hn(B) are bits in burst B indicating flags. */ void gsm0503_tch_fr_deinterleave(sbit_t *cB, sbit_t *iB) { int j, k, B; for (k=0; k<456; k++) { B = k & 7; j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); cB[k] = iB[B * 114 + j]; } } void gsm0503_tch_fr_interleave(ubit_t *cB, ubit_t *iB) { int j, k, B; for (k=0; k<456; k++) { B = k & 7; j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); iB[B * 114 + j] = cB[k]; } } /* * GSM TCH HR/AHS interleaving and burst mapping * * Interleaving: * * Given 288 coded input bits, form 4 blocks of 114 bits, * where even bits of the first 2 blocks and odd bits of the last 2 blocks * are used: * * i(B, j) = c(n, k) k = 0, ..., 227 * n = 0, ..., N, N + 1, ... * B = B_0 + 2n + b * j, b = table[k]; * * Mapping on Burst: * * e(B, j) = i(B, j) * e(B, 59 + j) = i(B, 57 + j) j = 0, ..., 56 * e(B, 57) = h_l(B) * e(B, 58) = h_n(B) * * Where hl(B) and hn(B) are bits in burst B indicating flags. */ void gsm0503_tch_hr_deinterleave(sbit_t *cB, sbit_t *iB) { int j, k, B; for (k=0; k<228; k++) { B = gsm0503_tch_hr_interleaving[k][1]; j = gsm0503_tch_hr_interleaving[k][0]; cB[k] = iB[B * 114 + j]; } } void gsm0503_tch_hr_interleave(ubit_t *cB, ubit_t *iB) { int j, k, B; for (k=0; k<228; k++) { B = gsm0503_tch_hr_interleaving[k][1]; j = gsm0503_tch_hr_interleaving[k][0]; iB[B * 114 + j] = cB[k]; } } osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_interleaving.h000066400000000000000000000006601260026426200224430ustar00rootroot00000000000000#ifndef _0503_INTERLEAVING_H #define _0503_INTERLEAVING_H void gsm0503_xcch_deinterleave(sbit_t *cB, sbit_t *iB); void gsm0503_xcch_interleave(ubit_t *cB, ubit_t *iB); void gsm0503_tch_fr_deinterleave(sbit_t *cB, sbit_t *iB); void gsm0503_tch_fr_interleave(ubit_t *cB, ubit_t *iB); void gsm0503_tch_hr_deinterleave(sbit_t *cB, sbit_t *iB); void gsm0503_tch_hr_interleave(ubit_t *cB, ubit_t *iB); #endif /* _0503_INTERLEAVING_H */ osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_mapping.c000066400000000000000000000021221260026426200213750ustar00rootroot00000000000000 #include #include #include #include "gsm0503_mapping.h" void gsm0503_xcch_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *hl, sbit_t *hn) { memcpy(iB, eB, 57); memcpy(iB+57, eB+59, 57); if (hl) *hl = eB[57]; if (hn) *hn = eB[58]; } void gsm0503_xcch_burst_map(ubit_t *iB, ubit_t *eB, const ubit_t *hl, const ubit_t *hn) { memcpy(eB, iB, 57); memcpy(eB+59, iB+57, 57); if (hl) eB[57] = *hl; if (hn) eB[58] = *hn; } void gsm0503_tch_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *h, int odd) { int i; /* brainfuck: only copy even or odd bits */ if (iB) { for (i=odd; i<57; i+=2) iB[i] = eB[i]; for (i=58-odd; i<114; i+=2) iB[i] = eB[i+2]; } if (h) { if (!odd) *h = eB[58]; else *h = eB[57]; } } void gsm0503_tch_burst_map(ubit_t *iB, ubit_t *eB, const ubit_t *h, int odd) { int i; /* brainfuck: only copy even or odd bits */ if (eB) { for (i=odd; i<57; i+=2) eB[i] = iB[i]; for (i=58-odd; i<114; i+=2) eB[i+2] = iB[i]; } if (h) { if (!odd) eB[58] = *h; else eB[57] = *h; } } osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_mapping.h000066400000000000000000000006251260026426200214100ustar00rootroot00000000000000#ifndef _0503_MAPPING_H #define _0503_MAPPING_H void gsm0503_xcch_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *hl, sbit_t *hn); void gsm0503_xcch_burst_map(ubit_t *iB, ubit_t *eB, const ubit_t *hl, const ubit_t *hn); void gsm0503_tch_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *h, int odd); void gsm0503_tch_burst_map(ubit_t *iB, ubit_t *eB, const ubit_t *h, int odd); #endif /* _0503_INTERLEAVING_H */ osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_parity.c000066400000000000000000000027761260026426200212710ustar00rootroot00000000000000 #include #include #include "gsm0503_parity.h" /* * GSM (SACCH) parity (FIRE code) * * g(x) = (x^23 + 1)(x^17 + x^3 + 1) * = x^40 + x^26 + x^23 + x^17 + x^3 + a1 */ const struct osmo_crc64gen_code gsm0503_fire_crc40 = { .bits = 40, .poly = 0x0004820009ULL, .init = 0x0000000000ULL, .remainder = 0xffffffffffULL, }; /* * GSM PDTCH CS-2, CS-3, CS-4 parity * * g(x) = x^16 + x^12 + x^5 + 1 */ const struct osmo_crc16gen_code gsm0503_cs234_crc16 = { .bits = 16, .poly = 0x1021, .init = 0x0000, .remainder = 0xffff, }; /* * GSM RACH parity * * g(x) = x^6 + x^5 + x^3 + x^2 + x^1 + 1 */ const struct osmo_crc8gen_code gsm0503_rach_crc6 = { .bits = 6, .poly = 0x2f, .init = 0x00, .remainder = 0x3f, }; /* * GSM SCH parity * * g(x) = x^10 + x^8 + x^6 + x^5 + x^4 + x^2 + 1 */ const struct osmo_crc16gen_code gsm0503_sch_crc10 = { .bits = 10, .poly = 0x175, .init = 0x000, .remainder = 0x3ff, }; /* * GSM TCH FR/HR/EFR parity * * g(x) = x^3 + x + 1 */ const struct osmo_crc8gen_code gsm0503_tch_fr_crc3 = { .bits = 3, .poly = 0x3, .init = 0x0, .remainder = 0x7, }; /* * GSM TCH EFR parity * * g(x) = x^8 + x^4 + x^3 + x^2 + 1 */ const struct osmo_crc8gen_code gsm0503_tch_efr_crc8 = { .bits = 8, .poly = 0x1d, .init = 0x00, .remainder = 0x00, }; /* * GSM AMR parity * * g(x) = x^6 + x^5 + x^3 + x^2 + x^1 + 1 */ const struct osmo_crc8gen_code gsm0503_amr_crc6 = { .bits = 6, .poly = 0x2f, .init = 0x00, .remainder = 0x3f, }; osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_parity.h000066400000000000000000000006641260026426200212700ustar00rootroot00000000000000#ifndef _0503_PARITY_H #define _0503_PARITY_H const struct osmo_crc64gen_code gsm0503_fire_crc40; const struct osmo_crc16gen_code gsm0503_cs234_crc16; const struct osmo_crc8gen_code gsm0503_rach_crc6; const struct osmo_crc16gen_code gsm0503_sch_crc10; const struct osmo_crc8gen_code gsm0503_tch_fr_crc3; const struct osmo_crc8gen_code gsm0503_tch_efr_crc8; const struct osmo_crc8gen_code gsm0503_amr_crc6; #endif /* _0503_PARITY_H */ osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_tables.c000066400000000000000000000206641260026426200212270ustar00rootroot00000000000000 #include #include #include "gsm0503_tables.h" const ubit_t gsm0503_pdtch_hl_hn_ubit[4][8] = { { 1,1, 1,1, 1,1, 1,1 }, { 1,1, 0,0, 1,0, 0,0 }, { 0,0, 1,0, 0,0, 0,1 }, { 0,0, 0,1, 0,1, 1,0 }, }; const sbit_t gsm0503_pdtch_hl_hn_sbit[4][8] = { { -127,-127, -127,-127, -127,-127, -127,-127 }, { -127,-127, 127, 127, -127, 127, 127, 127 }, { 127, 127, -127, 127, 127, 127, 127,-127 }, { 127, 127, 127,-127, 127,-127, -127, 127 }, }; const ubit_t gsm0503_usf2six[8][6] = { { 0,0,0, 0,0,0 }, { 1,0,0, 1,0,1 }, { 0,1,0, 1,1,0 }, { 1,1,0, 0,1,1 }, { 0,0,1, 0,1,1 }, { 1,0,1, 1,1,0 }, { 0,1,1, 1,0,1 }, { 1,1,1, 0,0,0 }, }; const ubit_t gsm0503_usf2twelve_ubit[8][12] = { { 0,0,0, 0,0,0, 0,0,0, 0,0,0 }, { 1,1,0, 1,0,0, 0,0,1, 0,1,1 }, { 0,0,1, 1,0,1, 1,1,0, 1,1,0 }, { 1,1,1, 0,0,1, 1,1,1, 1,0,1 }, { 0,0,0, 0,1,1, 0,1,1, 1,0,1 }, { 1,1,0, 1,1,1, 0,1,0, 1,1,0 }, { 0,0,1, 1,1,0, 1,0,1, 0,1,1 }, { 1,1,1, 0,1,0, 1,0,0, 0,0,0 }, }; const sbit_t gsm0503_usf2twelve_sbit[8][12] = { { 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127 }, { -127,-127, 127, -127, 127, 127, 127, 127,-127, 127,-127,-127 }, { 127, 127,-127, -127, 127,-127, -127,-127, 127, -127,-127, 127 }, { -127,-127,-127, 127, 127,-127, -127,-127,-127, -127, 127,-127 }, { 127, 127, 127, 127,-127,-127, 127,-127,-127, -127, 127,-127 }, { -127,-127, 127, -127,-127,-127, 127,-127, 127, -127,-127, 127 }, { 127, 127,-127, -127,-127, 127, -127, 127,-127, 127,-127,-127 }, { -127,-127,-127, 127,-127, 127, -127, 127, 127, 127, 127, 127 }, }; const uint8_t gsm0503_puncture_cs2[588] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1 }; const uint8_t gsm0503_puncture_cs3[676] = { 0,0,0,0,0,0, 0,0,0,0,0,0, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,0 }; /* this corresponds to the bit-lengths of the individual codec * parameters as indicated in Table 1.1 of TS 06.10 */ const uint8_t gsm0503_gsm_fr_map[76] = { 6, 6, 5, 5, 4, 4, 3, 3, 7, 2, 2, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 2, 2, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 2, 2, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 2, 2, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }; /* this table describes the 65 most importaint bits from EFR coded * bits as indicated in TS 05.03 (3.1.1.1) */ const uint8_t gsm0503_gsm_efr_protected_bits[65] = { 39, 40, 41, 42, 43, 44, 48, 87, 45, 2, 3, 8, 10, 18, 19, 24, 46, 47,142,143, 144,145,146,147, 92, 93,195,196, 98,137, 148, 94,197,149,150, 95,198, 4, 5, 11, 12, 16, 9, 6, 7, 13, 17, 20, 96,199, 1, 14, 15, 21, 25, 26, 28,151,201,190, 240, 88,138,191,241 }; /* Encoded in-band data for speech frames */ const ubit_t gsm0503_afs_ic_ubit[4][8] = { { 0,0,0,0,0,0,0,0 }, { 0,1,0,1,1,1,0,1 }, { 1,0,1,1,1,0,1,0 }, { 1,1,1,0,0,1,1,1 }, }; const sbit_t gsm0503_afs_ic_sbit[4][8] = { { 127, 127, 127, 127, 127, 127, 127, 127 }, { 127,-127, 127,-127,-127,-127, 127,-127 }, { -127, 127,-127,-127,-127, 127,-127, 127 }, { -127,-127,-127, 127, 127,-127,-127,-127 }, }; const ubit_t gsm0503_ahs_ic_ubit[4][4] = { { 0,0,0,0 }, { 1,0,0,1 }, { 1,1,1,0 }, { 0,1,1,1 }, }; const sbit_t gsm0503_ahs_ic_sbit[4][4] = { { 127, 127, 127, 127 }, { -127, 127, 127,-127 }, { -127,-127,-127, 127 }, { 127,-127,-127,-127 }, }; const uint8_t gsm0503_tch_hr_interleaving[228][2] = { { 0 ,0 }, { 1 ,2 }, { 78 ,1 }, { 79 ,3 }, { 48 ,0 }, { 49 ,2 }, { 54 ,1 }, { 55 ,3 }, { 24 ,0 }, { 25 ,2 }, { 30 ,1 }, { 31 ,3 }, { 72 ,0 }, { 73 ,2 }, { 6 ,1 }, { 7 ,3 }, { 96 ,0 }, { 97 ,2 }, { 12 ,0 }, { 13 ,2 }, { 102,1 }, { 103,3 }, { 60 ,0 }, { 61 ,2 }, { 66 ,1 }, { 67 ,3 }, { 90 ,1 }, { 91 ,3 }, { 36 ,0 }, { 37 ,2 }, { 42 ,1 }, { 43 ,3 }, { 18 ,1 }, { 19 ,3 }, { 84 ,0 }, { 85 ,2 }, { 108,0 }, { 109,2 }, { 2 ,0 }, { 3 ,2 }, { 80 ,1 }, { 81 ,3 }, { 50 ,0 }, { 51 ,2 }, { 56 ,1 }, { 57 ,3 }, { 26 ,0 }, { 27 ,2 }, { 32 ,1 }, { 33 ,3 }, { 74 ,0 }, { 75 ,2 }, { 8 ,1 }, { 9 ,3 }, { 98 ,0 }, { 99 ,2 }, { 14 ,0 }, { 15 ,2 }, { 104,1 }, { 105,3 }, { 62 ,0 }, { 63 ,2 }, { 68 ,1 }, { 69 ,3 }, { 92 ,1 }, { 93 ,3 }, { 38 ,0 }, { 39 ,2 }, { 44 ,1 }, { 45 ,3 }, { 20 ,1 }, { 21 ,3 }, { 86 ,0 }, { 87 ,2 }, { 110,0 }, { 111,2 }, { 4 ,0 }, { 5 ,2 }, { 82 ,1 }, { 83 ,3 }, { 52 ,0 }, { 53 ,2 }, { 58 ,1 }, { 59 ,3 }, { 28 ,0 }, { 29 ,2 }, { 34 ,1 }, { 35 ,3 }, { 76 ,0 }, { 77 ,2 }, { 10 ,1 }, { 12 ,3 }, { 100,0 }, { 101,2 }, { 16 ,0 }, { 17 ,2 }, { 106,1 }, { 107,3 }, { 64 ,0 }, { 65 ,2 }, { 70 ,1 }, { 71 ,3 }, { 94 ,1 }, { 95 ,3 }, { 40 ,0 }, { 41 ,2 }, { 46 ,1 }, { 47 ,3 }, { 22 ,1 }, { 23 ,3 }, { 88 ,0 }, { 89 ,2 }, { 112,0 }, { 113,2 }, { 6 ,0 }, { 7 ,2 }, { 84 ,1 }, { 85 ,3 }, { 54 ,0 }, { 55 ,2 }, { 60 ,1 }, { 61 ,3 }, { 30 ,0 }, { 31 ,2 }, { 36 ,1 }, { 37 ,3 }, { 78 ,0 }, { 79 ,2 }, { 12 ,1 }, { 13 ,3 }, { 102,0 }, { 103,2 }, { 18 ,0 }, { 19 ,2 }, { 108,1 }, { 109,3 }, { 66 ,0 }, { 67 ,2 }, { 72 ,1 }, { 73 ,3 }, { 96 ,1 }, { 97 ,3 }, { 42 ,0 }, { 43 ,2 }, { 48 ,1 }, { 49 ,3 }, { 24 ,1 }, { 25 ,3 }, { 90 ,0 }, { 91 ,2 }, { 0 ,1 }, { 1 ,3 }, { 8 ,0 }, { 9 ,2 }, { 86 ,1 }, { 87 ,3 }, { 56 ,0 }, { 57 ,2 }, { 62 ,1 }, { 63 ,3 }, { 32 ,0 }, { 33 ,2 }, { 38 ,1 }, { 39 ,3 }, { 80 ,0 }, { 81 ,2 }, { 14 ,1 }, { 15 ,3 }, { 104,0 }, { 105,2 }, { 20 ,0 }, { 21 ,2 }, { 110,1 }, { 111,3 }, { 68 ,0 }, { 69 ,2 }, { 74 ,1 }, { 75 ,3 }, { 98 ,1 }, { 99 ,3 }, { 44 ,0 }, { 45 ,2 }, { 50 ,1 }, { 51 ,3 }, { 26 ,1 }, { 27 ,3 }, { 92 ,0 }, { 93 ,2 }, { 2 ,1 }, { 3 ,3 }, { 10 ,0 }, { 11 ,2 }, { 88 ,1 }, { 89 ,3 }, { 58 ,0 }, { 59 ,2 }, { 64 ,1 }, { 65 ,3 }, { 34 ,0 }, { 35 ,2 }, { 40 ,1 }, { 41 ,3 }, { 82 ,0 }, { 83 ,2 }, { 16 ,1 }, { 17 ,3 }, { 106,0 }, { 107,2 }, { 22 ,0 }, { 23 ,2 }, { 112,1 }, { 113,3 }, { 70 ,0 }, { 71 ,2 }, { 76 ,1 }, { 77 ,3 }, { 100,1 }, { 101,3 }, { 46 ,0 }, { 47 ,2 }, { 52 ,1 }, { 53 ,3 }, { 28 ,1 }, { 29 ,3 }, { 94 ,0 }, { 95 ,2 }, { 4 ,1 }, { 5 ,3 }, }; osmo-bts-0.4.0/src/osmo-bts-trx/gsm0503_tables.h000066400000000000000000000014031260026426200212220ustar00rootroot00000000000000#ifndef _0503_TABLES_H #define _0503_TABLES_H extern const ubit_t gsm0503_pdtch_hl_hn_ubit[4][8]; extern const sbit_t gsm0503_pdtch_hl_hn_sbit[4][8]; extern const ubit_t gsm0503_usf2six[8][6]; extern const ubit_t gsm0503_usf2twelve_ubit[8][12]; extern const sbit_t gsm0503_usf2twelve_sbit[8][12]; extern const uint8_t gsm0503_puncture_cs2[588]; extern const uint8_t gsm0503_puncture_cs3[676]; extern const uint8_t gsm0503_gsm_fr_map[76]; extern const uint8_t gsm0503_gsm_efr_protected_bits[65]; extern const ubit_t gsm0503_afs_ic_ubit[4][8]; extern const sbit_t gsm0503_afs_ic_sbit[4][8]; extern const ubit_t gsm0503_ahs_ic_ubit[4][4]; extern const sbit_t gsm0503_ahs_ic_sbit[4][4]; extern const uint8_t gsm0503_tch_hr_interleaving[228][2]; #endif /* _0503_TABLES_H */ osmo-bts-0.4.0/src/osmo-bts-trx/l1_if.c000066400000000000000000000443151260026426200175700ustar00rootroot00000000000000/* * layer 1 primitive handling and interface * * Copyright (C) 2013 Andreas Eversberg * Copyright (C) 2015 Alexander Chemeris * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "l1_if.h" #include "trx_if.h" #include "scheduler.h" static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = { [GSM_PCHAN_NONE] = 8, [GSM_PCHAN_CCCH] = 4, [GSM_PCHAN_CCCH_SDCCH4] = 5, [GSM_PCHAN_TCH_F] = 1, [GSM_PCHAN_TCH_H] = 2, [GSM_PCHAN_SDCCH8_SACCH8C] = 7, [GSM_PCHAN_PDCH] = 13, //[GSM_PCHAN_TCH_F_PDCH] = FIXME, [GSM_PCHAN_UNKNOWN] = 0, }; /* * create/destroy trx l1 instance */ struct trx_l1h *l1if_open(struct gsm_bts_trx *trx) { struct trx_l1h *l1h; int rc; l1h = talloc_zero(tall_bts_ctx, struct trx_l1h); if (!l1h) return NULL; l1h->trx = trx; trx->role_bts.l1h = l1h; trx_sched_init(l1h); rc = trx_if_open(l1h); if (rc < 0) { LOGP(DL1C, LOGL_FATAL, "Cannot initialize scheduler\n"); goto err; } return l1h; err: l1if_close(l1h); trx->role_bts.l1h = NULL; return NULL; } void l1if_close(struct trx_l1h *l1h) { trx_if_close(l1h); trx_sched_exit(l1h); talloc_free(l1h); } void l1if_reset(struct trx_l1h *l1h) { } static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail) { struct gsm_bts_trx *trx = l1h->trx; uint8_t tn; /* HACK, we should change state when we receive first clock from * transceiver */ if (avail) { /* signal availability */ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); oml_mo_tx_sw_act_rep(&trx->mo); oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); for (tn = 0; tn < 8; tn++) oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, (l1h->config.slotmask & (1 << tn)) ? NM_AVSTATE_DEPENDENCY : NM_AVSTATE_NOT_INSTALLED); } else { oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); for (tn = 0; tn < 8; tn++) oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); } } int check_transceiver_availability(struct gsm_bts *bts, int avail) { struct gsm_bts_trx *trx; struct trx_l1h *l1h; llist_for_each_entry(trx, &bts->trx_list, list) { l1h = trx_l1h_hdl(trx); check_transceiver_availability_trx(l1h, avail); } return 0; } /* * transceiver provisioning */ int l1if_provision_transceiver_trx(struct trx_l1h *l1h) { uint8_t tn; if (!transceiver_available) return -EIO; if (l1h->config.poweron && l1h->config.tsc_valid && l1h->config.bsic_valid && l1h->config.arfcn_valid) { /* before power on */ if (l1h->config.arfcn_valid && !l1h->config.arfcn_sent) { trx_if_cmd_rxtune(l1h, l1h->config.arfcn); trx_if_cmd_txtune(l1h, l1h->config.arfcn); l1h->config.arfcn_sent = 1; } if (l1h->config.tsc_valid && !l1h->config.tsc_sent) { trx_if_cmd_settsc(l1h, l1h->config.tsc); l1h->config.tsc_sent = 1; } if (l1h->config.bsic_valid && !l1h->config.bsic_sent) { trx_if_cmd_setbsic(l1h, l1h->config.bsic); l1h->config.bsic_sent = 1; } if (!l1h->config.poweron_sent) { trx_if_cmd_poweron(l1h); l1h->config.poweron_sent = 1; } /* after power on */ if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) { trx_if_cmd_setrxgain(l1h, l1h->config.rxgain); l1h->config.rxgain_sent = 1; } if (l1h->config.power_valid && !l1h->config.power_sent) { trx_if_cmd_setpower(l1h, l1h->config.power); l1h->config.power_sent = 1; } if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) { trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly); l1h->config.maxdly_sent = 1; } for (tn = 0; tn < 8; tn++) { if (l1h->config.slottype_valid[tn] && !l1h->config.slottype_sent[tn]) { trx_if_cmd_setslot(l1h, tn, l1h->config.slottype[tn]); l1h->config.slottype_sent[tn] = 1; } } return 0; } if (!l1h->config.poweron && !l1h->config.poweron_sent) { trx_if_cmd_poweroff(l1h); l1h->config.poweron_sent = 1; l1h->config.rxgain_sent = 0; l1h->config.power_sent = 0; l1h->config.maxdly_sent = 0; for (tn = 0; tn < 8; tn++) l1h->config.slottype_sent[tn] = 0; } return 0; } int l1if_provision_transceiver(struct gsm_bts *bts) { struct gsm_bts_trx *trx; struct trx_l1h *l1h; uint8_t tn; llist_for_each_entry(trx, &bts->trx_list, list) { l1h = trx_l1h_hdl(trx); l1h->config.arfcn_sent = 0; l1h->config.tsc_sent = 0; l1h->config.bsic_sent = 0; l1h->config.poweron_sent = 0; l1h->config.rxgain_sent = 0; l1h->config.power_sent = 0; l1h->config.maxdly_sent = 0; for (tn = 0; tn < 8; tn++) l1h->config.slottype_sent[tn] = 0; l1if_provision_transceiver_trx(l1h); } return 0; } /* * activation/configuration/deactivation of transceiver's TRX */ /* initialize the layer1 */ static int trx_init(struct gsm_bts_trx *trx) { struct trx_l1h *l1h = trx_l1h_hdl(trx); /* power on transceiver, if not already */ if (!l1h->config.poweron) { l1h->config.poweron = 1; l1h->config.poweron_sent = 0; l1if_provision_transceiver_trx(l1h); } if (trx == trx->bts->c0) lchan_init_lapdm(&trx->ts[0].lchan[4]); /* Set to Operational State: Enabled */ oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); /* Send OPSTART ack */ return oml_mo_opstart_ack(&trx->mo); } /* deactivate transceiver */ int bts_model_trx_close(struct gsm_bts_trx *trx) { struct trx_l1h *l1h = trx_l1h_hdl(trx); enum gsm_phys_chan_config pchan = trx->ts[0].pchan; /* close all logical channels and reset timeslots */ trx_sched_reset(l1h); /* deactivate lchan for CCCH */ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4) { lchan_set_state(&trx->ts[0].lchan[4], LCHAN_S_INACTIVE); } /* power off transceiver, if not already */ if (l1h->config.poweron) { l1h->config.poweron = 0; l1h->config.poweron_sent = 0; l1if_provision_transceiver_trx(l1h); } /* Set to Operational State: Disabled */ check_transceiver_availability_trx(l1h, 0); return 0; } /* on RSL failure, deactivate transceiver */ void bts_model_abis_close(struct gsm_bts *bts) { struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) bts_model_trx_close(trx); } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { /* we always implement the power control loop in osmo-bts software, as * there is no automatism in the underlying osmo-trx */ return 0; } /* set bts attributes */ static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr) { struct gsm_bts_trx *trx; struct trx_l1h *l1h; uint8_t bsic = bts->bsic; struct gsm_bts_role_bts *btsb = bts_role_bts(bts); if (TLVP_PRESENT(new_attr, NM_ATT_CONN_FAIL_CRIT)) { const uint8_t *val = TLVP_VAL(new_attr, NM_ATT_CONN_FAIL_CRIT); btsb->radio_link_timeout = val[1]; } llist_for_each_entry(trx, &bts->trx_list, list) { l1h = trx_l1h_hdl(trx); if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) { l1h->config.bsic = bsic; l1h->config.bsic_valid = 1; l1h->config.bsic_sent = 0; l1if_provision_transceiver_trx(l1h); } } check_transceiver_availability(bts, transceiver_available); return 0; } /* set trx attributes */ static uint8_t trx_set_trx(struct gsm_bts_trx *trx) { struct trx_l1h *l1h = trx_l1h_hdl(trx); uint16_t arfcn = trx->arfcn; if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) { l1h->config.arfcn = arfcn; l1h->config.arfcn_valid = 1; l1h->config.arfcn_sent = 0; l1if_provision_transceiver_trx(l1h); } if (l1h->config.power_oml) { l1h->config.power = trx->max_power_red; l1h->config.power_valid = 1; l1h->config.power_sent = 0; l1if_provision_transceiver_trx(l1h); } return 0; } /* set ts attributes */ static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) { struct trx_l1h *l1h = trx_l1h_hdl(ts->trx); uint8_t tn = ts->nr; uint16_t tsc = ts->tsc; enum gsm_phys_chan_config pchan = ts->pchan; uint8_t slottype; int rc; /* all TSC of all timeslots must be equal, because transceiver only * supports one TSC per TRX */ if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) { l1h->config.tsc = tsc; l1h->config.tsc_valid = 1; l1h->config.tsc_sent = 0; l1if_provision_transceiver_trx(l1h); } /* set physical channel */ rc = trx_sched_set_pchan(l1h, tn, pchan); if (rc) return NM_NACK_RES_NOTAVAIL; /* activate lchan for CCCH */ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4) { ts->lchan[4].rel_act_kind = LCHAN_REL_ACT_OML; lchan_set_state(&ts->lchan[4], LCHAN_S_ACTIVE); } slottype = transceiver_chan_types[pchan]; if (l1h->config.slottype[tn] != slottype || !l1h->config.slottype_valid[tn]) { l1h->config.slottype[tn] = slottype; l1h->config.slottype_valid[tn] = 1; l1h->config.slottype_sent[tn] = 0; l1if_provision_transceiver_trx(l1h); } return 0; } /* * primitive handling */ /* enable ciphering */ static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, int downlink) { /* ciphering already enabled in both directions */ if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) return -EINVAL; if (!downlink) { /* set uplink */ trx_sched_set_cipher(l1h, chan_nr, 0, lchan->encr.alg_id - 1, lchan->encr.key, lchan->encr.key_len); lchan->ciph_state = LCHAN_CIPH_RX_CONF; } else { /* set downlink and also set uplink, if not already */ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) { trx_sched_set_cipher(l1h, chan_nr, 0, lchan->encr.alg_id - 1, lchan->encr.key, lchan->encr.key_len); } trx_sched_set_cipher(l1h, chan_nr, 1, lchan->encr.alg_id - 1, lchan->encr.key, lchan->encr.key_len); lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; } return 0; } static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr, enum osmo_mph_info_type type, uint8_t cause) { struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, NULL); l1sap.u.info.type = type; l1sap.u.info.u.act_cnf.chan_nr = chan_nr; l1sap.u.info.u.act_cnf.cause = cause; return l1sap_up(l1h->trx, &l1sap); } int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn) { struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_INDICATION, NULL); l1sap.u.info.type = PRIM_INFO_TIME; l1sap.u.info.u.time_ind.fn = fn; if (!bts->c0) return -EINVAL; return l1sap_up(bts->c0, &l1sap); } void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta, float ber, float rssi) { memset(l1sap, 0, sizeof(*l1sap)); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_INDICATION, NULL); l1sap->u.info.type = PRIM_INFO_MEAS; l1sap->u.info.u.meas_ind.chan_nr = chan_nr; l1sap->u.info.u.meas_ind.ta_offs_qbits = (int16_t)(ta*4); l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000); l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1); } int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, int n_errors, int n_bits_total, float rssi, float toa) { struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)]; struct osmo_phsap_prim l1sap; /* 100% BER is n_bits_total is 0 */ float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total; LOGP(DMEAS, LOGL_DEBUG, "RX L1 frame %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa=%.2f\n", gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa); l1if_fill_meas_res(&l1sap, chan_nr, lchan->rqd_ta + toa, ber, rssi); return l1sap_up(trx, &l1sap); } /* primitive from common part */ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { struct trx_l1h *l1h = trx_l1h_hdl(trx); struct msgb *msg = l1sap->oph.msg; uint8_t chan_nr; uint8_t tn, ss; int rc = 0; struct gsm_lchan *lchan; switch (OSMO_PRIM_HDR(&l1sap->oph)) { case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ return trx_sched_ph_data_req(l1h, l1sap); case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ return trx_sched_tch_req(l1h, l1sap); case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): switch (l1sap->u.info.type) { case PRIM_INFO_ACT_CIPH: chan_nr = l1sap->u.info.u.ciph_req.chan_nr; tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); lchan = &trx->ts[tn].lchan[ss]; if (l1sap->u.info.u.ciph_req.uplink) l1if_set_ciphering(l1h, lchan, chan_nr, 0); if (l1sap->u.info.u.ciph_req.downlink) l1if_set_ciphering(l1h, lchan, chan_nr, 1); break; case PRIM_INFO_ACTIVATE: case PRIM_INFO_DEACTIVATE: case PRIM_INFO_MODIFY: chan_nr = l1sap->u.info.u.act_req.chan_nr; tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); lchan = &trx->ts[tn].lchan[ss]; if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { if ((chan_nr & 0x80)) { LOGP(DL1C, LOGL_ERROR, "Cannot activate" " chan_nr 0x%02x\n", chan_nr); break; } /* activate dedicated channel */ trx_sched_set_lchan(l1h, chan_nr, 0x00, 1); /* activate associated channel */ trx_sched_set_lchan(l1h, chan_nr, 0x40, 1); /* set mode */ trx_sched_set_mode(l1h, chan_nr, lchan->rsl_cmode, lchan->tch_mode, lchan->tch.amr_mr.num_modes, lchan->tch.amr_mr.mode[0].mode, lchan->tch.amr_mr.mode[1].mode, lchan->tch.amr_mr.mode[2].mode, lchan->tch.amr_mr.mode[3].mode, amr_get_initial_mode(lchan), (lchan->ho.active == 1)); /* init lapdm */ lchan_init_lapdm(lchan); /* set lchan active */ lchan_set_state(lchan, LCHAN_S_ACTIVE); /* set initial ciphering */ l1if_set_ciphering(l1h, lchan, chan_nr, 0); l1if_set_ciphering(l1h, lchan, chan_nr, 1); if (lchan->encr.alg_id) lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; else lchan->ciph_state = LCHAN_CIPH_NONE; /* confirm */ mph_info_chan_confirm(l1h, chan_nr, PRIM_INFO_ACTIVATE, 0); break; } if (l1sap->u.info.type == PRIM_INFO_MODIFY) { /* change mode */ trx_sched_set_mode(l1h, chan_nr, lchan->rsl_cmode, lchan->tch_mode, lchan->tch.amr_mr.num_modes, lchan->tch.amr_mr.mode[0].mode, lchan->tch.amr_mr.mode[1].mode, lchan->tch.amr_mr.mode[2].mode, lchan->tch.amr_mr.mode[3].mode, amr_get_initial_mode(lchan), 0); break; } if ((chan_nr & 0x80)) { LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " "chan_nr 0x%02x\n", chan_nr); break; } /* deactivate associated channel */ trx_sched_set_lchan(l1h, chan_nr, 0x40, 0); if (!l1sap->u.info.u.act_req.sacch_only) { /* set lchan inactive */ lchan_set_state(lchan, LCHAN_S_NONE); /* deactivate dedicated channel */ trx_sched_set_lchan(l1h, chan_nr, 0x00, 0); /* confirm only on dedicated channel */ mph_info_chan_confirm(l1h, chan_nr, PRIM_INFO_DEACTIVATE, 0); lchan->ciph_state = 0; /* FIXME: do this in common/\*.c */ } break; default: LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", l1sap->u.info.type); rc = -EINVAL; goto done; } break; default: LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", l1sap->oph.primitive, l1sap->oph.operation); rc = -EINVAL; goto done; } done: if (msg) msgb_free(msg); return rc; } /* * oml handling */ /* callback from OML */ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj) { /* FIXME: check if the attributes are valid */ return 0; } /* callback from OML */ int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int kind, void *obj) { struct abis_om_fom_hdr *foh = msgb_l3(msg); int cause = 0; switch (foh->msg_type) { case NM_MT_SET_BTS_ATTR: cause = trx_set_bts(obj, new_attr); break; case NM_MT_SET_RADIO_ATTR: cause = trx_set_trx(obj); break; case NM_MT_SET_CHAN_ATTR: cause = trx_set_ts(obj); break; } return oml_fom_ack_nack(msg, cause); } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { int rc; switch (mo->obj_class) { case NM_OC_RADIO_CARRIER: /* activate transceiver */ rc = trx_init(obj); break; case NM_OC_CHANNEL: /* configure timeslot */ rc = 0; //ts_connect(obj); /* Set to Operational State: Enabled */ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); /* Send OPSTART ack */ rc = oml_mo_opstart_ack(mo); break; case NM_OC_BTS: case NM_OC_SITE_MANAGER: case NM_OC_BASEB_TRANSC: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); rc = oml_mo_opstart_ack(mo); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); } return rc; } int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { /* blindly accept all state changes */ mo->nm_state.administrative = adm_state; return oml_mo_statechg_ack(mo); } int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { return 0; } int bts_model_oml_estab(struct gsm_bts *bts) { return 0; } osmo-bts-0.4.0/src/osmo-bts-trx/l1_if.h000066400000000000000000000107271260026426200175750ustar00rootroot00000000000000#ifndef L1_IF_H_TRX #define L1_IF_H_TRX /* These types define the different channels on a multiframe. * Each channel has queues and can be activated individually. */ enum trx_chan_type { TRXC_IDLE = 0, TRXC_FCCH, TRXC_SCH, TRXC_BCCH, TRXC_RACH, TRXC_CCCH, TRXC_TCHF, TRXC_TCHH_0, TRXC_TCHH_1, TRXC_SDCCH4_0, TRXC_SDCCH4_1, TRXC_SDCCH4_2, TRXC_SDCCH4_3, TRXC_SDCCH8_0, TRXC_SDCCH8_1, TRXC_SDCCH8_2, TRXC_SDCCH8_3, TRXC_SDCCH8_4, TRXC_SDCCH8_5, TRXC_SDCCH8_6, TRXC_SDCCH8_7, TRXC_SACCHTF, TRXC_SACCHTH_0, TRXC_SACCHTH_1, TRXC_SACCH4_0, TRXC_SACCH4_1, TRXC_SACCH4_2, TRXC_SACCH4_3, TRXC_SACCH8_0, TRXC_SACCH8_1, TRXC_SACCH8_2, TRXC_SACCH8_3, TRXC_SACCH8_4, TRXC_SACCH8_5, TRXC_SACCH8_6, TRXC_SACCH8_7, TRXC_PDTCH, TRXC_PTCCH, _TRX_CHAN_MAX }; /* States each channel on a multiframe */ struct trx_chan_state { /* scheduler */ uint8_t active; /* Channel is active */ ubit_t *dl_bursts; /* burst buffer for TX */ sbit_t *ul_bursts; /* burst buffer for RX */ uint32_t ul_first_fn; /* fn of first burst */ uint8_t ul_mask; /* mask of received bursts */ /* RSSI / TOA */ uint8_t rssi_num; /* number of RSSI values */ float rssi_sum; /* sum of RSSI values */ uint8_t toa_num; /* number of TOA values */ float toa_sum; /* sum of TOA values */ /* loss detection */ uint8_t lost; /* (SACCH) loss detection */ /* mode */ uint8_t rsl_cmode, tch_mode; /* mode for TCH channels */ /* AMR */ uint8_t codec[4]; /* 4 possible codecs for amr */ int codecs; /* number of possible codecs */ float ber_sum; /* sum of bit error rates */ int ber_num; /* number of bit error rates */ uint8_t ul_ft; /* current uplink FT index */ uint8_t dl_ft; /* current downlink FT index */ uint8_t ul_cmr; /* current uplink CMR index */ uint8_t dl_cmr; /* current downlink CMR index */ uint8_t amr_loop; /* if AMR loop is enabled */ /* TCH/H */ uint8_t dl_ongoing_facch; /* FACCH/H on downlink */ uint8_t ul_ongoing_facch; /* FACCH/H on uplink */ /* encryption */ int ul_encr_algo; /* A5/x encry algo downlink */ int dl_encr_algo; /* A5/x encry algo uplink */ int ul_encr_key_len; int dl_encr_key_len; uint8_t ul_encr_key[8]; uint8_t dl_encr_key[8]; /* measurements */ struct { uint8_t clock; /* cyclic clock counter */ int8_t rssi[32]; /* last RSSI values */ int rssi_count; /* received RSSI values */ int rssi_valid_count; /* number of stored value */ int rssi_got_burst; /* any burst received so far */ float toa_sum; /* sum of TOA values */ int toa_num; /* number of TOA value */ } meas; /* handover */ uint8_t ho_rach_detect; /* if rach detection is on */ }; struct trx_config { uint8_t poweron; /* poweron(1) or poweroff(0) */ int poweron_sent; int arfcn_valid; uint16_t arfcn; int arfcn_sent; int tsc_valid; uint8_t tsc; int tsc_sent; int bsic_valid; uint8_t bsic; int bsic_sent; int rxgain_valid; int rxgain; int rxgain_sent; int power_valid; int power; int power_oml; int power_sent; int maxdly_valid; int maxdly; int maxdly_sent; uint8_t slotmask; int slottype_valid[8]; uint8_t slottype[8]; int slottype_sent[8]; }; struct trx_l1h { struct llist_head trx_ctrl_list; struct gsm_bts_trx *trx; struct osmo_fd trx_ofd_ctrl; struct osmo_timer_list trx_ctrl_timer; struct osmo_fd trx_ofd_data; /* transceiver config */ struct trx_config config; uint8_t mf_index[8]; /* selected multiframe index */ uint32_t mf_last_fn[8]; /* last received frame */ uint8_t mf_period[8]; /* period of multiframe */ struct trx_sched_frame *mf_frames[8]; /* pointer to frame layout */ /* Channel states for all channels on all timeslots */ struct trx_chan_state chan_states[8][_TRX_CHAN_MAX]; struct llist_head dl_prims[8]; /* Queue primitves for TX */ uint8_t ho_rach_detect[8][8]; }; struct trx_l1h *l1if_open(struct gsm_bts_trx *trx); void l1if_close(struct trx_l1h *l1h); void l1if_reset(struct trx_l1h *l1h); int check_transceiver_availability(struct gsm_bts *bts, int avail); int l1if_provision_transceiver_trx(struct trx_l1h *l1h); int l1if_provision_transceiver(struct gsm_bts *bts); int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn); void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta, float ber, float rssi); int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, int n_errors, int n_bits_total, float rssi, float toa); #endif /* L1_IF_H_TRX */ osmo-bts-0.4.0/src/osmo-bts-trx/loops.c000066400000000000000000000212271260026426200177270ustar00rootroot00000000000000/* Loop control for OsmoBTS-TRX */ /* (C) 2013 by Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include "trx_if.h" #include "l1_if.h" #include "loops.h" #define MS_PWR_DBM(lvl) ms_pwr_dbm(gsm_arfcn2band(l1h->config.arfcn), lvl) /* * MS Power loop */ int trx_ms_power_loop = 0; int8_t trx_target_rssi = -10; static int ms_power_diff(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, struct trx_chan_state *chan_state, int8_t diff) { int8_t new_power; new_power = lchan->ms_power - (diff >> 1); if (diff == 0) return 0; if (new_power < 0) new_power = 0; // FIXME: to go above 1W, we need to know classmark of MS if (l1h->config.arfcn >= 512 && l1h->config.arfcn <= 885) { if (new_power > 15) new_power = 15; } else { if (new_power > 19) new_power = 19; } /* a higher value means a lower level (and vice versa) */ if (new_power > lchan->ms_power + MS_LOWER_MAX) new_power = lchan->ms_power + MS_LOWER_MAX; else if (new_power < lchan->ms_power - MS_RAISE_MAX) new_power = lchan->ms_power - MS_RAISE_MAX; if (lchan->ms_power == new_power) { LOGP(DLOOP, LOGL_INFO, "Keeping MS new_power of trx=%u " "chan_nr=0x%02x at control level %d (%d dBm)\n", l1h->trx->nr, chan_nr, new_power, MS_PWR_DBM(new_power)); return 0; } LOGP(DLOOP, LOGL_INFO, "%s MS new_power of trx=%u chan_nr=0x%02x from " "control level %d (%d dBm) to %d (%d dBm)\n", (diff > 0) ? "Raising" : "Lowering", l1h->trx->nr, chan_nr, lchan->ms_power, MS_PWR_DBM(lchan->ms_power), new_power, MS_PWR_DBM(new_power)); lchan->ms_power = new_power; return 0; } static int ms_power_val(struct trx_chan_state *chan_state, int8_t rssi) { /* ignore inserted dummy frames, treat as lost frames */ if (rssi < -127) return 0; LOGP(DLOOP, LOGL_DEBUG, "Got RSSI value of %d\n", rssi); chan_state->meas.rssi_count++; chan_state->meas.rssi_got_burst = 1; /* store and process RSSI */ if (chan_state->meas.rssi_valid_count == ARRAY_SIZE(chan_state->meas.rssi)) return 0; chan_state->meas.rssi[chan_state->meas.rssi_valid_count++] = rssi; chan_state->meas.rssi_valid_count++; return 0; } static int ms_power_clock(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, struct trx_chan_state *chan_state) { int rssi; int i; /* skip every second clock, to prevent oscillating due to roundtrip * delay */ if (!(chan_state->meas.clock & 1)) return 0; LOGP(DLOOP, LOGL_DEBUG, "Got SACCH master clock at RSSI count %d\n", chan_state->meas.rssi_count); /* wait for initial burst */ if (!chan_state->meas.rssi_got_burst) return 0; /* if no burst was received from MS at clock */ if (chan_state->meas.rssi_count == 0) { LOGP(DLOOP, LOGL_NOTICE, "LOST SACCH frame of trx=%u " "chan_nr=0x%02x, so we raise MS power\n", l1h->trx->nr, chan_nr); return ms_power_diff(l1h, lchan, chan_nr, chan_state, MS_RAISE_MAX); } /* reset total counter */ chan_state->meas.rssi_count = 0; /* check the minimum level received after MS acknowledged the ordered * power level */ if (chan_state->meas.rssi_valid_count == 0) return 0; for (rssi = 999, i = 0; i < chan_state->meas.rssi_valid_count; i++) { if (rssi > chan_state->meas.rssi[i]) rssi = chan_state->meas.rssi[i]; } /* reset valid counter */ chan_state->meas.rssi_valid_count = 0; /* change RSSI */ LOGP(DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current " "MS power: %d (%d dBm) of trx=%u chan_nr=0x%02x\n", rssi, trx_target_rssi, lchan->ms_power, MS_PWR_DBM(lchan->ms_power), l1h->trx->nr, chan_nr); ms_power_diff(l1h, lchan, chan_nr, chan_state, trx_target_rssi - rssi); return 0; } /* * Timing Advance loop */ int trx_ta_loop = 1; int ta_val(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, struct trx_chan_state *chan_state, float toa) { /* check if the current L1 header acks to the current ordered TA */ if (lchan->meas.l1_info[1] != lchan->rqd_ta) return 0; /* sum measurement */ chan_state->meas.toa_sum += toa; if (++(chan_state->meas.toa_num) < 16) return 0; /* complete set */ toa = chan_state->meas.toa_sum / chan_state->meas.toa_num; /* check for change of TOA */ if (toa < -0.9F && lchan->rqd_ta > 0) { LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " "early (%.2f), now lowering TA from %d to %d\n", l1h->trx->nr, chan_nr, toa, lchan->rqd_ta, lchan->rqd_ta - 1); lchan->rqd_ta--; } else if (toa > 0.9F && lchan->rqd_ta < 63) { LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " "late (%.2f), now raising TA from %d to %d\n", l1h->trx->nr, chan_nr, toa, lchan->rqd_ta, lchan->rqd_ta + 1); lchan->rqd_ta++; } else LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is " "correct (%.2f), keeping current TA of %d\n", l1h->trx->nr, chan_nr, toa, lchan->rqd_ta); chan_state->meas.toa_num = 0; chan_state->meas.toa_sum = 0; return 0; } int trx_loop_sacch_input(struct trx_l1h *l1h, uint8_t chan_nr, struct trx_chan_state *chan_state, int8_t rssi, float toa) { struct gsm_lchan *lchan = &l1h->trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; if (trx_ms_power_loop) ms_power_val(chan_state, rssi); if (trx_ta_loop) ta_val(l1h, lchan, chan_nr, chan_state, toa); return 0; } int trx_loop_sacch_clock(struct trx_l1h *l1h, uint8_t chan_nr, struct trx_chan_state *chan_state) { struct gsm_lchan *lchan = &l1h->trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; if (trx_ms_power_loop) ms_power_clock(l1h, lchan, chan_nr, chan_state); /* count the number of SACCH clocks */ chan_state->meas.clock++; return 0; } int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, struct trx_chan_state *chan_state, float ber) { struct gsm_lchan *lchan = &l1h->trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; int c_i; /* check if loop is enabled */ if (!chan_state->amr_loop) return 0; /* wait for MS to use the requested codec */ if (chan_state->ul_ft != chan_state->dl_cmr) return 0; /* count bit errors */ if (L1SAP_IS_CHAN_TCHH(chan_nr)) { chan_state->ber_num += 2; chan_state->ber_sum += (ber + ber); } else { chan_state->ber_num++; chan_state->ber_sum += ber; } /* count frames */ if (chan_state->ber_num < 48) return 0; /* calculate average (reuse ber variable) */ ber = chan_state->ber_sum / chan_state->ber_num; /* FIXME: calculate C/I from BER */ c_i = ber * 100; /* reset bit errors */ chan_state->ber_num = 0; chan_state->ber_sum = 0; LOGP(DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f " "codec id %d of trx=%u chan_nr=0x%02x\n", ber, chan_state->ul_ft, l1h->trx->nr, chan_nr); /* degrade */ if (chan_state->dl_cmr > 0) { /* degrade, if ber is above threshold FIXME: C/I */ if (ber > lchan->tch.amr_mr.mode[chan_state->dl_cmr-1].threshold_bts) { LOGP(DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f " "from codec id %d to %d of trx=%u " "chan_nr=0x%02x\n", ber, chan_state->dl_cmr, chan_state->dl_cmr - 1, l1h->trx->nr, chan_nr); chan_state->dl_cmr--; } return 0; } /* upgrade */ if (chan_state->dl_cmr < chan_state->codecs - 1) { /* degrade, if ber is above threshold FIXME: C/I*/ if (ber < lchan->tch.amr_mr.mode[chan_state->dl_cmr].threshold_bts - lchan->tch.amr_mr.mode[chan_state->dl_cmr].hysteresis_bts) { LOGP(DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f " "from codec id %d to %d of trx=%u " "chan_nr=0x%02x\n", ber, chan_state->dl_cmr, chan_state->dl_cmr + 1, l1h->trx->nr, chan_nr); chan_state->dl_cmr++; } return 0; } return 0; } int trx_loop_amr_set(struct trx_chan_state *chan_state, int loop) { if (chan_state->amr_loop && !loop) { chan_state->amr_loop = 0; return 0; } if (!chan_state->amr_loop && loop) { chan_state->amr_loop = 1; /* reset bit errors */ chan_state->ber_num = 0; chan_state->ber_sum = 0; return 0; } return 0; } osmo-bts-0.4.0/src/osmo-bts-trx/loops.h000066400000000000000000000013531260026426200177320ustar00rootroot00000000000000#ifndef _TRX_LOOPS_H #define _TRX_LOOPS_H /* * calibration of loops */ /* how much power levels do we raise/lower as maximum (1 level = 2 dB) */ #define MS_RAISE_MAX 4 #define MS_LOWER_MAX 1 /* * loops api */ extern int trx_ms_power_loop; extern int8_t trx_target_rssi; extern int trx_ta_loop; int trx_loop_sacch_input(struct trx_l1h *l1h, uint8_t chan_nr, struct trx_chan_state *chan_state, int8_t rssi, float toa); int trx_loop_sacch_clock(struct trx_l1h *l1h, uint8_t chan_nr, struct trx_chan_state *chan_state); int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, struct trx_chan_state *chan_state, float ber); int trx_loop_amr_set(struct trx_chan_state *chan_state, int loop); #endif /* _TRX_LOOPS_H */ osmo-bts-0.4.0/src/osmo-bts-trx/main.c000066400000000000000000000215451260026426200175220ustar00rootroot00000000000000/* Main program for OsmoBTS-TRX */ /* (C) 2011 by Harald Welte * (C) 2013 by Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "l1_if.h" #include "trx_if.h" #include "scheduler.h" const int pcu_direct = 0; int quit = 0; static const char *config_file = "osmo-bts.cfg"; static int daemonize = 0; static char *gsmtap_ip = 0; static int rt_prio = -1; static int trx_num = 1; char *software_version = "0.0"; uint8_t abis_mac[6] = { 0, 1, 2, 3, 4, 5 }; char *bsc_host = "localhost"; char *bts_id = "1801/0"; // FIXME this is a hack static void get_mac(void) { struct if_nameindex *ifn = if_nameindex(); struct ifreq ifr; int sock; int ret; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) return; memset(&ifr, 0, sizeof(ifr)); if (!ifn) return; while (ifn->if_name) { strncpy(ifr.ifr_name, ifn->if_name, sizeof(ifr.ifr_name)-1); ret = ioctl(sock, SIOCGIFHWADDR, &ifr); if (ret == 0 && !!memcmp(ifr.ifr_hwaddr.sa_data, "\0\0\0\0\0\0", 6)) { memcpy(abis_mac, ifr.ifr_hwaddr.sa_data, 6); printf("Using MAC address of %s: " "'%02x:%02x:%02x:%02x:%02x:%02x'\n", ifn->if_name, abis_mac[0], abis_mac[1], abis_mac[2], abis_mac[3], abis_mac[4], abis_mac[5]); break; } ifn++; } // if_freenameindex(ifn); } int bts_model_init(struct gsm_bts *bts) { void *l1h; struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) { l1h = l1if_open(trx); if (!l1h) { LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n"); goto error; } trx->role_bts.l1h = l1h; trx->nominal_power = 23; l1if_reset(l1h); } bts_model_vty_init(bts); return 0; error: llist_for_each_entry(trx, &bts->trx_list, list) { l1h = trx->role_bts.l1h; if (l1h) l1if_close(l1h); } return -EIO; } /* dummy, since no direct dsp support */ uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) { return 0; } static void print_help() { printf( "Some useful options:\n" " -h --help this text\n" " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" " -D --daemonize For the process into a background daemon\n" " -c --config-file Specify the filename of the config file\n" " -s --disable-color Don't use colors in stderr log output\n" " -T --timestamp Prefix every log line with a timestamp\n" " -V --version Print version information and exit\n" " -e --log-level Set a global log-level\n" " -t --trx-num Set number of TRX (default=%d)\n" " -i --gsmtap-ip The destination IP used for GSMTAP.\n" " -r --realtime PRIO Set realtime scheduler with given prio\n" " -I --local-trx-ip Local IP for transceiver to connect (default=%s)\n" ,trx_num, transceiver_ip); } /* FIXME: finally get some option parsing code into libosmocore */ static void handle_options(int argc, char **argv) { while (1) { int option_idx = 0, c; static const struct option long_options[] = { /* FIXME: all those are generic Osmocom app options */ { "help", 0, 0, 'h' }, { "debug", 1, 0, 'd' }, { "daemonize", 0, 0, 'D' }, { "config-file", 1, 0, 'c' }, { "disable-color", 0, 0, 's' }, { "timestamp", 0, 0, 'T' }, { "version", 0, 0, 'V' }, { "log-level", 1, 0, 'e' }, { "trx-num", 1, 0, 't' }, { "gsmtap-ip", 1, 0, 'i' }, { "realtime", 1, 0, 'r' }, { "local-trx-ip", 1, 0, 'I' }, { 0, 0, 0, 0 } }; c = getopt_long(argc, argv, "hc:d:Dc:sTVe:t:i:r:I:", long_options, &option_idx); if (c == -1) break; switch (c) { case 'h': print_help(); exit(0); break; case 's': log_set_use_color(osmo_stderr_target, 0); break; case 'd': log_parse_category_mask(osmo_stderr_target, optarg); break; case 'D': daemonize = 1; break; case 'c': config_file = strdup(optarg); break; case 'T': log_set_print_timestamp(osmo_stderr_target, 1); break; case 'V': print_version(1); exit(0); break; case 'e': log_set_log_level(osmo_stderr_target, atoi(optarg)); break; case 't': trx_num = atoi(optarg); if (trx_num < 1) trx_num = 1; break; case 'i': gsmtap_ip = optarg; break; case 'r': rt_prio = atoi(optarg); break; case 'I': transceiver_ip = strdup(optarg); break; default: break; } } } static struct gsm_bts *bts; static void signal_handler(int signal) { fprintf(stderr, "signal %u received\n", signal); switch (signal) { case SIGINT: //osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); if (!quit) bts_shutdown(bts, "SIGINT"); quit++; break; case SIGABRT: case SIGUSR1: case SIGUSR2: talloc_report_full(tall_bts_ctx, stderr); break; default: break; } } static int write_pid_file(char *procname) { FILE *outf; char tmp[PATH_MAX+1]; snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname); tmp[PATH_MAX-1] = '\0'; outf = fopen(tmp, "w"); if (!outf) return -1; fprintf(outf, "%d\n", getpid()); fclose(outf); return 0; } int main(int argc, char **argv) { struct gsm_bts_role_bts *btsb; struct gsm_bts_trx *trx; struct e1inp_line *line; void *tall_msgb_ctx; int rc, i; printf("((*))\n |\n / \\ OsmoBTS\n"); get_mac(); tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); bts_log_init(NULL); handle_options(argc, argv); bts = gsm_bts_alloc(tall_bts_ctx); if (!bts) { fprintf(stderr, "Failed to create BTS structure\n"); exit(1); } for (i = 1; i < trx_num; i++) { trx = gsm_bts_trx_alloc(bts); if (!trx) { fprintf(stderr, "Failed to TRX structure\n"); exit(1); } } vty_init(&bts_vty_info); e1inp_vty_init(); bts_vty_init(bts, &bts_log_info); if (bts_init(bts) < 0) { fprintf(stderr, "unable to to open bts\n"); exit(1); } btsb = bts_role_bts(bts); btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2); if (gsmtap_ip) { gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); if (!gsmtap) { fprintf(stderr, "Failed during gsmtap_init()\n"); exit(1); } gsmtap_source_add_sink(gsmtap); } abis_init(bts); rc = vty_read_config_file(config_file, NULL); if (rc < 0) { fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); exit(1); } if (!settsc_enabled && !setbsic_enabled) settsc_enabled = setbsic_enabled = 1; write_pid_file("osmo-bts"); rc = telnet_init(tall_bts_ctx, NULL, 4241); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); } if (pcu_sock_init()) { fprintf(stderr, "PCU L1 socket failed\n"); exit(-1); } signal(SIGINT, &signal_handler); //signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); osmo_init_ignore_signals(); if (!btsb->bsc_oml_host) { fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n"); exit(1); } line = abis_open(bts, btsb->bsc_oml_host, "sysmoBTS"); if (!line) { fprintf(stderr, "unable to connect to BSC\n"); exit(1); } if (daemonize) { rc = osmo_daemonize(); if (rc < 0) { perror("Error during daemonize"); exit(1); } } if (rt_prio != -1) { struct sched_param schedp; /* high priority scheduling required for handling bursts */ memset(&schedp, 0, sizeof(schedp)); schedp.sched_priority = rt_prio; rc = sched_setscheduler(0, SCHED_RR, &schedp); if (rc) { fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio); } } while (quit < 2) { log_reset_context(); osmo_select_main(0); } #if 0 telnet_exit(); talloc_report_full(tall_bts_ctx, stderr); #endif return 0; } osmo-bts-0.4.0/src/osmo-bts-trx/scheduler.c000066400000000000000000003672471260026426200205700ustar00rootroot00000000000000/* Scheduler for OsmoBTS-TRX */ /* (C) 2013 by Andreas Eversberg * (C) 2015 by Alexander Chemeris * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include #include #include #include #include #include #include #include "l1_if.h" #include "scheduler.h" #include "gsm0503_coding.h" #include "trx_if.h" #include "loops.h" #include "amr.h" #include "loops.h" /* Enable this to multiply TOA of RACH by 10. * This is usefull to check tenth of timing advances with RSSI test tool. * Note that regular phones will not work when using this test! */ //#define TA_TEST void *tall_bts_ctx; static struct gsm_bts *bts; /* clock states */ static uint32_t transceiver_lost; uint32_t transceiver_last_fn; static struct timeval transceiver_clock_tv; static struct osmo_timer_list transceiver_clock_timer; /* clock advance for the transceiver */ uint32_t trx_clock_advance = 20; /* advance RTS to give some time for data processing. (especially PCU) */ uint32_t trx_rts_advance = 5; /* about 20ms */ typedef int trx_sched_rts_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan); typedef ubit_t *trx_sched_dl_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); typedef int trx_sched_ul_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa); static int rts_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan); static int rts_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan); static int rts_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan); static ubit_t *tx_idle_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static ubit_t *tx_fcch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static ubit_t *tx_sch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static ubit_t *tx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static ubit_t *tx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static ubit_t *tx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static ubit_t *tx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid); static int rx_rach_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa); static int rx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa); static int rx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa); static int rx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa); static int rx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa); static ubit_t dummy_burst[148] = { 0,0,0, 1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0, 0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0, 0,1,0,1,1,1,0,0,0,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0, 0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,0,0,1, 0,0,1,0,1,1,1,1,1,0,1,0,1,0, 0,0,0, }; static ubit_t fcch_burst[148] = { 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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0, }; static const ubit_t tsc[8][26] = { { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, }, { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, }, { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, }, { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, }, { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, }, { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, }, { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, }, { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, }, }; static const ubit_t sch_train[64] = { 1,0,1,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,1, 0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,0,1,1, }; /* * subchannel description structure */ struct trx_chan_desc { int pdch; enum trx_chan_type chan; uint8_t chan_nr; uint8_t link_id; const char *name; trx_sched_rts_func *rts_fn; trx_sched_dl_func *dl_fn; trx_sched_ul_func *ul_fn; int auto_active; }; struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { { 0, TRXC_IDLE, 0, 0, "IDLE", NULL, tx_idle_fn, NULL, 1 }, { 0, TRXC_FCCH, 0, 0, "FCCH", NULL, tx_fcch_fn, NULL, 1 }, { 0, TRXC_SCH, 0, 0, "SCH", NULL, tx_sch_fn, NULL, 1 }, { 0, TRXC_BCCH, 0x80, 0x00, "BCCH", rts_data_fn, tx_data_fn, NULL, 1 }, { 0, TRXC_RACH, 0x88, 0x00, "RACH", NULL, NULL, rx_rach_fn, 1 }, { 0, TRXC_CCCH, 0x90, 0x00, "CCCH", rts_data_fn, tx_data_fn, NULL, 1 }, { 0, TRXC_TCHF, 0x08, 0x00, "TCH/F", rts_tchf_fn, tx_tchf_fn, rx_tchf_fn, 0 }, { 0, TRXC_TCHH_0, 0x10, 0x00, "TCH/H(0)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 }, { 0, TRXC_TCHH_1, 0x18, 0x00, "TCH/H(1)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 }, { 0, TRXC_SDCCH4_0, 0x20, 0x00, "SDCCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH4_1, 0x28, 0x00, "SDCCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH4_2, 0x30, 0x00, "SDCCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH4_3, 0x38, 0x00, "SDCCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_0, 0x40, 0x00, "SDCCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_1, 0x48, 0x00, "SDCCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_2, 0x50, 0x00, "SDCCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_3, 0x58, 0x00, "SDCCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_4, 0x60, 0x00, "SDCCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_5, 0x68, 0x00, "SDCCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_6, 0x70, 0x00, "SDCCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SDCCH8_7, 0x78, 0x00, "SDCCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCHTF, 0x08, 0x40, "SACCH/TF", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCHTH_0, 0x10, 0x40, "SACCH/TH(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCHTH_1, 0x18, 0x40, "SACCH/TH(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH4_0, 0x20, 0x40, "SACCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH4_1, 0x28, 0x40, "SACCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH4_2, 0x30, 0x40, "SACCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH4_3, 0x38, 0x40, "SACCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_0, 0x40, 0x40, "SACCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_1, 0x48, 0x40, "SACCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_2, 0x50, 0x40, "SACCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_3, 0x58, 0x40, "SACCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_4, 0x60, 0x40, "SACCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_5, 0x68, 0x40, "SACCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_6, 0x70, 0x40, "SACCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 0, TRXC_SACCH8_7, 0x78, 0x40, "SACCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, { 1, TRXC_PDTCH, 0x08, 0x00, "PDTCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 }, { 1, TRXC_PTCCH, 0x08, 0x00, "PTCCH", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, }; /* * init / exit */ int trx_sched_init(struct trx_l1h *l1h) { uint8_t tn; int i; struct trx_chan_state *chan_state; LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1h->trx->nr); /* hack to get bts */ bts = l1h->trx->bts; for (tn = 0; tn < 8; tn++) { l1h->mf_index[tn] = 0; l1h->mf_last_fn[tn] = 0; INIT_LLIST_HEAD(&l1h->dl_prims[tn]); for (i = 0; i < _TRX_CHAN_MAX; i++) { chan_state = &l1h->chan_states[tn][i]; chan_state->active = 0; } } return 0; } void trx_sched_exit(struct trx_l1h *l1h) { uint8_t tn; int i; struct trx_chan_state *chan_state; LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1h->trx->nr); for (tn = 0; tn < 8; tn++) { msgb_queue_flush(&l1h->dl_prims[tn]); for (i = 0; i < _TRX_CHAN_MAX; i++) { chan_state = &l1h->chan_states[tn][i]; if (chan_state->dl_bursts) { talloc_free(chan_state->dl_bursts); chan_state->dl_bursts = NULL; } if (chan_state->ul_bursts) { talloc_free(chan_state->ul_bursts); chan_state->ul_bursts = NULL; } } /* clear lchan channel states */ for (i = 0; i < 8; i++) l1h->trx->ts[tn].lchan[i].state = LCHAN_S_NONE; } } /* close all logical channels and reset timeslots */ void trx_sched_reset(struct trx_l1h *l1h) { trx_sched_exit(l1h); trx_sched_init(l1h); } /* * data request (from upper layer) */ int trx_sched_ph_data_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap) { uint8_t tn = l1sap->u.data.chan_nr & 7; LOGP(DL1C, LOGL_INFO, "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x " "fn=%u ts=%u trx=%u\n", l1sap->u.data.chan_nr, l1sap->u.data.link_id, l1sap->u.data.fn, tn, l1h->trx->nr); if (!l1sap->oph.msg) abort(); /* ignore empty frame */ if (!msgb_l2len(l1sap->oph.msg)) { msgb_free(l1sap->oph.msg); return 0; } msgb_enqueue(&l1h->dl_prims[tn], l1sap->oph.msg); return 0; } int trx_sched_tch_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap) { uint8_t tn = l1sap->u.tch.chan_nr & 7; LOGP(DL1C, LOGL_INFO, "TCH.req: chan_nr=0x%02x " "fn=%u ts=%u trx=%u\n", l1sap->u.tch.chan_nr, l1sap->u.tch.fn, tn, l1h->trx->nr); if (!l1sap->oph.msg) abort(); /* ignore empty frame */ if (!msgb_l2len(l1sap->oph.msg)) { msgb_free(l1sap->oph.msg); return 0; } msgb_enqueue(&l1h->dl_prims[tn], l1sap->oph.msg); return 0; } /* * ready-to-send indication (to upper layer) */ /* RTS for data frame */ static int rts_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan) { uint8_t chan_nr, link_id; struct msgb *msg; struct osmo_phsap_prim *l1sap; /* get data for RTS indication */ chan_nr = trx_chan_desc[chan].chan_nr | tn; link_id = trx_chan_desc[chan].link_id; if (!chan_nr) { LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); return -ENODEV; } LOGP(DL1C, LOGL_INFO, "PH-RTS.ind: chan=%s chan_nr=0x%02x " "link_id=0x%02x fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, chan_nr, link_id, fn, tn, l1h->trx->nr); /* send clock information to loops process */ if (L1SAP_IS_LINK_SACCH(link_id)) trx_loop_sacch_clock(l1h, chan_nr, &l1h->chan_states[tn][chan]); /* generate prim */ msg = l1sap_msgb_alloc(200); if (!msg) return -ENOMEM; l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = link_id; l1sap->u.data.fn = fn; return l1sap_up(l1h->trx, l1sap); } static int rts_tch_common(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, int facch) { uint8_t chan_nr, link_id; struct msgb *msg; struct osmo_phsap_prim *l1sap; int rc = 0; /* get data for RTS indication */ chan_nr = trx_chan_desc[chan].chan_nr | tn; link_id = trx_chan_desc[chan].link_id; if (!chan_nr) { LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); return -ENODEV; } LOGP(DL1C, LOGL_INFO, "TCH RTS.ind: chan=%s chan_nr=0x%02x " "fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, chan_nr, fn, tn, l1h->trx->nr); /* only send, if FACCH is selected */ if (facch) { /* generate prim */ msg = l1sap_msgb_alloc(200); if (!msg) return -ENOMEM; l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = link_id; l1sap->u.data.fn = fn; rc = l1sap_up(l1h->trx, l1sap); } /* dont send, if TCH is in signalling only mode */ if (l1h->chan_states[tn][chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { /* generate prim */ msg = l1sap_msgb_alloc(200); if (!msg) return -ENOMEM; l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, PRIM_OP_INDICATION, msg); l1sap->u.tch.chan_nr = chan_nr; l1sap->u.tch.fn = fn; return l1sap_up(l1h->trx, l1sap); } return rc; } /* RTS for full rate traffic frame */ static int rts_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan) { /* TCH/F may include FACCH on every 4th burst */ return rts_tch_common(l1h, tn, fn, chan, 1); } /* RTS for half rate traffic frame */ static int rts_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan) { /* the FN 4/5, 13/14, 21/22 defines that FACCH may be included. */ return rts_tch_common(l1h, tn, fn, chan, ((fn % 26) >> 2) & 1); } /* * TX on downlink */ /* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ static ubit_t *tx_idle_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); return NULL; } static ubit_t *tx_fcch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); return fcch_burst; } static ubit_t *tx_sch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { static ubit_t bits[148], burst[78]; uint8_t sb_info[4]; struct gsm_time t; uint8_t t3p, bsic; LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); /* create SB info from GSM time and BSIC */ gsm_fn2gsmtime(&t, fn); t3p = t.t3 / 10; bsic = l1h->trx->bts->bsic; sb_info[0] = ((bsic & 0x3f) << 2) | ((t.t1 & 0x600) >> 9); sb_info[1] = ((t.t1 & 0x1fe) >> 1); sb_info[2] = ((t.t1 & 0x001) << 7) | ((t.t2 & 0x1f) << 2) | ((t3p & 0x6) >> 1); sb_info[3] = (t3p & 0x1); /* encode bursts */ sch_encode(burst, sb_info); /* compose burst */ memset(bits, 0, 3); memcpy(bits + 3, burst, 39); memcpy(bits + 42, sch_train, 64); memcpy(bits + 106, burst + 39, 39); memset(bits + 145, 0, 3); return bits; } static struct msgb *dequeue_prim(struct trx_l1h *l1h, int8_t tn,uint32_t fn, enum trx_chan_type chan) { struct msgb *msg, *msg2; struct osmo_phsap_prim *l1sap; uint32_t prim_fn; uint8_t chan_nr, link_id; /* get prim of current fn from queue */ llist_for_each_entry_safe(msg, msg2, &l1h->dl_prims[tn], list) { l1sap = msgb_l1sap_prim(msg); if (l1sap->oph.operation != PRIM_OP_REQUEST) { wrong_type: LOGP(DL1C, LOGL_ERROR, "Prim for ts=%u at fn=%u has " "wrong type.\n", tn, fn); free_msg: /* unlink and free message */ llist_del(&msg->list); msgb_free(msg); return NULL; } switch (l1sap->oph.primitive) { case PRIM_PH_DATA: chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; prim_fn = ((l1sap->u.data.fn + 2715648 - fn) % 2715648); break; case PRIM_TCH: chan_nr = l1sap->u.tch.chan_nr; link_id = 0; prim_fn = ((l1sap->u.tch.fn + 2715648 - fn) % 2715648); break; default: goto wrong_type; } if (prim_fn > 100) { LOGP(DL1C, LOGL_NOTICE, "Prim for trx=%u ts=%u at fn=%u " "is out of range, or channel already disabled. " "If this happens in conjunction with PCU, " "increase 'rts-advance' by 5. (current fn=%u)\n", l1h->trx->nr, tn, l1sap->u.data.fn, fn); /* unlink and free message */ llist_del(&msg->list); msgb_free(msg); continue; } if (prim_fn > 0) continue; goto found_msg; } return NULL; found_msg: if ((chan_nr ^ (trx_chan_desc[chan].chan_nr | tn)) || ((link_id & 0xc0) ^ trx_chan_desc[chan].link_id)) { LOGP(DL1C, LOGL_ERROR, "Prim for ts=%u at fn=%u has wrong " "chan_nr=%02x link_id=%02x, expecting chan_nr=%02x " "link_id=%02x.\n", tn, fn, chan_nr, link_id, trx_chan_desc[chan].chan_nr | tn, trx_chan_desc[chan].link_id); goto free_msg; } /* unlink and return message */ llist_del(&msg->list); return msg; } static int compose_ph_data_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t *l2, uint8_t l2_len, float rssi) { struct msgb *msg; struct osmo_phsap_prim *l1sap; uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; /* compose primitive */ msg = l1sap_msgb_alloc(l2_len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = trx_chan_desc[chan].link_id; l1sap->u.data.fn = fn; l1sap->u.data.rssi = (int8_t) (rssi); msg->l2h = msgb_put(msg, l2_len); if (l2_len) memcpy(msg->l2h, l2, l2_len); if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) l1h->chan_states[tn][chan].lost = 0; /* forward primitive */ l1sap_up(l1h->trx, l1sap); return 0; } static int compose_tch_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len) { struct msgb *msg; struct osmo_phsap_prim *l1sap; /* compose primitive */ msg = l1sap_msgb_alloc(tch_len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); l1sap->u.tch.chan_nr = trx_chan_desc[chan].chan_nr | tn; l1sap->u.tch.fn = fn; msg->l2h = msgb_put(msg, tch_len); if (tch_len) memcpy(msg->l2h, tch, tch_len); if (l1h->chan_states[tn][chan].lost) l1h->chan_states[tn][chan].lost--; /* forward primitive */ l1sap_up(l1h->trx, l1sap); return 0; } static ubit_t *tx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { struct msgb *msg = NULL; /* make GCC happy */ ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; static ubit_t bits[148]; /* send burst, if we already got a frame */ if (bid > 0) { if (!*bursts_p) return NULL; goto send_burst; } /* get burst from queue */ msg = dequeue_prim(l1h, tn, fn, chan); if (msg) goto got_msg; LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " "trx=%u ts=%u at fn=%u to transmit.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); no_msg: /* free burst memory */ if (*bursts_p) { talloc_free(*bursts_p); *bursts_p = NULL; } return NULL; got_msg: /* check validity of message */ if (msgb_l2len(msg) != 23) { LOGP(DL1C, LOGL_FATAL, "Prim not 23 bytes, please FIX! " "(len=%d)\n", msgb_l2len(msg)); /* free message */ msgb_free(msg); goto no_msg; } /* handle loss detection of sacch */ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { /* count and send BFI */ if (++(l1h->chan_states[tn][chan].lost) > 1) { /* TODO: Should we pass old TOA here? Otherwise we risk * unnecessary decreasing TA */ /* Send uplnk measurement information to L2 */ l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, 456, 456, -110, 0); compose_ph_data_ind(l1h, tn, 0, chan, NULL, 0, -110); } } /* alloc burst memory, if not already */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 464); if (!*bursts_p) return NULL; } /* encode bursts */ xcch_encode(*bursts_p, msg->l2h); /* free message */ msgb_free(msg); send_burst: /* compose burst */ burst = *bursts_p + bid * 116; memset(bits, 0, 3); memcpy(bits + 3, burst, 58); memcpy(bits + 61, tsc[l1h->config.tsc], 26); memcpy(bits + 87, burst + 58, 58); memset(bits + 145, 0, 3); LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); return bits; } static ubit_t *tx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { struct msgb *msg = NULL; /* make GCC happy */ ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; static ubit_t bits[148]; int rc; /* send burst, if we already got a frame */ if (bid > 0) { if (!*bursts_p) return NULL; goto send_burst; } /* get burst from queue */ msg = dequeue_prim(l1h, tn, fn, chan); if (msg) goto got_msg; LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " "trx=%u ts=%u at fn=%u to transmit.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); no_msg: /* free burst memory */ if (*bursts_p) { talloc_free(*bursts_p); *bursts_p = NULL; } return NULL; got_msg: /* alloc burst memory, if not already */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 464); if (!*bursts_p) return NULL; } /* encode bursts */ rc = pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); /* check validity of message */ if (rc) { LOGP(DL1C, LOGL_FATAL, "Prim invalid length, please FIX! " "(len=%d)\n", rc); /* free message */ msgb_free(msg); goto no_msg; } /* free message */ msgb_free(msg); send_burst: /* compose burst */ burst = *bursts_p + bid * 116; memset(bits, 0, 3); memcpy(bits + 3, burst, 58); memcpy(bits + 61, tsc[l1h->config.tsc], 26); memcpy(bits + 87, burst + 58, 58); memset(bits + 145, 0, 3); LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); return bits; } static void tx_tch_common(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, struct msgb **_msg_facch, int codec_mode_request) { struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; uint8_t rsl_cmode = chan_state->rsl_cmode; uint8_t tch_mode = chan_state->tch_mode; struct osmo_phsap_prim *l1sap; /* handle loss detection of received TCH frames */ if (rsl_cmode == RSL_CMOD_SPD_SPEECH && ++(l1h->chan_states[tn][chan].lost) > 5) { uint8_t tch_data[33]; int len; LOGP(DL1C, LOGL_NOTICE, "Missing TCH bursts detected, sending " "BFI for %s\n", trx_chan_desc[chan].name); /* indicate bad frame */ switch (tch_mode) { case GSM48_CMODE_SPEECH_V1: /* FR / HR */ if (chan != TRXC_TCHF) { /* HR */ tch_data[0] = 0x70; /* F = 0, FT = 111 */ memset(tch_data + 1, 0, 14); len = 15; break; } memset(tch_data, 0, 33); len = 33; break; case GSM48_CMODE_SPEECH_EFR: /* EFR */ if (chan != TRXC_TCHF) goto inval_mode1; memset(tch_data, 0, 31); len = 31; break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ len = amr_compose_payload(tch_data, chan_state->codec[chan_state->dl_cmr], chan_state->codec[chan_state->dl_ft], 1); if (len < 2) break; memset(tch_data + 2, 0, len - 2); compose_tch_ind(l1h, tn, 0, chan, tch_data, len); break; default: inval_mode1: LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, please " "fix!\n"); len = 0; } if (len) compose_tch_ind(l1h, tn, 0, chan, tch_data, len); } /* get frame and unlink from queue */ msg1 = dequeue_prim(l1h, tn, fn, chan); msg2 = dequeue_prim(l1h, tn, fn, chan); if (msg1) { l1sap = msgb_l1sap_prim(msg1); if (l1sap->oph.primitive == PRIM_TCH) { msg_tch = msg1; if (msg2) { l1sap = msgb_l1sap_prim(msg2); if (l1sap->oph.primitive == PRIM_TCH) { LOGP(DL1C, LOGL_FATAL, "TCH twice, " "please FIX! "); msgb_free(msg2); } else msg_facch = msg2; } } else { msg_facch = msg1; if (msg2) { l1sap = msgb_l1sap_prim(msg2); if (l1sap->oph.primitive != PRIM_TCH) { LOGP(DL1C, LOGL_FATAL, "FACCH twice, " "please FIX! "); msgb_free(msg2); } else msg_tch = msg2; } } } else if (msg2) { l1sap = msgb_l1sap_prim(msg2); if (l1sap->oph.primitive == PRIM_TCH) msg_tch = msg2; else msg_facch = msg2; } /* check validity of message */ if (msg_facch && msgb_l2len(msg_facch) != 23) { LOGP(DL1C, LOGL_FATAL, "Prim not 23 bytes, please FIX! " "(len=%d)\n", msgb_l2len(msg_facch)); /* free message */ msgb_free(msg_facch); msg_facch = NULL; } /* check validity of message, get AMR ft and cmr */ if (!msg_facch && msg_tch) { int len; uint8_t bfi, cmr_codec, ft_codec; int cmr, ft, i; if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { LOGP(DL1C, LOGL_NOTICE, "%s Dropping speech frame, " "because we are not in speech mode trx=%u " "ts=%u at fn=%u.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto free_bad_msg; } switch (tch_mode) { case GSM48_CMODE_SPEECH_V1: /* FR / HR */ if (chan != TRXC_TCHF) { /* HR */ len = 15; if (msgb_l2len(msg_tch) >= 1 && (msg_tch->l2h[0] & 0xf0) != 0x00) { LOGP(DL1C, LOGL_NOTICE, "%s " "Transmitting 'bad " "HR frame' trx=%u ts=%u at " "fn=%u.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto free_bad_msg; } break; } len = 33; if (msgb_l2len(msg_tch) >= 1 && (msg_tch->l2h[0] >> 4) != 0xd) { LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " "FR frame' trx=%u ts=%u at fn=%u.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto free_bad_msg; } break; case GSM48_CMODE_SPEECH_EFR: /* EFR */ if (chan != TRXC_TCHF) goto inval_mode2; len = 31; if (msgb_l2len(msg_tch) >= 1 && (msg_tch->l2h[0] >> 4) != 0xc) { LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " "EFR frame' trx=%u ts=%u at fn=%u.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto free_bad_msg; } break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ len = amr_decompose_payload(msg_tch->l2h, msgb_l2len(msg_tch), &cmr_codec, &ft_codec, &bfi); cmr = -1; ft = -1; for (i = 0; i < chan_state->codecs; i++) { if (chan_state->codec[i] == cmr_codec) cmr = i; if (chan_state->codec[i] == ft_codec) ft = i; } if (cmr >= 0) { /* new request */ chan_state->dl_cmr = cmr; /* disable AMR loop */ trx_loop_amr_set(chan_state, 0); } else { /* enable AMR loop */ trx_loop_amr_set(chan_state, 1); } if (ft < 0) { LOGP(DL1C, LOGL_ERROR, "%s Codec (FT = %d) " " of RTP frame not in list. " "trx=%u ts=%u\n", trx_chan_desc[chan].name, ft_codec, l1h->trx->nr, tn); goto free_bad_msg; } if (codec_mode_request && chan_state->dl_ft != ft) { LOGP(DL1C, LOGL_NOTICE, "%s Codec (FT = %d) " " of RTP cannot be changed now, but in " "next frame. trx=%u ts=%u\n", trx_chan_desc[chan].name, ft_codec, l1h->trx->nr, tn); goto free_bad_msg; } chan_state->dl_ft = ft; if (bfi) { LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " "AMR frame' trx=%u ts=%u at fn=%u.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto free_bad_msg; } break; default: inval_mode2: LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, please " "fix!\n"); goto free_bad_msg; } if (len < 0) { LOGP(DL1C, LOGL_ERROR, "Cannot send invalid AMR " "payload\n"); goto free_bad_msg; } if (msgb_l2len(msg_tch) != len) { LOGP(DL1C, LOGL_ERROR, "Cannot send payload with " "invalid length! (expecing %d, received %d)\n", len, msgb_l2len(msg_tch)); free_bad_msg: /* free message */ msgb_free(msg_tch); msg_tch = NULL; goto send_frame; } } send_frame: *_msg_tch = msg_tch; *_msg_facch = msg_facch; } static ubit_t *tx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { struct msgb *msg_tch = NULL, *msg_facch = NULL; struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; uint8_t tch_mode = chan_state->tch_mode; ubit_t *burst, **bursts_p = &chan_state->dl_bursts; static ubit_t bits[148]; /* send burst, if we already got a frame */ if (bid > 0) { if (!*bursts_p) return NULL; goto send_burst; } tx_tch_common(l1h, tn, fn, chan, bid, &msg_tch, &msg_facch, (((fn + 4) % 26) >> 2) & 1); /* alloc burst memory, if not already, * otherwise shift buffer by 4 bursts for interleaving */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 928); if (!*bursts_p) return NULL; } else { memcpy(*bursts_p, *bursts_p + 464, 464); memset(*bursts_p + 464, 0, 464); } /* no message at all */ if (!msg_tch && !msg_facch) { LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " "trx=%u ts=%u at fn=%u to transmit.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto send_burst; } /* encode bursts (priorize FACCH) */ if (msg_facch) tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch), 1); else if (tch_mode == GSM48_CMODE_SPEECH_AMR) /* the first FN 4,13,21 defines that CMI is included in frame, * the first FN 0,8,17 defines that CMR is included in frame. */ tch_afs_encode(*bursts_p, msg_tch->l2h + 2, msgb_l2len(msg_tch) - 2, (((fn + 4) % 26) >> 2) & 1, chan_state->codec, chan_state->codecs, chan_state->dl_ft, chan_state->dl_cmr); else tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1); /* free message */ if (msg_tch) msgb_free(msg_tch); if (msg_facch) msgb_free(msg_facch); send_burst: /* compose burst */ burst = *bursts_p + bid * 116; memset(bits, 0, 3); memcpy(bits + 3, burst, 58); memcpy(bits + 61, tsc[l1h->config.tsc], 26); memcpy(bits + 87, burst + 58, 58); memset(bits + 145, 0, 3); LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); return bits; } static ubit_t *tx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid) { struct msgb *msg_tch = NULL, *msg_facch = NULL; struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; uint8_t tch_mode = chan_state->tch_mode; ubit_t *burst, **bursts_p = &chan_state->dl_bursts; static ubit_t bits[148]; /* send burst, if we already got a frame */ if (bid > 0) { if (!*bursts_p) return NULL; goto send_burst; } /* get TCH and/or FACCH */ tx_tch_common(l1h, tn, fn, chan, bid, &msg_tch, &msg_facch, (((fn + 4) % 26) >> 2) & 1); /* check for FACCH alignment */ if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { LOGP(DL1C, LOGL_ERROR, "%s Cannot transmit FACCH starting on " "even frames, please fix RTS!\n", trx_chan_desc[chan].name); msgb_free(msg_facch); msg_facch = NULL; } /* alloc burst memory, if not already, * otherwise shift buffer by 2 bursts for interleaving */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 696); if (!*bursts_p) return NULL; } else { memcpy(*bursts_p, *bursts_p + 232, 232); if (chan_state->dl_ongoing_facch) { memcpy(*bursts_p + 232, *bursts_p + 464, 232); memset(*bursts_p + 464, 0, 232); } else { memset(*bursts_p + 232, 0, 232); } } /* no message at all */ if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " "trx=%u ts=%u at fn=%u to transmit.\n", trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); goto send_burst; } /* encode bursts (priorize FACCH) */ if (msg_facch) { tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch)); chan_state->dl_ongoing_facch = 1; /* first of two tch frames */ } else if (chan_state->dl_ongoing_facch) /* second of two tch frames */ chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */ else if (tch_mode == GSM48_CMODE_SPEECH_AMR) /* the first FN 4,13,21 or 5,14,22 defines that CMI is included * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is * included in frame. */ tch_ahs_encode(*bursts_p, msg_tch->l2h + 2, msgb_l2len(msg_tch) - 2, (((fn + 4) % 26) >> 2) & 1, chan_state->codec, chan_state->codecs, chan_state->dl_ft, chan_state->dl_cmr); else tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch)); /* free message */ if (msg_tch) msgb_free(msg_tch); if (msg_facch) msgb_free(msg_facch); send_burst: /* compose burst */ burst = *bursts_p + bid * 116; memset(bits, 0, 3); memcpy(bits + 3, burst, 58); memcpy(bits + 61, tsc[l1h->config.tsc], 26); memcpy(bits + 87, burst + 58, 58); memset(bits + 145, 0, 3); LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); return bits; } /* * RX on uplink (indication to upper layer) */ static int rx_rach_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa) { uint8_t chan_nr; struct osmo_phsap_prim l1sap; uint8_t ra; int rc; chan_nr = trx_chan_desc[chan].chan_nr | tn; LOGP(DL1C, LOGL_NOTICE, "Received Access Burst on %s fn=%u toa=%.2f\n", trx_chan_desc[chan].name, fn, toa); /* decode */ rc = rach_decode(&ra, bits + 8 + 41, l1h->trx->bts->bsic); if (rc) { LOGP(DL1C, LOGL_NOTICE, "Received bad AB frame at fn=%u " "(%u/51)\n", fn, fn % 51); return 0; } /* compose primitive */ /* generate prim */ memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL); l1sap.u.rach_ind.chan_nr = chan_nr; l1sap.u.rach_ind.ra = ra; #ifdef TA_TEST #warning TIMING ADVANCE TEST-HACK IS ENABLED!!! toa *= 10; #endif l1sap.u.rach_ind.acc_delay = (toa >= 0) ? toa : 0; l1sap.u.rach_ind.fn = fn; /* forward primitive */ l1sap_up(l1h->trx, &l1sap); return 0; } static int rx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa) { struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; sbit_t *burst, **bursts_p = &chan_state->ul_bursts; uint32_t *first_fn = &chan_state->ul_first_fn; uint8_t *mask = &chan_state->ul_mask; float *rssi_sum = &chan_state->rssi_sum; uint8_t *rssi_num = &chan_state->rssi_num; float *toa_sum = &chan_state->toa_sum; uint8_t *toa_num = &chan_state->toa_num; uint8_t l2[23], l2_len; int n_errors, n_bits_total; int rc; /* handle rach, if handover rach detection is turned on */ if (chan_state->ho_rach_detect == 1) return rx_rach_fn(l1h, tn, fn, chan, bid, bits, rssi, toa); LOGP(DL1C, LOGL_DEBUG, "Data received %s fn=%u ts=%u trx=%u bid=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); /* alloc burst memory, if not already */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 464); if (!*bursts_p) return -ENOMEM; } /* clear burst & store frame number of first burst */ if (bid == 0) { memset(*bursts_p, 0, 464); *mask = 0x0; *first_fn = fn; *rssi_sum = 0; *rssi_num = 0; *toa_sum = 0; *toa_num = 0; } /* update mask + rssi */ *mask |= (1 << bid); *rssi_sum += rssi; (*rssi_num)++; *toa_sum += toa; (*toa_num)++; /* copy burst to buffer of 4 bursts */ burst = *bursts_p + bid * 116; memcpy(burst, bits + 3, 58); memcpy(burst + 58, bits + 87, 58); /* send burst information to loops process */ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { trx_loop_sacch_input(l1h, trx_chan_desc[chan].chan_nr | tn, chan_state, rssi, toa); } /* wait until complete set of bursts */ if (bid != 3) return 0; /* check for complete set of bursts */ if ((*mask & 0xf) != 0xf) { LOGP(DL1C, LOGL_NOTICE, "Received incomplete data frame at " "fn=%u (%u/%u) for %s\n", *first_fn, (*first_fn) % l1h->mf_period[tn], l1h->mf_period[tn], trx_chan_desc[chan].name); /* we require first burst to have correct FN */ if (!(*mask & 0x1)) { *mask = 0x0; return 0; } } *mask = 0x0; /* decode */ rc = xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total); if (rc) { LOGP(DL1C, LOGL_NOTICE, "Received bad data frame at fn=%u " "(%u/%u) for %s\n", *first_fn, (*first_fn) % l1h->mf_period[tn], l1h->mf_period[tn], trx_chan_desc[chan].name); l2_len = 0; } else l2_len = 23; /* Send uplnk measurement information to L2 */ l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa_sum / *toa_num); return compose_ph_data_ind(l1h, tn, *first_fn, chan, l2, l2_len, *rssi_sum / *rssi_num); } static int rx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa) { struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; sbit_t *burst, **bursts_p = &chan_state->ul_bursts; uint8_t *mask = &chan_state->ul_mask; float *rssi_sum = &chan_state->rssi_sum; uint8_t *rssi_num = &chan_state->rssi_num; float *toa_sum = &chan_state->toa_sum; uint8_t *toa_num = &chan_state->toa_num; uint8_t l2[54+1]; int n_errors, n_bits_total; int rc; LOGP(DL1C, LOGL_DEBUG, "PDTCH received %s fn=%u ts=%u trx=%u bid=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); /* alloc burst memory, if not already */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 464); if (!*bursts_p) return -ENOMEM; } /* clear burst */ if (bid == 0) { memset(*bursts_p, 0, 464); *mask = 0x0; *rssi_sum = 0; *rssi_num = 0; *toa_sum = 0; *toa_num = 0; } /* update mask + rssi */ *mask |= (1 << bid); *rssi_sum += rssi; (*rssi_num)++; *toa_sum += toa; (*toa_num)++; /* copy burst to buffer of 4 bursts */ burst = *bursts_p + bid * 116; memcpy(burst, bits + 3, 58); memcpy(burst + 58, bits + 87, 58); /* wait until complete set of bursts */ if (bid != 3) return 0; /* check for complete set of bursts */ if ((*mask & 0xf) != 0xf) { LOGP(DL1C, LOGL_NOTICE, "Received incomplete PDTCH block " "ending at fn=%u (%u/%u) for %s\n", fn, fn % l1h->mf_period[tn], l1h->mf_period[tn], trx_chan_desc[chan].name); } *mask = 0x0; /* decode */ rc = pdtch_decode(l2 + 1, *bursts_p, NULL, &n_errors, &n_bits_total); /* Send uplnk measurement information to L2 */ l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa_sum / *toa_num); if (rc <= 0) { LOGP(DL1C, LOGL_NOTICE, "Received bad PDTCH block ending at " "fn=%u (%u/%u) for %s\n", fn, fn % l1h->mf_period[tn], l1h->mf_period[tn], trx_chan_desc[chan].name); return 0; } l2[0] = 7; /* valid frame */ return compose_ph_data_ind(l1h, tn, (fn + 2715648 - 3) % 2715648, chan, l2, rc + 1, *rssi_sum / *rssi_num); } static int rx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa) { struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; sbit_t *burst, **bursts_p = &chan_state->ul_bursts; uint8_t *mask = &chan_state->ul_mask; uint8_t rsl_cmode = chan_state->rsl_cmode; uint8_t tch_mode = chan_state->tch_mode; uint8_t tch_data[128]; /* just to be safe */ int rc, amr = 0; int n_errors, n_bits_total; /* handle rach, if handover rach detection is turned on */ if (chan_state->ho_rach_detect == 1) return rx_rach_fn(l1h, tn, fn, chan, bid, bits, rssi, toa); LOGP(DL1C, LOGL_DEBUG, "TCH/F received %s fn=%u ts=%u trx=%u bid=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); /* alloc burst memory, if not already */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 928); if (!*bursts_p) return -ENOMEM; } /* clear burst */ if (bid == 0) { memset(*bursts_p + 464, 0, 464); *mask = 0x0; } /* update mask */ *mask |= (1 << bid); /* copy burst to end of buffer of 8 bursts */ burst = *bursts_p + bid * 116 + 464; memcpy(burst, bits + 3, 58); memcpy(burst + 58, bits + 87, 58); /* wait until complete set of bursts */ if (bid != 3) return 0; /* check for complete set of bursts */ if ((*mask & 0xf) != 0xf) { LOGP(DL1C, LOGL_NOTICE, "Received incomplete TCH frame ending " "at fn=%u (%u/%u) for %s\n", fn, fn % l1h->mf_period[tn], l1h->mf_period[tn], trx_chan_desc[chan].name); } *mask = 0x0; /* decode * also shift buffer by 4 bursts for interleaving */ switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1 : tch_mode) { case GSM48_CMODE_SPEECH_V1: /* FR */ rc = tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total); break; case GSM48_CMODE_SPEECH_EFR: /* EFR */ rc = tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total); break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ /* the first FN 0,8,17 defines that CMI is included in frame, * the first FN 4,13,21 defines that CMR is included in frame. * NOTE: A frame ends 7 FN after start. */ rc = tch_afs_decode(tch_data + 2, *bursts_p, (((fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec, chan_state->codecs, &chan_state->ul_ft, &chan_state->ul_cmr, &n_errors, &n_bits_total); if (rc) trx_loop_amr_input(l1h, trx_chan_desc[chan].chan_nr | tn, chan_state, (float)n_errors/(float)n_bits_total); amr = 2; /* we store tch_data + 2 header bytes */ /* only good speech frames get rtp header */ if (rc != 23 && rc >= 4) { rc = amr_compose_payload(tch_data, chan_state->codec[chan_state->ul_cmr], chan_state->codec[chan_state->ul_ft], 0); } break; default: LOGP(DL1C, LOGL_ERROR, "TCH mode %u invalid, please fix!\n", tch_mode); return -EINVAL; } memcpy(*bursts_p, *bursts_p + 464, 464); /* Send uplnk measurement information to L2 */ l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr|tn, n_errors, n_bits_total, rssi, toa); /* Check if the frame is bad */ if (rc < 0) { LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " "fn=%u for %s\n", fn, trx_chan_desc[chan].name); goto bfi; } if (rc < 4) { LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " "fn=%u for %s with codec mode %d (out of range)\n", fn, trx_chan_desc[chan].name, rc); goto bfi; } /* FACCH */ if (rc == 23) { compose_ph_data_ind(l1h, tn, (fn + 2715648 - 7) % 2715648, chan, tch_data + amr, 23, rssi); bfi: if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { /* indicate bad frame */ switch (tch_mode) { case GSM48_CMODE_SPEECH_V1: /* FR */ memset(tch_data, 0, 33); rc = 33; break; case GSM48_CMODE_SPEECH_EFR: /* EFR */ memset(tch_data, 0, 31); rc = 31; break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ rc = amr_compose_payload(tch_data, chan_state->codec[chan_state->dl_cmr], chan_state->codec[chan_state->dl_ft], 1); if (rc < 2) break; memset(tch_data + 2, 0, rc - 2); break; default: LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, " "please fix!\n"); return -EINVAL; } } } if (rsl_cmode != RSL_CMOD_SPD_SPEECH) return 0; /* TCH or BFI */ return compose_tch_ind(l1h, tn, (fn + 2715648 - 7) % 2715648, chan, tch_data, rc); } static int rx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, float toa) { struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; sbit_t *burst, **bursts_p = &chan_state->ul_bursts; uint8_t *mask = &chan_state->ul_mask; uint8_t rsl_cmode = chan_state->rsl_cmode; uint8_t tch_mode = chan_state->tch_mode; uint8_t tch_data[128]; /* just to be safe */ int rc, amr = 0; int n_errors, n_bits_total; /* handle rach, if handover rach detection is turned on */ if (chan_state->ho_rach_detect == 1) return rx_rach_fn(l1h, tn, fn, chan, bid, bits, rssi, toa); LOGP(DL1C, LOGL_DEBUG, "TCH/H received %s fn=%u ts=%u trx=%u bid=%u\n", trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); /* alloc burst memory, if not already */ if (!*bursts_p) { *bursts_p = talloc_zero_size(tall_bts_ctx, 696); if (!*bursts_p) return -ENOMEM; } /* clear burst */ if (bid == 0) { memset(*bursts_p + 464, 0, 232); *mask = 0x0; } /* update mask */ *mask |= (1 << bid); /* copy burst to end of buffer of 6 bursts */ burst = *bursts_p + bid * 116 + 464; memcpy(burst, bits + 3, 58); memcpy(burst + 58, bits + 87, 58); /* wait until complete set of bursts */ if (bid != 1) return 0; /* check for complete set of bursts */ if ((*mask & 0x3) != 0x3) { LOGP(DL1C, LOGL_NOTICE, "Received incomplete TCH frame ending " "at fn=%u (%u/%u) for %s\n", fn, fn % l1h->mf_period[tn], l1h->mf_period[tn], trx_chan_desc[chan].name); } *mask = 0x0; /* skip second of two TCH frames of FACCH was received */ if (chan_state->ul_ongoing_facch) { chan_state->ul_ongoing_facch = 0; memcpy(*bursts_p, *bursts_p + 232, 232); memcpy(*bursts_p + 232, *bursts_p + 464, 232); goto bfi; } /* decode * also shift buffer by 4 bursts for interleaving */ switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1 : tch_mode) { case GSM48_CMODE_SPEECH_V1: /* HR or signalling */ /* Note on FN-10: If we are at FN 10, we decoded an even aligned * TCH/FACCH frame, because our burst buffer carries 6 bursts. * Even FN ending at: 10,11,19,20,2,3 */ rc = tch_hr_decode(tch_data, *bursts_p, (((fn + 26 - 10) % 26) >> 2) & 1, &n_errors, &n_bits_total); break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ /* the first FN 0,8,17 or 1,9,18 defines that CMI is included * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR * is included in frame. */ rc = tch_ahs_decode(tch_data + 2, *bursts_p, (((fn + 26 - 10) % 26) >> 2) & 1, (((fn + 26 - 10) % 26) >> 2) & 1, chan_state->codec, chan_state->codecs, &chan_state->ul_ft, &chan_state->ul_cmr, &n_errors, &n_bits_total); if (rc) trx_loop_amr_input(l1h, trx_chan_desc[chan].chan_nr | tn, chan_state, (float)n_errors/(float)n_bits_total); amr = 2; /* we store tch_data + 2 two */ /* only good speech frames get rtp header */ if (rc != 23 && rc >= 4) { rc = amr_compose_payload(tch_data, chan_state->codec[chan_state->ul_cmr], chan_state->codec[chan_state->ul_ft], 0); } break; default: LOGP(DL1C, LOGL_ERROR, "TCH mode %u invalid, please fix!\n", tch_mode); return -EINVAL; } memcpy(*bursts_p, *bursts_p + 232, 232); memcpy(*bursts_p + 232, *bursts_p + 464, 232); /* Send uplnk measurement information to L2 */ l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr|tn, n_errors, n_bits_total, rssi, toa); /* Check if the frame is bad */ if (rc < 0) { LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " "fn=%u for %s\n", fn, trx_chan_desc[chan].name); goto bfi; } if (rc < 4) { LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " "fn=%u for %s with codec mode %d (out of range)\n", fn, trx_chan_desc[chan].name, rc); goto bfi; } /* FACCH */ if (rc == 23) { chan_state->ul_ongoing_facch = 1; compose_ph_data_ind(l1h, tn, (fn + 2715648 - 10 - ((fn % 26) >= 19)) % 2715648, chan, tch_data + amr, 23, rssi); bfi: if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { /* indicate bad frame */ switch (tch_mode) { case GSM48_CMODE_SPEECH_V1: /* HR */ tch_data[0] = 0x70; /* F = 0, FT = 111 */ memset(tch_data + 1, 0, 14); rc = 15; break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ rc = amr_compose_payload(tch_data, chan_state->codec[chan_state->dl_cmr], chan_state->codec[chan_state->dl_ft], 1); if (rc < 2) break; memset(tch_data + 2, 0, rc - 2); break; default: LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, " "please fix!\n"); return -EINVAL; } } } if (rsl_cmode != RSL_CMOD_SPD_SPEECH) return 0; /* TCH or BFI */ /* Note on FN 19 or 20: If we received the last burst of a frame, * it actually starts at FN 8 or 9. A burst starting there, overlaps * with the slot 12, so an extra FN must be substracted to get correct * start of frame. */ return compose_tch_ind(l1h, tn, (fn + 2715648 - 10 - ((fn%26)==19) - ((fn%26)==20)) % 2715648, chan, tch_data, rc); } /* * multiframe structure */ /* frame structures */ struct trx_sched_frame { enum trx_chan_type dl_chan; uint8_t dl_bid; enum trx_chan_type ul_chan; uint8_t ul_bid; }; static struct trx_sched_frame frame_bcch[51] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_BCCH, 0, TRXC_RACH, 0 }, { TRXC_BCCH, 1, TRXC_RACH, 0 }, { TRXC_BCCH, 2, TRXC_RACH, 0 }, { TRXC_BCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_IDLE, 0, TRXC_RACH, 0 }, }; static struct trx_sched_frame frame_bcch_sdcch4[102] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, { TRXC_BCCH, 2, TRXC_RACH, 0 }, { TRXC_BCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 }, { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 }, { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 }, { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 }, { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 }, { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 }, { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 }, { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, { TRXC_BCCH, 2, TRXC_RACH, 0 }, { TRXC_BCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, { TRXC_FCCH, 0, TRXC_RACH, 0 }, { TRXC_SCH, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 }, { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 }, { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 }, { TRXC_SACCH4_2, 3, TRXC_RACH, 0 }, { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 }, { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 }, { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 }, { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, }; static struct trx_sched_frame frame_sdcch8[102] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 }, { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 }, { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 }, { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 }, { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 }, { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 }, { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 }, { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 }, { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 }, { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 }, { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 }, { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 }, { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 }, { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 }, { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 }, { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 }, { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, }; static struct trx_sched_frame frame_tchf_ts0[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, }; static struct trx_sched_frame frame_tchf_ts1[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, }; static struct trx_sched_frame frame_tchf_ts2[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, }; static struct trx_sched_frame frame_tchf_ts3[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, }; static struct trx_sched_frame frame_tchf_ts4[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, }; static struct trx_sched_frame frame_tchf_ts5[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, }; static struct trx_sched_frame frame_tchf_ts6[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, }; static struct trx_sched_frame frame_tchf_ts7[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, }; static struct trx_sched_frame frame_tchh_ts01[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, }; static struct trx_sched_frame frame_tchh_ts23[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, }; static struct trx_sched_frame frame_tchh_ts45[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, }; static struct trx_sched_frame frame_tchh_ts67[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, }; static struct trx_sched_frame frame_pdch[104] = { /* dl_chan dl_bid ul_chan ul_bid */ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PTCCH, 0, TRXC_PTCCH, 0 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PTCCH, 1, TRXC_PTCCH, 1 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PTCCH, 2, TRXC_PTCCH, 2 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PTCCH, 3, TRXC_PTCCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, { TRXC_IDLE, 0, TRXC_IDLE, 0 }, }; /* multiframe structure */ struct trx_sched_multiframe { enum gsm_phys_chan_config pchan; uint8_t slotmask; uint8_t period; struct trx_sched_frame *frames; const char *name; }; static struct trx_sched_multiframe trx_sched_multiframes[] = { { GSM_PCHAN_NONE, 0xff, 0, NULL, "NONE"}, { GSM_PCHAN_CCCH, 0xff, 51, frame_bcch, "BCCH+CCCH" }, { GSM_PCHAN_CCCH_SDCCH4, 0xff, 102, frame_bcch_sdcch4, "BCCH+CCCH+SDCCH/4+SACCH/4" }, { GSM_PCHAN_SDCCH8_SACCH8C, 0xff, 102, frame_sdcch8, "SDCCH/8+SACCH/8" }, { GSM_PCHAN_TCH_F, 0x01, 104, frame_tchf_ts0, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x02, 104, frame_tchf_ts1, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x04, 104, frame_tchf_ts2, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x08, 104, frame_tchf_ts3, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x10, 104, frame_tchf_ts4, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x20, 104, frame_tchf_ts5, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x40, 104, frame_tchf_ts6, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_F, 0x80, 104, frame_tchf_ts7, "TCH/F+SACCH" }, { GSM_PCHAN_TCH_H, 0x03, 104, frame_tchh_ts01, "TCH/H+SACCH" }, { GSM_PCHAN_TCH_H, 0x0c, 104, frame_tchh_ts23, "TCH/H+SACCH" }, { GSM_PCHAN_TCH_H, 0x30, 104, frame_tchh_ts45, "TCH/H+SACCH" }, { GSM_PCHAN_TCH_H, 0xc0, 104, frame_tchh_ts67, "TCH/H+SACCH" }, { GSM_PCHAN_PDCH, 0xff, 104, frame_pdch, "PDCH" }, }; /* * scheduler functions */ /* set multiframe scheduler to given pchan */ int trx_sched_set_pchan(struct trx_l1h *l1h, uint8_t tn, enum gsm_phys_chan_config pchan) { int i; /* ignore disabled slots */ if (!(l1h->config.slotmask & (1 << tn))) return -ENOTSUP; for (i = 0; i < ARRAY_SIZE(trx_sched_multiframes); i++) { if (trx_sched_multiframes[i].pchan == pchan && (trx_sched_multiframes[i].slotmask & (1 << tn))) { l1h->mf_index[tn] = i; l1h->mf_period[tn] = trx_sched_multiframes[i].period; l1h->mf_frames[tn] = trx_sched_multiframes[i].frames; LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with " "%s trx=%d ts=%d\n", trx_sched_multiframes[i].name, l1h->trx->nr, tn); return 0; } } LOGP(DL1C, LOGL_NOTICE, "Failed to configuring multiframe " "trx=%d ts=%d\n", l1h->trx->nr, tn); return -ENOTSUP; } /* setting all logical channels given attributes to active/inactive */ int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, int active) { uint8_t tn = L1SAP_CHAN2TS(chan_nr); uint8_t ss = l1sap_chan2ss(chan_nr); int i; int rc = -EINVAL; struct trx_chan_state *chan_state; /* look for all matching chan_nr/link_id */ for (i = 0; i < _TRX_CHAN_MAX; i++) { /* skip if pchan type does not match pdch flag */ if ((trx_sched_multiframes[l1h->mf_index[tn]].pchan == GSM_PCHAN_PDCH) != trx_chan_desc[i].pdch) continue; if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) && trx_chan_desc[i].link_id == link_id) { chan_state = &l1h->chan_states[tn][i]; rc = 0; if (chan_state->active == active) continue; LOGP(DL1C, LOGL_NOTICE, "%s %s on trx=%d ts=%d\n", (active) ? "Activating" : "Deactivating", trx_chan_desc[i].name, l1h->trx->nr, tn); if (active) memset(chan_state, 0, sizeof(*chan_state)); chan_state->active = active; /* free burst memory, to cleanly start with burst 0 */ if (chan_state->dl_bursts) { talloc_free(chan_state->dl_bursts); chan_state->dl_bursts = NULL; } if (chan_state->ul_bursts) { talloc_free(chan_state->ul_bursts); chan_state->ul_bursts = NULL; } } } /* disable handover detection (on deactivation) */ if (l1h->ho_rach_detect[tn][ss]) { l1h->ho_rach_detect[tn][ss] = 0; trx_if_cmd_nohandover(l1h, tn, ss); } return rc; } /* setting all logical channels given attributes to active/inactive */ int trx_sched_set_mode(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t rsl_cmode, uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1, uint8_t codec2, uint8_t codec3, uint8_t initial_id, uint8_t handover) { uint8_t tn = L1SAP_CHAN2TS(chan_nr); uint8_t ss = l1sap_chan2ss(chan_nr); int i; int rc = -EINVAL; struct trx_chan_state *chan_state; /* no mode for PDCH */ if (trx_sched_multiframes[l1h->mf_index[tn]].pchan == GSM_PCHAN_PDCH) return 0; /* look for all matching chan_nr/link_id */ for (i = 0; i < _TRX_CHAN_MAX; i++) { if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) && trx_chan_desc[i].link_id == 0x00) { chan_state = &l1h->chan_states[tn][i]; LOGP(DL1C, LOGL_NOTICE, "Set mode %u, %u, handover %u " "on %s of trx=%d ts=%d\n", rsl_cmode, tch_mode, handover, trx_chan_desc[i].name, l1h->trx->nr, tn); chan_state->rsl_cmode = rsl_cmode; chan_state->tch_mode = tch_mode; chan_state->ho_rach_detect = handover; if (rsl_cmode == RSL_CMOD_SPD_SPEECH && tch_mode == GSM48_CMODE_SPEECH_AMR) { chan_state->codecs = codecs; chan_state->codec[0] = codec0; chan_state->codec[1] = codec1; chan_state->codec[2] = codec2; chan_state->codec[3] = codec3; chan_state->ul_ft = initial_id; chan_state->dl_ft = initial_id; chan_state->ul_cmr = initial_id; chan_state->dl_cmr = initial_id; chan_state->ber_sum = 0; chan_state->ber_num = 0; } rc = 0; } } /* command rach detection * always enable handover, even if state is still set (due to loss * of transceiver link). * disable handover, if state is still set, since we might not know * the actual state of transceiver (due to loss of link) */ if (handover) { l1h->ho_rach_detect[tn][ss] = 1; trx_if_cmd_handover(l1h, tn, ss); } else if (l1h->ho_rach_detect[tn][ss]) { l1h->ho_rach_detect[tn][ss] = 0; trx_if_cmd_nohandover(l1h, tn, ss); } return rc; } /* setting cipher on logical channels */ int trx_sched_set_cipher(struct trx_l1h *l1h, uint8_t chan_nr, int downlink, int algo, uint8_t *key, int key_len) { uint8_t tn = L1SAP_CHAN2TS(chan_nr); int i; int rc = -EINVAL; struct trx_chan_state *chan_state; /* no cipher for PDCH */ if (trx_sched_multiframes[l1h->mf_index[tn]].pchan == GSM_PCHAN_PDCH) return 0; /* no algorithm given means a5/0 */ if (algo <= 0) algo = 0; else if (key_len != 8) { LOGP(DL1C, LOGL_ERROR, "Algo A5/%d not supported with given " "key len=%d\n", algo, key_len); return -ENOTSUP; } /* look for all matching chan_nr */ for (i = 0; i < _TRX_CHAN_MAX; i++) { /* skip if pchan type */ if (trx_chan_desc[i].pdch) continue; if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)) { chan_state = &l1h->chan_states[tn][i]; LOGP(DL1C, LOGL_NOTICE, "Set a5/%d %s for %s on trx=%d " "ts=%d\n", algo, (downlink) ? "downlink" : "uplink", trx_chan_desc[i].name, l1h->trx->nr, tn); if (downlink) { chan_state->dl_encr_algo = algo; memcpy(chan_state->dl_encr_key, key, key_len); chan_state->dl_encr_key_len = key_len; } else { chan_state->ul_encr_algo = algo; memcpy(chan_state->ul_encr_key, key, key_len); chan_state->ul_encr_key_len = key_len; } rc = 0; } } return rc; } /* process ready-to-send */ static int trx_sched_rts(struct trx_l1h *l1h, uint8_t tn, uint32_t fn) { struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_rts_func *func; enum trx_chan_type chan; /* no multiframe set */ if (!l1h->mf_index[tn]) return 0; /* get frame from multiframe */ period = l1h->mf_period[tn]; offset = fn % period; frame = l1h->mf_frames[tn] + offset; chan = frame->dl_chan; bid = frame->dl_bid; func = trx_chan_desc[frame->dl_chan].rts_fn; /* only on bid == 0 */ if (bid != 0) return 0; /* no RTS function */ if (!func) return 0; /* check if channel is active */ if (!trx_chan_desc[chan].auto_active && !l1h->chan_states[tn][chan].active) return -EINVAL; return func(l1h, tn, fn, frame->dl_chan); } /* process downlink burst */ static const ubit_t *trx_sched_dl_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn) { struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_dl_func *func; enum trx_chan_type chan; ubit_t *bits = NULL; if (!l1h->mf_index[tn]) goto no_data; /* get frame from multiframe */ period = l1h->mf_period[tn]; offset = fn % period; frame = l1h->mf_frames[tn] + offset; chan = frame->dl_chan; bid = frame->dl_bid; func = trx_chan_desc[chan].dl_fn; /* check if channel is active */ if (!trx_chan_desc[chan].auto_active && !l1h->chan_states[tn][chan].active) goto no_data; /* get burst from function */ bits = func(l1h, tn, fn, chan, bid); /* encrypt */ if (bits && l1h->chan_states[tn][chan].dl_encr_algo) { ubit_t ks[114]; int i; osmo_a5(l1h->chan_states[tn][chan].dl_encr_algo, l1h->chan_states[tn][chan].dl_encr_key, fn, ks, NULL); for (i = 0; i < 57; i++) { bits[i + 3] ^= ks[i]; bits[i + 88] ^= ks[i + 57]; } } no_data: /* in case of C0, we need a dummy burst to maintain RF power */ if (bits == NULL && l1h->trx == l1h->trx->bts->c0) { if (0) if (chan != TRXC_IDLE) // hack LOGP(DL1C, LOGL_DEBUG, "No burst data for %s fn=%u ts=%u " "burst=%d on C0, so filling with dummy burst\n", trx_chan_desc[chan].name, fn, tn, bid); bits = dummy_burst; } return bits; } /* process uplink burst */ int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t current_fn, sbit_t *bits, int8_t rssi, float toa) { struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_ul_func *func; enum trx_chan_type chan; uint32_t fn, elapsed; if (!l1h->mf_index[tn]) return -EINVAL; /* calculate how many frames have been elapsed */ elapsed = (current_fn + 2715648 - l1h->mf_last_fn[tn]) % 2715648; /* start counting from last fn + 1, but only if not too many fn have * been elapsed */ if (elapsed < 10) fn = (l1h->mf_last_fn[tn] + 1) % 2715648; else fn = current_fn; while (42) { /* get frame from multiframe */ period = l1h->mf_period[tn]; offset = fn % period; frame = l1h->mf_frames[tn] + offset; chan = frame->ul_chan; bid = frame->ul_bid; func = trx_chan_desc[chan].ul_fn; /* check if channel is active */ if (!trx_chan_desc[chan].auto_active && !l1h->chan_states[tn][chan].active) goto next_frame; /* omit bursts which have no handler, like IDLE bursts */ if (!func) goto next_frame; /* put burst to function */ if (fn == current_fn) { /* decrypt */ if (bits && l1h->chan_states[tn][chan].ul_encr_algo) { ubit_t ks[114]; int i; osmo_a5(l1h->chan_states[tn][chan].ul_encr_algo, l1h->chan_states[tn][chan].ul_encr_key, fn, NULL, ks); for (i = 0; i < 57; i++) { if (ks[i]) bits[i + 3] = - bits[i + 3]; if (ks[i + 57]) bits[i + 88] = - bits[i + 88]; } } func(l1h, tn, fn, chan, bid, bits, rssi, toa); } else if (chan != TRXC_RACH && !l1h->chan_states[tn][chan].ho_rach_detect) { sbit_t spare[148]; memset(spare, 0, 148); func(l1h, tn, fn, chan, bid, spare, -128, 0); } next_frame: /* reached current fn */ if (fn == current_fn) break; fn = (fn + 1) % 2715648; } l1h->mf_last_fn[tn] = fn; return 0; } /* schedule all frames of all TRX for given FN */ static int trx_sched_fn(uint32_t fn) { struct gsm_bts_trx *trx; struct trx_l1h *l1h; uint8_t tn; const ubit_t *bits; uint8_t gain; /* send time indication */ l1if_mph_time_ind(bts, fn); /* advance frame number, so the transceiver has more time until * it must be transmitted. */ fn = (fn + trx_clock_advance) % 2715648; /* process every TRX */ llist_for_each_entry(trx, &bts->trx_list, list) { l1h = trx_l1h_hdl(trx); /* we don't schedule, if power is off */ if (!l1h->config.poweron) continue; /* process every TS of TRX */ for (tn = 0; tn < 8; tn++) { /* ignore disabled slots */ if (!(l1h->config.slotmask & (1 << tn))) continue; /* ready-to-send */ trx_sched_rts(l1h, tn, (fn + trx_rts_advance) % 2715648); /* get burst for FN */ bits = trx_sched_dl_burst(l1h, tn, fn); if (!bits) { #if 0 /* if no bits, send dummy burst with no gain */ bits = dummy_burst; gain = 128; #else /* if no bits, send no burst */ continue; #endif } else gain = 0; trx_if_data(l1h, tn, fn, gain, bits); } } return 0; } /* * frame clock */ #define FRAME_DURATION_uS 4615 #define MAX_FN_SKEW 50 #define TRX_LOSS_FRAMES 400 extern int quit; /* this timer fires for every FN to be processed */ static void trx_ctrl_timer_cb(void *data) { struct timeval tv_now, *tv_clock = &transceiver_clock_tv; int32_t elapsed; /* check if transceiver is still alive */ if (transceiver_lost++ == TRX_LOSS_FRAMES) { struct gsm_bts_trx *trx; LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n"); no_clock: transceiver_available = 0; /* flush pending messages of transceiver */ /* close all logical channels and reset timeslots */ llist_for_each_entry(trx, &bts->trx_list, list) { trx_if_flush(trx_l1h_hdl(trx)); trx_sched_reset(trx_l1h_hdl(trx)); if (trx->nr == 0) trx_if_cmd_poweroff(trx_l1h_hdl(trx)); } /* tell BSC */ check_transceiver_availability(bts, 0); return; } gettimeofday(&tv_now, NULL); elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + (tv_now.tv_usec - tv_clock->tv_usec); /* if someone played with clock, or if the process stalled */ if (elapsed > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed < 0) { LOGP(DL1C, LOGL_NOTICE, "PC clock skew: elapsed uS %d\n", elapsed); goto no_clock; } /* schedule next FN clock */ while (elapsed > FRAME_DURATION_uS / 2) { tv_clock->tv_usec += FRAME_DURATION_uS; if (tv_clock->tv_usec >= 1000000) { tv_clock->tv_sec++; tv_clock->tv_usec -= 1000000; } transceiver_last_fn = (transceiver_last_fn + 1) % 2715648; trx_sched_fn(transceiver_last_fn); elapsed -= FRAME_DURATION_uS; } osmo_timer_schedule(&transceiver_clock_timer, 0, FRAME_DURATION_uS - elapsed); } /* receive clock from transceiver */ int trx_sched_clock(uint32_t fn) { struct timeval tv_now, *tv_clock = &transceiver_clock_tv; int32_t elapsed; int32_t elapsed_fn; if (quit) return 0; /* reset lost counter */ transceiver_lost = 0; gettimeofday(&tv_now, NULL); /* clock becomes valid */ if (!transceiver_available) { LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", fn); transceiver_available = 1; /* start provisioning transceiver */ l1if_provision_transceiver(bts); /* tell BSC */ check_transceiver_availability(bts, 1); new_clock: transceiver_last_fn = fn; trx_sched_fn(transceiver_last_fn); /* schedule first FN clock */ memcpy(tv_clock, &tv_now, sizeof(struct timeval)); memset(&transceiver_clock_timer, 0, sizeof(transceiver_clock_timer)); transceiver_clock_timer.cb = trx_ctrl_timer_cb; transceiver_clock_timer.data = bts; osmo_timer_schedule(&transceiver_clock_timer, 0, FRAME_DURATION_uS); return 0; } osmo_timer_del(&transceiver_clock_timer); /* calculate elapsed time since last_fn */ elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + (tv_now.tv_usec - tv_clock->tv_usec); /* how much frames have been elapsed since last fn processed */ elapsed_fn = (fn + 2715648 - transceiver_last_fn) % 2715648; if (elapsed_fn >= 135774) elapsed_fn -= 2715648; /* check for max clock skew */ if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) { LOGP(DL1C, LOGL_NOTICE, "GSM clock skew: old fn=%u, " "new fn=%u\n", transceiver_last_fn, fn); goto new_clock; } LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %d\n", elapsed_fn * FRAME_DURATION_uS - elapsed); /* too many frames have been processed already */ if (elapsed_fn < 0) { /* set clock to the time or last FN should have been * transmitted. */ tv_clock->tv_sec = tv_now.tv_sec; tv_clock->tv_usec = tv_now.tv_usec + (0 - elapsed_fn) * FRAME_DURATION_uS; if (tv_clock->tv_usec >= 1000000) { tv_clock->tv_sec++; tv_clock->tv_usec -= 1000000; } /* set time to the time our next FN has to be transmitted */ osmo_timer_schedule(&transceiver_clock_timer, 0, FRAME_DURATION_uS * (1 - elapsed_fn)); return 0; } /* transmit what we still need to transmit */ while (fn != transceiver_last_fn) { transceiver_last_fn = (transceiver_last_fn + 1) % 2715648; trx_sched_fn(transceiver_last_fn); } /* schedule next FN to be transmitted */ memcpy(tv_clock, &tv_now, sizeof(struct timeval)); osmo_timer_schedule(&transceiver_clock_timer, 0, FRAME_DURATION_uS); return 0; } osmo-bts-0.4.0/src/osmo-bts-trx/scheduler.h000066400000000000000000000026421260026426200205560ustar00rootroot00000000000000#ifndef TRX_SCHEDULER_H #define TRX_SCHEDULER_H extern uint32_t trx_clock_advance; extern uint32_t trx_rts_advance; extern uint32_t transceiver_last_fn; int trx_sched_init(struct trx_l1h *l1h); void trx_sched_exit(struct trx_l1h *l1h); int trx_sched_ph_data_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap); int trx_sched_tch_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap); int trx_sched_clock(uint32_t fn); int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, sbit_t *bits, int8_t rssi, float toa); /* set multiframe scheduler to given pchan */ int trx_sched_set_pchan(struct trx_l1h *l1h, uint8_t tn, enum gsm_phys_chan_config pchan); /* setting all logical channels given attributes to active/inactive */ int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, int active); /* setting all logical channels given attributes to active/inactive */ int trx_sched_set_mode(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t rsl_cmode, uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1, uint8_t codec2, uint8_t codec3, uint8_t initial_codec, uint8_t handover); /* setting cipher on logical channels */ int trx_sched_set_cipher(struct trx_l1h *l1h, uint8_t chan_nr, int downlink, int algo, uint8_t *key, int key_len); /* close all logical channels and reset timeslots */ void trx_sched_reset(struct trx_l1h *l1h); #endif /* TRX_SCHEDULER_H */ osmo-bts-0.4.0/src/osmo-bts-trx/trx_if.c000066400000000000000000000306161260026426200200700ustar00rootroot00000000000000/* * OpenBTS TRX interface handling * * Copyright (C) 2013 Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "l1_if.h" #include "trx_if.h" #include "scheduler.h" /* enable to print RSSI level graph */ //#define TOA_RSSI_DEBUG int transceiver_available = 0; const char *transceiver_ip = "127.0.0.1"; int settsc_enabled = 0; int setbsic_enabled = 0; /* * socket */ static uint16_t base_port_local = 5800; /* open socket */ static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port, int (*cb)(struct osmo_fd *fd, unsigned int what)) { struct sockaddr_storage sas; struct sockaddr *sa = (struct sockaddr *)&sas; socklen_t sa_len; int rc; /* Init */ ofd->fd = -1; ofd->cb = cb; ofd->data = priv; /* Listen / Binds */ rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, transceiver_ip, port, OSMO_SOCK_F_BIND); if (rc < 0) return rc; /* Connect */ sa_len = sizeof(sas); rc = getsockname(ofd->fd, sa, &sa_len); if (rc) return rc; if (sa->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; sin->sin_port = htons(ntohs(sin->sin_port) - 100); } else if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; sin6->sin6_port = htons(ntohs(sin6->sin6_port) - 100); } else { return -EINVAL; } rc = connect(ofd->fd, sa, sa_len); if (rc) return rc; return 0; } /* close socket */ static void trx_udp_close(struct osmo_fd *ofd) { if (ofd->fd > 0) { osmo_fd_unregister(ofd); close(ofd->fd); ofd->fd = -1; } } /* * clock */ static struct osmo_fd trx_ofd_clk; /* get clock from clock socket */ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) { char buf[1500]; int len; uint32_t fn; len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); if (len <= 0) return len; buf[len] = '\0'; if (!!strncmp(buf, "IND CLOCK ", 10)) { LOGP(DTRX, LOGL_NOTICE, "Unknown message on clock port: %s\n", buf); return 0; } sscanf(buf, "IND CLOCK %u", &fn); LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn); if (fn >= 2715648) { fn %= 2715648; LOGP(DTRX, LOGL_ERROR, "Indicated clock's FN is not wrapping " "correctly, correcting to fn=%u\n", fn); } trx_sched_clock(fn); return 0; } /* * ctrl */ static void trx_ctrl_timer_cb(void *data); /* send first ctrl message and start timer */ static void trx_ctrl_send(struct trx_l1h *l1h) { struct trx_ctrl_msg *tcm; /* get first command */ if (llist_empty(&l1h->trx_ctrl_list)) return; tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to trx=%u\n", tcm->cmd, l1h->trx->nr); /* send command */ send(l1h->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd)+1, 0); /* start timer */ l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb; l1h->trx_ctrl_timer.data = l1h; osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0); } /* send first ctrl message and start timer */ static void trx_ctrl_timer_cb(void *data) { struct trx_l1h *l1h = data; LOGP(DTRX, LOGL_NOTICE, "No response from transceiver for trx=%d\n", l1h->trx->nr); trx_ctrl_send(l1h); } /* add a new ctrl command */ static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd, const char *fmt, ...) { struct trx_ctrl_msg *tcm; va_list ap; int l, pending = 0; if (!transceiver_available && !!strcmp(cmd, "POWEROFF")) { LOGP(DTRX, LOGL_ERROR, "CTRL ignored: No clock from " "transceiver, please fix!\n"); return -EIO; } if (!llist_empty(&l1h->trx_ctrl_list)) pending = 1; /* create message */ tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg); if (!tcm) return -ENOMEM; if (fmt && fmt[0]) { l = snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "CMD %s ", cmd); va_start(ap, fmt); vsnprintf(tcm->cmd + l, sizeof(tcm->cmd) - l - 1, fmt, ap); va_end(ap); } else snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "CMD %s", cmd); tcm->cmd_len = strlen(cmd); tcm->critical = critical; llist_add_tail(&tcm->list, &l1h->trx_ctrl_list); LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd); /* send message, if no pending message */ if (!pending) trx_ctrl_send(l1h); return 0; } int trx_if_cmd_poweroff(struct trx_l1h *l1h) { if (l1h->trx->nr == 0) return trx_ctrl_cmd(l1h, 1, "POWEROFF", ""); else return 0; } int trx_if_cmd_poweron(struct trx_l1h *l1h) { if (l1h->trx->nr == 0) return trx_ctrl_cmd(l1h, 1, "POWERON", ""); else return 0; } int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc) { if (!settsc_enabled) return 0; /* if TSC is enabled only, the positive response is mandatory */ return trx_ctrl_cmd(l1h, (setbsic_enabled) ? 0 : 1, "SETTSC", "%d", tsc); } int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic) { if (!setbsic_enabled) return 0; /* if BSIC is enabled only, the positive response is mandatory */ return trx_ctrl_cmd(l1h, (settsc_enabled) ? 0 : 1, "SETBSIC", "%d", bsic); } int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db) { return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db); } int trx_if_cmd_setpower(struct trx_l1h *l1h, int db) { return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db); } int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly) { return trx_ctrl_cmd(l1h, 0, "SETMAXDLY", "%d", dly); } int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type) { return trx_ctrl_cmd(l1h, 1, "SETSLOT", "%d %d", tn, type); } int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn) { uint16_t freq10; freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */ if (freq10 == 0xffff) { LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", arfcn); return -ENOTSUP; } return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100); } int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn) { uint16_t freq10; freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */ if (freq10 == 0xffff) { LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", arfcn); return -ENOTSUP; } return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100); } int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss) { return trx_ctrl_cmd(l1h, 1, "HANDOVER", "%d %d", tn, ss); } int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss) { return trx_ctrl_cmd(l1h, 1, "NOHANDOVER", "%d %d", tn, ss); } /* get response from ctrl socket */ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) { struct trx_l1h *l1h = ofd->data; char buf[1500]; int len, resp; len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); if (len <= 0) return len; buf[len] = '\0'; if (!strncmp(buf, "RSP ", 4)) { struct trx_ctrl_msg *tcm; char *p; int rsp_len = 0; /* calculate the length of response item */ p = strchr(buf + 4, ' '); if (p) rsp_len = p - buf - 4; else rsp_len = strlen(buf) - 4; LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf); /* abort timer and send next message, if any */ if (osmo_timer_pending(&l1h->trx_ctrl_timer)) osmo_timer_del(&l1h->trx_ctrl_timer); /* get command for response message */ if (llist_empty(&l1h->trx_ctrl_list)) { LOGP(DTRX, LOGL_NOTICE, "Response message without " "command\n"); return -EINVAL; } tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); /* check if respose matches command */ if (rsp_len != tcm->cmd_len) { notmatch: LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE, "Response message '%s' does not match command " "message '%s'\n", buf, tcm->cmd); goto rsp_error; } if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) goto notmatch; /* check for response code */ sscanf(p + 1, "%d", &resp); if (resp) { LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE, "transceiver (trx=%d) rejected TRX command " "with response: '%s'\n", l1h->trx->nr, buf); rsp_error: if (tcm->critical) { bts_shutdown(l1h->trx->bts, "SIGINT"); /* keep tcm list, so process is stopped */ return -EIO; } } /* remove command from list */ llist_del(&tcm->list); talloc_free(tcm); trx_ctrl_send(l1h); } else LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n", buf); return 0; } /* * data */ static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) { struct trx_l1h *l1h = ofd->data; uint8_t buf[256]; int len; uint8_t tn; int8_t rssi; float toa = 0.0; uint32_t fn; sbit_t bits[148]; int i; len = recv(ofd->fd, buf, sizeof(buf), 0); if (len <= 0) return len; if (len != 158) { LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght " "'%d'\n", len); return -EINVAL; } tn = buf[0]; fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; rssi = -(int8_t)buf[5]; toa = ((int16_t)(buf[6] << 8) | buf[7]) / 256.0F; /* copy and convert bits {254..0} to sbits {-127..127} */ for (i = 0; i < 148; i++) { if (buf[8 + i] == 255) bits[i] = -127; else bits[i] = 127 - buf[8 + i]; } if (tn >= 8) { LOGP(DTRX, LOGL_ERROR, "Illegal TS %d\n", tn); return -EINVAL; } if (fn >= 2715648) { LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn); return -EINVAL; } LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%.2f\n", tn, fn, rssi, toa); #ifdef TOA_RSSI_DEBUG char deb[128]; sprintf(deb, "| 0 " " | rssi=%4d toa=%4.2f fn=%u", rssi, toa, fn); deb[1 + (128 + rssi) / 4] = '*'; fprintf(stderr, "%s\n", deb); #endif trx_sched_ul_burst(l1h, tn, fn, bits, rssi, toa); return 0; } int trx_if_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, const ubit_t *bits) { uint8_t buf[256]; LOGP(DTRX, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr); buf[0] = tn; buf[1] = (fn >> 24) & 0xff; buf[2] = (fn >> 16) & 0xff; buf[3] = (fn >> 8) & 0xff; buf[4] = (fn >> 0) & 0xff; buf[5] = pwr; /* copy ubits {0,1} */ memcpy(buf + 6, bits, 148); /* we must be sure that we have clock, and we have sent all control * data */ if (transceiver_available && llist_empty(&l1h->trx_ctrl_list)) { send(l1h->trx_ofd_data.fd, buf, 154, 0); } else LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, transceiver " "offline.\n"); return 0; } /* * open/close */ int trx_if_open(struct trx_l1h *l1h) { int rc; LOGP(DTRX, LOGL_NOTICE, "Open transceiver for trx=%u\n", l1h->trx->nr); /* initialize ctrl queue */ INIT_LLIST_HEAD(&l1h->trx_ctrl_list); /* open sockets */ if (l1h->trx->nr == 0) { rc = trx_udp_open(NULL, &trx_ofd_clk, base_port_local, trx_clk_read_cb); if (rc < 0) return rc; LOGP(DTRX, LOGL_NOTICE, "Waiting for transceiver send clock\n"); } rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl, base_port_local + (l1h->trx->nr << 1) + 1, trx_ctrl_read_cb); if (rc < 0) goto err; rc = trx_udp_open(l1h, &l1h->trx_ofd_data, base_port_local + (l1h->trx->nr << 1) + 2, trx_data_read_cb); if (rc < 0) goto err; /* enable all slots */ l1h->config.slotmask = 0xff; if (l1h->trx->nr == 0) trx_if_cmd_poweroff(l1h); return 0; err: trx_if_close(l1h); return rc; } /* flush pending control messages */ void trx_if_flush(struct trx_l1h *l1h) { struct trx_ctrl_msg *tcm; /* free ctrl message list */ while (!llist_empty(&l1h->trx_ctrl_list)) { tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); llist_del(&tcm->list); talloc_free(tcm); } } void trx_if_close(struct trx_l1h *l1h) { LOGP(DTRX, LOGL_NOTICE, "Close transceiver for trx=%u\n", l1h->trx->nr); trx_if_flush(l1h); /* close sockets */ if (l1h->trx->nr == 0) trx_udp_close(&trx_ofd_clk); trx_udp_close(&l1h->trx_ofd_ctrl); trx_udp_close(&l1h->trx_ofd_data); } osmo-bts-0.4.0/src/osmo-bts-trx/trx_if.h000066400000000000000000000022671260026426200200760ustar00rootroot00000000000000#ifndef TRX_IF_H #define TRX_IF_H extern int transceiver_available; extern const char *transceiver_ip; extern int settsc_enabled; extern int setbsic_enabled; struct trx_ctrl_msg { struct llist_head list; char cmd[128]; int cmd_len; int critical; }; int trx_if_cmd_poweroff(struct trx_l1h *l1h); int trx_if_cmd_poweron(struct trx_l1h *l1h); int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc); int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic); int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db); int trx_if_cmd_setpower(struct trx_l1h *l1h, int db); int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly); int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type); int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn); int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn); int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss); int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss); int trx_if_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, const ubit_t *bits); int trx_if_open(struct trx_l1h *l1h); void trx_if_flush(struct trx_l1h *l1h); void trx_if_close(struct trx_l1h *l1h); #endif /* TRX_IF_H */ osmo-bts-0.4.0/src/osmo-bts-trx/trx_vty.c000066400000000000000000000260101260026426200203050ustar00rootroot00000000000000/* VTY interface for sysmoBTS */ /* (C) 2013 by Andreas Eversberg * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "l1_if.h" #include "scheduler.h" #include "trx_if.h" #include "loops.h" static struct gsm_bts *vty_bts; DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver", SHOW_STR "Display information about transceivers\n") { struct gsm_bts *bts = vty_bts; struct gsm_bts_trx *trx; struct trx_l1h *l1h; uint8_t tn; if (!transceiver_available) { vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE); } else { vty_out(vty, "transceiver is connected, current fn=%u%s", transceiver_last_fn, VTY_NEWLINE); } llist_for_each_entry(trx, &bts->trx_list, list) { l1h = trx_l1h_hdl(trx); vty_out(vty, "TRX %d%s", trx->nr, VTY_NEWLINE); vty_out(vty, " %s%s", (l1h->config.poweron) ? "poweron":"poweroff", VTY_NEWLINE); if (l1h->config.arfcn_valid) vty_out(vty, " arfcn : %d%s%s", (l1h->config.arfcn & ~ARFCN_PCS), (l1h->config.arfcn & ARFCN_PCS) ? " (PCS)" : "", VTY_NEWLINE); else vty_out(vty, " arfcn : undefined%s", VTY_NEWLINE); if (l1h->config.tsc_valid) vty_out(vty, " tsc : %d%s", l1h->config.tsc, VTY_NEWLINE); else vty_out(vty, " tsc : undefined%s", VTY_NEWLINE); if (l1h->config.bsic_valid) vty_out(vty, " bsic : %d%s", l1h->config.bsic, VTY_NEWLINE); else vty_out(vty, " bisc : undefined%s", VTY_NEWLINE); if (l1h->config.rxgain_valid) vty_out(vty, " rxgain : %d%s", l1h->config.rxgain, VTY_NEWLINE); else vty_out(vty, " rxgain : undefined%s", VTY_NEWLINE); if (l1h->config.power_valid) vty_out(vty, " power : %d%s", l1h->config.power, VTY_NEWLINE); else vty_out(vty, " power : undefined%s", VTY_NEWLINE); if (l1h->config.maxdly_valid) vty_out(vty, " maxdly : %d%s", l1h->config.maxdly, VTY_NEWLINE); else vty_out(vty, " maxdly : undefined%s", VTY_NEWLINE); for (tn = 0; tn < 8; tn++) { if (!((1 << tn) & l1h->config.slotmask)) vty_out(vty, " slot #%d: unsupported%s", tn, VTY_NEWLINE); else if (l1h->config.slottype_valid[tn]) vty_out(vty, " slot #%d: type %d%s", tn, l1h->config.slottype[tn], VTY_NEWLINE); else vty_out(vty, " slot #%d: undefined%s", tn, VTY_NEWLINE); } } return CMD_SUCCESS; } DEFUN(cfg_bts_fn_advance, cfg_bts_fn_advance_cmd, "fn-advance <0-30>", "Set the number of frames to be transmitted to transceiver in advance " "of current FN\n" "Advance in frames\n") { trx_clock_advance = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(cfg_bts_rts_advance, cfg_bts_rts_advance_cmd, "rts-advance <0-30>", "Set the number of frames to be requested (PCU) in advance of current " "FN. Do not change this, unless you have a good reason!\n" "Advance in frames\n") { trx_rts_advance = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(cfg_bts_ms_power_loop, cfg_bts_ms_power_loop_cmd, "ms-power-loop <-127-127>", "Enable MS power control loop\nTarget RSSI value (transceiver specific, " "should be 6dB or more above noise floor)\n") { trx_ms_power_loop = 1; trx_target_rssi = atoi(argv[0]); return CMD_SUCCESS; } DEFUN(cfg_bts_no_ms_power_loop, cfg_bts_no_ms_power_loop_cmd, "no ms-power-loop", NO_STR "Disable MS power control loop\n") { trx_ms_power_loop = 0; return CMD_SUCCESS; } DEFUN(cfg_bts_timing_advance_loop, cfg_bts_timing_advance_loop_cmd, "timing-advance-loop", "Enable timing advance control loop\n") { trx_ta_loop = 1; return CMD_SUCCESS; } DEFUN(cfg_bts_no_timing_advance_loop, cfg_bts_no_timing_advance_loop_cmd, "no timing-advance-loop", NO_STR "Disable timing advance control loop\n") { trx_ta_loop = 0; return CMD_SUCCESS; } DEFUN(cfg_bts_settsc, cfg_bts_settsc_cmd, "settsc", "Use SETTSC to configure transceiver\n") { settsc_enabled = 1; return CMD_SUCCESS; } DEFUN(cfg_bts_setbsic, cfg_bts_setbsic_cmd, "setbsic", "Use SETBSIC to configure transceiver\n") { setbsic_enabled = 1; return CMD_SUCCESS; } DEFUN(cfg_bts_no_settsc, cfg_bts_no_settsc_cmd, "no settsc", NO_STR "Disable SETTSC to configure transceiver\n") { settsc_enabled = 0; if (!setbsic_enabled) { vty_out(vty, "%% Auto enabling SETBSIC.%s", VTY_NEWLINE); setbsic_enabled = 1; } return CMD_SUCCESS; } DEFUN(cfg_bts_no_setbsic, cfg_bts_no_setbsic_cmd, "no setbsic", NO_STR "Disable SETBSIC to configure transceiver\n") { setbsic_enabled = 0; if (!settsc_enabled) { vty_out(vty, "%% Auto enabling SETTSC.%s", VTY_NEWLINE); settsc_enabled = 1; } return CMD_SUCCESS; } DEFUN(cfg_trx_rxgain, cfg_trx_rxgain_cmd, "rxgain <0-50>", "Set the receiver gain in dB\n" "Gain in dB\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.rxgain = atoi(argv[0]); l1h->config.rxgain_valid = 1; l1h->config.rxgain_sent = 0; l1if_provision_transceiver_trx(l1h); return CMD_SUCCESS; } DEFUN(cfg_trx_power, cfg_trx_power_cmd, "power <0-50>", "Set the transmitter power dampening\n" "Power dampening in dB\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.power = atoi(argv[0]); l1h->config.power_oml = 0; l1h->config.power_valid = 1; l1h->config.power_sent = 0; l1if_provision_transceiver_trx(l1h); return CMD_SUCCESS; } DEFUN(cfg_trx_poweroml_, cfg_trx_power_oml_cmd, "power oml", "Set the transmitter power dampening\n" "Given by NM_ATT_RF_MAXPOWR_R (max power reduction) via OML\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.power = trx->max_power_red; l1h->config.power_oml = 1; l1h->config.power_valid = 1; l1h->config.power_sent = 0; l1if_provision_transceiver_trx(l1h); return CMD_SUCCESS; } DEFUN(cfg_trx_maxdly, cfg_trx_maxdly_cmd, "maxdly <0-31>", "Set the maximum delay of GSM symbols\n" "GSM symbols (approx. 1.1km per symbol)\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.maxdly = atoi(argv[0]); l1h->config.maxdly_valid = 1; l1h->config.maxdly_sent = 0; l1if_provision_transceiver_trx(l1h); return CMD_SUCCESS; } DEFUN(cfg_trx_slotmask, cfg_trx_slotmask_cmd, "slotmask (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0)", "Set the supported slots\n" "TS0 supported\nTS0 unsupported\nTS1 supported\nTS1 unsupported\n" "TS2 supported\nTS2 unsupported\nTS3 supported\nTS3 unsupported\n" "TS4 supported\nTS4 unsupported\nTS5 supported\nTS5 unsupported\n" "TS6 supported\nTS6 unsupported\nTS7 supported\nTS7 unsupported\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); uint8_t tn; l1h->config.slotmask = 0; for (tn = 0; tn < 8; tn++) if (argv[tn][0] == '1') l1h->config.slotmask |= (1 << tn); return CMD_SUCCESS; } DEFUN(cfg_trx_no_rxgain, cfg_trx_no_rxgain_cmd, "no rxgain <0-50>", NO_STR "Unset the receiver gain in dB\n" "Gain in dB\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.rxgain_valid = 0; return CMD_SUCCESS; } DEFUN(cfg_trx_no_power, cfg_trx_no_power_cmd, "no power <0-50>", NO_STR "Unset the transmitter power dampening\n" "Power dampening in dB\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.power_valid = 0; return CMD_SUCCESS; } DEFUN(cfg_trx_no_maxdly, cfg_trx_no_maxdly_cmd, "no maxdly <0-31>", NO_STR "Unset the maximum delay of GSM symbols\n" "GSM symbols (approx. 1.1km per symbol)\n") { struct gsm_bts_trx *trx = vty->index; struct trx_l1h *l1h = trx_l1h_hdl(trx); l1h->config.maxdly_valid = 0; return CMD_SUCCESS; } void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) { vty_out(vty, " fn-advance %d%s", trx_clock_advance, VTY_NEWLINE); vty_out(vty, " rts-advance %d%s", trx_rts_advance, VTY_NEWLINE); if (trx_ms_power_loop) vty_out(vty, " ms-power-loop %d%s", trx_target_rssi, VTY_NEWLINE); else vty_out(vty, " no ms-power-loop%s", VTY_NEWLINE); vty_out(vty, " %stiming-advance-loop%s", (trx_ta_loop) ? "":"no ", VTY_NEWLINE); if (settsc_enabled) vty_out(vty, " settsc%s", VTY_NEWLINE); if (setbsic_enabled) vty_out(vty, " setbsic%s", VTY_NEWLINE); } void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) { struct trx_l1h *l1h = trx_l1h_hdl(trx); if (l1h->config.rxgain_valid) vty_out(vty, " rxgain %d%s", l1h->config.rxgain, VTY_NEWLINE); if (l1h->config.power_valid) { if (l1h->config.power_oml) vty_out(vty, " power oml%s", VTY_NEWLINE); else vty_out(vty, " power %d%s", l1h->config.power, VTY_NEWLINE); } if (l1h->config.maxdly_valid) vty_out(vty, " maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE); if (l1h->config.slotmask != 0xff) vty_out(vty, " slotmask %d %d %d %d %d %d %d %d%s", l1h->config.slotmask & 1, (l1h->config.slotmask >> 1) & 1, (l1h->config.slotmask >> 2) & 1, (l1h->config.slotmask >> 3) & 1, (l1h->config.slotmask >> 4) & 1, (l1h->config.slotmask >> 5) & 1, (l1h->config.slotmask >> 6) & 1, l1h->config.slotmask >> 7, VTY_NEWLINE); } int bts_model_vty_init(struct gsm_bts *bts) { vty_bts = bts; install_element_ve(&show_transceiver_cmd); install_element(BTS_NODE, &cfg_bts_fn_advance_cmd); install_element(BTS_NODE, &cfg_bts_rts_advance_cmd); install_element(BTS_NODE, &cfg_bts_ms_power_loop_cmd); install_element(BTS_NODE, &cfg_bts_no_ms_power_loop_cmd); install_element(BTS_NODE, &cfg_bts_timing_advance_loop_cmd); install_element(BTS_NODE, &cfg_bts_no_timing_advance_loop_cmd); install_element(BTS_NODE, &cfg_bts_settsc_cmd); install_element(BTS_NODE, &cfg_bts_setbsic_cmd); install_element(BTS_NODE, &cfg_bts_no_settsc_cmd); install_element(BTS_NODE, &cfg_bts_no_setbsic_cmd); install_element(TRX_NODE, &cfg_trx_rxgain_cmd); install_element(TRX_NODE, &cfg_trx_power_cmd); install_element(TRX_NODE, &cfg_trx_power_oml_cmd); install_element(TRX_NODE, &cfg_trx_maxdly_cmd); install_element(TRX_NODE, &cfg_trx_slotmask_cmd); install_element(TRX_NODE, &cfg_trx_no_rxgain_cmd); install_element(TRX_NODE, &cfg_trx_no_power_cmd); install_element(TRX_NODE, &cfg_trx_no_maxdly_cmd); return 0; } osmo-bts-0.4.0/tests/000077500000000000000000000000001260026426200144205ustar00rootroot00000000000000osmo-bts-0.4.0/tests/Makefile.am000066400000000000000000000031661260026426200164620ustar00rootroot00000000000000SUBDIRS = paging cipher agch misc bursts handover if ENABLE_SYSMOBTS SUBDIRS += sysmobts endif if ENABLE_SYSMOBTS SUBDIRS += sysmobts endif # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ echo '# Signature of the current package.' && \ echo 'm4_define([AT_PACKAGE_NAME],' && \ echo ' [$(PACKAGE_NAME)])' && \ echo 'm4_define([AT_PACKAGE_TARNAME],' && \ echo ' [$(PACKAGE_TARNAME)])' && \ echo 'm4_define([AT_PACKAGE_VERSION],' && \ echo ' [$(PACKAGE_VERSION)])' && \ echo 'm4_define([AT_PACKAGE_STRING],' && \ echo ' [$(PACKAGE_STRING)])' && \ echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \ echo ' [$(PACKAGE_BUGREPORT)])'; \ echo 'm4_define([AT_PACKAGE_URL],' && \ echo ' [$(PACKAGE_URL)])'; \ } >'$(srcdir)/package.m4' EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) TESTSUITE = $(srcdir)/testsuite DISTCLEANFILES = atconfig check-local: atconfig $(TESTSUITE) $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) installcheck-local: atconfig $(TESTSUITE) $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \ $(TESTSUITEFLAGS) clean-local: test ! -f '$(TESTSUITE)' || \ $(SHELL) '$(TESTSUITE)' --clean AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te AUTOTEST = $(AUTOM4TE) --language=autotest $(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4 $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at mv $@.tmp $@ osmo-bts-0.4.0/tests/agch/000077500000000000000000000000001260026426200153225ustar00rootroot00000000000000osmo-bts-0.4.0/tests/agch/Makefile.am000066400000000000000000000007161260026426200173620ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) -lortp noinst_PROGRAMS = agch_test EXTRA_DIST = agch_test.ok agch_test_SOURCES = agch_test.c $(srcdir)/../stubs.c agch_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) osmo-bts-0.4.0/tests/agch/agch_test.c000066400000000000000000000153451260026426200174370ustar00rootroot00000000000000/* testing the agch code */ /* (C) 2011 by Holger Hans Peter Freyther * (C) 2014 by sysmocom s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 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 static struct gsm_bts *bts; static struct gsm_bts_role_bts *btsb; static const uint8_t static_ilv[] = { 0x08, 0x59, 0x51, 0x30, 0x99, 0x00, 0x00, 0x00, 0x19 }; static int count_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej) { int count = 0; count++; if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) { count++; } if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3)) && memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) { count++; } if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4)) && memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4)) && memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) { count++; } return count; } static void put_imm_ass_rej(struct msgb *msg, int idx, uint8_t wait_ind) { /* GSM CCCH - Immediate Assignment Reject */ static const unsigned char gsm_a_ccch_data[23] = { 0x4d, 0x06, 0x3a, 0x03, 0x25, 0x00, 0x00, 0x0a, 0x25, 0x00, 0x00, 0x0a, 0x25, 0x00, 0x00, 0x0a, 0x25, 0x00, 0x00, 0x0a, 0x2b, 0x2b, 0x2b }; struct gsm48_imm_ass_rej *rej; msg->l3h = msgb_put(msg, sizeof(gsm_a_ccch_data)); rej = (struct gsm48_imm_ass_rej *)msg->l3h; memmove(msg->l3h, gsm_a_ccch_data, sizeof(gsm_a_ccch_data)); rej->req_ref1.t1 = idx; rej->wait_ind1 = wait_ind; rej->req_ref2.t1 = idx; rej->req_ref3.t1 = idx; rej->req_ref4.t1 = idx; } static void put_imm_ass(struct msgb *msg, int idx) { /* GSM CCCH - Immediate Assignment */ static const unsigned char gsm_a_ccch_data[23] = { 0x2d, 0x06, 0x3f, 0x03, 0x0c, 0xe3, 0x69, 0x25, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b }; struct gsm48_imm_ass *ima; msg->l3h = msgb_put(msg, sizeof(gsm_a_ccch_data)); ima = (struct gsm48_imm_ass *)msg->l3h; memmove(msg->l3h, gsm_a_ccch_data, sizeof(gsm_a_ccch_data)); ima->req_ref.t1 = idx; } static void test_agch_queue(void) { int rc; uint8_t out_buf[GSM_MACBLOCK_LEN]; struct gsm_time g_time; const int num_rounds = 40; const int num_ima_per_round = 2; const int num_rej_per_round = 16; int round, idx; int count = 0; struct msgb *msg = NULL; int multiframes = 0; int imm_ass_count = 0; int imm_ass_rej_count = 0; int imm_ass_rej_ref_count = 0; g_time.fn = 0; g_time.t1 = 0; g_time.t2 = 0; g_time.t3 = 6; printf("Testing AGCH messages queue handling.\n"); btsb->agch_max_queue_length = 32; btsb->agch_queue_low_level = 30; btsb->agch_queue_high_level = 30; btsb->agch_queue_thresh_level = 60; for (round = 1; round <= num_rounds; round++) { for (idx = 0; idx < num_ima_per_round; idx++) { msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__); put_imm_ass(msg, ++count); bts_agch_enqueue(bts, msg); imm_ass_count++; } for (idx = 0; idx < num_rej_per_round; idx++) { msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__); put_imm_ass_rej(msg, ++count, 10); bts_agch_enqueue(bts, msg); imm_ass_rej_count++; imm_ass_rej_ref_count++; } } printf("AGCH filled: count %u, imm.ass %d, imm.ass.rej %d (refs %d), " "queue limit %u, occupied %d, " "dropped %llu, merged %llu, rejected %llu, " "ag-res %llu, non-res %llu\n", count, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count, btsb->agch_max_queue_length, btsb->agch_queue_length, btsb->agch_queue_dropped_msgs, btsb->agch_queue_merged_msgs, btsb->agch_queue_rejected_msgs, btsb->agch_queue_agch_msgs, btsb->agch_queue_pch_msgs); imm_ass_count = 0; imm_ass_rej_count = 0; imm_ass_rej_ref_count = 0; for (idx = 0; 1; idx++) { struct gsm48_imm_ass *ima; int is_agch = (idx % 3) == 0; /* 1 AG reserved, 2 PCH */ if (is_agch) multiframes++; rc = bts_ccch_copy_msg(bts, out_buf, &g_time, is_agch); ima = (struct gsm48_imm_ass *)out_buf; switch (ima->msg_type) { case GSM48_MT_RR_IMM_ASS: imm_ass_count++; break; case GSM48_MT_RR_IMM_ASS_REJ: imm_ass_rej_count++; imm_ass_rej_ref_count += count_imm_ass_rej_refs((struct gsm48_imm_ass_rej *)ima); break; default: break; } if (is_agch && rc <= 0) break; } printf("AGCH drained: multiframes %u, imm.ass %d, imm.ass.rej %d (refs %d), " "queue limit %u, occupied %d, " "dropped %llu, merged %llu, rejected %llu, " "ag-res %llu, non-res %llu\n", multiframes, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count, btsb->agch_max_queue_length, btsb->agch_queue_length, btsb->agch_queue_dropped_msgs, btsb->agch_queue_merged_msgs, btsb->agch_queue_rejected_msgs, btsb->agch_queue_agch_msgs, btsb->agch_queue_pch_msgs); } static void test_agch_queue_length_computation(void) { static const int ccch_configs[] = { RSL_BCCH_CCCH_CONF_1_NC, RSL_BCCH_CCCH_CONF_1_C, RSL_BCCH_CCCH_CONF_2_NC, RSL_BCCH_CCCH_CONF_3_NC, RSL_BCCH_CCCH_CONF_4_NC, }; static const uint8_t tx_integer[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50, }; int T_idx, c_idx, max_len; printf("Testing AGCH queue length computation.\n"); printf("T\t\tBCCH slots\n"); printf("\t1(NC)\t1(C)\t2(NC)\t3(NC)\t4(NC)\n"); for (T_idx = 0; T_idx < ARRAY_SIZE(tx_integer); T_idx++) { printf("%d", tx_integer[T_idx]); for (c_idx = 0; c_idx < ARRAY_SIZE(ccch_configs); c_idx++) { max_len = bts_agch_max_queue_length(tx_integer[T_idx], ccch_configs[c_idx]); printf("\t%d", max_len); } printf("\n"); } } int main(int argc, char **argv) { void *tall_msgb_ctx; tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); bts_log_init(NULL); bts = gsm_bts_alloc(tall_bts_ctx); if (bts_init(bts) < 0) { fprintf(stderr, "unable to open bts\n"); exit(1); } btsb = bts_role_bts(bts); test_agch_queue_length_computation(); test_agch_queue(); printf("Success\n"); return 0; } osmo-bts-0.4.0/tests/agch/agch_test.ok000066400000000000000000000013101260026426200176110ustar00rootroot00000000000000Testing AGCH queue length computation. T BCCH slots 1(NC) 1(C) 2(NC) 3(NC) 4(NC) 3 20 9 20 20 20 4 28 11 28 28 28 5 40 13 40 40 40 6 59 19 59 59 59 7 79 25 79 79 79 8 21 9 21 21 21 9 28 12 28 28 28 10 40 13 40 40 40 11 60 20 60 60 60 12 80 26 80 80 80 14 22 10 22 22 22 16 30 13 30 30 30 20 42 14 42 42 42 25 63 21 63 63 63 32 83 28 83 83 83 50 28 14 28 28 28 Testing AGCH messages queue handling. AGCH filled: count 720, imm.ass 80, imm.ass.rej 640 (refs 640), queue limit 32, occupied 240, dropped 0, merged 480, rejected 0, ag-res 0, non-res 0 AGCH drained: multiframes 32, imm.ass 80, imm.ass.rej 12 (refs 48), queue limit 32, occupied 0, dropped 148, merged 480, rejected 0, ag-res 31, non-res 61 Success osmo-bts-0.4.0/tests/bursts/000077500000000000000000000000001260026426200157425ustar00rootroot00000000000000osmo-bts-0.4.0/tests/bursts/Makefile.am000066400000000000000000000013211260026426200177730ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) noinst_PROGRAMS = bursts_test EXTRA_DIST = bursts_test.ok bursts_test_SOURCES = bursts_test.c \ $(top_builddir)/src/osmo-bts-trx/gsm0503_coding.c \ $(top_builddir)/src/osmo-bts-trx/gsm0503_conv.c \ $(top_builddir)/src/osmo-bts-trx/gsm0503_interleaving.c \ $(top_builddir)/src/osmo-bts-trx/gsm0503_mapping.c \ $(top_builddir)/src/osmo-bts-trx/gsm0503_tables.c \ $(top_builddir)/src/osmo-bts-trx/gsm0503_parity.c bursts_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) osmo-bts-0.4.0/tests/bursts/bursts_test.c000066400000000000000000000375471260026426200205070ustar00rootroot00000000000000/* (C) 2013 by Andreas Eversberg * (C) 2015 by Alexander Chemeris * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include #include "../../src/osmo-bts-trx/gsm0503_coding.h" #include #define ASSERT_TRUE(rc) \ if (!(rc)) { \ printf("Assert failed in %s:%d.\n", \ __FILE__, __LINE__); \ abort(); \ } /* set condition to 1, to show debugging */ #define printd if (0) printf static int ubits2sbits(ubit_t *ubits, sbit_t *sbits, int count) { int i; for (i = 0; i < count; i++) { if (*ubits == 0x23) { ubits++; sbits++; continue; } if ((*ubits++) & 1) *sbits++ = -127; else *sbits++ = 127; } return count; } static void test_xcch(uint8_t *l2) { uint8_t result[23]; ubit_t bursts_u[116 * 4]; sbit_t bursts_s[116 * 4]; int n_errors, n_bits_total; printd("Encoding: %s\n", osmo_hexdump(l2, 23)); /* encode */ xcch_encode(bursts_u, l2); printd("U-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump(bursts_u, 57), bursts_u[57], bursts_u[58]); printd("%s\n", osmo_hexdump(bursts_u + 59, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 116, 57), bursts_u[57 + 116], bursts_u[58 + 116]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 232, 57), bursts_u[57 + 232], bursts_u[58 + 232]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 348, 57), bursts_u[57 + 348], bursts_u[58 + 348]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 348, 57)); ubits2sbits(bursts_u, bursts_s, 116 * 4); printd("S-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s, 57), (uint8_t)bursts_s[57], (uint8_t)bursts_s[58]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 116, 57), (uint8_t)bursts_s[57 + 116], (uint8_t)bursts_s[58 + 116]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 232, 57), (uint8_t)bursts_s[57 + 232], (uint8_t)bursts_s[58 + 232]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 348, 57), (uint8_t)bursts_s[57 + 348], (uint8_t)bursts_s[58 + 348]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 348, 57)); /* destroy */ memset(bursts_s, 0, 30); memset(bursts_s + 116, 0, 30); /* decode */ xcch_decode(result, bursts_s, &n_errors, &n_bits_total); ASSERT_TRUE(n_bits_total == 456); printd("Decoded: %s\n", osmo_hexdump(result, 23)); printf("xcch_decode: n_errors=%d n_bits_total=%d ber=%.2f\n", n_errors, n_bits_total, (float)n_errors/n_bits_total); ASSERT_TRUE(!memcmp(l2, result, 23)); printd("\n"); } static void test_rach(uint8_t bsic, uint8_t ra) { uint8_t result; ubit_t bursts_u[36]; sbit_t bursts_s[36]; printd("Encoding: %02x\n", ra); /* encode */ rach_encode(bursts_u, &ra, bsic); printd("U-Bits:\n"); printd("%s\n", osmo_hexdump(bursts_u, 36)); ubits2sbits(bursts_u, bursts_s, 36); printd("S-Bits:\n"); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s, 36)); /* destroy */ memset(bursts_s + 6, 0, 8); /* decode */ rach_decode(&result, bursts_s, bsic); printd("Decoded: %02x\n", result); ASSERT_TRUE(ra == result); printd("\n"); } static void test_sch(uint8_t *info) { uint8_t result[4]; ubit_t bursts_u[78]; sbit_t bursts_s[78]; /* zero bits 25 and above */ info[3] &= 1; result[3] = 0; printd("Encoding: %s\n", osmo_hexdump(info, 4)); /* encode */ sch_encode(bursts_u, info); printd("U-Bits:\n"); printd("%s\n", osmo_hexdump(bursts_u, 78)); ubits2sbits(bursts_u, bursts_s, 78); printd("S-Bits:\n"); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s, 78)); /* destroy */ memset(bursts_s + 6, 0, 10); /* decode */ sch_decode(result, bursts_s); printd("Decoded: %s\n", osmo_hexdump(result, 4)); ASSERT_TRUE(!memcmp(info, result, 4)); printd("\n"); } static void test_fr(uint8_t *speech, int len) { uint8_t result[33]; ubit_t bursts_u[116 * 8]; sbit_t bursts_s[116 * 8]; int n_errors, n_bits_total; int rc; memset(bursts_u, 0x23, sizeof(bursts_u)); memset(bursts_s, 0, sizeof(bursts_s)); printd("Encoding: %s\n", osmo_hexdump(speech, len)); /* encode */ tch_fr_encode(bursts_u, speech, len, 1); printd("U-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump(bursts_u, 57), bursts_u[57], bursts_u[58]); printd("%s\n", osmo_hexdump(bursts_u + 59, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 116, 57), bursts_u[57 + 116], bursts_u[58 + 116]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 232, 57), bursts_u[57 + 232], bursts_u[58 + 232]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 348, 57), bursts_u[57 + 348], bursts_u[58 + 348]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 348, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 464, 57), bursts_u[57 + 464], bursts_u[58 + 464]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 464, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 580, 57), bursts_u[57 + 580], bursts_u[58 + 580]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 580, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 696, 57), bursts_u[57 + 696], bursts_u[58 + 696]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 696, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 812, 57), bursts_u[57 + 812], bursts_u[58 + 812]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 812, 57)); ubits2sbits(bursts_u, bursts_s, 116 * 8); printd("S-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s, 57), (uint8_t)bursts_s[57], (uint8_t)bursts_s[58]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 116, 57), (uint8_t)bursts_s[57 + 116], (uint8_t)bursts_s[58 + 116]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 232, 57), (uint8_t)bursts_s[57 + 232], (uint8_t)bursts_s[58 + 232]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 348, 57), (uint8_t)bursts_s[57 + 348], (uint8_t)bursts_s[58 + 348]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 348, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 464, 57), (uint8_t)bursts_s[57 + 464], (uint8_t)bursts_s[58 + 464]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 464, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 580, 57), (uint8_t)bursts_s[57 + 580], (uint8_t)bursts_s[58 + 580]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 580, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 696, 57), (uint8_t)bursts_s[57 + 696], (uint8_t)bursts_s[58 + 696]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 696, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 812, 57), (uint8_t)bursts_s[57 + 812], (uint8_t)bursts_s[58 + 812]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 812, 57)); /* destroy */ memset(bursts_s + 6, 0, 20); /* decode */ rc = tch_fr_decode(result, bursts_s, 1, len == 31, &n_errors, &n_bits_total); ASSERT_TRUE(rc == len); printd("Decoded: %s\n", osmo_hexdump(result, len)); printf("tch_fr_decode: n_errors=%d n_bits_total=%d ber=%.2f\n", n_errors, n_bits_total, (float)n_errors/n_bits_total); ASSERT_TRUE(!memcmp(speech, result, len)); printd("\n"); } static void test_hr(uint8_t *speech, int len) { uint8_t result[23]; ubit_t bursts_u[116 * 6]; sbit_t bursts_s[116 * 6]; int n_errors, n_bits_total; int rc; memset(bursts_u, 0x23, sizeof(bursts_u)); memset(bursts_s, 0, sizeof(bursts_s)); printd("Encoding: %s\n", osmo_hexdump(speech, len)); /* encode */ tch_hr_encode(bursts_u, speech, len); printd("U-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump(bursts_u, 57), bursts_u[57], bursts_u[58]); printd("%s\n", osmo_hexdump(bursts_u + 59, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 116, 57), bursts_u[57 + 116], bursts_u[58 + 116]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 232, 57), bursts_u[57 + 232], bursts_u[58 + 232]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 348, 57), bursts_u[57 + 348], bursts_u[58 + 348]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 348, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 464, 57), bursts_u[57 + 464], bursts_u[58 + 464]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 464, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 580, 57), bursts_u[57 + 580], bursts_u[58 + 580]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 580, 57)); ubits2sbits(bursts_u, bursts_s, 116 * 6); printd("S-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s, 57), (uint8_t)bursts_s[57], (uint8_t)bursts_s[58]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 116, 57), (uint8_t)bursts_s[57 + 116], (uint8_t)bursts_s[58 + 116]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 232, 57), (uint8_t)bursts_s[57 + 232], (uint8_t)bursts_s[58 + 232]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 348, 57), (uint8_t)bursts_s[57 + 348], (uint8_t)bursts_s[58 + 348]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 348, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 464, 57), (uint8_t)bursts_s[57 + 464], (uint8_t)bursts_s[58 + 464]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 464, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 580, 57), (uint8_t)bursts_s[57 + 580], (uint8_t)bursts_s[58 + 580]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 580, 57)); /* destroy */ memset(bursts_s + 6, 0, 20); /* decode */ rc = tch_hr_decode(result, bursts_s, 0, &n_errors, &n_bits_total); ASSERT_TRUE(rc == len); printd("Decoded: %s\n", osmo_hexdump(result, len)); printf("tch_hr_decode: n_errors=%d n_bits_total=%d ber=%.2f\n", n_errors, n_bits_total, (float)n_errors/n_bits_total); ASSERT_TRUE(!memcmp(speech, result, len)); printd("\n"); } static void test_pdtch(uint8_t *l2, int len) { uint8_t result[len]; ubit_t bursts_u[116 * 4]; sbit_t bursts_s[116 * 4]; int n_errors, n_bits_total; int rc; /* zero the not coded tail bits */ switch (len) { case 34: case 54: l2[len - 1] &= 0x7f; result[len - 1] &= 0x7f; break; case 40: l2[len - 1] &= 0x07; result[len - 1] &= 0x07; break; } printd("Encoding: %s\n", osmo_hexdump(l2, len)); /* encode */ pdtch_encode(bursts_u, l2, len); printd("U-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump(bursts_u, 57), bursts_u[57], bursts_u[58]); printd("%s\n", osmo_hexdump(bursts_u + 59, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 116, 57), bursts_u[57 + 116], bursts_u[58 + 116]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 232, 57), bursts_u[57 + 232], bursts_u[58 + 232]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump(bursts_u + 348, 57), bursts_u[57 + 348], bursts_u[58 + 348]); printd("%s\n", osmo_hexdump(bursts_u + 59 + 348, 57)); ubits2sbits(bursts_u, bursts_s, 116 * 4); printd("S-Bits:\n"); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s, 57), (uint8_t)bursts_s[57], (uint8_t)bursts_s[58]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 116, 57), (uint8_t)bursts_s[57 + 116], (uint8_t)bursts_s[58 + 116]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 116, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 232, 57), (uint8_t)bursts_s[57 + 232], (uint8_t)bursts_s[58 + 232]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 232, 57)); printd("%s %02x %02x ", osmo_hexdump((uint8_t *)bursts_s + 348, 57), (uint8_t)bursts_s[57 + 348], (uint8_t)bursts_s[58 + 348]); printd("%s\n", osmo_hexdump((uint8_t *)bursts_s + 59 + 348, 57)); /* decode */ rc = pdtch_decode(result, bursts_s, NULL, &n_errors, &n_bits_total); ASSERT_TRUE(rc == len); printd("Decoded: %s\n", osmo_hexdump(result, len)); printf("pdtch_decode: n_errors=%d n_bits_total=%d ber=%.2f\n", n_errors, n_bits_total, (float)n_errors/n_bits_total); ASSERT_TRUE(!memcmp(l2, result, len)); printd("\n"); } uint8_t test_l2[][23] = { /* dummy frame */ { 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* random frame */ { 0xa3, 0xaf, 0x5f, 0xc6, 0x36, 0x43, 0x44, 0xab, 0xd9, 0x6d, 0x7d, 0x62, 0x24, 0xc9, 0xd2, 0x92, 0xfa, 0x27, 0x5d, 0x71, 0x7a, 0x59, 0xa8 }, /* jolly frame */ { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }, }; uint8_t test_macblock[][54] = { /* random frame */ { 0xa3, 0xaf, 0x5f, 0xc6, 0x36, 0x43, 0x44, 0xab, 0xd9, 0x6d, 0x7d, 0x62, 0x24, 0xc9, 0xd2, 0x92, 0xfa, 0x27, 0x5d, 0x71, 0x7a, 0x59, 0xa8, 0x42, 0xa3, 0xaf, 0x5f, 0xc6, 0x36, 0x43, 0x44, 0xab, 0xa3, 0xaf, 0x5f, 0xc6, 0x36, 0x43, 0x44, 0xab, 0xd9, 0x6d, 0x7d, 0x62, 0x24, 0xc9, 0xd2, 0x92, 0xfa, 0x27, 0x5d, 0x71, 0x7a, 0xa8 }, /* jolly frame */ { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }, }; uint8_t test_speech_fr[33]; uint8_t test_speech_efr[31]; uint8_t test_speech_hr[15]; int main(int argc, char **argv) { int i; bts_log_init(NULL); for (i = 0; i < sizeof(test_l2) / sizeof(test_l2[0]); i++) test_xcch(test_l2[i]); for (i = 0; i < 256; i++) { test_rach(0x3f, i); test_rach(0x00, i); test_rach(0x1a, i); } for (i = 0; i < sizeof(test_l2) / sizeof(test_l2[0]); i++) test_sch(test_l2[i]); for (i = 0; i < sizeof(test_speech_fr); i++) test_speech_fr[i] = i; test_speech_fr[0] = 0xd0; test_fr(test_speech_fr, sizeof(test_speech_fr)); for (i = 0; i < sizeof(test_speech_efr); i++) test_speech_efr[i] = i; test_speech_efr[0] = 0xc0; test_fr(test_speech_efr, sizeof(test_speech_efr)); for (i = 0; i < sizeof(test_l2) / sizeof(test_l2[0]); i++) test_fr(test_l2[i], sizeof(test_l2[0])); for (i = 0; i < sizeof(test_speech_hr); i++) test_speech_hr[i] = i*17; test_speech_hr[0] = 0x00; test_hr(test_speech_hr, sizeof(test_speech_hr)); for (i = 0; i < sizeof(test_l2) / sizeof(test_l2[0]); i++) test_hr(test_l2[i], sizeof(test_l2[0])); for (i = 0; i < sizeof(test_macblock) / sizeof(test_macblock[0]); i++) { test_pdtch(test_macblock[i], 23); test_pdtch(test_macblock[i], 34); test_pdtch(test_macblock[i], 40); test_pdtch(test_macblock[i], 54); } printf("Success\n"); return 0; } osmo-bts-0.4.0/tests/bursts/bursts_test.ok000066400000000000000000000020341260026426200206550ustar00rootroot00000000000000xcch_decode: n_errors=60 n_bits_total=456 ber=0.13 xcch_decode: n_errors=60 n_bits_total=456 ber=0.13 xcch_decode: n_errors=60 n_bits_total=456 ber=0.13 tch_fr_decode: n_errors=8 n_bits_total=378 ber=0.02 tch_fr_decode: n_errors=8 n_bits_total=378 ber=0.02 tch_fr_decode: n_errors=10 n_bits_total=456 ber=0.02 tch_fr_decode: n_errors=10 n_bits_total=456 ber=0.02 tch_fr_decode: n_errors=10 n_bits_total=456 ber=0.02 tch_hr_decode: n_errors=11 n_bits_total=211 ber=0.05 tch_hr_decode: n_errors=10 n_bits_total=456 ber=0.02 tch_hr_decode: n_errors=10 n_bits_total=456 ber=0.02 tch_hr_decode: n_errors=10 n_bits_total=456 ber=0.02 pdtch_decode: n_errors=0 n_bits_total=456 ber=0.00 pdtch_decode: n_errors=132 n_bits_total=588 ber=0.22 pdtch_decode: n_errors=220 n_bits_total=676 ber=0.33 pdtch_decode: n_errors=0 n_bits_total=444 ber=0.00 pdtch_decode: n_errors=0 n_bits_total=456 ber=0.00 pdtch_decode: n_errors=132 n_bits_total=588 ber=0.22 pdtch_decode: n_errors=220 n_bits_total=676 ber=0.33 pdtch_decode: n_errors=0 n_bits_total=444 ber=0.00 Success osmo-bts-0.4.0/tests/cipher/000077500000000000000000000000001260026426200156725ustar00rootroot00000000000000osmo-bts-0.4.0/tests/cipher/Makefile.am000066400000000000000000000007301260026426200177260ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) -lortp noinst_PROGRAMS = cipher_test EXTRA_DIST = cipher_test.ok cipher_test_SOURCES = cipher_test.c $(srcdir)/../stubs.c cipher_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) osmo-bts-0.4.0/tests/cipher/cipher_test.c000066400000000000000000000051101260026426200203440ustar00rootroot00000000000000/* (C) 2012 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include static struct gsm_bts *bts; static struct gsm_bts_role_bts *btsb; #define ASSERT_TRUE(rc) \ if (!(rc)) { \ printf("Assert failed in %s:%d.\n", \ __FILE__, __LINE__); \ abort(); \ } static void test_cipher_parsing(void) { int i; btsb->support.ciphers = 0; /* always support A5/0 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x0) == -ENOTSUP); ASSERT_TRUE(bts_supports_cipher(btsb, 0x1) == 1); /* A5/0 */ for (i = 2; i <= 8; ++i) { ASSERT_TRUE(bts_supports_cipher(btsb, i) == 0); } /* checking default A5/1 to A5/3 support */ btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); ASSERT_TRUE(bts_supports_cipher(btsb, 0x0) == -ENOTSUP); ASSERT_TRUE(bts_supports_cipher(btsb, 0x1) == 1); /* A5/0 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x2) == 1); /* A5/1 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x3) == 1); /* A5/2 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x4) == 1); /* A5/3 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x5) == 0); /* A5/4 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x6) == 0); /* A5/5 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x7) == 0); /* A5/6 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x8) == 0); /* A5/7 */ ASSERT_TRUE(bts_supports_cipher(btsb, 0x9) == -ENOTSUP); } int main(int argc, char **argv) { void *tall_msgb_ctx; tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); bts_log_init(NULL); bts = gsm_bts_alloc(tall_bts_ctx); if (bts_init(bts) < 0) { fprintf(stderr, "unable to open bts\n"); exit(1); } btsb = bts_role_bts(bts); test_cipher_parsing(); printf("Success\n"); return 0; } osmo-bts-0.4.0/tests/cipher/cipher_test.ok000066400000000000000000000000101260026426200205250ustar00rootroot00000000000000Success osmo-bts-0.4.0/tests/handover/000077500000000000000000000000001260026426200162265ustar00rootroot00000000000000osmo-bts-0.4.0/tests/handover/Makefile.am000066400000000000000000000007111260026426200202610ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS)$(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) noinst_PROGRAMS = handover_test EXTRA_DIST = handover_test.ok handover_test_SOURCES = handover_test.c handover_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) osmo-bts-0.4.0/tests/handover/handover_test.c000066400000000000000000000171011260026426200212370ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint8_t phys_info[] = { 0x03, 0x03, 0x0d, 0x06, 0x2d, 0x00, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b }; static struct gsm_bts *bts; struct gsm_bts_trx *trx; int quit = 0; uint8_t abis_mac[6] = { 0, 1, 2, 3, 4, 5 }; const int pcu_direct = 0; int modify_count = 0; static void expect_phys_info(struct lapdm_entity *le) { struct osmo_phsap_prim pp; int rc; rc = lapdm_phsap_dequeue_prim(le, &pp); OSMO_ASSERT(rc == 0); OSMO_ASSERT(sizeof(phys_info) == pp.oph.msg->len); OSMO_ASSERT(!memcmp(phys_info, pp.oph.msg->data, pp.oph.msg->len)); msgb_free(pp.oph.msg); } int main(int argc, char **argv) { struct gsm_bts_role_bts *btsb; void *tall_bts_ctx; void *tall_msgb_ctx; struct e1inp_line *line; struct gsm_lchan *lchan; struct osmo_phsap_prim nl1sap; struct msgb *msg; struct abis_rsl_dchan_hdr *rslh; int i; tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); bts_log_init(NULL); osmo_stderr_target->categories[DHO].loglevel = LOGL_DEBUG; bts = gsm_bts_alloc(tall_bts_ctx); if (!bts) { fprintf(stderr, "Failed to create BTS structure\n"); exit(1); } trx = gsm_bts_trx_alloc(bts); if (!trx) { fprintf(stderr, "Failed to TRX structure\n"); exit(1); } if (bts_init(bts) < 0) { fprintf(stderr, "unable to to open bts\n"); exit(1); } btsb = bts_role_bts(bts); libosmo_abis_init(NULL); line = e1inp_line_create(0, "ipa"); OSMO_ASSERT(line); e1inp_ts_config_sign(&line->ts[E1INP_SIGN_RSL-1], line); trx->rsl_link = e1inp_sign_link_create(&line->ts[E1INP_SIGN_RSL-1], E1INP_SIGN_RSL, NULL, 0, 0); OSMO_ASSERT(trx->rsl_link); trx->rsl_link->trx = trx; fprintf(stderr, "test 1: without timeout\n"); /* create two lchans for handover */ lchan = &trx->ts[1].lchan[0]; l1sap_chan_act(lchan->ts->trx, 0x09, NULL); lchan = &trx->ts[2].lchan[0]; lchan->ho.active = HANDOVER_ENABLED; lchan->ho.ref = 23; l1sap_chan_act(lchan->ts->trx, 0x0a, NULL); OSMO_ASSERT(msgb_dequeue(&trx->rsl_link->tx_list)); OSMO_ASSERT(msgb_dequeue(&trx->rsl_link->tx_list)); OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); /* send access burst with wrong ref */ memset(&nl1sap, 0, sizeof(nl1sap)); osmo_prim_init(&nl1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL); nl1sap.u.rach_ind.chan_nr = 0x0a; nl1sap.u.rach_ind.ra = 42; l1sap_up(trx, &nl1sap); /* expect no action */ OSMO_ASSERT(modify_count == 0); OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); /* send access burst with correct ref */ nl1sap.u.rach_ind.ra = 23; l1sap_up(trx, &nl1sap); OSMO_ASSERT(modify_count == 1); /* expect PHYS INFO */ expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch); /* expect exactly one HO.DET */ OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list)); rslh = msgb_l2(msg); OSMO_ASSERT(rslh->c.msg_type == RSL_MT_HANDO_DET); OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); /* expect T3105 running */ OSMO_ASSERT(osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) /* indicate frame */ handover_frame(&trx->ts[2].lchan[0]); /* expect T3105 not running */ OSMO_ASSERT(!osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) fprintf(stderr, "test 2: with timeout\n"); /* enable handover again */ lchan = &trx->ts[2].lchan[0]; lchan->ho.active = HANDOVER_ENABLED; lchan->ho.ref = 23; modify_count = 0; /* send access burst with correct ref */ nl1sap.u.rach_ind.ra = 23; l1sap_up(trx, &nl1sap); OSMO_ASSERT(modify_count == 1); /* expect PHYS INFO */ expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch); /* expect exactly one HO.DET */ OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list)); rslh = msgb_l2(msg); OSMO_ASSERT(rslh->c.msg_type == RSL_MT_HANDO_DET); OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); for (i = 0; i < btsb->ny1 - 1; i++) { /* expect T3105 running */ OSMO_ASSERT(osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) /* timeout T3105 */ gettimeofday(&trx->ts[2].lchan[0].ho.t3105.timeout, NULL); osmo_select_main(0); /* expect PHYS INFO */ expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch); } /* timeout T3105 */ gettimeofday(&trx->ts[2].lchan[0].ho.t3105.timeout, NULL); osmo_select_main(0); /* expect T3105 not running */ OSMO_ASSERT(!osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) /* expect exactly one CONN.FAIL */ OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list)); rslh = msgb_l2(msg); OSMO_ASSERT(rslh->c.msg_type == RSL_MT_CONN_FAIL); OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); #if 0 while (!quit) { log_reset_context(); osmo_select_main(0); } #endif printf("Success\n"); return 0; } void bts_model_abis_close(struct gsm_bts *bts) { } int bts_model_oml_estab(struct gsm_bts *bts) { return 0; } int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { int rc = 0; uint8_t chan_nr; uint8_t tn, ss; struct gsm_lchan *lchan; struct msgb *msg = l1sap->oph.msg; struct osmo_phsap_prim nl1sap; switch (OSMO_PRIM_HDR(&l1sap->oph)) { case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): switch (l1sap->u.info.type) { case PRIM_INFO_ACTIVATE: chan_nr = l1sap->u.info.u.act_req.chan_nr; tn = L1SAP_CHAN2TS(chan_nr); ss = l1sap_chan2ss(chan_nr); lchan = &trx->ts[tn].lchan[ss]; lchan_init_lapdm(lchan); lchan_set_state(lchan, LCHAN_S_ACTIVE); memset(&nl1sap, 0, sizeof(nl1sap)); osmo_prim_init(&nl1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, NULL); nl1sap.u.info.type = PRIM_INFO_ACTIVATE; nl1sap.u.info.u.act_cnf.chan_nr = chan_nr; return l1sap_up(trx, &nl1sap); case PRIM_INFO_MODIFY: modify_count++; break; default: LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", l1sap->u.info.type); rc = -EINVAL; goto done; } break; default: LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", l1sap->oph.primitive, l1sap->oph.operation); rc = -EINVAL; goto done; } done: if (msg) msgb_free(msg); return rc; } int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj) { return 0; } int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int obj_kind, void *obj) { return 0; } int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { return 0; } int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { return 0; } int bts_model_init(struct gsm_bts *bts) { return 0; } int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { return 0; } int bts_model_trx_close(struct gsm_bts_trx *trx) { return 0; } void trx_get_hlayer1(void) {} int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { return 0; } osmo-bts-0.4.0/tests/handover/handover_test.ok000066400000000000000000000000101260026426200214150ustar00rootroot00000000000000Success osmo-bts-0.4.0/tests/misc/000077500000000000000000000000001260026426200153535ustar00rootroot00000000000000osmo-bts-0.4.0/tests/misc/Makefile.am000066400000000000000000000006151260026426200174110ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) noinst_PROGRAMS = misc_test EXTRA_DIST = misc_test.ok misc_test_SOURCES = misc_test.c $(srcdir)/../stubs.c misc_test_LDADD = $(top_builddir)/src/common/libbts.a \ $(LIBOSMOABIS_LIBS) $(LIBOSMOTRAU_LIBS) $(LDADD) osmo-bts-0.4.0/tests/misc/misc_test.c000066400000000000000000000111541260026426200175130ustar00rootroot00000000000000/* testing misc code */ /* (C) 2011 by Holger Hans Peter Freyther * (C) 2014 by sysmocom s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 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 static const uint8_t ipa_rsl_connect[] = { 0x00, 0x1c, 0xff, 0x10, 0x80, 0x00, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x70, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x00, 0xe0, 0x04, 0x00, 0x00, 0xff, 0x85, 0x00, 0x81, 0x0b, 0xbb }; static const uint8_t osmo_rsl_power[] = { 0x00, 0x18, 0xff, 0x10, 0x80, 0x00, 0x07, 0x0c, 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x73, 0x6d, 0x6f, 0x63, 0x6f, 0x6d, 0x00, 0x44, 0x02, 0x00, 0x00, 0xff, 0xfe, 0x04 }; static const uint8_t etsi_oml_opstart[] = { 0x00, 0x09, 0xff, 0x80, 0x80, 0x00, 0x05, 0x74, 0x00, 0xff, 0xff, 0xff }; static void test_msg_utils_ipa(void) { struct msgb *msg; int rc, size; printf("Testing IPA structure\n"); msg = msgb_alloc(sizeof(ipa_rsl_connect), "IPA test"); msg->l1h = msgb_put(msg, sizeof(ipa_rsl_connect)); memcpy(msg->l1h, ipa_rsl_connect, sizeof(ipa_rsl_connect)); rc = msg_verify_ipa_structure(msg); OSMO_ASSERT(rc == 0); msgb_free(msg); /* test truncated messages and they should fail */ for (size = sizeof(ipa_rsl_connect) - 1; size >= 0; --size) { msg = msgb_alloc(sizeof(ipa_rsl_connect) - 1, "IPA test"); msg->l1h = msgb_put(msg, size); memcpy(msg->l1h, ipa_rsl_connect, size); rc = msg_verify_ipa_structure(msg); OSMO_ASSERT(rc == -1); msgb_free(msg); } /* change the type of the message */ msg = msgb_alloc(sizeof(ipa_rsl_connect), "IPA test"); msg->l1h = msgb_put(msg, sizeof(ipa_rsl_connect)); memcpy(msg->l1h, ipa_rsl_connect, sizeof(ipa_rsl_connect)); msg->l1h[2] = 0x23; rc = msg_verify_ipa_structure(msg); OSMO_ASSERT(rc == 0); msgb_free(msg); } static void test_oml_data(const uint8_t *data, const size_t len, const int exp) { int rc; struct msgb *msg; msg = msgb_alloc(len, "IPA test"); msg->l2h = msgb_put(msg, len); memcpy(msg->l2h, data, len); rc = msg_verify_oml_structure(msg); if (rc >= 0) OSMO_ASSERT(msg->l3h > msg->l2h); OSMO_ASSERT(rc == exp); msgb_free(msg); } static void test_msg_utils_oml(void) { static const size_t hh_size = sizeof(struct ipaccess_head); int size; printf("Testing OML structure\n"); /* test with IPA message */ printf(" Testing IPA messages.\n"); test_oml_data(ipa_rsl_connect + hh_size, sizeof(ipa_rsl_connect) - hh_size, OML_MSG_TYPE_IPA); /* test truncated messages and they should fail */ for (size = sizeof(ipa_rsl_connect) - hh_size - 1; size >=0; --size) test_oml_data(ipa_rsl_connect + hh_size, size, -1); /* test with Osmo message */ printf(" Testing Osmo messages.\n"); test_oml_data(osmo_rsl_power + hh_size, sizeof(osmo_rsl_power) - hh_size, OML_MSG_TYPE_OSMO); for (size = sizeof(osmo_rsl_power) - hh_size - 1; size >=0; --size) test_oml_data(osmo_rsl_power + hh_size, size, -1); /* test with plain ETSI message */ printf(" Testing ETSI messages.\n"); test_oml_data(etsi_oml_opstart + hh_size, sizeof(etsi_oml_opstart) - hh_size, OML_MSG_TYPE_ETSI); for (size = sizeof(etsi_oml_opstart) - hh_size - 1; size >=0; --size) test_oml_data(etsi_oml_opstart + hh_size, size, -1); } static void test_sacch_get(void) { struct gsm_lchan lchan; int i, off; printf("Testing lchan_sacch_get\n"); memset(&lchan, 0, sizeof(lchan)); /* initialize the input. */ for (i = 1; i < _MAX_SYSINFO_TYPE; ++i) { lchan.si.valid |= (1 << i); memset(&lchan.si.buf[i], i, sizeof(lchan.si.buf[i])); } /* It will start with '1' */ for (i = 1, off = 0; i <= 32; ++i) { uint8_t *data = lchan_sacch_get(&lchan); off = (off + 1) % _MAX_SYSINFO_TYPE; if (off == 0) off += 1; //printf("i=%d (%%=%d) -> data[0]=%d\n", i, off, data[0]); OSMO_ASSERT(data[0] == off); } } int main(int argc, char **argv) { bts_log_init(NULL); test_sacch_get(); test_msg_utils_ipa(); test_msg_utils_oml(); return EXIT_SUCCESS; } osmo-bts-0.4.0/tests/misc/misc_test.ok000066400000000000000000000002131260026426200176740ustar00rootroot00000000000000Testing lchan_sacch_get Testing IPA structure Testing OML structure Testing IPA messages. Testing Osmo messages. Testing ETSI messages. osmo-bts-0.4.0/tests/paging/000077500000000000000000000000001260026426200156655ustar00rootroot00000000000000osmo-bts-0.4.0/tests/paging/Makefile.am000066400000000000000000000007301260026426200177210ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) -lortp noinst_PROGRAMS = paging_test EXTRA_DIST = paging_test.ok paging_test_SOURCES = paging_test.c $(srcdir)/../stubs.c paging_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) osmo-bts-0.4.0/tests/paging/paging_test.c000066400000000000000000000066371260026426200203510ustar00rootroot00000000000000/* testing the paging code */ /* (C) 2011 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 static struct gsm_bts *bts; static struct gsm_bts_role_bts *btsb; static const uint8_t static_ilv[] = { 0x08, 0x59, 0x51, 0x30, 0x99, 0x00, 0x00, 0x00, 0x19 }; #define ASSERT_TRUE(rc) \ if (!(rc)) { \ printf("Assert failed in %s:%d.\n", \ __FILE__, __LINE__); \ abort(); \ } static void test_paging_smoke(void) { int rc; uint8_t out_buf[GSM_MACBLOCK_LEN]; struct gsm_time g_time; int is_empty = -1; printf("Testing that paging messages expire.\n"); /* add paging entry */ rc = paging_add_identity(btsb->paging_state, 0, static_ilv, 0); ASSERT_TRUE(rc == 0); ASSERT_TRUE(paging_queue_length(btsb->paging_state) == 1); /* generate messages */ g_time.fn = 0; g_time.t1 = 0; g_time.t2 = 0; g_time.t3 = 6; rc = paging_gen_msg(btsb->paging_state, out_buf, &g_time, &is_empty); ASSERT_TRUE(rc == 13); ASSERT_TRUE(is_empty == 0); ASSERT_TRUE(paging_group_queue_empty(btsb->paging_state, 0)); ASSERT_TRUE(paging_queue_length(btsb->paging_state) == 0); /* now test the empty queue */ g_time.fn = 0; g_time.t1 = 0; g_time.t2 = 0; g_time.t3 = 6; rc = paging_gen_msg(btsb->paging_state, out_buf, &g_time, &is_empty); ASSERT_TRUE(rc == 6); ASSERT_TRUE(is_empty == 1); /* * TODO: test all the cases of different amount tmsi/imsi and check * if we fill the slots in a optimal way. */ } static void test_paging_sleep(void) { int rc; uint8_t out_buf[GSM_MACBLOCK_LEN]; struct gsm_time g_time; int is_empty = -1; printf("Testing that paging messages expire with sleep.\n"); /* add paging entry */ rc = paging_add_identity(btsb->paging_state, 0, static_ilv, 0); ASSERT_TRUE(rc == 0); ASSERT_TRUE(paging_queue_length(btsb->paging_state) == 1); /* sleep */ sleep(1); /* generate messages */ g_time.fn = 0; g_time.t1 = 0; g_time.t2 = 0; g_time.t3 = 6; rc = paging_gen_msg(btsb->paging_state, out_buf, &g_time, &is_empty); ASSERT_TRUE(rc == 13); ASSERT_TRUE(is_empty == 0); ASSERT_TRUE(paging_group_queue_empty(btsb->paging_state, 0)); ASSERT_TRUE(paging_queue_length(btsb->paging_state) == 0); } int main(int argc, char **argv) { void *tall_msgb_ctx; tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); msgb_set_talloc_ctx(tall_msgb_ctx); bts_log_init(NULL); bts = gsm_bts_alloc(tall_bts_ctx); if (bts_init(bts) < 0) { fprintf(stderr, "unable to open bts\n"); exit(1); } btsb = bts_role_bts(bts); test_paging_smoke(); test_paging_sleep(); printf("Success\n"); return 0; } osmo-bts-0.4.0/tests/paging/paging_test.ok000066400000000000000000000001351260026426200205230ustar00rootroot00000000000000Testing that paging messages expire. Testing that paging messages expire with sleep. Success osmo-bts-0.4.0/tests/stubs.c000066400000000000000000000036131260026426200157270ustar00rootroot00000000000000#include /* * Stubs to provide an empty bts model implementation for testing. * If we ever want to re-define such a symbol we can make them weak * here. */ int pcu_direct = 0; int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { return 0; } int bts_model_init(struct gsm_bts *bts) { return 0; } int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int kind, void *obj) { return 0; } int bts_model_rsl_chan_rel(struct gsm_lchan *lchan) { return 0;} int bts_model_rsl_deact_sacch(struct gsm_lchan *lchan) { return 0; } int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { return 0; } int bts_model_trx_close(struct gsm_bts_trx *trx) { return 0; } int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj) { return 0; } int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { return 0; } int bts_model_rsl_chan_act(struct gsm_lchan *lchan, struct tlv_parsed *tp) { return 0; } int bts_model_rsl_mode_modify(struct gsm_lchan *lchan) { return 0; } int bts_model_rsl_chan_mod(struct gsm_lchan *lchan) { return 0; } void bts_model_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, unsigned int rtp_pl_len) {} int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { return 0; } int l1if_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len) { return 0; } uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) { return 0; } int bts_model_oml_estab(struct gsm_bts *bts) { return 0; } int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power) { return 0; } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { return 0; } void bts_model_abis_close(struct gsm_bts *bts) { } osmo-bts-0.4.0/tests/sysmobts/000077500000000000000000000000001260026426200163035ustar00rootroot00000000000000osmo-bts-0.4.0/tests/sysmobts/Makefile.am000066400000000000000000000016351260026426200203440ustar00rootroot00000000000000AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) -I$(top_srcdir)/src/osmo-bts-sysmo AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) -lortp noinst_PROGRAMS = sysmobts_test EXTRA_DIST = sysmobts_test.ok sysmobts_test_SOURCES = sysmobts_test.c $(top_srcdir)/src/osmo-bts-sysmo/utils.c \ $(top_srcdir)/src/osmo-bts-sysmo/l1_if.c \ $(top_srcdir)/src/osmo-bts-sysmo/oml.c \ $(top_srcdir)/src/osmo-bts-sysmo/l1_transp_hw.c \ $(top_srcdir)/src/osmo-bts-sysmo/tch.c \ $(top_srcdir)/src/osmo-bts-sysmo/calib_file.c \ $(top_srcdir)/src/osmo-bts-sysmo/calib_fixup.c \ $(top_srcdir)/src/osmo-bts-sysmo/misc/sysmobts_par.c \ $(top_srcdir)/src/osmo-bts-sysmo/eeprom.c sysmobts_test_LDADD = $(top_builddir)/src/common/libbts.a $(LIBOSMOABIS_LIBS) $(LDADD) osmo-bts-0.4.0/tests/sysmobts/sysmobts_test.c000066400000000000000000000175001260026426200213740ustar00rootroot00000000000000/* * (C) 2013,2014 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "femtobts.h" #include "l1_if.h" #include "utils.h" #include #include int pcu_direct = 0; static int direct_map[][3] = { { GSM_BAND_850, GsmL1_FreqBand_850, 128 }, { GSM_BAND_900, GsmL1_FreqBand_900, 1 }, { GSM_BAND_1800, GsmL1_FreqBand_1800, 600 }, { GSM_BAND_1900, GsmL1_FreqBand_1900, 600 }, }; static int dcs_to_dcs[][3] = { { GSM_BAND_900, GsmL1_FreqBand_1800, 600 }, { GSM_BAND_1800, GsmL1_FreqBand_900, 1 }, { GSM_BAND_900, -1, 438 }, }; static int pcs_to_pcs[][3] = { { GSM_BAND_850, GsmL1_FreqBand_1900, 512 }, { GSM_BAND_1900, GsmL1_FreqBand_850, 128 }, { GSM_BAND_900, -1, 438 }, }; static void test_sysmobts_auto_band(void) { struct gsm_bts bts; struct gsm_bts_role_bts btsb; struct gsm_bts_trx trx; struct femtol1_hdl hdl; int i; memset(&bts, 0, sizeof(bts)); memset(&btsb, 0, sizeof(btsb)); memset(&trx, 0, sizeof(trx)); memset(&hdl, 0, sizeof(hdl)); bts.role = &btsb; trx.bts = &bts; trx.role_bts.l1h = &hdl; /* claim to support all hw_info's */ hdl.hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | GSM_BAND_1800 | GSM_BAND_1900; /* start with the current option */ printf("Testing the no auto-band mapping.\n"); for (i = 0; i < ARRAY_SIZE(direct_map); ++i) { uint16_t arfcn; int res; btsb.auto_band = 0; bts.band = direct_map[i][0]; arfcn = direct_map[i][2]; res = sysmobts_select_femto_band(&trx, arfcn); printf("No auto-band band(%d) arfcn(%u) want(%d) got(%d)\n", bts.band, arfcn, direct_map[i][1], res); OSMO_ASSERT(res == direct_map[i][1]); } /* Check if auto-band does not break things */ printf("Checking the mapping with auto-band.\n"); for (i = 0; i < ARRAY_SIZE(direct_map); ++i) { uint16_t arfcn; int res; btsb.auto_band = 1; bts.band = direct_map[i][0]; arfcn = direct_map[i][2]; res = sysmobts_select_femto_band(&trx, arfcn); printf("Auto-band band(%d) arfcn(%u) want(%d) got(%d)\n", bts.band, arfcn, direct_map[i][1], res); OSMO_ASSERT(res == direct_map[i][1]); } /* Check DCS to DCS change */ printf("Checking DCS to DCS\n"); for (i = 0; i < ARRAY_SIZE(dcs_to_dcs); ++i) { uint16_t arfcn; int res; btsb.auto_band = 1; bts.band = dcs_to_dcs[i][0]; arfcn = dcs_to_dcs[i][2]; res = sysmobts_select_femto_band(&trx, arfcn); printf("DCS to DCS band(%d) arfcn(%u) want(%d) got(%d)\n", bts.band, arfcn, dcs_to_dcs[i][1], res); OSMO_ASSERT(res == dcs_to_dcs[i][1]); } /* Check for a PCS to PCS change */ printf("Checking PCS to PCS\n"); for (i = 0; i < ARRAY_SIZE(pcs_to_pcs); ++i) { uint16_t arfcn; int res; btsb.auto_band = 1; bts.band = pcs_to_pcs[i][0]; arfcn = pcs_to_pcs[i][2]; res = sysmobts_select_femto_band(&trx, arfcn); printf("PCS to PCS band(%d) arfcn(%u) want(%d) got(%d)\n", bts.band, arfcn, pcs_to_pcs[i][1], res); OSMO_ASSERT(res == pcs_to_pcs[i][1]); } } static void test_sysmobts_cipher(void) { static const uint8_t cipher_cmd[] = { 0x03, 0x00, 0x0d, 0x06, 0x35, 0x11, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b }; static const uint8_t too_early_classmark[] = { 0x01, 0x00, 0x4d, 0x06, 0x16, 0x03, 0x30, 0x18, 0xa2, 0x20, 0x0b, 0x60, 0x14, 0x4c, 0xa7, 0x7b, 0x29, 0x11, 0xdc, 0x40, 0x04, 0x00, 0x2b }; static const uint8_t first_ciphered_cipher_cmpl[] = { 0x01, 0x30, 0x4d, 0x06, 0x16, 0x03, 0x30, 0x18, 0xa2, 0x20, 0x0b, 0x60, 0x14, 0x4c, 0xa7, 0x7b, 0x29, 0x11, 0xdc, 0x40, 0x04, 0x00, 0x2b }; struct gsm_lchan lchan; struct femtol1_hdl fl1h; struct msgb *msg; GsmL1_MsgUnitParam_t unit; int rc; memset(&lchan, 0, sizeof(lchan)); memset(&fl1h, 0, sizeof(fl1h)); /* Inject the cipher mode command */ msg = msgb_alloc_headroom(128, 64, "ciphering mode command"); lchan.ciph_state = LCHAN_CIPH_NONE; memcpy(msgb_put(msg, ARRAY_SIZE(cipher_cmd)), cipher_cmd, ARRAY_SIZE(cipher_cmd)); rc = bts_check_for_ciph_cmd(&fl1h, msg, &lchan); OSMO_ASSERT(rc == 1); OSMO_ASSERT(lchan.ciph_state == LCHAN_CIPH_RX_REQ); OSMO_ASSERT(lchan.ciph_ns == 1); msgb_free(msg); /* Move to the confirmed state */ lchan.ciph_state = LCHAN_CIPH_RX_CONF; /* Handle message sent before ciphering was received */ memcpy(&unit.u8Buffer[0], too_early_classmark, ARRAY_SIZE(too_early_classmark)); unit.u8Size = ARRAY_SIZE(too_early_classmark); rc = bts_check_for_first_ciphrd(&lchan, unit.u8Buffer, unit.u8Size); OSMO_ASSERT(rc == 0); OSMO_ASSERT(lchan.ciph_state == LCHAN_CIPH_RX_CONF); /* Now send the first ciphered message */ memcpy(&unit.u8Buffer[0], first_ciphered_cipher_cmpl, ARRAY_SIZE(first_ciphered_cipher_cmpl)); unit.u8Size = ARRAY_SIZE(first_ciphered_cipher_cmpl); rc = bts_check_for_first_ciphrd(&lchan, unit.u8Buffer, unit.u8Size); OSMO_ASSERT(rc == 1); /* we cannot test for lchan.ciph_state == * LCHAN_CIPH_RX_CONF_TX_REQ, as * this happens asynchronously on the other side of the l1sap queue */ } static void test_sysmobts_loop(void) { struct gsm_bts bts; struct gsm_bts_role_bts btsb; struct gsm_bts_trx trx; struct gsm_bts_trx_ts ts; struct gsm_lchan *lchan; int ret; memset(&bts, 0, sizeof(bts)); memset(&btsb, 0, sizeof(btsb)); memset(&trx, 0, sizeof(trx)); memset(&ts, 0, sizeof(ts)); lchan = &ts.lchan[0]; lchan->ts = &ts; ts.trx = &trx; trx.bts = &bts; bts.role = &btsb; bts.band = GSM_BAND_1800; trx.ms_power_control = 1; btsb.ul_power_target = -75; printf("Testing sysmobts power control\n"); /* Simply clamping */ lchan->state = LCHAN_S_NONE; lchan->ms_power_ctrl.current = ms_pwr_ctl_lvl(GSM_BAND_1800, 0); OSMO_ASSERT(lchan->ms_power_ctrl.current == 15); ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, -60); OSMO_ASSERT(ret == 0); OSMO_ASSERT(lchan->ms_power_ctrl.current == 15); /* * Now 15 dB too little and we should power it up. Could be a * power level of 7 or 8 for 15 dBm */ ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, -90); OSMO_ASSERT(ret == 1); OSMO_ASSERT(lchan->ms_power_ctrl.current == 7); /* It should be clamped to level 0 and 30 dBm */ ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, -100); OSMO_ASSERT(ret == 1); OSMO_ASSERT(lchan->ms_power_ctrl.current == 0); /* Fix it and jump down */ lchan->ms_power_ctrl.fixed = 1; ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, -60); OSMO_ASSERT(ret == 0); OSMO_ASSERT(lchan->ms_power_ctrl.current == 0); /* And leave it again */ lchan->ms_power_ctrl.fixed = 0; ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, -40); OSMO_ASSERT(ret == 1); OSMO_ASSERT(lchan->ms_power_ctrl.current == 15); } int main(int argc, char **argv) { printf("Testing sysmobts routines\n"); test_sysmobts_auto_band(); test_sysmobts_cipher(); test_sysmobts_loop(); return 0; } /* * some local stubs. We need to pull in a lot more code and can't * use the generic stubs unless we make all of them weak */ void bts_update_status(enum bts_global_status which, int on) {} int bts_model_init(struct gsm_bts *bts) { return 0; } int bts_model_oml_estab(struct gsm_bts *bts) { return 0; } void bts_model_abis_close(struct gsm_bts *bts) { } osmo-bts-0.4.0/tests/sysmobts/sysmobts_test.ok000066400000000000000000000014401260026426200215570ustar00rootroot00000000000000Testing sysmobts routines Testing the no auto-band mapping. No auto-band band(1) arfcn(128) want(0) got(0) No auto-band band(2) arfcn(1) want(1) got(1) No auto-band band(4) arfcn(600) want(2) got(2) No auto-band band(8) arfcn(600) want(3) got(3) Checking the mapping with auto-band. Auto-band band(1) arfcn(128) want(0) got(0) Auto-band band(2) arfcn(1) want(1) got(1) Auto-band band(4) arfcn(600) want(2) got(2) Auto-band band(8) arfcn(600) want(3) got(3) Checking DCS to DCS DCS to DCS band(2) arfcn(600) want(2) got(2) DCS to DCS band(4) arfcn(1) want(1) got(1) DCS to DCS band(2) arfcn(438) want(-1) got(-1) Checking PCS to PCS PCS to PCS band(1) arfcn(512) want(3) got(3) PCS to PCS band(8) arfcn(128) want(0) got(0) PCS to PCS band(2) arfcn(438) want(-1) got(-1) Testing sysmobts power control osmo-bts-0.4.0/tests/testsuite.at000066400000000000000000000021711260026426200170000ustar00rootroot00000000000000AT_INIT AT_BANNER([Regression tests.]) AT_SETUP([paging]) AT_KEYWORDS([paging]) cat $abs_srcdir/paging/paging_test.ok > expout AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/paging/paging_test], [], [expout], [ignore]) AT_CLEANUP AT_SETUP([agch]) AT_KEYWORDS([agch]) cat $abs_srcdir/agch/agch_test.ok > expout AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/agch/agch_test], [], [expout], [ignore]) AT_CLEANUP AT_SETUP([cipher]) AT_KEYWORDS([cipher]) cat $abs_srcdir/cipher/cipher_test.ok > expout AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/cipher/cipher_test], [], [expout], [ignore]) AT_CLEANUP AT_SETUP([misc]) AT_KEYWORDS([misc]) cat $abs_srcdir/misc/misc_test.ok > expout AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/misc/misc_test], [], [expout], [ignore]) AT_CLEANUP AT_SETUP([bursts]) AT_KEYWORDS([bursts]) cat $abs_srcdir/bursts/bursts_test.ok > expout AT_CHECK([$abs_top_builddir/tests/bursts/bursts_test], [], [expout], [ignore]) AT_CLEANUP AT_SETUP([handover]) AT_KEYWORDS([handover]) cat $abs_srcdir/handover/handover_test.ok > expout AT_CHECK([$abs_top_builddir/tests/handover/handover_test], [], [expout], [ignore]) AT_CLEANUP