diff options
325 files changed, 82523 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d81bd450 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +*.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 +libtool +ltmain.sh +core +core.* + +# git-version-gen magic +.tarball-version +.version + +src/osmo-bts-sysmo/sysmobts-calib +src/osmo-bts-sysmo/l1fwd-proxy +src/osmo-bts-sysmo/osmo-bts-sysmo +src/osmo-bts-sysmo/osmo-bts-sysmo-remote +src/osmo-bts-sysmo/sysmobts-mgr +src/osmo-bts-sysmo/sysmobts-util + +src/osmo-bts-litecell15/lc15bts-mgr +src/osmo-bts-litecell15/lc15bts-util +src/osmo-bts-litecell15/misc/.dirstamp +src/osmo-bts-litecell15/osmo-bts-lc15 + +src/osmo-bts-trx/osmo-bts-trx + +src/osmo-bts-octphy/osmo-bts-octphy + +src/osmo-bts-virtual/osmo-bts-virtual +src/osmo-bts-omldummy/osmo-bts-omldummy + +tests/atconfig +tests/package.m4 +tests/agch/agch_test +tests/paging/paging_test +tests/cipher/cipher_test +tests/sysmobts/sysmobts_test +tests/meas/meas_test +tests/misc/misc_test +tests/handover/handover_test +tests/tx_power/tx_power_test +tests/testsuite +tests/testsuite.log + +# Possible generated file +doc/vty_reference.xml + +# Backups, vi, merges +*~ +*.sw? +*.orig +*.sav + +# debian +.tarball-version +debian/autoreconf.after +debian/autoreconf.before +debian/files +debian/*.debhelper.log +debian/*.substvars +debian/osmo-bts-trx-dbg/ +debian/osmo-bts-trx/ +debian/tmp/ +/tests/power/power_test diff --git a/.gitreview b/.gitreview new file mode 100644 index 00000000..2fcadd2c --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=osmo-bts diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..cda40579 --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ +Harald Welte <laforge@gnumonks.org> +Harald Welte <laforge@gnumonks.org> <laflocal@hanuman.gnumonks.org> +Harald Welte <laforge@gnumonks.org> <laflocal@goeller.de.gnumonks.org> +Holger Hans Peter Freyther <holger@moiji-mobile.com> <zecke@selfish.org> +Holger Hans Peter Freyther <holger@moiji-mobile.com> <ich@tamarin.(none)> +Holger Hans Peter Freyther <holgre@moiji-mobile.com> <holger@freyther.de> +Andreas Eversberg <jolly@eversberg.eu> +Andreas Eversberg <jolly@eversberg.eu> <Andreas.Eversberg@versatel.de> +Andreas Eversberg <jolly@eversberg.eu> <root@nuedel.(none)> +Pablo Neira Ayuso <pablo@soleta.eu> <pablo@gnumonks.org> +Max Suraev <msuraev@sysmocom.de> +Tom Tsou <tom.tsou@ettus.com> <tom@tsou.cc> diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..dba13ed2 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..5b49bb71 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,23 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +SUBDIRS = include src tests doc contrib + + +# package the contrib and doc +EXTRA_DIST = \ + contrib/dump_docs.py contrib/screenrc-l1fwd \ + contrib/l1fwd.init contrib/screenrc-sysmobts contrib/respawn.sh \ + git-version-gen .version \ + README.md + +DISTCHECK_CONFIGURE_FLAGS = \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + +@RELMAKE@ + +BUILT_SOURCES = $(top_srcdir)/.version + +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/README.md b/README.md new file mode 100644 index 00000000..38b4bd9f --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +osmo-bts - Osmocom BTS Implementation +==================================== + +This repository contains a C-language implementation of a GSM Base +Transceiver Station (BTS). It is part of the +[Osmocom](https://osmocom.org/) Open Source Mobile Communications +project. + +This code implements 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. + +Several kinds of BTS hardware are supported: + * sysmocom sysmoBTS + * Octasic octphy + * Nutaq litecell 1.5 + * software-defined radio based osmo-bts-trx (e.g. USRP B210, UmTRX) + +Homepage +-------- + +The official homepage of the project is +https://osmocom.org/projects/osmobts/wiki + +GIT Repository +-------------- + +You can clone from the official osmo-bts.git repository using + + git clone git://git.osmocom.org/osmo-bts.git + +There is a cgit interface at http://git.osmocom.org/osmo-bts/ + +Documentation +------------- + +We provide a +[User Manual](http://ftp.osmocom.org/docs/latest/osmobts-usermanual.pdf) +as well as a +[VTY Reference Manual](http://ftp.osmocom.org/docs/latest/osmobsc-vty-reference.pdf) +and a +[Abis refrence MAnual](http://ftp.osmocom.org/docs/latest/osmobts-abis.pdf) +describing the OsmoBTS specific A-bis dialect. + +Mailing List +------------ + +Discussions related to osmo-bts are happening on the +openbsc@lists.osmocom.org mailing list, please see +https://lists.osmocom.org/mailman/listinfo/openbsc for subscription +options and the list archive. + +Please observe the [Osmocom Mailing List +Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules) +when posting. + +Contributing +------------ + +Our coding standards are described at +https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards + +We us a gerrit based patch submission/review process for managing +contributions. Please see +https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit for +more details + +The current patch queue for osmo-bts can be seen at +https://gerrit.osmocom.org/#/q/project:osmo-bts+status:open + +Known Limitations +================= + +As of March 17, 2017, 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 + * 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 + * 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-octphy +--------------- + + * No support of EFR, HR voice codec (lack of PHY support?) + * No re-transmission of PHY primitives in case of time-out + * Link Quality / Measurement processing incomplete + * impossible to modify encryption parameters using RSL MODE MODIFY + * no clear indication of nominal transmit power, various power related + computations are likely off + * no OML attribute validation during bts_model_check_oml() + +osmo-bts-trx +------------ + + * TCH/F_PDCH cannel not working as voice (https://osmocom.org/issues/1865) + * No BER value delivered to OsmoPCU (https://osmocom.org/issues/1855) + * No 11bit RACH support (https://osmocom.org/issues/1854) diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..9a8d58f1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,350 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([osmo-bts], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc@lists.osmocom.org]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +AM_INIT_AUTOMAKE([dist-bzip2]) +AC_CONFIG_TESTDIR(tests) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT + +dnl check for pkg-config (explained in detail in libosmocore/configure.ac) +AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no) +if test "x$PKG_CONFIG_INSTALLED" = "xno"; then + AC_MSG_WARN([You need to install pkg-config]) +fi +PKG_PROG_PKG_CONFIG([0.20]) + +dnl checks for header files +AC_HEADER_STDC + +dnl Checks for typedefs, structures and compiler characteristics + +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING([--enable-sanitize], [Compile with address sanitizer enabled], )], + [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +dnl checks for libraries +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.0) +PKG_CHECK_MODULES(LIBOSMOTRAU, libosmotrau >= 0.5.0) + +AC_MSG_CHECKING([whether to enable support for sysmobts calibration tool]) +AC_ARG_ENABLE(sysmobts-calib, + AC_HELP_STRING([--enable-sysmobts-calib], + [enable code for sysmobts calibration tool [default=no]]), + [enable_sysmobts_calib="yes"],[enable_sysmobts_calib="no"]) +AC_MSG_RESULT([$enable_sysmobts_calib]) +AM_CONDITIONAL(ENABLE_SYSMOBTS_CALIB, test "x$enable_sysmobts_calib" = "xyes") + +AC_MSG_CHECKING([whether to enable support for sysmoBTS L1/PHY support]) +AC_ARG_ENABLE(sysmocom-bts, + AC_HELP_STRING([--enable-sysmocom-bts], + [enable code for sysmoBTS L1/PHY [default=no]]), + [enable_sysmocom_bts="yes"],[enable_sysmocom_bts="no"]) +AC_ARG_WITH([sysmobts], [AS_HELP_STRING([--with-sysmobts=INCLUDE_DIR], [Location of the sysmobts API header files, implies --enable-sysmocom-bts])], + [sysmobts_incdir="$withval"],[sysmobts_incdir="$incdir"]) +if test "x$sysmobts_incdir" != "x"; then + # --with-sysmobts was passed, imply enable_sysmocom_bts + enable_sysmocom_bts="yes" +fi +AC_SUBST([SYSMOBTS_INCDIR], -I$sysmobts_incdir) +AC_MSG_RESULT([$enable_sysmocom_bts]) +AM_CONDITIONAL(ENABLE_SYSMOBTS, test "x$enable_sysmocom_bts" = "xyes") +if test "$enable_sysmocom_bts" = "yes"; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $SYSMOBTS_INCDIR -I$srcdir/include" + AC_CHECK_HEADER([sysmocom/femtobts/superfemto.h],[], + [AC_MSG_ERROR([sysmocom/femtobts/superfemto.h can not be found in $sysmobts_incdir])], + [#include <sysmocom/femtobts/superfemto.h>]) + + # Check for the sbts2050_header.h that was added after the 3.6 release + AC_CHECK_HEADER([sysmocom/femtobts/sbts2050_header.h], [sysmo_uc_header="yes"], []) + if test "$sysmo_uc_header" = "yes" ; then + AC_DEFINE(BUILD_SBTS2050, 1, [Define if we want to build SBTS2050]) + fi + + PKG_CHECK_MODULES(LIBGPS, libgps) + CPPFLAGS=$oldCPPFLAGS +fi +AM_CONDITIONAL(BUILD_SBTS2050, test "x$sysmo_uc_header" = "xyes") + +AC_MSG_CHECKING([whether to enable support for osmo-trx based L1/PHY support]) +AC_ARG_ENABLE(trx, + AC_HELP_STRING([--enable-trx], + [enable code for osmo-trx L1/PHY [default=no]]), + [enable_trx="yes"],[enable_trx="no"]) +AC_MSG_RESULT([$enable_trx]) +AM_CONDITIONAL(ENABLE_TRX, test "x$enable_trx" = "xyes") + +AC_MSG_CHECKING([whether to enable support for Octasic OCTPHY-2G]) +AC_ARG_ENABLE(octphy, + AC_HELP_STRING([--enable-octphy], + [enable code for Octasic OCTPHY-2G [default=no]]), + [enable_octphy="yes"],[enable_octphy="no"]) +AC_ARG_WITH([octsdr-2g], [AS_HELP_STRING([--with-octsdr-2g], [Location of the OCTSDR-2G API header files])], + [octsdr2g_incdir="$withval"],[octsdr2g_incdir="`cd $srcdir; pwd`/src/osmo-bts-octphy/"]) +AC_SUBST([OCTSDR2G_INCDIR], -I$octsdr2g_incdir) +AC_MSG_RESULT([$enable_octphy]) +AM_CONDITIONAL(ENABLE_OCTPHY, test "x$enable_octphy" = "xyes") +if test "$enable_octphy" = "yes" ; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $OCTSDR2G_INCDIR -I$srcdir/include" + + AC_CHECK_HEADER([octphy/octvc1/gsm/octvc1_gsm_default.h],[], + [AC_MSG_ERROR([octphy/octvc1/gsm/octvc1_gsm_default.h can not be found in $octsdr2g_incdir])], + [#include <octphy/octvc1/gsm/octvc1_gsm_default.h>]) + + AC_CHECK_MEMBER([tOCTVC1_GSM_TRX_CONFIG.usCentreArfcn], + AC_DEFINE([OCTPHY_MULTI_TRX], + [1], + [Define to 1 if your octphy header files support multi-trx]), + [], + [#include <octphy/octvc1/gsm/octvc1_gsm_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_RF_PORT_RX_STATS.Frequency], + AC_DEFINE([OCTPHY_USE_FREQUENCY], + [1], + [Define to 1 if your octphy header files support tOCTVC1_RADIO_FREQUENCY_VALUE type]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP.TxConfig], + AC_DEFINE([OCTPHY_USE_TX_CONFIG], + [1], + [Define to 1 if your octphy header files support tOCTVC1_HW_RF_PORT_ANTENNA_TX_CONFIG type]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP.RxConfig], + AC_DEFINE([OCTPHY_USE_RX_CONFIG], + [1], + [Define to 1 if your octphy header files support tOCTVC1_HW_RF_PORT_ANTENNA_RX_CONFIG type]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_GSM_RF_CONFIG.ulTxAntennaId], + AC_DEFINE([OCTPHY_USE_ANTENNA_ID], + [1], + [Define to 1 if your octphy header files support antenna ids in tOCTVC1_GSM_RF_CONFIG]), + [], + [#include <octphy/octvc1/gsm/octvc1_gsm_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP.ulClkSourceSelection], + AC_DEFINE([OCTPHY_USE_CLK_SOURCE_SELECTION], + [1], + [Define to 1 if your octphy header files supports ulClkSourceSelection in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.lClockError], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR], + [1], + [Define to 1 if your octphy header files supports lClockError in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.lDroppedCycles], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES], + [1], + [Define to 1 if your octphy header files supports lDroppedCycles in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulPllFreqHz], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ], + [1], + [Define to 1 if your octphy header files supports ulPllFreqHz in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulPllFractionalFreqHz], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ], + [1], + [Define to 1 if your octphy header files supports ulPllFractionalFreqHz in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSlipCnt], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT], + [1], + [Define to 1 if your octphy header files supports ulSlipCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSyncLosseCnt], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT], + [1], + [Define to 1 if your octphy header files supports ulSyncLosseCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSyncLossCnt], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT], + [1], + [Define to 1 if your octphy header files supports ulSyncLossCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSourceState], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE], + [1], + [Define to 1 if your octphy header files supports ulSourceState in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulDacState], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE], + [1], + [Define to 1 if your octphy header files supports ulDacState in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulDriftElapseTimeUs], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US], + [1], + [Define to 1 if your octphy header files supports ulDriftElapseTimeUs in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include <octphy/octvc1/hw/octvc1_hw_api.h>]) + + AC_CHECK_MEMBER([tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD.ulOversample16xEnableFlag], + AC_DEFINE([OCTPHY_USE_16X_OVERSAMPLING], + [1], + [Define to 1 if your octphy header files support 16x oversampling]), + [], + [#include <octphy/octvc1/gsm/octvc1_gsm_api.h>]) + + CPPFLAGS=$oldCPPFLAGS +fi + +AC_MSG_CHECKING([whether to enable NuRAN Wireless Litecell 1.5 hardware support]) +AC_ARG_ENABLE(litecell15, + AC_HELP_STRING([--enable-litecell15], + [enable code for NuRAN Wireless Litecell15 bts [default=no]]), + [enable_litecell15="yes"],[enable_litecell15="no"]) +AC_ARG_WITH([litecell15], [AS_HELP_STRING([--with-litecell15=INCLUDE_DIR], [Location of the litecell 1.5 API header files])], + [litecell15_incdir="$withval"],[litecell15_incdir="$incdir"]) +AC_SUBST([LITECELL15_INCDIR], -I$litecell15_incdir) +AC_MSG_RESULT([$enable_litecell15]) +AM_CONDITIONAL(ENABLE_LC15BTS, test "x$enable_litecell15" = "xyes") +if test "$enable_litecell15" = "yes"; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LITECELL15_INCDIR -I$srcdir/include" + AC_CHECK_HEADER([nrw/litecell15/litecell15.h],[], + [AC_MSG_ERROR([nrw/litecell15/litecell15.h can not be found in $litecell15_incdir])], + [#include <nrw/litecell15/litecell15.h>]) + PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd) + CPPFLAGS=$oldCPPFLAGS +fi + +AC_MSG_CHECKING([whether to enable NuRAN Wireless OC-2G hardware support]) +AC_ARG_ENABLE(oc2g, + AC_HELP_STRING([--enable-oc2g], + [enable code for NuRAN Wireless OC-2G bts [default=no]]), + [enable_oc2g="yes"],[enable_oc2g="no"]) +AC_ARG_WITH([oc2g], [AS_HELP_STRING([--with-oc2g=INCLUDE_DIR], [Location of the OC-2G API header files])], + [oc2g_incdir="$withval"],[oc2g_incdir="$incdir"]) +AC_SUBST([OC2G_INCDIR], -I$oc2g_incdir) +AC_MSG_RESULT([$enable_oc2g]) +AM_CONDITIONAL(ENABLE_OC2GBTS, test "x$enable_oc2g" = "xyes") +if test "$enable_oc2g" = "yes"; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $OC2G_INCDIR -I$srcdir/include" + AC_CHECK_HEADER([nrw/oc2g/oc2g.h],[], + [AC_MSG_ERROR([nrw/oc2g/oc2g.h can not be found in $oc2g_incdir])], + [#include <nrw/oc2g/oc2g.h>]) + PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd) + PKG_CHECK_MODULES(LIBGPS, libgps) + CPPFLAGS=$oldCPPFLAGS +fi + +# https://www.freedesktop.org/software/systemd/man/daemon.html +AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) +AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + +AM_CONFIG_HEADER(btsconfig.h) + +AC_OUTPUT( + src/Makefile + src/common/Makefile + src/osmo-bts-virtual/Makefile + src/osmo-bts-omldummy/Makefile + src/osmo-bts-sysmo/Makefile + src/osmo-bts-litecell15/Makefile + src/osmo-bts-oc2g/Makefile + src/osmo-bts-trx/Makefile + src/osmo-bts-octphy/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/handover/Makefile + tests/tx_power/Makefile + tests/power/Makefile + tests/meas/Makefile + doc/Makefile + doc/examples/Makefile + contrib/Makefile + contrib/systemd/Makefile + Makefile) diff --git a/contrib/Makefile.am b/contrib/Makefile.am new file mode 100644 index 00000000..3439c97b --- /dev/null +++ b/contrib/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = systemd diff --git a/contrib/dtx_check.gawk b/contrib/dtx_check.gawk new file mode 100755 index 00000000..9a3ddcf6 --- /dev/null +++ b/contrib/dtx_check.gawk @@ -0,0 +1,89 @@ +#!/usr/bin/gawk -f + +# Expected input format: FN TYPE + +BEGIN { + DELTA = 0 + ERR = 0 + FORCE = 0 + FN = 0 + SILENCE = 0 + TYPE = "" + CHK = "" + U_MAX = 8 * 20 + 120 / 26 + U_MIN = 8 * 20 - 120 / 26 + F_MAX = 3 * 20 + 120 / 26 + F_MIN = 3 * 20 - 120 / 26 +} + +{ + if (NR > 2) { # we have data from previous record to compare to + DELTA = ($1 - FN) * 120 / 26 + CHK = "OK" + if ("FACCH" == $2 && "ONSET" == TYPE) { # ONSET due to FACCH is NOT a talkspurt + SILENCE = 1 + } + if (("UPDATE" == TYPE || "FIRST" == TYPE) && ("FACCH" == $2 || "SPEECH" == $2)) { # check for missing ONSET: + CHK = "FAIL: missing ONSET (" $2 ") after " TYPE "." + ERR++ + } + if ("SID_P1" == $2) { + CHK = "FAIL: regular AMR payload with FT SID and STI=0 (should be either pyaload Update or STI=1)." + ERR++ + } + if ("FORCED_FIRST" == $2 || "FORCED_NODATA" == $2 || "FORCED_F_P2" == $2 || "FORCED_F_INH" == $2 || "FORCED_U_INH" == $2) { + CHK = "FAIL: event " $2 " inserted by DSP." + FORCE++ + ERR++ + } + if ("FIRST_P2" != $2 && "FIRST_P1" == TYPE) { + CHK = "FAIL: " TYPE " followed by " $2 " instead of P2." + ERR++ + } + if ("FIRST" == $2 && "FIRST" == TYPE) { + CHK = "FAIL: multiple SID FIRST in a row." + ERR++ + } + if ("OK" == CHK && "ONSET" != $2) { # check inter-SID distances: + if ("UPDATE" == TYPE) { + if (DELTA > U_MAX) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too big " DELTA "ms > " U_MAX "ms." + ERR++ + } + if ("UPDATE" == $2 && DELTA < U_MIN) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too small " DELTA "ms < " U_MIN "ms." + ERR++ + } + } + if ("FIRST" == TYPE) { + if (DELTA > F_MAX) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID FIRST (@" FN ") too big " DELTA "ms > " F_MAX "ms." + ERR++ + } + if ("UPDATE" == $2 && DELTA < F_MIN) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too small " DELTA "ms < " F_MIN "ms." + ERR++ + } + } + } + if ("FACCH" == TYPE && "FIRST" != $2 && "FACCH" != $2 && 1 == SILENCE) { # check FACCH handling + CHK = "FAIL: incorrect silence resume with " $2 " after FACCH." + ERR++ + } + } + if ("SPEECH" == $2 || "ONSET" == $2) { # talkspurt + SILENCE = 0 + } + if ("UPDATE" == $2 || "FIRST" == $2) { # silence + SILENCE = 1 + } + print $1, $2, CHK + if ($2 != "EMPTY") { # skip over EMPTY records + TYPE = $2 + FN = $1 + } +} + +END { + print "Check completed: found " ERR " errors (" FORCE " events inserted by DSP) in " NR " records." +} diff --git a/contrib/dump_docs.py b/contrib/dump_docs.py new file mode 100755 index 00000000..59f2a61d --- /dev/null +++ b/contrib/dump_docs.py @@ -0,0 +1,40 @@ +#!/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/sysmo/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() + diff --git a/contrib/eeprom_reader.c b/contrib/eeprom_reader.c new file mode 100644 index 00000000..d0d41363 --- /dev/null +++ b/contrib/eeprom_reader.c @@ -0,0 +1,91 @@ +/* GPLv3+ to read sysmobts-v2 revD or later EEPROM from userspace */ + + +#include <stdio.h> +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <sys/ioctl.h> + +#include <errno.h> +#include <stdint.h> +#include <unistd.h> +#include <string.h> + + +/* Can read a 16bit at24 eeprom with 8192 byte in storage (24c64) */ +static int dump_eeprom(int fd, int out) +{ +#define STEP 8192 +#define SIZE 8192 + uint8_t buf[STEP + 2]; + int rc = 0; + int i; + + for (i = 0; i < SIZE; i += STEP) { + /* write the address */ + buf[0] = i >> 8; + buf[1] = i; + rc = write(fd, buf, 2); + if (rc != 2) { + fprintf(stderr, "writing address failed: %d/%d/%s\n", rc, errno, strerror(errno)); + return 1; + } + + /* execute step amount of reads */ + rc = read(fd, buf, STEP); + if (rc != STEP) { + fprintf(stderr, "Failed to read: %d/%d/%s\n", rc, errno, strerror(errno)); + return 1; + } + + write(out, buf, STEP); + } + return 0; +} + +int main(int argc, char **argv) +{ + int i2c_fd, out_fd; + char *filename = "/dev/i2c-1"; + char *out_file = "eeprom.out"; + int addr = 0x50; + int rc; + + i2c_fd = open(filename, O_RDWR); + if (i2c_fd < 0) { + fprintf(stderr, "Failed to open i2c device %d/%d/%s\n", + i2c_fd, errno, strerror(errno)); + return EXIT_FAILURE; + } + + /* force using that address it is already bound with a driver */ + rc = ioctl(i2c_fd, I2C_SLAVE_FORCE, addr); + if (rc < 0) { + fprintf(stderr, "Failed to claim i2c device %d/%d/%s\n", + rc, errno, strerror(errno)); + return EXIT_FAILURE; + } + + if (argc >= 2) + out_file = argv[1]; + out_fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (out_fd < 0) { + fprintf(stderr, "Failed to open out device %s %d/%d/%s\n", + out_file, rc, errno, strerror(errno)); + return EXIT_FAILURE; + } + + if (dump_eeprom(i2c_fd, out_fd) != 0) { + unlink(out_file); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/contrib/jenkins_bts_model.sh b/contrib/jenkins_bts_model.sh new file mode 100755 index 00000000..9aa943fa --- /dev/null +++ b/contrib/jenkins_bts_model.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# this is a dispatcher script which will call the bts-model-specific +# script based on the bts model specified as command line argument + +bts_model="$1" + +if [ "x$bts_model" = "x" ]; then + echo "Error: You have to specify the BTS model as first argument, e.g. $0 sysmo" + exit 2 +fi + +if [ ! -d "./contrib" ]; then + echo "Run ./contrib/jenkins_bts_model.sh from the root of the osmo-bts tree" + exit 1 +fi + +set -x -e + +case "$bts_model" in + + sysmo) + ./contrib/jenkins_sysmobts.sh + ;; + + oct) + ./contrib/jenkins_oct.sh + ;; + + lc15) + ./contrib/jenkins_lc15.sh + ;; + + oc2g) + ./contrib/jenkins_oc2g.sh + ;; + + trx) + ./contrib/jenkins_bts_trx.sh + ;; + + oct+trx) + ./contrib/jenkins_oct_and_bts_trx.sh + ;; + + *) + set +x + echo "Unknown BTS model '$bts_model'" + ;; +esac diff --git a/contrib/jenkins_bts_trx.sh b/contrib/jenkins_bts_trx.sh new file mode 100755 index 00000000..54efa56f --- /dev/null +++ b/contrib/jenkins_bts_trx.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-trx + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +osmo-build-dep.sh libosmo-abis + +cd "$deps" + +configure_flags="\ + --enable-sanitize \ + --enable-werror \ + --enable-trx \ + " + +build_bts "osmo-bts-trx" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_common.sh b/contrib/jenkins_common.sh new file mode 100644 index 00000000..bdb12d55 --- /dev/null +++ b/contrib/jenkins_common.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# this is a common helper script that is shared among all BTS model +# specific helper scripts like jenkins_sysmobts.sh. You shouldn't call +# this directly, but rather indirectly via the bts-specific scripts + +if ! [ -x "$(command -v osmo-deps.sh)" ]; then + echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !" + exit 2 +fi + +set -ex + +base="$PWD" +deps="$base/deps" +inst="$deps/install" + +export deps inst + +osmo-clean-workspace.sh + +mkdir -p "$deps" + +verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]") + +# generic project build function, usage: +# build "PROJECT-NAME" "CONFIGURE OPTIONS" +build_bts() { + set +x + echo + echo + echo + echo " =============================== $1 ===============================" + echo + set -x + + cd $deps + osmo-deps.sh libosmocore + cd $base + shift + conf_flags="$*" + autoreconf --install --force + ./configure $conf_flags + $MAKE $PARALLEL_MAKE + $MAKE check || cat-testlogs.sh + DISTCHECK_CONFIGURE_FLAGS="$conf_flags" $MAKE distcheck || cat-testlogs.sh +} diff --git a/contrib/jenkins_lc15.sh b/contrib/jenkins_lc15.sh new file mode 100755 index 00000000..c7d62c96 --- /dev/null +++ b/contrib/jenkins_lc15.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-lc15 + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh lc15 "$FIRMWARE_VERSION" + +configure_flags="\ + --enable-sanitize \ + --with-litecell15=$deps/layer1-headers/inc/ \ + --enable-litecell15 \ + " + +build_bts "osmo-bts-lc15" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_oc2g.sh b/contrib/jenkins_oc2g.sh new file mode 100755 index 00000000..b8badce6 --- /dev/null +++ b/contrib/jenkins_oc2g.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-oc2g + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh oc2g "$FIRMWARE_VERSION" + +configure_flags="\ + --enable-sanitize \ + --with-oc2g=$deps/layer1-headers/inc/ \ + --enable-oc2g \ + " + +build_bts "osmo-bts-oc2g" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_oct.sh b/contrib/jenkins_oct.sh new file mode 100755 index 00000000..bd57dd18 --- /dev/null +++ b/contrib/jenkins_oct.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-octphy + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh oct "$FIRMWARE_VERSION" + +configure_flags="\ + --enable-sanitize \ + --enable-werror \ + --with-octsdr-2g=$deps/layer1-headers/ \ + --enable-octphy \ + " + +build_bts "osmo-bts-octphy" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_oct_and_bts_trx.sh b/contrib/jenkins_oct_and_bts_trx.sh new file mode 100755 index 00000000..67f67aa4 --- /dev/null +++ b/contrib/jenkins_oct_and_bts_trx.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-octphy + osmo-bts-trx + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +osmo-build-dep.sh libosmo-abis + +cd "$deps" + +osmo-layer1-headers.sh oct "$FIRMWARE_VERSION" + +configure_flags="\ + --enable-werror \ + --with-octsdr-2g=$deps/layer1-headers/ \ + --enable-octphy \ + --enable-trx \ + " + +build_bts "osmo-bts-octphy+trx" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_sysmobts.sh b/contrib/jenkins_sysmobts.sh new file mode 100755 index 00000000..d0d05ae6 --- /dev/null +++ b/contrib/jenkins_sysmobts.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-sysmo + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh sysmo "$FIRMWARE_VERSION" +mkdir -p "$inst/include/sysmocom/femtobts" +ln -s $deps/layer1-headers/include/* "$inst/include/sysmocom/femtobts/" + +configure_flags="\ + --enable-sanitize \ + --enable-werror \ + --enable-sysmocom-bts \ + --with-sysmobts=$inst/include/ \ + " + +# This will not work for the femtobts +if [ $FIRMWARE_VERSION != "femtobts_v2.7" ]; then + configure_flags="$configure_flags --enable-sysmobts-calib" +fi + +build_bts "osmo-bts-sysmo" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/l1fwd.init b/contrib/l1fwd.init new file mode 100755 index 00000000..b228580e --- /dev/null +++ b/contrib/l1fwd.init @@ -0,0 +1,31 @@ +#!/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 diff --git a/contrib/respawn-only.sh b/contrib/respawn-only.sh new file mode 100755 index 00000000..478abd66 --- /dev/null +++ b/contrib/respawn-only.sh @@ -0,0 +1,13 @@ +#!/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 diff --git a/contrib/respawn.sh b/contrib/respawn.sh new file mode 100755 index 00000000..196edadc --- /dev/null +++ b/contrib/respawn.sh @@ -0,0 +1,18 @@ +#!/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 diff --git a/contrib/screenrc-l1fwd b/contrib/screenrc-l1fwd new file mode 100644 index 00000000..4256a381 --- /dev/null +++ b/contrib/screenrc-l1fwd @@ -0,0 +1,3 @@ +chdir /tmp +screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/l1fwd-proxy +detach diff --git a/contrib/screenrc-sysmobts b/contrib/screenrc-sysmobts new file mode 100644 index 00000000..9c810d9f --- /dev/null +++ b/contrib/screenrc-sysmobts @@ -0,0 +1,5 @@ +chdir /tmp +screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/osmo-bts-sysmo -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 diff --git a/contrib/si_check.gawk b/contrib/si_check.gawk new file mode 100755 index 00000000..0a54ed46 --- /dev/null +++ b/contrib/si_check.gawk @@ -0,0 +1,91 @@ +#!/usr/bin/gawk -f + +# Usage example: +# tshark -2 -t r -E 'header=n' -E 'separator=,' -E 'quote=n' -T fields -e gsmtap.frame_nr -e gsmtap.ts -e gsmtap.arfcn -e _ws.col.Info -Y 'gsmtap' -r test.pcapng.gz | grep Information | env ARFCN=878 ./si_check.gawk +# read summary on number of bis/ter messages and adjust BT_BOTH and BT_NONE environment variables accordingly + +BEGIN { + FS = "," + FAILED = 0 + IGNORE = 0 + BIS = 0 + TER = 0 + QUA = 0 + BT_BOTH = ENVIRON["BOTH"] + BT_NONE = ENVIRON["NONE"] + TC_INDEX = 0 + TC4[4] = 0 +} + +{ # expected .csv input as follows: gsmtap.frame_nr,gsmtap.ts,gsmtap.arfcn,_ws.col.Info + if ("ARFCN" in ENVIRON) { # ARFCN filtering is enabled + if (ENVIRON["ARFCN"] != $3) { # ignore other ARFCNs + IGNORE++ + next + } + } + type = get_si_type($4) + tc = get_tc($1) + result = "FAIL" + + if (1 == check_si_tc(tc, type)) { result = "OK" } + else { FAILED++ } + + if (4 == tc) { + TC4[TC_INDEX] = type + TC_INDEX = int((TC_INDEX + 1) % 4) + if (0 == check_tc4c(type) && "OK" == result) { + result = "FAIL" + FAILED++ + } + } + if (type == "2bis") { BIS++ } + if (type == "2ter") { TER++ } + if (type == "2quater") { QUA++ } + # for (i in TC4) print TC4[i] # debugging + printf "ARFCN=%d FN=%d TS=%d TC=%d TYPE=%s %s\n", $3, $1, $2, tc, type, result +} + +END { + printf "check completed: total %d, failed %d, ignored %d, ok %d\nSI2bis = %d, SI2ter = %d, SI2quater = %d\n", NR, FAILED, IGNORE, NR - FAILED - IGNORE, BIS, TER, QUA + if ((BIS > 0 || TER > 0) && BT_NONE) { printf "please re-run with correct environment: unset 'NONE' variable\n" } + if ((BIS > 0 && TER > 0) && !BT_BOTH) { printf "please re-run with correct environment: set 'BOTH' variable\n" } +} + +func get_si_type(s, x) { # we rely on format of Info column in wireshark output - if it's changed we're screwed + return x[split(s, x, " ")] +} + +func get_tc(f) { # N. B: all numbers in awk are float + return int(int(f / 51) % 8) +} + +func check_tc4c(si, count) { # check for "once in 4 consecutive occurrences" rule + count = 0 + if ("2quater" != si || "2ter" != si) { return 1 } # rules is not applicable to other types + if (BT_NONE && "2quater" == si) { return 0 } # should be on TC=5 instead + if (!BT_BOTH && "2ter" == si) { return 0 } # should be on TC=5 instead + if (0 in TC4 && 1 in TC4 && 2 in TC4 && 3 in TC4) { # only check if we have 4 consecutive occurrences already + if (si == TC4[0]) { count++ } + if (si == TC4[1]) { count++ } + if (si == TC4[2]) { count++ } + if (si == TC4[3]) { count++ } + if (0 == count) { return 0 } + } + return 1 +} + +func check_si_tc(tc, si) { # check that SI scheduling on BCCH Norm is matching rules from 3GPP TS 05.02 § 6.3.1.3 + switch (si) { + case "1": return (0 == tc) ? 1 : 0 + case "2": return (1 == tc) ? 1 : 0 + case "2bis": return (5 == tc) ? 1 : 0 + case "13": return (4 == tc) ? 1 : 0 + case "9": return (4 == tc) ? 1 : 0 + case "2ter": if (BT_BOTH) { return (4 == tc) ? 1 : 0 } else { return (5 == tc) ? 1 : 0 } + case "2quater": if (BT_NONE) { return (5 == tc) ? 1 : 0 } else { return (4 == tc) ? 1 : 0 } + case "3": return (2 == tc || 6 == tc) ? 1 : 0 + case "4": return (3 == tc || 7 == tc) ? 1 : 0 + } + return 0 +} diff --git a/contrib/superfemto.sh b/contrib/superfemto.sh new file mode 100755 index 00000000..19dc4107 --- /dev/null +++ b/contrib/superfemto.sh @@ -0,0 +1,110 @@ +#!/bin/sh + +# Split common DSP call log file (produced by superfemto-compatible firmware) into 4 separate call leg files (MO/MT & DL/UL) with events in format "FN EVENT_TYPE": +# MO Mobile Originated +# MT Mobile Terminated +# DL DownLink (BTS -> L1) +# UL UpLink (L1 -> BTS) + +if [ -z $1 ]; then + echo "expecting DSP log file name as parameter" + exit 1 +fi + +# MO handle appear 1st in the logs +MO=$(grep 'h=' $1 | head -n 1 | cut -f2 -d',' | cut -f2 -d= | cut -f1 -d']') + +# direction markers: +DLST="_CodeBurst" +ULST="_DecodeAndIdentify" + +# DL sed filters: +D_EMP='s/ Empty frame request!/EMPTY/i' +D_FAC='s/ Coding a FACCH\/. frame !!/FACCH/i' +D_FST='s/ Coding a RTP SID First frame !!/FIRST/i' +D_FS1='s/ Coding a SID First P1 frame !!/FIRST_P1/i' +D_FS2='s/ Coding a SID First P2 frame !!/FIRST_P2/i' +D_RP1='s/ Coding a RTP SID P1 frame !!/SID_P1/i' +D_UPD='s/ Coding a RTP SID Update frame !!/UPDATE/i' +D_SPE='s/ Coding a RTP Speech frame !!/SPEECH/i' +D_ONS='s/ Coding a Onset frame !!/ONSET/i' +D_FO1='s/ A speech frame is following a NoData or SID First without an Onset./FORCED_FIRST/i' +D_FO2='s/ A speech frame is following a NoData without an Onset./FORCED_NODATA/i' +D_FP2='s/ A speech frame is following a NoData or SID_FIRST_P2 without an Onset./FORCED_F_P2/i' +D_FIN='s/ A speech frame is following a SID_FIRST without inhibit. A SID_FIRST_INH will be inserted./FORCED_F_INH/i' +D_UIN='s/ A speech frame is following a SID_UPDATE without inhibit. A SID_UPDATE_INH will be inserted./FORCED_U_INH/i' + +# UL sed filters: +U_NOD='s/ It is a No Data frame !!/NODATA/i' +U_ONS='s/ It is an ONSET frame !!/ONSET/i' +U_UPD='s/ It is a SID UPDATE frame !!/UPDATE/i' +U_FST='s/ It is a SID FIRST frame !!/FIRST/i' +U_FP1='s/ It is a SID-First P1 frame !!/FIRST_P1/i' +U_FP2='s/ It is a SID-First P2 frame !!/FIRST_P2/i' +U_SPE='s/ It is a SPEECH frame *!!/SPEECH/i' +U_UIN='s/ It is a SID update InH frame !!/UPD_INH/i' +U_FIN='s/ It is a SID-First InH frame !!/FST_INH/i' +U_RAT='s/ It is a RATSCCH data frame !!/RATSCCH/i' + +DL () { # filter downlink-related entries + grep $DLST $1 > $1.DL.tmp +} + +UL () { # uplink does not require special fix + grep $ULST $1 > $1.UL.tmp.fix +} + +DL $1 +UL $1 + +FIX() { # add MO/MT marker from preceding line to inserted ONSETs so filtering works as expected + cat $1.DL.tmp | awk 'BEGIN{ FS=" h="; H="" } { if (NF > 1) { H = $2; print $1 "h=" $2 } else { print $1 ", h=" H } }' > $1.DL.tmp.fix +} + +FIX $1 + +MO() { # filter MO call DL or UL logs + grep "h=$MO" $1.tmp.fix > $1.MO.raw +} + +MT() { # filter MT call DL or UL logs + grep -v "h=$MO" $1.tmp.fix > $1.MT.raw +} + +MO $1.DL +MT $1.DL +MO $1.UL +MT $1.UL + +PREP() { # prepare logs for reformatting + cat $1.raw | cut -f2 -d')' | cut -f1 -d',' | cut -f2 -d'>' | sed 's/\[u32Fn/fn/' | sed 's/\[ u32Fn/fn/' | sed 's/fn = /fn=/' | sed 's/fn=//' | sed 's/\[Fn=//' | sed 's/ An Onset will be inserted.//' > $1.tmp1 +} + +PREP "$1.DL.MT" +PREP "$1.DL.MO" +PREP "$1.UL.MT" +PREP "$1.UL.MO" + +RD() { # reformat DL logs for consistency checks + cat $1.tmp1 | sed "$D_FST" | sed "$D_SPE" | sed "$D_FS1" | sed "$D_FS2" | sed "$D_UIN" | sed "$D_FIN" | sed "$D_UPD" | sed "$D_INH" | sed "$D_RP1" | sed "$D_ONS" | sed "$D_EMP" | sed "$D_FAC" | sed "$D_FO1" | sed "$D_FO2" | sed "$D_FP2" > $1.tmp2 +} + +RU() { # reformat UL logs for consistency checks + cat $1.tmp1 | sed "$U_FST" | sed "$U_SPE" | sed "$U_FP1" | sed "$U_FP2" | sed "$U_UPD" | sed "$U_ONS" | sed "$U_NOD" | sed "$U_UIN" | sed "$U_FIN" | sed "$U_RAT" > $1.tmp2 +} + +RD "$1.DL.MT" +RD "$1.DL.MO" +RU "$1.UL.MT" +RU "$1.UL.MO" + +SW() { # swap fields + cat $1.tmp2 | awk '{ print $2, $1 }' > $1 +} + +SW "$1.DL.MT" +SW "$1.DL.MO" +SW "$1.UL.MT" +SW "$1.UL.MO" + +rm $1.*.tmp* diff --git a/contrib/sysmobts.init b/contrib/sysmobts.init new file mode 100755 index 00000000..2b9d2814 --- /dev/null +++ b/contrib/sysmobts.init @@ -0,0 +1,29 @@ +#!/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 diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am new file mode 100644 index 00000000..16463087 --- /dev/null +++ b/contrib/systemd/Makefile.am @@ -0,0 +1,18 @@ +if HAVE_SYSTEMD +SYSTEMD_SERVICES = osmo-bts-virtual.service + +if ENABLE_SYSMOBTS +SYSTEMD_SERVICES += osmo-bts-sysmo.service sysmobts-mgr.service +endif + +if ENABLE_TRX +SYSTEMD_SERVICES += osmo-bts-trx.service +endif + +if ENABLE_LC15BTS +SYSTEMD_SERVICES += osmo-bts-lc15.service lc15bts-mgr.service +endif + +EXTRA_DIST = $(SYSTEMD_SERVICES) +systemdsystemunit_DATA = $(SYSTEMD_SERVICES) +endif # HAVE_SYSTEMD diff --git a/contrib/systemd/lc15bts-mgr.service b/contrib/systemd/lc15bts-mgr.service new file mode 100644 index 00000000..bf788e61 --- /dev/null +++ b/contrib/systemd/lc15bts-mgr.service @@ -0,0 +1,29 @@ +[Unit] +Description=osmo-bts manager for LC15 / sysmoBTS 2100 +After=lc15-sysdev-remap.service +Wants=lc15-sysdev-remap.service + +[Service] +Type=simple +NotifyAccess=all +WatchdogSec=21780s +Restart=always +RestartSec=2 + +# Make sure directories and symbolic link exist +ExecStartPre=/bin/sh -c 'test -d /mnt/storage/var/run/lc15bts-mgr || mkdir -p /mnt/storage/var/run/lc15bts-mgr ; test -d /var/run/lc15bts-mgr || ln -sf /mnt/storage/var/run/lc15bts-mgr/ /var/run' +# Make sure BTS operation hour exist +ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/lc15bts-mgr/hours-running || echo 0 > /mnt/storage/var/run/lc15bts-mgr/hours-running' +# Shutdown all PA correctly +ExecStartPre=/bin/sh -c 'echo disabled > /var/lc15/pa-state/pa0/state; echo disabled > /var/lc15/pa-state/pa1/state' +ExecStartPre=/bin/sh -c 'echo 0 > /var/lc15/pa-supply/max_microvolts; echo 0 > /var/lc15/pa-supply/min_microvolts' + +ExecStart=/usr/bin/lc15bts-mgr -s -c /etc/osmocom/lc15bts-mgr.cfg + +# Shutdown all PA correctly +ExecStopPost=/bin/sh -c 'echo disabled > /var/lc15/pa-state/pa0/state; echo disabled > /var/lc15/pa-state/pa1/state' +ExecStopPost=/bin/sh -c 'echo 0 > /var/lc15/pa-supply/max_microvolts; echo 0 > /var/lc15/pa-supply/min_microvolts' + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-mgr.service diff --git a/contrib/systemd/oc2gbts-mgr.service b/contrib/systemd/oc2gbts-mgr.service new file mode 100644 index 00000000..ed915b33 --- /dev/null +++ b/contrib/systemd/oc2gbts-mgr.service @@ -0,0 +1,29 @@ +[Unit] +Description=osmo-bts manager for OC-2G +After=oc2g-sysdev-remap.service +Wants=oc2g-sysdev-remap.service + +[Service] +Type=simple +NotifyAccess=all +WatchdogSec=21780s +Restart=always +RestartSec=2 + +# Make sure directories and symbolic link exist +ExecStartPre=/bin/sh -c 'test -d /mnt/storage/var/run/oc2gbts-mgr || mkdir -p /mnt/storage/var/run/oc2gbts-mgr ; test -d /var/run/oc2gbts-mgr || ln -sf /mnt/storage/var/run/oc2gbts-mgr/ /var/run' +# Make sure BTS operation hour exist +ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/oc2gbts-mgr/hours-running || echo 0 > /mnt/storage/var/run/oc2gbts-mgr/hours-running' +# Shutdown all PA correctly +ExecStartPre=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;' +#ExecStartPre=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts' + +ExecStart=/usr/bin/oc2gbts-mgr -s -c /etc/osmocom/oc2gbts-mgr.cfg + +# Shutdown all PA correctly +ExecStopPost=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;' +#ExecStopPost=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts' + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-mgr.service diff --git a/contrib/systemd/osmo-bts-lc15.service b/contrib/systemd/osmo-bts-lc15.service new file mode 100644 index 00000000..90e7fc29 --- /dev/null +++ b/contrib/systemd/osmo-bts-lc15.service @@ -0,0 +1,21 @@ +[Unit] +Description=osmo-bts for LC15 / sysmoBTS 2100 + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr1/brightness' +ExecStart=/usr/bin/osmo-bts-lc15 -t 2 -s -c /etc/osmocom/osmo-bts-lc15.cfg -M +ExecStopPost=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/usr1/brightness' +Restart=always +RestartSec=2 +RestartPreventExitStatus=1 + +# The msg queues must be read fast enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts.service diff --git a/contrib/systemd/osmo-bts-oc2g.service b/contrib/systemd/osmo-bts-oc2g.service new file mode 100644 index 00000000..2f2d8378 --- /dev/null +++ b/contrib/systemd/osmo-bts-oc2g.service @@ -0,0 +1,21 @@ +[Unit] +Description=osmo-bts for OC-2G + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr1/brightness' +ExecStart=/usr/bin/osmo-bts-oc2g -s -c /etc/osmocom/osmo-bts.cfg -M +ExecStopPost=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/usr1/brightness' +Restart=always +RestartSec=2 +RestartPreventExitStatus=1 + +# The msg queues must be read fast enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts.service diff --git a/contrib/systemd/osmo-bts-sysmo.service b/contrib/systemd/osmo-bts-sysmo.service new file mode 100644 index 00000000..92558172 --- /dev/null +++ b/contrib/systemd/osmo-bts-sysmo.service @@ -0,0 +1,21 @@ +[Unit] +Description=osmo-bts for sysmocom sysmoBTS + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' +ExecStart=/usr/bin/osmo-bts-sysmo -s -c /etc/osmocom/osmo-bts-sysmo.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 +Alias=sysmobts.service +Alias=osmo-bts.service diff --git a/contrib/systemd/osmo-bts-trx.service b/contrib/systemd/osmo-bts-trx.service new file mode 100644 index 00000000..97c2b070 --- /dev/null +++ b/contrib/systemd/osmo-bts-trx.service @@ -0,0 +1,15 @@ +[Unit] +Description=Osmocom osmo-bts for osmo-trx + +[Service] +Type=simple +ExecStart=/usr/bin/osmo-bts-trx -s -c /etc/osmocom/osmo-bts-trx.cfg +Restart=always +RestartSec=2 + +# Let it process messages quickly enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target diff --git a/contrib/systemd/osmo-bts-virtual.service b/contrib/systemd/osmo-bts-virtual.service new file mode 100644 index 00000000..16332669 --- /dev/null +++ b/contrib/systemd/osmo-bts-virtual.service @@ -0,0 +1,15 @@ +[Unit] +Description=Osmocom GSM BTS for virtual Um layer based on GSMTAP/UDP + +[Service] +Type=simple +ExecStart=/usr/bin/osmo-bts-virtual -s -c /etc/osmocom/osmo-bts-virtual.cfg +Restart=always +RestartSec=2 + +# Let it process messages quickly enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target diff --git a/contrib/systemd/sysmobts-mgr.service b/contrib/systemd/sysmobts-mgr.service new file mode 100644 index 00000000..4346991d --- /dev/null +++ b/contrib/systemd/sysmobts-mgr.service @@ -0,0 +1,12 @@ +[Unit] +Description=osmo-bts manager for sysmoBTS + +[Service] +Type=simple +ExecStart=/usr/bin/sysmobts-mgr -ns -c /etc/osmocom/sysmobts-mgr.cfg +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-mgr.service diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..09e2a831 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,755 @@ +osmo-bts (0.8.1) unstable; urgency=medium + + [ Neels Hofmeyr ] + * cosmetic: dyn TS: clarify rsl_tx_rf_rel_ack() with a switch + * dyn TS: fix TCH/F_TCH/H_PDCH: properly record release of PDCH TS + * dyn TS: rx_rf_chan_rel: properly mark PDCH rel when no PCU, clarify + * dyn TS: clear TCH state upon reconnecting as PDCH + * cosmetic: dyn TS: clarify chan_nr composition + * ignore RSL RF CHAN REL for inactive lchans + * fix RSL Chan Activ Nack messages + * ip.access dyn ts: properly NACK a PDCH ACT on a still active lchan + * add/improve various logging around dyn ts + * dyn TS: be less strict on chan_nr, to allow arbitrary pchan switches + + [ Stefan Sperling ] + * send a State Changed Event Report when rf is locked/unlocked + + [ Harald Welte ] + * rsl: log errors when parsing of encryption information fails + * rsl: Make channel activation fail if encryption algorithm not supported + * rsl: Properly NACK CHAN_ACKT / MODE_MODIFY + * rsl: If CHAN ACT or MODE MODIF fails, send respective NACK + * osmo-bts-trx: Enable A5/3 cipher support + + -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 15 May 2018 14:08:47 +0200 + +osmo-bts (0.8.0) unstable; urgency=medium + + [ Neels Hofmeyr ] + * vty: skip installing cmds now always installed by default + * jenkins_common.sh: fix build_bts distcheck for more than one conf_flag + * fix build: tests/sysmobts: add missing -I$(SYSMOBTS_INCDIR) + * fix handover: handle_ph_ra_ind(): evaluate ra_ind before msgb_trim() + * implement support for 3-digit MNC with leading zeros + * configure: add --enable-werror + * use osmo_init_logging2() with proper talloc ctx + + [ Pau Espin Pedrol ] + * lc15: Fix cfg indentation + * l1sap: Fix abort on big RTP packet received + * bts-trx: trx_ctrl_cmd: Simplify var assignment logic + * bts-trx: Avoid enqueueing consecutive duplicate messages to TRX + * Fix malformed Resource Indication packet + * debian/control: Remove uneeded dep libosmo-netif-dev + * jenkins.sh: Disable building doxygen for deps + * oml.c: Fix use of htons instead of ntohs + * bts-trx: trx_if.c: Log timedout+retransmitted CMD + * bts-trx: trx_if.c: trx_ctrl_read_cb: Move error handling to end of func + * bts-trx: trx_if.c: Improve parsing of received RSP messages from TRX + * bts-trx: Detect duplicated responses for retransmitted commands + * gsm_pchan2chan_nr: move warning to pragma message and track issue + * Remove unused variables + * bts-trx: scheduler_trx.c: Fix missing header + * l1sap.c: l1sap_tch_rts_ind: Remove unused variables + * octphy: octpkt.c: Remove unused static functions + * vty.c: Remove warning message + * virtual: l1_if.c: Remove unneeded warning message + * main.c: bts_main: fix typo in error message + * l1sap: Validate incoming RTP payload, drop bw-efficient AMR + * l1sap: Avoid assumption that l1sap is at head of msgb + * contrib: jenkins_bts_model: Fix bashism expr + * Include missing headers for osmo_init_logging2 + * common/sysinfo.c: Fix no return on on-void function + * gsm_data_shared.h: Remove unused enum gsm_paging_event + * scheduler_trx: Fix signed integer overflow in clock calculations + + [ Harald Welte ] + * trx: Better be safe than sorry before calling strlen + * trx: Avoid NULL+1 dereference in trx_ctrl_read_cb() + * trx: Don't call osmo_fr_check_sid with negative 'rc' + * trx: Don't assume phy_instance_by_num() returns non-NULL + * l1sap: fix wrong return value of is_fill_frame() + * measurement.c: Fix various typos in comments + * Comments on individual members of struct gsm_abis_mo + * scheduler: Harmonize log line format; Always print TS name + decoded FN + * scheduler_trx: L1P is for PH (data), L1M for MPH (control) + * l1sap: Fix log subsystem: Use DRTP for RTP related bits, L1C for MPH + * measurment.c: Introduce INFO category for DMEAS logging + * osmo-bts-octphy: Remove bogus warning about BS_AG_BLKS_RES + * rsl.c: Log RTP socket related errors as DRTP, not DRSL + * Put useful information in RTCP SDES. + * osmo-bts-trx: Fix reported frame number during PRIM_INFO_MEAS + * DTX: avoid illegal character contained in DTX FSM allocation which causes BTS crash + * gsm_lchan: remove unused member fields + * Add 'show (bts|trx|ts|lchan)' commands + * Print much more information during 'show lchan' + * vty: don't print "Bound IP / Port" if it isn't bound [yet] + * osmo-bts: Add talloc context introspection via VTY + * sysmo: Fix compiler warnings in eeprom.c + * sysmo+lc15: Add missign include for readv/writev + * trx: make l1if_fill_meas_res() static + * RSL: Properly reject RSL CHAN_NR IE for incompatible PCHAN + * RSL: Ensure we don't accept DCHAN messages for CCHAN + * osmo-bts-virtual: Shut down gracefully on socket creation failure + * osmo-bts-virtual: Generate PRIM_INFO_MEAS (with bogus values) + * Introduce + use LOG/DEBUGP with frame number prefixing/printing + * osmo-bts-virtual: Make use of LOGL1S() macro for context + * osmo-bts-virtual: Make sure PRIM_INFO_MEAS have non-zero frame number + * scheduler.c: Factor out find_sched_mframe_idx() function + * scheduler: add trx_sched_is_sacch_fn() function + * Revert "measurement: fix measurement computation" + * measurement.c: Hand Frame Number into measurement computation + * l1sap: Pass is_sub from L1 primitive into the Uplink Measurement + * osmo-bts-trx: Add missing frame number to l1if_process_meas_res() + * scheduler.c: Print message when burst substitution happens + * load_indication: Fix start of load indication timer + * RSL: Implement DELETE INDICATION on AGCH overflow + * RSL: Send ERROR REPORT on too short/truncated messages + wrong discriminator + * BTS: add rate_ctr about CCCH (paging, agch, pch) + * paging: Drop + Log paging requests for non-existant paging groups + * paging.c: Fix encoding of optional Mobile ID RR PAGING TYPE 1 / 2 + * rsl: Improve ERROR REPORTing + * paging: Fix encoding of PAGING TYPE 3 Rest Octets + * RSL IPA DLCX: Avoid null-pointer dereference + * RSL: Fix encoding of ConnectionID in IPA_DLCX_ACK + * RSL IPA DLCX: Avoid another null-pointer dereference + * measurement.c: Fix sdcch4_meas_rep_fn102 / sdcch8_meas_rep_fn102 + * counters: split rach:sent into rach:drop, rach:ho, rach:cs and rach:ps + * octphy: Remove code duplication for BER / RSSI conversion + * {sysmo,lc15}: Correctly report BER to L1SAP in INFO_MEAS_IND + * {sysmo,lc15}: Fix RACH reporting in combined CBCH case + * split scheduler_mframe.c from scheduler.c + * measurement: Compute RX{LEV,QUAL}-SUB for SDCCH and non-AMR TCH + * measurement.c: Don't silently copy "FULL" measurements to "SUB" + * scheduler: Add missing \n at end of LOG statement + * Move rach_busy counting above L1SAP + * RACH decoding: Use BER threshold for RACH ghost detection + * trx/scheduler: Use integer math for TOA (Timing of Arrival) + * measurement.c: higher-precision TA/TOA math + * L1SAP: Increase resolution of reported burst timing + * measurement: Keep average of high-accurate ToA value in lchan + * Add high-accuracy ToA value to Uplink Measurement Reports + * pcu_sock: Discard messages that are too short + * pcu_sock: Don't overflow the timeslot array + * pcu_sock: Log an error message and discard PCU primitives for BTS != 0 + * pcu_sock: LOG + drop DATA.req from PCU for non-PDCH timeslot + * pcu_sock: LOG + drop PCU DATA.req for inactive lchan + * sysinfo.c: SI1 is optional; Send SI2 at TC=0 if no SI1 exists + * sysmobts: Compatibility with older firmware versions + * cosmetic: Document some SI scheduling related function API + * sysinfo: Fix scheduling of downlink SACCH information + * gsm_data_shared: Remove unused definitions/members/functions + * cosmetic: Move agch_queue to sub-structure of gsm_bts_role_bts + * Get rid of 'struct gsm_bts_role_bts' + * virtual: Correctly set+report BTS variant in OML attributes + * Add 'osmo-bts-omldummy' to bring up only OML without RSL + * fix inverted logic bug in omldummy patch + * omldummy: Suppress RSL transmission errors + * debian: Split osmo-bts-virtual from osmo-bts-trx + * fox chan_nr_is_dchan() for RSL_CHAN_OSMO_PDCH + * rsl_tx_dyn_pdch_ack: Add missing FRAME_NR information element + * fix activation of osmocom-style dynamic PDCH as TCH/F or TCH/H + + [ Philipp Maier ] + * octphy: override firmware version check + * cosmetic: meas_test: fix section comment + * cosmetic: tests/Makefile.am: remove excess whitespace + * cosmetic: tests/power: remove unused var "ret" + * cosmetic: tests/agch: remove unused var "static_ilv" + * octphy: l1_oml: check returncode of trx_by_l1h() + * meas_test: fix header file references + * rsl: fix double-free in rsl_rx_mode_modif() + * fix nullpointer deref in rsl_tx_mode_modif_nack() + * rsl: do not allow MODE MODIFY request with unsupp. codec/rate + * gsm_data_shared: extend bts feature list with speech codecs + * octphy: ensure all BTS models set features + * vty: display bts features in vty command show bts + * bts: use feature list instead of speech codec table + * octphy: replace #warning with #pragma message + * ipac: fix log output + * rsl: remove unused variable + * l1_tch: remove dead code + * cosmetic: remove dead code + * cosmetic: remove unused variable + * cosmetic: remove unused variable in osmo-bts-omldummy/main.c + * octphy: integrate octasics latest header release + * osmo-bts-trx: perform error concealment for FR frames + + [ Max ] + * Remove leftover comments and checks + * Log filenames on L1 errors + * Add --enable-sanitize configure option + * Use existing function to obtain TSC + * Remove BSC-specific parts + * Print FN delta on L1 errors + * Move sysmobts-calib into osmo-bts-sysmo + * Allow specifying sysmocom headers explicitly + * fix build: tests/misc: missing libosmo-abis and -trau flags + * Enable optional static builds + * Remove unneeded LIBOSMOCORE_CFLAGS from tests + * sysmobts: use proper includes for sbts2050 test + * Move -I inside *INCDIR variable + * sysmobts: remove weird default header location + * sysmobts: move header check to appropriate place + * CI: drop unused OsmoPCU dependency + * Enable sanitize for CI tests + * Add helper to get BCC from BSIC + * osmo-bts-trx: init nbits to know value + * osmo-bts-trx: ignore frame offset error on startup + + [ Vadim Yanitskiy ] + * doc/examples: add CalypsoBTS configuration example + * common/pcu_sock.c: fix double field assignment + * scheduler_trx.c: remove ToA (Time of Arrival) hack + * common/l1sap.c: increase the BTS_CTR_RACH_DROP in RACH BER check + * common/l1sap.c: increment valid RACH counter after all checks + * common/l1sap.c: clean up noise / ghost RACH filtering + * common/l1sap.c: perform noise / ghost filtering for handover RACH + * common/l1sap.c: limit the minimal ToA for RACH bursts + * common/vty.c: remove unused variables + * common/main.c: track talloc NULL contexts by default + + [ Alexander Huemer ] + * cosmetic: Makefile.am whitespace + * various Makefile.am: add missing CFLAGS + * gitignore: add missing entries + + [ Stefan Sperling ] + * Cosmetic fixes for power ramping code. + * respond with NACK for non-hopping BTS with multiple ARFCN + * cosmetic: fix typos in src/common/oml.c + * return NACK codes instead of errno values from oml_tx_attr_resp() + + [ Alexander Couzens ] + * pcuif_proto: correct indention of gsm_pcu_if_data + * pcu_if: move definition PCU_SOCK_DEFAULT into pcuif_proto.h + * pcuif_proto: add version 8 features + + [ Keith ] + * osmo-bts-sysmo eeprom.c Restore ability to read/write EEPROM + + -- Pau Espin Pedrol <pespin@sysmocom.de> Thu, 03 May 2018 17:02:19 +0200 + +osmo-bts (0.7.0) unstable; urgency=medium + + [ Max ] + * Use value string check from osmo-ci + * Support sending SI13 to PCU + * Support removing SI13 from PCU + * trx: avoid deactivating lchan on LCHAN_REL_ACT_REACT + * Check readv() return value to prevent crash + * OML: print actual type of report sent to BSC + * Replace dead code + * vty: print version and description for each phy + * Remove build dependency on legacy OpenBSC + * Fix multiple SI2q reception + * jenkins: remove openbsc dependency + * sysmo: use clock calibration source wrapper + * sysmo: don't override clock source with defaults + * Fix race condition in attribute reporting + * Move power loop to generic tests + * Make power test more verbose + + [ Neels Hofmeyr ] + * vty: mgr: sysmobts, lc15: install default commands for ACT_NORM_NODE + * osmo-bts-trx: vty: various fixes of 'write file' and doc + * jenkins: use osmo-clean-workspace.sh before and after build + + [ Pau Espin Pedrol ] + * l1sap: Improve log msg when frame diff >1 + * vty: Print string for Administrative state + + [ Harald Welte ] + * Fix Downlink AMR FSM name to avoid illegal space character + * update dependencies to latest libosmo-* + * configure.ac: Fix Mailing list address + + -- Harald Welte <laforge@gnumonks.org> Sat, 28 Oct 2017 20:53:21 +0200 + +osmo-bts (0.6.0) unstable; urgency=medium + + [ Holger Hans Peter Freyther ] + * Initial release. + * misc: Ignore files generated by a debian packaging build + * jenkins: Add the build script from jenkins here + * jenkins: Add the build script from jenkins here + * sysmobts: Add the barebox boot state reservation + * sysmobts: Fix eeprom padding before gpg key + * ci/spatch: Remove the "static" analysis handling + * oct: Attempt to enable the Octphy for the osmo-bts-oct build + * debian: Use the header files installed by openbsc-dev + * build: Do not require more headers from OpenBSC + * sysmobts: Make reservation for mode/netmask/ip and suc + * sysmobts: Store a simple network config in the EEPROM as well + + [ Max ] + * Ensure TRX invariant + * Use libosmocore function for uplink measurements + * Fix debug output + * Fix RTP timestamps in case of DTX + * Add DTXd support for sysmoBTS and LC15 + * Use libosmocodec for AMR RTP + * octphy: Use the app. info. defaults as base + * Fix debug output + * DTXd: store/repeat last SID + * DTXd: store/repeat last SID + * DTXu: mark beginning of speech burst in RTP + * Fix OML activation + * TRX: Add vty command to power on/off transceiver + * TRX: add configuration example + * Add .gitreview + * DTX: add support for AMR/HR + * Move copy-pasted code into common part + * Use libosmocodec functions for AMR + * Use error values instead of number for RSL error + * Clarify logging message + * Make get_lchan_by_chan_nr globally available + * DTXu: move copy-pasted code to common part + * Remove duplicated nibble shift code + * TRX: add Uplink DTX support for FR/HR + * Mark array as static const + * sysmobts: dump PRACH and PTCCH parameters + * Activate PTCCH UL + * Fix dsp tracing at phy config + * octphy: fix build + * Fill measurements data for L1SAP + * sysmo: ts_connect: log channel combination name instead of number + * DTX: fix last SID saving + * DTX: fix SID repeat scheduling + * DTX: fix SID logic + * lc15, sysmo: Use SID_FIRST_P1 to initiate DTX + * DTX: check Marker bit to send ONSET to L1 + * DTX: remove misleading comment + * LC15: Clarify msgb ownership / fix memory leaks + * DTX: move scheduling check inside repeat_last_sid + * DTX: further AMR SID cache fixes (lc15, sysmo) + * DTX: move ONSET detection into separate function + * DTX: send AMR voice alongside with ONSET + * DTX: fix conversion from fn to ms + * Move copy-pasted array into shared header + * DTX DL: use FSM for AMR + * TRX: fix building with latest DTX changes + * DTX: fix array size calculation + * DTX AMR - fix buffer length check + * Replace magic number with define + * Fix lc15 build + * Extend RTP RX callback parameters + * DTX HR - fix array size calculation + * Fix DTX DL AMR SIDscheduling logic + * Add tools to check DTX operation + * DTX DL: split ONSET state handling + * Remove obsolete define + * DTX DL: add AMR HR support to scheduling check + * DTX fix ONSET handling + * dtx_check.gawk: Fix false-positives in DTX check + * Fix tests linking with libosmocodec + * DTX DL: tighten check for enabled operation + * DTX: wrap FSM signal dispatching + * Add libosmocodec for octphy build + * dtx_check.gawk: add check for repetitive SID FIRST + * Remove duplicated code + * Replace link_id constant with define + * DTX DL AMR: rewrite FSM recursion + * Remove duplicated code + * Fix AGCH/PCH proportional allocation + * TRX: prevent segfault upon phy init + * DTX: add explicit check if DTX enabled + * Save RTP metadata in Control Buffer + * osmo-bts-trx: fix lchan deactivation + * DTX: fix TS adjustment for ONSET + * Optionally use adaptive RTP jitter buffering + * Integrate Debian packaging changes + * DTX AMR HR: fix inhibition + * Add copyright for .deb packages + * Move code to libosmocore + * Log socket path on error + * Add Abis OML failure event reporting + * Alarm on various errors + * Remove obsolete define TLVP_PRES_LEN + * scheduler: log lchan on which prim error occured + * deb: use gsm_data_shared.* from openbsc-dev + * OML: internalize failure reporting + * Add ctrl command to send OML alert + * Fix typo in TCH/H interleaving table + * Use oml-alert CTRL command for temp report + * Remove code duplication + * Handle ctrl cmd allocation failures + * Check for suitable lchan type when detecting HO + * osmo-bts-trx: fix scheduling of broken frames + * Sync protocol with OsmoPCU + * vty: reduce code duplication + * Handle TXT indication from OsmoPCU + * Add MS TO to RSL measurements + * Signal to BSC when PCU disconnects + * Prepare for extended SI2quater support + * Set BTS variant while initializing BTS model + * Prepare for BTS attribute reporting via OML + * osmo-bts-trx: use libosmocoding + * Remove redundant test + * Implement basic Get Attribute responder + * Add version to phy_instance + * OML: fix Coverity-reported issues + * Re-add version to phy_instance + * Use systemd template specifiers + * Place *-mgr config examples according to BTS model + * lc15: add example systemd service file + * Extend Get Attribute responder + * Set and report BTS features + * Cleanup SI scheduling + * RSL: receive and send multiple SI2q messages + * RSL: check for abnormal SI2q values + * lc15bts-mgr: use extended config file example + * Move parameter file opening into separate function + * Move common steps into common jenkins helper + * lc15: add jenkins helper + * Use generic L1 headers helper + * Copy sysmobts.service to osmo-bts-sysmo + * OML: move BTS number check into separate function + * lc15: make jenkins helper executable + * lc15: fix jenkins build + * Add missing include for abis.h header file + * RSL: receive and send multiple SI2q messages + * Use release helper from libosmocore + * si2q: do not consider count update as error + * Cleanup example config files + * Fix .deb build + * Unify *.service files + * lc15: cleanup board parameters reading + * lc15-mgr: update parameter read/write + * lc15: fix BTS revision and hw options + * lc15: make default config usable + * lc15: port lc15bts-mgr changes + * lc15bts-mgr: separate service file + * lc15: port lc15bts-mgr dependency changes + * Simplify jenkins build scripts + * OML: use fom_hdr while handling attr. request + * osmo-bts-trx: fix 'osmotrx legacy-setbsic' + * osmo-bts-trx: remove global variables from loops + + [ Daniel Laszlo Sitzer ] + * octphy: Update outdated config param name in error message. + + [ Jason DSouza ] + * Close TRX session before opening new one + + [ Minh-Quang Nguyen ] + * l1sap.h: fix wrong L1SAP_FN2PTCCHBLOCK calculation according to TS 45.002 Table 6 + * common/abis.c: fix 100% CPU usage after disconnecting OML/RSL link (Bug #1703) + * LC15: Bring back DSP trace argument + * LC15: Hardware changes + * LC15: TRX nominal TX power can be used from EEPROM or from BTS configuration + * rsl: Fix dropping of LAPDm UA message. + * LC15: properly handle BS-AG-BLKS-RES as received from BSC + + [ Neels Hofmeyr ] + * sysmo: add L3 handle to l1prim messages + * pcu_sock: add pcu_connected() to query PCU availability + * tests/stubs.c: remove unused stubs + * fix typo in error message ('at lEast') + * oml, Set Chan Attr: treat unknown PCHAN types as error + * dyn PDCH: rsl rx dchan: also log ip.access message names + * doc: add ladder diagram on dynamic PDCH, add msc-README + * add missing DSUM entry to bts_log_info_cat + * fix compiler warning: printf format for sizeof() + * fix compiler warning: add missing case (PHY_LINK_CONNECTING) + * fix two compiler warnings: add two opaque struct declarations + * dyn PDCH: add bts_model_ts_connect() and _disconnect() stubs + * dyn PDCH: conf_lchans_for_pchan(): handle TCH/F_PDCH + * dyn PDCH: pcu_tx_info_ind(): handle TCH/F_PDCH in PDCH mode + * dyn PDCH: chan_nr_by_sapi(): handle TCH/F_PDCH according to ts->flags + * dyn PDCH: implement main dyn PDCH logic in common/ + * dyn PDCH: sysmo-bts/oml.c: add ts_connect_as(), absorbing ts_connect() guts + * dyn PDCH: sysmo: handle TCH/F_PDCH init like TCH/F + * dyn PDCH: complete for sysmo-bts: implement bts_model_ts_*() + * error log: two minor clarifications + * debug log: log lchan state transitions + * debug log: log TS pchan type on connect + * fix lc15 build: put src/common/libbts.a left of -losmogsm + * lc15: add L3 handle to l1prim messages + * dyn PDCH: lc15: chan_nr_by_sapi(): handle TCH/F_PDCH according to ts->flags + * dyn PDCH: lc15: add ts_connect_as(), absorbing ts_connect() guts + * dyn PDCH: lc15: handle TCH/F_PDCH init like TCH/F + * dyn PDCH: lc15: complete for litecell15-bts: implement bts_model_ts_*() + * dyn PDCH: safeguard: exit if nothing pending in dyn_pdch_ts_disconnected() + * vty: install orphaned trx nominal power command + * fix compiler warnings: include bts_model.h in phy_link.c + * fix compiler warning: remove useless 'static' storage class for struct decl + * fix compiler warning: remove unused variable 'i' in calib_verify() + * log: osmo-bts-trx: change access burst logs to DEBUG level + * log: osmo-bts-trx: change PDTCH block logs to DEBUG level + * osmo-bts-trx: init OML only once by sending AVSTATE_OK with OPSTATE_ENABLED + * doc: move dyn_pdch.msc to osmo-gsm-manuals.git + * error log: rsl.c: typo x2 + * info log: l1sap.c: add '0x' to hex output + * fix compiler warning: msg_utils.c: fn_chk() constify arg + * fix compiler warning: msg_utils.c: fn_chk() constify arg + * info log: l1sap.c: add '0x' to hex output + * error log: rsl.c: typo x2 + * dyn PDCH: code dup: use conf_lchans_as_pchan() + * prepare dyn TS: split/replace conf_lchans_for_pchan() + * code dup: join [rsl_]lchan_lookup() from libbsc and osmo-bts + * dyn TS: common TCH/F_TCH/H_PDCH implementation + * sysmo/oml.c: rename ts_connect() to ts_opstart() + * dyn TS: implement SysmoBTS specifics + * lc15/oml.c: rename ts_connect() to ts_opstart() + * dyn TS: implement litecell15 specifics + * comment typo: common/l1sap.c + * log typo: trx_sched_set_pchan() + * dyn TS: sysmo,lc15: chan_nr_by_sapi(): add missing assertion + * fix comment in common/l1sap.c, function name changed + * dyn TS, dyn PDCH: common/l1sap.c: properly notice PDCH + * dyn PDCH: trx l1_if.c: factor out trx_set_ts_as_pchan() from trx_set_ts() + * dyn PDCH: complete for trx: implement bts_model_ts_[dis]connect() + * dyn PDCH: trx l1_if.c: drop fixme, add comment + * dyn TS: complete for TRX + * dyn TS: measurement.c: replace fixme with comment + * sysmo,lc15: ts_connect_as(): log error also for pchan_as == TCH/F_PDCH + * sysmo: fix dyn TS: Revert "Activate PTCCH UL" [in sysmobts] + * log: l1sap: add 0x to hex output of chan_nr, 5 times + * dyn TS: measurement: use correct nr of subslots, rm code dup + * dyn TS: sysmo,lc15: ph_data_req: fix PDCH mode detection + * Fix ip.access style dyn PDCH, broken in 37af36e85eca546595081246aec010fa7f6fd0be + * common/rsl: move decision whether to chan act ack/nack to common function + * octphy: fix build: Revert "octphy: fix for multiple trx with more than 1 dsp" + * octphy: fix build: Revert "octphy: add support for multiple trx ids" + * octphy: fix build with OCTSDR-OPENBSC-02.07.00-B708: name changed + * dyn TS: if PCU is not connected, allow operation as TCH + * log: sysmo,lc15: tweak log about sapi_cmds queue + * log causing rx event for lchan_lookup errors + * heed VTY 'line vty'/'bind' command + * sysmobts_mgr, lc15bts_mgr: fix tall context for telnet vty + * build: be robust against install-sh files above the root dir + * configure: check for pkg-config presence + * jenkins.sh: use osmo-build-dep.sh, log test failures + * msgb ctx: use new msgb_talloc_ctx_init() in various main()s + * jenkins-oct.sh: fix build: typo in deps path + * fix 'osmo-bts-* --version' segfault + * osmo-bts-trx: remove obsolete include of netif/rtp.h + * add jenkins_bts_trx.sh + * add jenkins_oct_and_bts_trx.sh + * jenkins: add jenkins_bts_model.sh + * bursts test: test_pdtch: pre-init result mem + * fix: dyn ts: uplink measurement report + * fix missing ~ in bit logic for lchan->si.valid in rsl_rx_sacch_inf_mod() + * SACCH: fix sending of SI with an enum value > 7 + * SACCH SI: assert that SI enum vals fit in bit mask + * all models: fix vty write: bts_model_config_write_phy + * jenkins: add value_string termination check + * Revert "Add version to phy_instance" + * Revert "RSL: check for abnormal SI2q values" + * Revert "RSL: receive and send multiple SI2q messages" + + [ Harald Welte ] + * sysmobts: screnrc/systemd-service: Use osmo-bts-sysmo instead of sysmobts + * Add .mailmap for mapping mail addresses in shortlog + * vty: Ensure to not use negative (error) sapi value + * sysmobts: Add correct nominal transmit power for sysmoBTS 1020 + * sysmobts_eeprom.h: Fix/extend model number definitions + * Revert "sysmobts: Add correct nominal transmit power for sysmoBTS 1020" + * tx_power: Change PA calibration tables to use delta vales + * Add new unit-test for transmit power computation code + * sysmobts: fully support trx_power_params + * README: Add general project information and convert to markdown + * README: update some of the limitations + * sysmobts: Don't start with 0dBm TRX output power before ramping + * Remove unusued left-over gsm0503_conv.c + * scheduler_trx.c: Avoid code duplication for BER10k computation + * scheduler_trx: Avoid copy+pasting determining CMR from FN + * rx_tchh_fn(): Avoid copy+pasting formula to determine odd-ness of fn + * Consistently check for minimum attribute/TLV length in RSL and OML + * l1sap.c: Add spec reference to link timeout implementation + * osmo-bts-trx: Remove duplicate parsing of NM_ATT_CONN_FAIL_CRIT + * vty: Remove command for manual channel activation/deactivation + * l1_if: Add inline functions to check dsp/fgpa version at runtime + * sysmobts: Re-order the bit-endianness of every HR codec parameter + * OML Add osmocom-specific way to deactivate radio link timeout + * measurement: Remove dead code + * l1sap.c: Factor out function to limit message queue + * osmo-bts-sysmo/l1_if.c: PH-DATA.ind belongs to L1P, not L1C + * l1sap: if lchan is in loopback, don't accept incoming RTP + * TRX: Use timerfd and CLOCK_MONOTONIC for GSM frame timer (Closes: #2325) + * Add loopback support for PDTCH + * TRX: trx_if: Improve code description / comments + * trx_if: Improve error handling + * TRX: Rename trx_if_data() -> trx_if_send_burst() + * TRX: merge/simplify l1_if and trx_if code + * TRX: don't free l1h in trx_phy_inst_close() + * l1sap: Don't enqueue PTCCH blocks for loopback + * TRX: permit transmission of all-zero loopback frames + * jenkins helpers: some minimal documentation/comments + print errors + * VIRT-PHY: Initial check-in of a new virtual BTS + * VIRT-PHY: Fix handling of default values for vty configuration + * VIRT-PHY: Use IPv4 multicast groups for private / local scope + * VIRT-PHY: cause BTS to terminate in case of recv()/send() on udp socket returns 0 + * Ensure we don't send dummy UI frames on BCCH for TC=5 + * virt: Don't print NOTICE log message if ARFCN doesn't match + * VIRT-PHY: Report virtual RACH bursts with plausible burst type + * scheduler: Fix wrong log subsystem: L1C is L1 *control* not user data + * VIRT-PHY: Print NOTICE log message from unimplemented stubs + * TRX / VIRT-PHY: Make check for BCCH/CCCH more specific + * L1SAP: Print chan_nr and link_id always as hex + * VIRT-BTS: Support for GPRS + * L1SAP: Use RSL_CHAN_OSMO_PDCH across L1SAP + * GSMTAP: Don't log fill frames via GSMTAP + * TRX: Remove bogus extern global variable declarations + * l1sap/osmo-bts-sysmo: Improve logging + * TRX: Remove global variables, move SETBSIC/SETTSC handling into phy_link + * Fix build after recent gsm_bts_alloc() change + * Treat SIGTERM just like SIGINT in our programs + + [ Tom Tsou ] + * trx: Add EGPRS tables, sequences, and mappings + * trx: Add EGPRS coding and decoding procedures + * trx: Enable EGPRS handling through burst lengths + * trx: Fix coverity BER calculation NULL dereference + + [ Vadim Yanitskiy ] + * pcu_sock: use osmo_sock_unix_init() from libosmocore + * osmo-bts-trx/l1_if.c: use channel combination III for TCH/H + * scheduler_trx.c: strip unused variable + + [ Mike McTernan ] + * osmo-bts-trx: Fix PCS1900 operation + * osmo-bts-trx: log decoder bit errors as DEBUG, not NOTICE + + [ bhargava ] + * Change interface in osmo-bts for 11 bit RACH + * Update parameters in osmo-bts-sysmo for 11bit RACH + * 11bit RACH support for osmo-bts-litecell15 + * Initialize parameters in osmo-trx for 11bit RACH + + [ Philipp ] + * octphy: Fixing missing payload type in ph. chan. activation + * octphy: Fixing band selection for ARFCN 0 + * octphy: reintroducing multi-trx support + * octopy: fixing renamed constant + * octphy: prevent mismatch between dsp-firmware and octphy headers + * rsl: improving the log output + * octphy: multi-trx support: fix AC_CHECK order + * RSL: drop obsolete NULL check + * RSL: add assertions to check args of public API + * OML: fix possible segfault: add NULL check in oml_ipa_set_attr() + * CTRL: make the CTRL-Interface IP address configurable + * l1sap: Fix expired rach slot counting + * l1sap: fix missing 'else's causing wrong rach frame expiry counts + * octphy: set tx attenuation via VTY + * octphy: Improve OML ADM state handling + + [ Yves Godin ] + * DTX: fix 1st RTP packet drop + + [ Alexander Chemeris ] + * l1sap: Fix use-after-free in loopback mode. + * vty: Add commands to manually activate/deactivate a channel. + * trx: Add "maxdlynb" VTY command to control max TA for Normal Bursts. + * rsl: Output RTP stats before closing the socket. + * osmo-bts-trx: Fix MS power control loop. + * osmo-bts-trx: Remove an unused variable. Resolves a compiler warning. + * osmo-bts-trx: Increase a maximum allowed MS power reduction step from 2dB to 4dB. + * Fix static build of osmo-bts-trx and osmo-bts-virtual. + + [ Jean-Francois Dionne ] + * DTX: don't always perform AMR HR specific check + * DTX: fix SID-FIRST detection + * lc15,sysmobts l1_if: fix memleak in handle_mph_time_ind() + * sysmo,lc15: fix memory leak at each call placed + * DTX: fix "unexpected burst" error + * Fix AMR HR DTX FSM logic. + * Fix SACCH channel release indication not sent to BSC after location update. + * Fix RTP duration adjustment not done when speech resumes in DTX mode. + + [ Ruben Undheim ] + * Fix some spelling errors + + [ Holger Freyther ] + * Revert "deb: use gsm_data_shared.* from openbsc-dev" + + [ Philipp Maier ] + * octphy VTY: fix vty write output for octphy's phy section + * octphy: Fix VTY commands + * l1sap: fix rach reason (ra) parsing + * l1sap: fix PTCCH detection + * octphy: fix usage of wrong define constant + * octphy: add CBCH support + * l1sap: improve log output + * octphy: print log message for multi-trx support + * octphy: display hint in case of wrongly configured transceiver number + * octphy: add conditional compilation to support latest octasic header release + * octphy: set tx/rx antenne IDs via VTY + * bts: revert trx shutdown order + * octphy: activate CBCH after all physical channels are activated + * octphy: align frame number for new firmware versions + * octphy: ensure that 11 bit rach flag is not set + * measurement: fix measurement reporting period + * measurement: make lchan_meas_check_compute() available to l1sap.c + * measurement: Compute measurement results on measurement idication + * measurement: exclude idle channels from uplink measurement + * octphy: integrate channel measurement handling + * octphy: remove old event control code + * osmo-bts-sysmo: Include frame number in MEAS IND + * measurement: fix measurement computation + * octphy: fix segfault + * Revert "measurement: exclude idle channels from uplink measurement" + * sysmobts: normalize frame number in measurement indication + * measurement: Improve log output + * measurement: improve log output + * octphy: improve log output + * octphy: initalize l1msg and only when needed + * octphy: initalize nmsg only when needed + * octphy: remove log output + * Revert "sysmobts: normalize frame number in measurement indication" + * osmo-bts-trx: fix missing frame number in MEAS IND + * osmo-bts-litecell15: Fix missing frame number in MEAS IND + * Revert "osmo-bts-sysmo: Include frame number in MEAS IND" + * octphy: complete value strings (octphy_cid_vals) + * octphy: do not send empty frames to phy + * osmo-bts-sysmo: Include frame number in MEAS IND + * measurement: fix measurment report + * octphy: remap frame number in MEAS_IND + * octphy: implement support for dynamic timeslots + + [ Ivan Klyuchnikov ] + * osmo-trx-bts: Fix incorrect setting of RXGAIN and POWER parameters on second channel (TRX1) of osmo-trx + * osmo-trx-bts: Fix osmo-bts-trx crash on startup during reading phy instance parameters from config file + * osmo-trx-bts: Fix incorrect bts shutdown procedure in case of abis connection closure + * osmo-trx-bts: Fix incorrect bts shutdown procedure in case of clock loss from osmo-trx + + [ Ivan Kluchnikov ] + * oml: Fix incorrect usage of const variable abis_nm_att_tlvdef_ipa + + [ Pau Espin Pedrol ] + * phy_link: Fix typo in state being printed + * trx: Allow BTS and TRX to be on different IPs + * trx: Save osmotrx base-port vty properties + * sysmo/tch.c: Clean up use of empty buffer + * litecell15/tch.c: Clean up use of empty buffer + * Use L1P instead of L1C for TCH logging and allocation + * Fix annoying trailing whitespace + * sysmo, litecell15: Make sure all TCH events are triggered + * sysmo: Remove non longer valid -p option from help + * Allow passing low link quality buffers to upper layers + * l1sap.c: Avoid sending RTP frame with empty payload + * l1sap.c: fn_ms_adj: Add err logging and always return GSM_RTP_DURATION + * Move dump_gsmtime to libosmocore as osmo_dump_gsmtime + * Use osmo_dump_gsmtime to log fn across different layers + * lc15bts-mgr.cfg: Set default vswr to a value inside valid range + * litecell15: Register in vty limits for paX_pwr + * lc15: Tweak led colors used in service file + * lc-15, sysmo: l1_if: print name on PH-DATA.ind unknwon sapi + * lc15bts-mgr.service: Prepare dirs and sysctls for the process + * osmo-bts-trx: Enable osmotrx tx-attenuation oml by default + * osmo-bts-trx: Relax validation to allow TRX data bursts without padding + + [ Sebastian Stumpf ] + * VIRT-PHY: Added example configurations for openbsc and osmobts. + * VIRT-PHY: Fixed timeslot in gsmtap-msg on downlink which was always 0. + * VIRT-PHY: Added test option for fast hyperframe repeat. + + -- Max <msuraev@sysmocom.de> Fri, 25 Aug 2017 15:16:56 +0200 + +osmo-bts (0.5.0) unstable; urgency=medium + + * Initial release. + + -- Holger Hans Peter Freyther <holger@moiji-mobile.com> Fri, 01 Apr 2016 16:13:40 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..0377d9fd --- /dev/null +++ b/debian/control @@ -0,0 +1,50 @@ +Source: osmo-bts +Maintainer: Holger Hans Peter Freyther <holger@moiji-mobile.com> +Section: net +Priority: optional +Build-Depends: debhelper (>= 9), + pkg-config, + dh-autoreconf, + dh-systemd (>= 1.5), + autotools-dev, + pkg-config, + libosmocore-dev, + libosmo-abis-dev, + libgps-dev, + txt2man +Standards-Version: 3.9.8 +Vcs-Browser: http://git.osmocom.org/osmo-bts/ +Vcs-Git: git://git.osmocom.org/osmo-bts +Homepage: https://projects.osmocom.org/projects/osmobts + +Package: osmo-bts-trx +Architecture: any +Conflicts: osmo-bts +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: osmo-bts-trx GSM BTS with osmo-trx + osmo-bts-trx to be used with the osmo-trx application + +Package: osmo-bts-trx-dbg +Architecture: any +Section: debug +Priority: extra +Depends: osmo-bts-trx (= ${binary:Version}), ${misc:Depends} +Description: Debug symbols for the osmo-bts-trx + Make debugging possible + +Package: osmo-bts-virtual +Architecture: any +Conflicts: osmo-bts +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Virtual Osmocom GSM BTS (no RF hardware; GSMTAP/UDP) + This version of OsmoBTS doesn't use actual GSM PHY/Hardware/RF, but + utilizes GSMTAP-over-UDP frames for the Um interface. This is useful + in fully virtualized setups e.g. in combination with OsmocomBB virt_phy. + +Package: osmo-bts-virtual-dbg +Architecture: any +Section: debug +Priority: extra +Depends: osmo-bts-virtual (= ${binary:Version}), ${misc:Depends} +Description: Debug symbols for the osmo-bts-virtual + Make debugging possible diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..302d1d94 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,81 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: osmo-bts +Source: http://cgit.osmocom.org/osmo-bts/ + +Files: * +Copyright: 2008-2014 Harald Welte <laforge@gnumonks.org> + 2009,2011,2013 Andreas Eversberg <jolly@eversberg.eu> + 2010,2011 On-Waves + 2012-2015 Holger Hans Peter Freyther + 2014 sysmocom s.f.m.c. Gmbh + 2015 Alexander Chemeris <Alexander.Chemeris@fairwaves.co> +License: AGPL-3+ + +Files: src/osmo-bts-sysmo/eeprom.c + src/osmo-bts-sysmo/eeprom.h +Copyright: 2012 Nutaq +License: MIT +Comment: Yves Godin is the author + +Files: src/common/pcu_sock.c +Copyright: 2008-2010 Harald Welte <laforge@gnumonks.org> + 2009-2012 Andreas Eversberg <jolly@eversberg.eu> + 2012 Holger Hans Peter Freyther +License: GPL-2+ + +Files: debian/* +Copyright: 2015-2016 Ruben Undheim <ruben.undheim@gmail.com> +License: AGPL-3+ + + +License: AGPL-3+ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + +License: GPL-2+ + This package is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + + +License: MIT + 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. diff --git a/debian/osmo-bts-trx.install b/debian/osmo-bts-trx.install new file mode 100644 index 00000000..004a7ed0 --- /dev/null +++ b/debian/osmo-bts-trx.install @@ -0,0 +1,5 @@ +etc/osmocom/osmo-bts-trx.cfg +lib/systemd/system/osmo-bts-trx.service +usr/bin/osmo-bts-trx +usr/share/doc/osmo-bts/examples/osmo-bts-trx/osmo-bts-trx.cfg +usr/share/doc/osmo-bts/examples/osmo-bts-trx/osmo-bts-trx-calypso.cfg diff --git a/debian/osmo-bts-virtual.install b/debian/osmo-bts-virtual.install new file mode 100644 index 00000000..f4d988f4 --- /dev/null +++ b/debian/osmo-bts-virtual.install @@ -0,0 +1,6 @@ +etc/osmocom/osmo-bts-virtual.cfg +lib/systemd/system/osmo-bts-virtual.service +usr/bin/osmo-bts-virtual +usr/bin/osmo-bts-omldummy +usr/share/doc/osmo-bts/examples/osmo-bts-virtual/osmo-bts-virtual.cfg +usr/share/doc/osmo-bts/examples/osmo-bts-virtual/openbsc-virtual.cfg diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..27de11b8 --- /dev/null +++ b/debian/rules @@ -0,0 +1,28 @@ +#!/usr/bin/make -f + +DEBIAN := $(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2) +DEBVERS := $(shell echo '$(DEBIAN)' | cut -d- -f1) +VERSION := $(shell echo '$(DEBVERS)' | sed -e 's/[+-].*//' -e 's/~//g') + +#export DH_VERBOSE=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + + +%: + dh $@ --with=systemd --with autoreconf --fail-missing + +override_dh_strip: + dh_strip --package=osmo-bts-virtual --dbg-package=osmo-bts-virtual-dbg + dh_strip --package=osmo-bts-trx --dbg-package=osmo-bts-trx-dbg + +override_dh_auto_configure: + dh_auto_configure -- --enable-trx --with-systemdsystemunitdir=/lib/systemd/system + +override_dh_clean: + dh_clean + $(RM) tests/package.m4 + $(RM) tests/testsuite + +# Print test results in case of a failure +override_dh_auto_test: + dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false) diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..1d42b0aa --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + examples \ + $(NULL) diff --git a/doc/control_interface.txt b/doc/control_interface.txt new file mode 100644 index 00000000..5ad97172 --- /dev/null +++ b/doc/control_interface.txt @@ -0,0 +1,61 @@ +The 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: + +<pre> +bsc_control.py -d localhost -p 4238 -s trx.0.thermal-attenuation 3000 +Got message: SET_REPLY 1 trx.0.thermal-attenuation 3000 +</pre> + +<pre> +bsc_control.py -d localhost -p 4238 -g trx.0.thermal-attenuation +Got message: GET_REPLY 1 trx.0.thermal-attenuation 3000 +</pre> + + +h2. sysmobts specific + +h3. trx.0.clock-info + +obtain information on the current clock status: + +<pre> +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 +</pre> + +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: + +<pre> +bsc_control.py -d localhost -p 4238 -g trx.0.clock-correction +Got message: GET_REPLY 1 trx.0.clock-correction -100 +</pre> + +<pre> +bsc_control.py -d localhost -p 4238 -s trx.0.clock-correction -- -99 +Got message: SET_REPLY 1 trx.0.clock-correction success +</pre> diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am new file mode 100644 index 00000000..04f82798 --- /dev/null +++ b/doc/examples/Makefile.am @@ -0,0 +1,46 @@ +OSMOCONF_FILES = virtual/osmo-bts-virtual.cfg + +doc_virtualdir = $(docdir)/examples/osmo-bts-virtual +doc_virtual_DATA = \ + virtual/osmo-bts-virtual.cfg \ + virtual/openbsc-virtual.cfg +EXTRA_DIST = $(doc_virtual_DATA) + +if ENABLE_SYSMOBTS +doc_sysmodir = $(docdir)/examples/osmo-bts-sysmo +doc_sysmo_DATA = \ + sysmo/osmo-bts-sysmo.cfg \ + sysmo/sysmobts-mgr.cfg +EXTRA_DIST += $(doc_sysmo_DATA) +OSMOCONF_FILES += sysmo/osmo-bts-sysmo.cfg sysmo/sysmobts-mgr.cfg +endif + +if ENABLE_TRX +doc_trxdir = $(docdir)/examples/osmo-bts-trx +doc_trx_DATA = \ + trx/osmo-bts-trx.cfg \ + trx/osmo-bts-trx-calypso.cfg +EXTRA_DIST += $(doc_trx_DATA) +OSMOCONF_FILES += trx/osmo-bts-trx.cfg +endif + +if ENABLE_OCTPHY +doc_octphydir = $(docdir)/examples/osmo-bts-octphy +doc_octphy_DATA = \ + octphy/osmo-bts-trx2dsp1.cfg \ + octphy/osmo-bts-octphy.cfg +EXTRA_DIST += $(doc_octphy_DATA) +OSMOCONF_FILES += octphy/osmo-bts-octphy.cfg +endif + +if ENABLE_LC15BTS +doc_lc15dir = $(docdir)/examples/osmo-bts-lc15 +doc_lc15_DATA = \ + litecell15/osmo-bts-lc15.cfg \ + litecell15/lc15bts-mgr.cfg +EXTRA_DIST += $(doc_lc15_DATA) +OSMOCONF_FILES += litecell15/osmo-bts-lc15.cfg litecell15/lc15bts-mgr.cfg +endif + +osmoconfdir = $(sysconfdir)/osmocom +osmoconf_DATA = $(OSMOCONF_FILES) diff --git a/doc/examples/litecell15/lc15bts-mgr.cfg b/doc/examples/litecell15/lc15bts-mgr.cfg new file mode 100644 index 00000000..a92a3fd6 --- /dev/null +++ b/doc/examples/litecell15/lc15bts-mgr.cfg @@ -0,0 +1,43 @@ +! +! lc15bts-mgr (0.3.0.284-a7c2-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 0 + logging timestamp 0 + logging level temp info + logging level fw info + logging level find info + logging level calib 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 + logging level lctrl notice + logging level lgtp notice +! +line vty + no login +! +lc15bts-mgr + limits supply_volt + threshold warning min 17500 + threshold critical min 19000 + limits tx0_vswr + threshold warning max 1000 + limits tx1_vswr + threshold warning max 1000 + limits supply_pwr + threshold warning max 110 + threshold critical max 120 + limits pa0_pwr + threshold warning max 50 + threshold critical max 60 + limits pa1_pwr + threshold warning max 50 + threshold critical max 60 diff --git a/doc/examples/litecell15/osmo-bts-lc15.cfg b/doc/examples/litecell15/osmo-bts-lc15.cfg new file mode 100644 index 00000000..907d83a2 --- /dev/null +++ b/doc/examples/litecell15/osmo-bts-lc15.cfg @@ -0,0 +1,43 @@ +! +! OsmoBTS (0.0.1.100-0455-dirty) configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + 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 + logging level rtp notice + 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 +! +phy 0 + instance 0 + trx-calibration-path /mnt/rom/factory/calib +phy 1 + instance 0 + trx-calibration-path /mnt/rom/factory/calib +bts 0 + band 900 + ipa unit-id 1500 0 + oml remote-ip 192.168.234.185 + trx 0 + phy 0 instance 0 + trx 1 + phy 1 instance 0 diff --git a/doc/examples/oc2g/oc2gbts-mgr.cfg b/doc/examples/oc2g/oc2gbts-mgr.cfg new file mode 100644 index 00000000..8248f60d --- /dev/null +++ b/doc/examples/oc2g/oc2gbts-mgr.cfg @@ -0,0 +1,33 @@ +! +! oc2gbts-mgr (0.3.0.284-a7c2-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 0 + logging timestamp 0 + logging level temp info + logging level fw info + logging level find info + logging level calib 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 + logging level lctrl notice + logging level lgtp notice +! +line vty + no login +! +oc2gbts-mgr + limits supply_volt + threshold warning min 17500 + threshold critical min 19000 + limits supply_pwr + threshold warning max 110 + threshold critical max 120 diff --git a/doc/examples/oc2g/osmo-bts.cfg b/doc/examples/oc2g/osmo-bts.cfg new file mode 100644 index 00000000..f985f3bc --- /dev/null +++ b/doc/examples/oc2g/osmo-bts.cfg @@ -0,0 +1,38 @@ +! +! OsmoBTS (0.0.1.100-0455-dirty) configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + 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 + logging level rtp notice + 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 +! +phy 0 + instance 0 + trx-calibration-path /mnt/rom/factory/calib +bts 0 + band 900 + ipa unit-id 1500 0 + oml remote-ip 10.42.0.1 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/octphy/osmo-bts-octphy.cfg b/doc/examples/octphy/osmo-bts-octphy.cfg new file mode 100644 index 00000000..d6d03b5d --- /dev/null +++ b/doc/examples/octphy/osmo-bts-octphy.cfg @@ -0,0 +1,31 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + 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 info + logging level abis notice +! +line vty + no login +! +phy 0 + octphy hw-addr 00:0C:90:2e:80:1e + octphy net-device eth0.2342 + instance 0 +bts 0 + band 1800 + ipa unit-id 1234 0 + oml remote-ip 127.0.0.1 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/octphy/osmo-bts-trx2dsp1.cfg b/doc/examples/octphy/osmo-bts-trx2dsp1.cfg new file mode 100644 index 00000000..bf590f7d --- /dev/null +++ b/doc/examples/octphy/osmo-bts-trx2dsp1.cfg @@ -0,0 +1,34 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + 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 info + logging level abis notice +! +line vty + no login +! +phy 0 + octphy hw-addr 00:0c:de:ad:fa:ce + octphy net-device eth2 + instance 0 + instance 1 +bts 0 + band 1800 + ipa unit-id 1234 0 + oml remote-ip 127.0.0.1 + trx 0 + phy 0 instance 0 + trx 1 + phy 0 instance 1 diff --git a/doc/examples/sysmo/osmo-bts-sysmo.cfg b/doc/examples/sysmo/osmo-bts-sysmo.cfg new file mode 100644 index 00000000..6ff043d8 --- /dev/null +++ b/doc/examples/sysmo/osmo-bts-sysmo.cfg @@ -0,0 +1,29 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + 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 info + logging level abis notice +! +line vty + no login +! +phy 0 + instance 0 +bts 0 + band 1800 + ipa unit-id 666 0 + oml remote-ip 10.1.2.3 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/sysmo/sysmobts-mgr.cfg b/doc/examples/sysmo/sysmobts-mgr.cfg new file mode 100644 index 00000000..f891ada7 --- /dev/null +++ b/doc/examples/sysmo/sysmobts-mgr.cfg @@ -0,0 +1,23 @@ +! +! SysmoMgr (0.3.0.141-33e5) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging timestamp 0 + 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 diff --git a/doc/examples/trx/osmo-bts-trx-calypso.cfg b/doc/examples/trx/osmo-bts-trx-calypso.cfg new file mode 100644 index 00000000..6b52fd2a --- /dev/null +++ b/doc/examples/trx/osmo-bts-trx-calypso.cfg @@ -0,0 +1,38 @@ +! +! OsmoBTS configuration example for CalypsoBTS +! http://osmocom.org/projects/baseband/wiki/CalypsoBTS +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl notice + logging level oml notice + logging level rll notice + logging level rr notice + logging level meas error + logging level pag error + logging level l1c error + logging level l1p error + logging level dsp error + logging level abis error +! +line vty + no login +! +phy 0 + instance 0 + osmotrx rx-gain 1 + osmotrx ip local 127.0.0.1 + osmotrx ip remote 127.0.0.1 + osmotrx timing-advance-loop + osmotrx ms-power-loop -65 + osmotrx legacy-setbsic +bts 0 + oml remote-ip 127.0.0.1 + ipa unit-id 1801 0 + gsmtap-sapi pdtch + gsmtap-sapi ccch + band 900 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/trx/osmo-bts-trx.cfg b/doc/examples/trx/osmo-bts-trx.cfg new file mode 100644 index 00000000..83426979 --- /dev/null +++ b/doc/examples/trx/osmo-bts-trx.cfg @@ -0,0 +1,34 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl notice + logging level oml notice + logging level rll notice + logging level rr notice + logging level meas error + logging level pag error + logging level l1c error + logging level l1p error + logging level dsp error + logging level abis error +! +line vty + no login +! +phy 0 + instance 0 + osmotrx rx-gain 1 + osmotrx ip local 127.0.0.1 + osmotrx ip remote 127.0.0.1 +bts 0 + band 1800 + ipa unit-id 6969 0 + oml remote-ip 192.168.122.1 + gsmtap-sapi ccch + gsmtap-sapi pdtch + trx 0 + phy 0 instance 0 diff --git a/doc/examples/virtual/openbsc-virtual.cfg b/doc/examples/virtual/openbsc-virtual.cfg new file mode 100644 index 00000000..be79d589 --- /dev/null +++ b/doc/examples/virtual/openbsc-virtual.cfg @@ -0,0 +1,151 @@ +! +! OpenBSC (0.15.0.629-34f0-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 0 + logging print category 1 + logging timestamp 1 + logging level all info + logging level rll notice + logging level cc notice + logging level mm debug + logging level rr notice + logging level rsl notice + logging level nm info + logging level mncc notice + logging level pag notice + logging level meas notice + logging level sccp notice + logging level msc notice + logging level mgcp notice + logging level ho notice + logging level db notice + logging level ref notice + logging level gprs debug + logging level ns info + logging level bssgp debug + logging level llc debug + logging level sndcp debug + logging level nat notice + logging level ctrl notice + logging level smpp debug + logging level filter debug + logging level ranap debug + logging level sua debug + 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 + logging level lctrl notice + logging level lgtp notice + logging level lstats notice + logging level lgsup notice + logging level loap notice +! +stats interval 5 +! +line vty + no login +! +e1_input + e1_line 0 driver ipa + e1_line 0 port 0 + no e1_line 0 keepalive +network + network country code 262 + mobile network code 42 + short name OpenBSC + long name OpenBSC + auth policy accept-all + authorized-regexp .* + location updating reject cause 13 + encryption a5 0 + neci 1 + paging any use tch 0 + rrlp mode ms-based + mm info 1 + handover 0 + handover window rxlev averaging 10 + handover window rxqual averaging 1 + handover window rxlev neighbor averaging 10 + handover power budget interval 6 + handover power budget hysteresis 3 + handover maximum distance 9999 + timer t3101 10 + timer t3103 0 + timer t3105 0 + timer t3107 0 + timer t3109 4 + timer t3111 0 + timer t3113 60 + timer t3115 0 + timer t3117 0 + timer t3119 0 + timer t3122 10 + timer t3141 0 + subscriber-keep-in-ram 0 + bts 0 + type sysmobts + band DCS1800 + cell_identity 6969 + location_area_code 1 + base_station_id_code 63 + ms max power 0 + cell reselection hysteresis 4 + rxlev access min 0 + periodic location update 30 + radio-link-timeout 32 + channel allocator descending + rach tx integer 9 + rach max transmission 7 + channel-descrption attach 1 + channel-descrption bs-pa-mfrms 5 + channel-descrption bs-ag-blks-res 1 + ip.access unit_id 6969 0 + oml ip.access stream_id 255 line 0 + neighbor-list mode automatic + codec-support fr + gprs mode none + no force-combined-si + trx 0 + rf_locked 0 + arfcn 666 + nominal power 0 + max_power_red 0 + rsl e1 tei 0 + timeslot 0 + phys_chan_config CCCH+SDCCH4 + hopping enabled 0 + timeslot 1 + phys_chan_config SDCCH8 + hopping enabled 0 + timeslot 2 + phys_chan_config TCH/F + hopping enabled 0 + timeslot 3 + phys_chan_config TCH/F + hopping enabled 0 + timeslot 4 + phys_chan_config TCH/F + hopping enabled 0 + timeslot 5 + phys_chan_config TCH/F + hopping enabled 0 + timeslot 6 + phys_chan_config TCH/F + hopping enabled 0 + timeslot 7 + phys_chan_config TCH/F + hopping enabled 0 +mncc-int + default-codec tch-f fr + default-codec tch-h hr +nitb + subscriber-create-on-demand + subscriber-create-on-demand random 1 24 + assign-tmsi diff --git a/doc/examples/virtual/osmo-bts-virtual.cfg b/doc/examples/virtual/osmo-bts-virtual.cfg new file mode 100644 index 00000000..dbdc22fa --- /dev/null +++ b/doc/examples/virtual/osmo-bts-virtual.cfg @@ -0,0 +1,61 @@ +! +! OsmoBTS (0.4.0.216-bc49-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 0 + logging color 0 + logging print category 1 + logging timestamp 0 + 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 error + logging level pcu notice + logging level ho debug + logging level trx notice + logging level loop notice + logging level abis debug + logging level rtp notice + logging level sum error + 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 + logging level lctrl notice + logging level lgtp notice + logging level lstats error +! +line vty + no login +! +e1_input + e1_line 0 driver ipa + e1_line 0 port 0 + no e1_line 0 keepalive +phy 0 + instance 0 +bts 0 + band DCS1800 + ipa unit-id 6969 0 + oml remote-ip 127.0.0.1 + rtp jitter-buffer 100 + paging queue-size 200 + paging lifetime 0 + uplink-power-target -75 + min-qual-rach 50 + min-qual-norm -5 + trx 0 + power-ramp max-initial 23000 mdBm + power-ramp step-size 2000 mdB + power-ramp step-interval 1 + ms-power-control dsp + phy 0 instance 0 diff --git a/doc/phy_link.txt b/doc/phy_link.txt new file mode 100644 index 00000000..c49e328f --- /dev/null +++ b/doc/phy_link.txt @@ -0,0 +1,57 @@ + +== OsmoBTS PHY interface abstraction + +The OsmoBTS PHY interface serves as an abstraction layer between given +PHY hardware and the actual logical transceivers (TRXs) of a BTS inside +the OsmoBTS code base. + + +=== PHY link + +A PHY link is a physical connection / link towards a given PHY. This +might be, for example, + +* a set of file descriptors to device nodes in the /dev/ directory + (sysmobts, litecell15) +* a packet socket for sending raw Ethernet frames to an OCTPHY +* a set of UDP sockets for interacting with OsmoTRX + +Each PHY interface has a set of attribute/parameters and a list of 1 to +n PHY instances. + +PHY links are numbered 0..n globally inside OsmoBTS. + +Each PHY link is configured via the VTY using its individual top-level +vty node. Given the different bts-model / phy specific properties, the +VTY configuration options (if any) of the PHY instance differ between +BTS models. + +The PHY links and instances must be configured above the BTS/TRX nodes +in the configuration file. If the file is saved via the VTY, the code +automatically ensures this. + + +=== PHY instance + +A PHY instance is an instance of a PHY, accessed via a PHY link. + +In the case of osmo-bts-sysmo and osmo-bts-trx, there is only one +instance in every PHY link. This is due to the fact that the API inside +that PHY link does not permit for distinguishing multiple different +logical TRXs. + +Other PHY implementations like the OCTPHY however do support addressing +multiple PHY instances via a single PHY link. + +PHY instances are numbered 0..n inside each PHY link. + +Each PHY instance is configured via the VTY as a separate node beneath each +PHY link. Given the different bts-model / phy specific properties, the +VTY configuration options (if any) of the PHY instance differ between +BTS models. + + +=== Mapping PHY instances to TRXs + +Each TRX node in the VTY must use the 'phy N instance M' command in +order to specify which PHY instance is allocated to this specific TRX. diff --git a/doc/startup.txt b/doc/startup.txt new file mode 100644 index 00000000..50766e48 --- /dev/null +++ b/doc/startup.txt @@ -0,0 +1,42 @@ + +== start-up / sequencing during OsmoBTS start + +The start-up procedure of OsmoBTS can be described as follows: + +|=== +| bts-specific | main() | +| common | bts_main() | initialization of talloc contexts +| common | osmo_init_logging2() | initialization of logging +| common | handle_options() | common option parsing +| bts-specific | bts_model_handle_options() | model-specific option parsing +| common | gsm_bts_alloc() | allocation of BTS/TRX/TS data structures +| common | vty_init() | Initialziation of VTY core, libosmo-abis and osmo-bts VTY +| common | main() | Setting of scheduler RR priority (if configured) +| common | main() | Initialization of GSMTAP (if configured) +| common | bts_init() | configuration of defaults in bts/trx/s object +| bts-specific | bts_model_init | ? +| common | abis_init() | Initialization of libosmo-abis +| common | vty_read_config_file() | Reading of configuration file +| bts-specific | bts_model_phy_link_set_defaults() | Called for every PHY link created +| bts-specific | bts_model_phy_instance_set_defaults() | Called for every PHY Instance created +| common | bts_controlif_setup() | Initialization of Control Interface +| bts-specific | bts_model_ctrl_cmds_install() +| common | telnet_init() | Initialization of telnet interface +| common | pcu_sock_init() | Initializaiton of PCU socket +| common | main() | Installation of signal handlers +| common | abis_open() | Start of the A-bis connection to BSC +| common | phy_links_open() | Iterate over list of configured PHY links +| bts-specific | bts_model_phy_link_open() | Open each of the configured PHY links +| common | write_pid_file() | Generate the pid file +| common | osmo_daemonize() | Fork as daemon in background (if configured) +| common | bts_main() | Run main loop until global variable quit >= 2 +| bts-specific | bts_model_oml_estab() | Called by core once OML link is established +| bts-specific | bts_model_check_oml() | called each time OML sets some attributes on a MO, checks if attributes are valid +| bts-specific | bts_model_apply_oml() | called each time OML sets some attributes on a MO, stores attribute contents in data structures +| bts-specific | bts_model_opstart() | for NM_OC_BTS, NM_OC_SITE_MANAGER, NM_OC_GPRS_NSE, NM_OC_GPRS_CELL, NMO_OC_GPRS_NSVC +| bts-specific | bts_model_opstart() | for NM_OC_RADIO_CARRIER for each trx +| bts-specific | bts_model_opstart() | for NM_OC_BASEB_TRANSC for each trx +| bts-specific | bts_model_opstart() | for NM_OC_CHANNEL for each timeslot on each trx +| bts-specific | bts_model_change_power() | change transmit power for each trx (power ramp-up/ramp-down + +| bts-specific | bts_model_abis_close() | called when either one of the RSL links or the OML link are down diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 00000000..42cf3d2b --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 00000000..7585a65f --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = osmo-bts diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am new file mode 100644 index 00000000..a15ce3d2 --- /dev/null +++ b/include/osmo-bts/Makefile.am @@ -0,0 +1,5 @@ +noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h gsm_data_shared.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 scheduler.h scheduler_backend.h phy_link.h \ + dtx_dl_amr_fsm.h diff --git a/include/osmo-bts/abis.h b/include/osmo-bts/abis.h new file mode 100644 index 00000000..62407ece --- /dev/null +++ b/include/osmo-bts/abis.h @@ -0,0 +1,29 @@ +#ifndef _ABIS_H +#define _ABIS_H + +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> + +#include <osmo-bts/gsm_data.h> + +#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 */ diff --git a/include/osmo-bts/amr.h b/include/osmo-bts/amr.h new file mode 100644 index 00000000..f3132874 --- /dev/null +++ b/include/osmo-bts/amr.h @@ -0,0 +1,18 @@ +#ifndef _OSMO_BTS_AMR_H +#define _OSMO_BTS_AMR_H + +#include <osmo-bts/gsm_data.h> + +#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); +void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc, + uint8_t cmi, uint8_t cmr); +unsigned int amr_get_initial_mode(struct gsm_lchan *lchan); + +#endif /* _OSMO_BTS_AMR_H */ diff --git a/include/osmo-bts/bts.h b/include/osmo-bts/bts.h new file mode 100644 index 00000000..d7c4bbf3 --- /dev/null +++ b/include/osmo-bts/bts.h @@ -0,0 +1,68 @@ +#ifndef _BTS_H +#define _BTS_H + +#include <osmocom/core/rate_ctr.h> +#include <osmo-bts/gsm_data.h> + +enum bts_global_status { + BTS_STATUS_RF_ACTIVE, + BTS_STATUS_RF_MUTE, + BTS_STATUS_LAST, +}; + +enum { + BTS_CTR_PAGING_RCVD, + BTS_CTR_PAGING_DROP, + BTS_CTR_PAGING_SENT, + BTS_CTR_RACH_RCVD, + BTS_CTR_RACH_DROP, + BTS_CTR_RACH_HO, + BTS_CTR_RACH_CS, + BTS_CTR_RACH_PS, + BTS_CTR_AGCH_RCVD, + BTS_CTR_AGCH_SENT, + BTS_CTR_AGCH_DELETED, +}; + +extern void *tall_bts_ctx; + +int bts_init(struct gsm_bts *bts); +int bts_trx_init(struct gsm_bts_trx *trx); +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); +int trx_set_available(struct gsm_bts_trx *trx, int avail); +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); +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, const 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); +uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg); +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); + +int bts_main(int argc, char **argv); + +int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, + enum gsm48_chan_mode cm); + +#endif /* _BTS_H */ + diff --git a/include/osmo-bts/bts_model.h b/include/osmo-bts/bts_model.h new file mode 100644 index 00000000..be0480c1 --- /dev/null +++ b/include/osmo-bts/bts_model.h @@ -0,0 +1,65 @@ +#ifndef BTS_MODEL_H +#define BTS_MODEL_H + +#include <stdint.h> + +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/gsm_data.h> + +struct phy_link; +struct phy_instance; + +/* BTS model specific functions needed by the common code */ + +int bts_model_init(struct gsm_bts *bts); +int bts_model_trx_init(struct gsm_bts_trx *trx); + +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); +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink); +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst); + +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); + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan); +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan); + +void bts_model_abis_close(struct gsm_bts *bts); + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts); + +int bts_model_handle_options(int argc, char **argv); +void bts_model_print_help(); + +void bts_model_phy_link_set_defaults(struct phy_link *plink); +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst); + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts); +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan); + +#endif diff --git a/include/osmo-bts/cbch.h b/include/osmo-bts/cbch.h new file mode 100644 index 00000000..b4ac409f --- /dev/null +++ b/include/osmo-bts/cbch.h @@ -0,0 +1,16 @@ +#pragma once + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts.h> + +/* 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); diff --git a/include/osmo-bts/control_if.h b/include/osmo-bts/control_if.h new file mode 100644 index 00000000..490c87af --- /dev/null +++ b/include/osmo-bts/control_if.h @@ -0,0 +1,5 @@ +#pragma once + +int bts_ctrl_cmds_install(struct gsm_bts *bts); +struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts, + const char *bind_addr, uint16_t port); diff --git a/include/osmo-bts/dtx_dl_amr_fsm.h b/include/osmo-bts/dtx_dl_amr_fsm.h new file mode 100644 index 00000000..c66ac7d6 --- /dev/null +++ b/include/osmo-bts/dtx_dl_amr_fsm.h @@ -0,0 +1,44 @@ +#pragma once + +#include <osmocom/core/fsm.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> + +/* DTX DL AMR FSM */ + +#define X(s) (1 << (s)) + +enum dtx_dl_amr_fsm_states { + ST_VOICE, + ST_SID_F1, + ST_SID_F2, + ST_F1_INH_V, + ST_F1_INH_F, + ST_U_INH_V, + ST_U_INH_F, + ST_U_NOINH, + ST_F1_INH_V_REC, + ST_F1_INH_F_REC, + ST_U_INH_V_REC, + ST_U_INH_F_REC, + ST_SID_U, + ST_ONSET_V, + ST_ONSET_F, + ST_ONSET_V_REC, + ST_ONSET_F_REC, + ST_FACCH, +}; + +enum dtx_dl_amr_fsm_events { + E_VOICE, + E_ONSET, + E_FACCH, + E_COMPL, + E_FIRST, + E_INHIB, + E_SID_F, + E_SID_U, +}; + +extern const struct value_string dtx_dl_amr_fsm_event_names[]; +extern struct osmo_fsm dtx_dl_amr_fsm; diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h new file mode 100644 index 00000000..9e62cdf0 --- /dev/null +++ b/include/osmo-bts/gsm_data.h @@ -0,0 +1,58 @@ +#ifndef _GSM_DATA_H +#define _GSM_DATA_H + +#include <osmocom/core/timer.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/gsm/gsm23003.h> + +#include <osmo-bts/paging.h> +#include <osmo-bts/tx_power.h> + +#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 */ + +#define GSM_SUPERFRAME (26*51) /* 1326 TDMA frames */ +#define GSM_HYPERFRAME (2048*GSM_SUPERFRAME) /* GSM_HYPERFRAME frames */ + +#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 gsm_network { + struct llist_head bts_list; + unsigned int num_bts; + struct osmo_plmn_id plmn; + struct pcu_sock_state *pcu_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, +}; + +#include <osmo-bts/gsm_data_shared.h> + +void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state); +int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan); + +/* cipher code */ +#define CIPHER_A5(x) (1 << (x-1)) + +int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher); + +bool ts_is_pdch(const struct gsm_bts_trx_ts *ts); + +int bts_model_check_cm_mode(enum gsm_phys_chan_config pchan, enum gsm48_chan_mode cm); + +#endif /* _GSM_DATA_H */ diff --git a/include/osmo-bts/gsm_data_shared.h b/include/osmo-bts/gsm_data_shared.h new file mode 100644 index 00000000..56ab5b19 --- /dev/null +++ b/include/osmo-bts/gsm_data_shared.h @@ -0,0 +1,854 @@ +#ifndef _GSM_DATA_SHAREDH +#define _GSM_DATA_SHAREDH + +#include <regex.h> +#include <stdbool.h> +#include <stdint.h> + +#include <osmocom/codec/ecu.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/bitvec.h> +#include <osmocom/core/statistics.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/rxlev_stat.h> +#include <osmocom/gsm/sysinfo.h> +#include <osmocom/gsm/meas_rep.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/abis/e1_input.h> +#include <osmocom/gsm/lapdm.h> + +/* 16 is the max. number of SI2quater messages according to 3GPP TS 44.018 Table 10.5.2.33b.1: + 4-bit index is used (2#1111 = 10#15) */ +#define SI2Q_MAX_NUM 16 +/* length in bits (for single SI2quater message) */ +#define SI2Q_MAX_LEN 160 +#define SI2Q_MIN_LEN 18 + +/* Channel Request reason */ +enum gsm_chreq_reason_t { + GSM_CHREQ_REASON_EMERG, + GSM_CHREQ_REASON_PAG, + GSM_CHREQ_REASON_CALL, + GSM_CHREQ_REASON_LOCATION_UPD, + GSM_CHREQ_REASON_OTHER, + GSM_CHREQ_REASON_PDCH, +}; + +/* lchans 0..3 are SDCCH in combined channel configuration, + use 4 as magic number for BCCH hack - see osmo-bts-../oml.c:opstart_compl() */ +#define CCCH_LCHAN 4 + +#define TRX_NR_TS 8 +#define TS_MAX_LCHAN 8 + +#define HARDCODED_ARFCN 123 +#define HARDCODED_BSIC 0x3f /* NCC = 7 / BCC = 7 */ + +/* for multi-drop config */ +#define HARDCODED_BTS0_TS 1 +#define HARDCODED_BTS1_TS 6 +#define HARDCODED_BTS2_TS 11 + +#define MAX_VERSION_LENGTH 64 + +#define MAX_BTS_FEATURES 128 + +enum gsm_hooks { + GSM_HOOK_NM_SWLOAD, + GSM_HOOK_RR_PAGING, + GSM_HOOK_RR_SECURITY, +}; + +enum bts_gprs_mode { + BTS_GPRS_NONE = 0, + BTS_GPRS_GPRS = 1, + BTS_GPRS_EGPRS = 2, +}; + +struct gsm_lchan; +struct osmo_rtp_socket; +struct pcu_sock_state; +struct smscb_msg; + +/* Network Management State */ +struct gsm_nm_state { + uint8_t operational; + uint8_t administrative; + uint8_t availability; +}; + +struct gsm_abis_mo { + /* A-bis OML Object Class */ + uint8_t obj_class; + /* is there still some procedure pending? */ + uint8_t procedure_pending; + /* A-bis OML Object Instance */ + struct abis_om_obj_inst obj_inst; + /* human-readable name */ + const char *name; + /* NM State */ + struct gsm_nm_state nm_state; + /* Attributes configured in this MO */ + struct tlv_parsed *nm_attr; + /* BTS to which this MO belongs */ + struct gsm_bts *bts; +}; + +#define MAX_A5_KEY_LEN (128/8) +#define A38_XOR_MIN_KEY_LEN 12 +#define A38_XOR_MAX_KEY_LEN 16 +#define A38_COMP128_KEY_LEN 16 +#define RSL_ENC_ALG_A5(x) (x+1) +#define MAX_EARFCN_LIST 32 + +/* is the data link established? who established it? */ +#define LCHAN_SAPI_UNUSED 0 +#define LCHAN_SAPI_MS 1 +#define LCHAN_SAPI_NET 2 +#define LCHAN_SAPI_REL 3 + +/* state of a logical channel */ +enum gsm_lchan_state { + LCHAN_S_NONE, /* channel is not active */ + LCHAN_S_ACT_REQ, /* channel activation requested */ + LCHAN_S_ACTIVE, /* channel is active and operational */ + LCHAN_S_REL_REQ, /* channel release has been requested */ + LCHAN_S_REL_ERR, /* channel is in an error state */ + LCHAN_S_BROKEN, /* channel is somehow unusable */ + LCHAN_S_INACTIVE, /* channel is set inactive */ +}; + +/* BTS ONLY */ +#define MAX_NUM_UL_MEAS 104 +#define LC_UL_M_F_L1_VALID (1 << 0) +#define LC_UL_M_F_RES_VALID (1 << 1) +#define LC_UL_M_F_OSMO_EXT_VALID (1 << 2) + +struct bts_ul_meas { + /* BER in units of 0.01%: 10.000 == 100% ber, 0 == 0% ber */ + uint16_t ber10k; + /* timing advance offset (in 1/256 bits) */ + int16_t ta_offs_256bits; + /* C/I ratio in dB */ + float c_i; + /* flags */ + uint8_t is_sub:1; + /* RSSI in dBm * -1 */ + uint8_t inv_rssi; +}; + +struct bts_codec_conf { + uint8_t hr; + uint8_t efr; + uint8_t amr; +}; + +struct amr_mode { + uint8_t mode; + uint8_t threshold; + uint8_t hysteresis; +}; + +struct amr_multirate_conf { + uint8_t gsm48_ie[2]; + struct amr_mode ms_mode[4]; + struct amr_mode bts_mode[4]; + uint8_t num_modes; +}; +/* /BTS ONLY */ + +enum lchan_csd_mode { + LCHAN_CSD_M_NT, + LCHAN_CSD_M_T_1200_75, + LCHAN_CSD_M_T_600, + LCHAN_CSD_M_T_1200, + LCHAN_CSD_M_T_2400, + LCHAN_CSD_M_T_9600, + LCHAN_CSD_M_T_14400, + LCHAN_CSD_M_T_29000, + LCHAN_CSD_M_T_32000, +}; + +/* State of the SAPIs in the lchan */ +enum lchan_sapi_state { + LCHAN_SAPI_S_NONE, + LCHAN_SAPI_S_REQ, + LCHAN_SAPI_S_ASSIGNED, + LCHAN_SAPI_S_REL, + LCHAN_SAPI_S_ERROR, +}; + +struct gsm_lchan { + /* The TS that we're part of */ + struct gsm_bts_trx_ts *ts; + /* The logical subslot number in the TS */ + uint8_t nr; + /* The logical channel type */ + enum gsm_chan_t type; + /* RSL channel mode */ + enum rsl_cmod_spd rsl_cmode; + /* If TCH, traffic channel mode */ + enum gsm48_chan_mode tch_mode; + enum lchan_csd_mode csd_mode; + /* State */ + enum gsm_lchan_state state; + const char *broken_reason; + /* Power levels for MS and BTS */ + uint8_t bs_power; + uint8_t ms_power; + /* Encryption information */ + struct { + uint8_t alg_id; + uint8_t key_len; + uint8_t key[MAX_A5_KEY_LEN]; + } encr; + + /* AMR bits */ + uint8_t mr_bts_lv[7]; + + /* Established data link layer services */ + int sacch_deact; + + struct { + uint32_t bound_ip; + uint32_t connect_ip; + uint16_t bound_port; + uint16_t connect_port; + uint16_t conn_id; + uint8_t rtp_payload; + uint8_t rtp_payload2; + uint8_t speech_mode; + struct osmo_rtp_socket *rtp_socket; + } abis_ip; + + uint8_t rqd_ta; + + char *name; + + /* Number of different GsmL1_Sapi_t used in osmo_bts_sysmo is 23. + * Currently we don't share these headers so this is a magic number. */ + struct llist_head sapi_cmds; + uint8_t sapis_dl[23]; + uint8_t sapis_ul[23]; + struct lapdm_channel lapdm_ch; + struct llist_head dl_tch_queue; + struct { + /* bitmask of all SI that are present/valid in si_buf */ + uint32_t valid; + /* bitmask of all SI that do not mirror the BTS-global SI values */ + uint32_t overridden; + uint32_t last; + /* buffers where we put the pre-computed SI: + SI2Q_MAX_NUM is the max number of SI2quater messages (see 3GPP TS 44.018) */ + sysinfo_buf_t buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM]; + } si; + struct { + uint8_t flags; + /* RSL measurment result number, 0 at lchan_act */ + uint8_t res_nr; + /* current Tx power level of the BTS */ + uint8_t bts_tx_pwr; + /* number of measurements stored in array below */ + uint8_t num_ul_meas; + struct bts_ul_meas uplink[MAX_NUM_UL_MEAS]; + /* last L1 header from the MS */ + uint8_t l1_info[2]; + struct gsm_meas_rep_unidir ul_res; + int16_t ms_toa256; + /* Frame number of the last measurement indication receceived */ + uint32_t last_fn; + /* Osmocom extended measurement results, see LC_UL_M_F_EXTD_VALID */ + struct { + /* minimum value of toa256 during measurement period */ + int16_t toa256_min; + /* maximum value of toa256 during measurement period */ + int16_t toa256_max; + /* standard deviation of toa256 value during measurement period */ + uint16_t toa256_std_dev; + } ext; + } meas; + struct { + struct amr_multirate_conf amr_mr; + struct { + struct osmo_fsm_inst *dl_amr_fsm; + /* TCH cache */ + uint8_t cache[20]; + /* FACCH cache */ + uint8_t facch[GSM_MACBLOCK_LEN]; + uint8_t len; + uint32_t fn; + bool is_update; + /* set for each SID frame to detect talkspurt for codecs + without explicit ONSET event */ + bool ul_sid; + /* indicates if DTXd was active during DL measurement + period */ + bool dl_active; + /* last UL SPEECH resume flag */ + bool is_speech_resume; + } dtx; + uint8_t last_cmr; + uint32_t last_fn; + } tch; + + /* 3GPP TS 48.058 § 9.3.37: [0; 255] ok, -1 means invalid*/ + int16_t ms_t_offs; + /* 3GPP TS 45.010 § 1.2 round trip propagation delay (in symbols) or -1 */ + int16_t p_offs; + + /* BTS-side ciphering state (rx only, bi-directional, ...) */ + uint8_t ciph_state; + uint8_t ciph_ns; + uint8_t loopback; + struct { + uint8_t active; + uint8_t ref; + /* T3105: PHYS INF retransmission */ + struct osmo_timer_list t3105; + /* counts up to Ny1 */ + unsigned int phys_info_count; + } ho; + /* S counter for link loss */ + int s; + /* Kind of the release/activation. E.g. RSL or PCU */ + int rel_act_kind; + /* RTP header Marker bit to indicate beginning of speech after pause */ + bool rtp_tx_marker; + /* power handling */ + struct { + uint8_t current; + uint8_t fixed; + } ms_power_ctrl; + + struct msgb *pending_rel_ind_msg; + + /* ECU (Error Concealment Unit) state */ + union { + struct osmo_ecu_fr_state fr; + } ecu_state; +}; + +static inline uint8_t lchan_get_ta(const struct gsm_lchan *lchan) +{ + return lchan->meas.l1_info[1]; +} + +extern const struct value_string lchan_ciph_state_names[]; +static inline const char *lchan_ciph_state_name(uint8_t state) { + return get_value_string(lchan_ciph_state_names, state); +} + +enum { + TS_F_PDCH_ACTIVE = 0x1000, + TS_F_PDCH_ACT_PENDING = 0x2000, + TS_F_PDCH_DEACT_PENDING = 0x4000, + TS_F_PDCH_PENDING_MASK = 0x6000 /*< + TS_F_PDCH_ACT_PENDING | TS_F_PDCH_DEACT_PENDING */ +} gsm_bts_trx_ts_flags; + +/* One Timeslot in a TRX */ +struct gsm_bts_trx_ts { + struct gsm_bts_trx *trx; + /* number of this timeslot at the TRX */ + uint8_t nr; + + enum gsm_phys_chan_config pchan; + + struct { + enum gsm_phys_chan_config pchan_is; + enum gsm_phys_chan_config pchan_want; + struct msgb *pending_chan_activ; + } dyn; + + unsigned int flags; + struct gsm_abis_mo mo; + struct tlv_parsed nm_attr; + uint8_t nm_chan_comb; + int tsc; /* -1 == use BTS TSC */ + + struct { + /* Parameters below are configured by VTY */ + int enabled; + uint8_t maio; + uint8_t hsn; + struct bitvec arfcns; + uint8_t arfcns_data[1024/8]; + /* This is the pre-computed MA for channel assignments */ + struct bitvec ma; + uint8_t ma_len; /* part of ma_data that is used */ + uint8_t ma_data[8]; /* 10.5.2.21: max 8 bytes value part */ + } hopping; + + struct gsm_lchan lchan[TS_MAX_LCHAN]; +}; + +/* One TRX in a BTS */ +struct gsm_bts_trx { + /* list header in bts->trx_list */ + struct llist_head list; + + struct gsm_bts *bts; + /* number of this TRX in the BTS */ + uint8_t nr; + /* human readable name / description */ + char *description; + /* how do we talk RSL with this TRX? */ + uint8_t rsl_tei; + struct e1inp_sign_link *rsl_link; + + /* Some BTS (specifically Ericsson RBS) have a per-TRX OML Link */ + struct e1inp_sign_link *oml_link; + + struct gsm_abis_mo mo; + struct tlv_parsed nm_attr; + struct { + struct gsm_abis_mo mo; + } bb_transc; + + uint16_t arfcn; + int nominal_power; /* in dBm */ + unsigned int max_power_red; /* in actual dB */ + uint8_t max_power_backoff_8psk; /* in actual dB OC-2G only */ + uint8_t c0_idle_power_red; /* in actual dB OC-2G only */ + + + struct trx_power_params power_params; + int ms_power_control; + + struct { + void *l1h; + } role_bts; + + union { + struct { + unsigned int test_state; + uint8_t test_nr; + struct rxlev_stats rxlev_stat; + } ipaccess; + }; + struct gsm_bts_trx_ts ts[TRX_NR_TS]; +}; + +#define GSM_BTS_SI2Q(bts, i) (struct gsm48_system_information_type_2quater *)((bts)->si_buf[SYSINFO_TYPE_2quater][i]) +#define GSM_BTS_HAS_SI(bts, i) ((bts)->si_valid & (1 << i)) +#define GSM_BTS_SI(bts, i) (void *)((bts)->si_buf[i][0]) +#define GSM_LCHAN_SI(lchan, i) (void *)((lchan)->si.buf[i][0]) + +enum gsm_bts_type_variant { + BTS_UNKNOWN, + BTS_OSMO_LITECELL15, + BTS_OSMO_OC2G, + BTS_OSMO_OCTPHY, + BTS_OSMO_SYSMO, + BTS_OSMO_TRX, + BTS_OSMO_VIRTUAL, + BTS_OSMO_OMLDUMMY, + _NUM_BTS_VARIANT +}; + +/* Used by OML layer for BTS Attribute reporting */ +enum bts_attribute { + BTS_TYPE_VARIANT, + BTS_SUB_MODEL, + TRX_PHY_VERSION, +}; + +struct vty; + +/* N. B: always add new features to the end of the list (right before _NUM_BTS_FEAT) to avoid breaking compatibility + with BTS compiled against earlier version of this header. Also make sure that the description strings + gsm_bts_features_descs[] in gsm_data_shared.c are also updated accordingly! */ +enum gsm_bts_features { + BTS_FEAT_HSCSD, + BTS_FEAT_GPRS, + BTS_FEAT_EGPRS, + BTS_FEAT_ECSD, + BTS_FEAT_HOPPING, + BTS_FEAT_MULTI_TSC, + BTS_FEAT_OML_ALERTS, + BTS_FEAT_AGCH_PCH_PROP, + BTS_FEAT_CBCH, + BTS_FEAT_SPEECH_F_V1, + BTS_FEAT_SPEECH_H_V1, + BTS_FEAT_SPEECH_F_EFR, + BTS_FEAT_SPEECH_F_AMR, + BTS_FEAT_SPEECH_H_AMR, + _NUM_BTS_FEAT +}; + +extern const struct value_string gsm_bts_features_descs[]; + +struct gsm_bts_gprs_nsvc { + struct gsm_bts *bts; + /* data read via VTY config file, to configure the BTS + * via OML from BSC */ + int id; + uint16_t nsvci; + uint16_t local_port; /* on the BTS */ + uint16_t remote_port; /* on the SGSN */ + uint32_t remote_ip; /* on the SGSN */ + + struct gsm_abis_mo mo; +}; + +enum gprs_rlc_par { + RLC_T3142, + RLC_T3169, + RLC_T3191, + RLC_T3193, + RLC_T3195, + RLC_N3101, + RLC_N3103, + RLC_N3105, + CV_COUNTDOWN, + T_DL_TBF_EXT, /* ms */ + T_UL_TBF_EXT, /* ms */ + _NUM_RLC_PAR +}; + +enum gprs_cs { + GPRS_CS1, + GPRS_CS2, + GPRS_CS3, + GPRS_CS4, + GPRS_MCS1, + GPRS_MCS2, + GPRS_MCS3, + GPRS_MCS4, + GPRS_MCS5, + GPRS_MCS6, + GPRS_MCS7, + GPRS_MCS8, + GPRS_MCS9, + _NUM_GRPS_CS +}; + +struct gprs_rlc_cfg { + uint16_t parameter[_NUM_RLC_PAR]; + struct { + uint16_t repeat_time; /* ms */ + uint8_t repeat_count; + } paging; + uint32_t cs_mask; /* bitmask of gprs_cs */ + uint8_t initial_cs; + uint8_t initial_mcs; +}; + +/* The amount of time within which a sudden disconnect of a newly established + * OML connection will cause a special warning to be logged. */ +#define OSMO_BTS_OML_CONN_EARLY_DISCONNECT 10 /* in seconds */ + +/* One BTS */ +struct gsm_bts { + /* list header in net->bts_list */ + struct llist_head list; + + /* Geographical location of the BTS */ + struct llist_head loc_list; + + /* number of ths BTS in network */ + uint8_t nr; + /* human readable name / description */ + char *description; + /* Cell Identity */ + uint16_t cell_identity; + /* location area code of this BTS */ + uint16_t location_area_code; + /* Base Station Identification Code (BSIC), lower 3 bits is BCC, + * which is used as TSC for the CCCH */ + uint8_t bsic; + /* type of BTS */ + enum gsm_bts_type_variant variant; + enum gsm_band band; + char version[MAX_VERSION_LENGTH]; + char sub_model[MAX_VERSION_LENGTH]; + + /* features of a given BTS set/reported via OML */ + struct bitvec features; + uint8_t _features_data[MAX_BTS_FEATURES/8]; + + /* Connected PCU version (if any) */ + char pcu_version[MAX_VERSION_LENGTH]; + + /* maximum Tx power that the MS is permitted to use in this cell */ + int ms_max_power; + + /* how do we talk OML with this TRX? */ + uint8_t oml_tei; + struct e1inp_sign_link *oml_link; + struct timespec oml_conn_established_timestamp; + + /* Abis network management O&M handle */ + struct abis_nm_h *nmh; + + struct gsm_abis_mo mo; + + /* number of this BTS on given E1 link */ + uint8_t bts_nr; + + /* DTX features of this BTS */ + enum gsm48_dtx_mode dtxu; + bool dtxd; + + /* CCCH is on C0 */ + struct gsm_bts_trx *c0; + + struct { + struct gsm_abis_mo mo; + } site_mgr; + + /* bitmask of all SI that are present/valid in si_buf */ + uint32_t si_valid; + /* 3GPP TS 44.018 Table 10.5.2.33b.1 INDEX and COUNT for SI2quater */ + uint8_t si2q_index; /* distinguish individual SI2quater messages */ + uint8_t si2q_count; /* si2q_index for the last (highest indexed) individual SI2quater message */ + /* buffers where we put the pre-computed SI */ + sysinfo_buf_t si_buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM]; + /* offsets used while generating SI2quater */ + size_t e_offset; + size_t u_offset; + + /* ip.accesss Unit ID's have Site/BTS/TRX layout */ + union { + struct { + uint16_t site_id; + uint16_t bts_id; + uint32_t flags; + uint32_t rsl_ip; + } ip_access; + }; + + /* Not entirely sure how ip.access specific this is */ + struct { + uint8_t supports_egprs_11bit_rach; + enum bts_gprs_mode mode; + struct { + struct gsm_abis_mo mo; + uint16_t nsei; + uint8_t timer[7]; + } nse; + struct { + struct gsm_abis_mo mo; + uint16_t bvci; + uint8_t timer[11]; + struct gprs_rlc_cfg rlc_cfg; + } cell; + struct gsm_bts_gprs_nsvc nsvc[2]; + uint8_t rac; + uint8_t net_ctrl_ord; + bool ctrl_ack_type_use_block; + } gprs; + + /* RACH NM values */ + int rach_b_thresh; + int rach_ldavg_slots; + + /* transceivers */ + int num_trx; + struct llist_head trx_list; + + /* SI related items */ + int force_combined_si; + int bcch_change_mark; + + struct rate_ctr_group *ctrs; + bool supp_meas_toa256; + + 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 { + struct llist_head queue; + int length; + int max_length; + + int thresh_level; /* Cleanup threshold in percent of max len */ + int low_level; /* Low water mark in percent of max len */ + int high_level; /* High water mark in percent of max len */ + + /* TODO: Use a rate counter group instead */ + uint64_t dropped_msgs; + uint64_t merged_msgs; + uint64_t rejected_msgs; + uint64_t agch_msgs; + uint64_t pch_msgs; + } agch_queue; + + struct paging_state *paging_state; + char *bsc_oml_host; + struct llist_head oml_queue; + unsigned int rtp_jitter_buf_ms; + bool rtp_jitter_adaptive; + + uint16_t rtp_port_range_start; + uint16_t rtp_port_range_end; + uint16_t rtp_port_range_next; + + 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; + /* Radio Link Timeout counter. -1 disables timeout for + * lab/measurement purpose */ + int 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; + + float min_qual_rach; /* minimum quality for RACH bursts */ + float min_qual_norm; /* minimum quality for normal daata */ + uint16_t max_ber10k_rach; /* Maximum permitted RACH BER in 0.01% */ + + struct { + char *sock_path; + } pcu; + + struct { + uint32_t last_fn; + struct timeval tv_clock; + struct osmo_timer_list fn_timer; + } vbts; +#ifdef ENABLE_OC2GBTS + /* specific to Open Cellular 2G BTS */ + struct { + uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */ + struct llist_head ceased_alarm_list; /* ceased alarm list*/ + unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */ + } oc2g; +#endif +}; + + +struct gsm_bts *gsm_bts_alloc(void *talloc_ctx, uint8_t bts_num); +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num); + +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts); +struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num); + +enum bts_attribute str2btsattr(const char *s); +const char *btsatttr2str(enum bts_attribute v); + +enum gsm_bts_type_variant str2btsvariant(const char *arg); +const char *btsvariant2str(enum gsm_bts_type_variant v); + +extern const struct value_string gsm_chreq_descs[]; +const struct value_string gsm_pchant_names[13]; +const struct value_string gsm_pchant_descs[13]; +const char *gsm_pchan_name(enum gsm_phys_chan_config c); +enum gsm_phys_chan_config gsm_pchan_parse(const char *name); +const char *gsm_lchant_name(enum gsm_chan_t c); +const char *gsm_chreq_name(enum gsm_chreq_reason_t c); +char *gsm_trx_name(const struct gsm_bts_trx *trx); +char *gsm_ts_name(const struct gsm_bts_trx_ts *ts); +char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts); +char *gsm_lchan_name_compute(const struct gsm_lchan *lchan); +const char *gsm_lchans_name(enum gsm_lchan_state s); + +static inline char *gsm_lchan_name(const struct gsm_lchan *lchan) +{ + return lchan->name; +} + +static inline int gsm_bts_set_feature(struct gsm_bts *bts, enum gsm_bts_features feat) +{ + OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES); + return bitvec_set_bit_pos(&bts->features, feat, 1); +} + +static inline bool gsm_bts_has_feature(const struct gsm_bts *bts, enum gsm_bts_features feat) +{ + OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES); + return bitvec_get_bit_pos(&bts->features, feat); +} + +void gsm_abis_mo_reset(struct gsm_abis_mo *mo); + +struct gsm_abis_mo * +gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst); + +struct gsm_nm_state * +gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst); +void * +gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst); + +uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, + uint8_t ts_nr, uint8_t lchan_nr); +uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan); +uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, + enum gsm_phys_chan_config as_pchan); + +/* return the gsm_lchan for the CBCH (if it exists at all) */ +struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts); + +/* + * help with parsing regexps + */ +int gsm_parse_reg(void *ctx, regex_t *reg, char **str, + int argc, const char **argv) __attribute__ ((warn_unused_result)); + +#define BSIC2BCC(bsic) ((bsic) & 0x3) + +static inline uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts) +{ + if (ts->tsc != -1) + return ts->tsc; + else + return ts->trx->bts->bsic & 7; +} + +struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + int *rc); + +enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts); +uint8_t ts_subslots(struct gsm_bts_trx_ts *ts); +bool ts_is_tch(struct gsm_bts_trx_ts *ts); +const char *gsm_trx_unit_id(struct gsm_bts_trx *trx); + +#endif diff --git a/include/osmo-bts/handover.h b/include/osmo-bts/handover.h new file mode 100644 index 00000000..35d5c690 --- /dev/null +++ b/include/osmo-bts/handover.h @@ -0,0 +1,12 @@ +#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); + diff --git a/include/osmo-bts/l1sap.h b/include/osmo-bts/l1sap.h new file mode 100644 index 00000000..3cf0ea58 --- /dev/null +++ b/include/osmo-bts/l1sap.h @@ -0,0 +1,103 @@ +#ifndef L1SAP_H +#define L1SAP_H + +#include <osmocom/gsm/protocol/gsm_04_08.h> + +/* lchan link ID */ +#define LID_SACCH 0x40 +#define LID_DEDIC 0x00 + +/* 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) +#define L1SAP_CHAN2SS_BCCH(chan_nr) (CCCH_LCHAN) + +/* logical channel from chan_nr + link_id */ +#define L1SAP_IS_LINK_SACCH(link_id) ((link_id & 0xC0) == LID_SACCH) +#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) +#define L1SAP_IS_CHAN_PDCH(chan_nr) ((chan_nr & 0xf8) == 0xc0) +#define L1SAP_IS_CHAN_CBCH(chan_nr) ((chan_nr & 0xf8) == 0xc8) + +/* rach type from ra */ +#define L1SAP_IS_PACKET_RACH(ra) ((ra & 0xf0) == 0x70 && (ra & 0x0f) != 0x0f) + +/* CCCH block from frame number */ +unsigned int l1sap_fn2ccch_block(uint32_t fn); + +/* PTCH layout from frame number */ +#define L1SAP_FN2MACBLOCK(fn) ((fn % 52) / 4) +#define L1SAP_FN2PTCCHBLOCK(fn) ((fn / 104) & 3) + +/* Calculate PTCCH occurrence, See also 3GPP TS 05.02, Clause 7, Table 6 of 9 */ +#define L1SAP_IS_PTCCH(fn) (((fn % 52) == 12) || ((fn % 52) == 38)) + + +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 +}; + +/* subslot from any chan_nr */ +static inline uint8_t l1sap_chan2ss(uint8_t chan_nr) +{ + if (L1SAP_IS_CHAN_BCCH(chan_nr)) + return L1SAP_CHAN2SS_BCCH(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; +} + +struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx, + unsigned int chan_nr); + +/* 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, uint16_t seq_number, + uint32_t timestamp, bool marker); + +/* 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; + +int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg, + struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn, + uint16_t ber10k, int16_t lqual_cb); + +#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); + +int is_ccch_for_agch(struct gsm_bts_trx *trx, uint32_t fn); + +#endif /* L1SAP_H */ diff --git a/include/osmo-bts/logging.h b/include/osmo-bts/logging.h new file mode 100644 index 00000000..852c3836 --- /dev/null +++ b/include/osmo-bts/logging.h @@ -0,0 +1,40 @@ +#ifndef _LOGGING_H +#define _LOGGING_H + +#define DEBUG +#include <osmocom/core/logging.h> + +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; + +/* LOGP with gsm_time prefix */ +#define LOGPGT(ss, lvl, gt, fmt, args...) \ + LOGP(ss, lvl, "%s " fmt, osmo_dump_gsmtime(gt), ## args) +#define DEBUGPGT(ss, gt, fmt, args...) \ + LOGP(ss, LOGL_DEBUG, "%s " fmt, osmo_dump_gsmtime(gt), ## args) + +/* LOGP with frame number prefix */ +#define LOGPFN(ss, lvl, fn, fmt, args...) \ + LOGP(ss, lvl, "%s " fmt, gsm_fn_as_gsmtime_str(fn), ## args) +#define DEBUGPFN(ss, fn, fmt, args...) \ + LOGP(ss, LOGL_DEBUG, "%s " fmt, gsm_fn_as_gsmtime_str(fn), ## args) + +#endif /* _LOGGING_H */ diff --git a/include/osmo-bts/measurement.h b/include/osmo-bts/measurement.h new file mode 100644 index 00000000..4f04ffa2 --- /dev/null +++ b/include/osmo-bts/measurement.h @@ -0,0 +1,19 @@ +#ifndef OSMO_BTS_MEAS_H +#define OSMO_BTS_MEAS_H + +#define MEAS_MAX_TIMING_ADVANCE 63 +#define MEAS_MIN_TIMING_ADVANCE 0 + +int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn); + +int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn); + +int lchan_meas_process_measurement(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn); + +void lchan_meas_reset(struct gsm_lchan *lchan); + +bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn, bool is_amr_sid_update); + +int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn); + +#endif diff --git a/include/osmo-bts/msg_utils.h b/include/osmo-bts/msg_utils.h new file mode 100644 index 00000000..7ddbe88f --- /dev/null +++ b/include/osmo-bts/msg_utils.h @@ -0,0 +1,48 @@ +/* + * Routines to check the structurally integrity of messages + */ + +#pragma once + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <osmocom/codec/codec.h> + +#include <stdbool.h> + +struct msgb; + +/* Access 1st part of msgb control buffer */ +#define rtpmsg_marker_bit(x) ((x)->cb[0]) + +/* Access 2nd part of msgb control buffer */ +#define rtpmsg_seq(x) ((x)->cb[1]) + +/* Access 3rd part of msgb control buffer */ +#define rtpmsg_ts(x) ((x)->cb[2]) + +/** + * 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, +}; + +void lchan_set_marker(bool t, struct gsm_lchan *lchan); +bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan); +void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e); +bool dtx_recursion(const struct gsm_lchan *lchan); +void dtx_int_signal(struct gsm_lchan *lchan); +bool dtx_is_first_p1(const struct gsm_lchan *lchan); +void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload, + size_t length, uint32_t fn, int update); +int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl, + size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload, + bool marker, uint8_t *len, uint8_t *ft_out); +uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn); +int msg_verify_ipa_structure(struct msgb *msg); +int msg_verify_oml_structure(struct msgb *msg); diff --git a/include/osmo-bts/oml.h b/include/osmo-bts/oml.h new file mode 100644 index 00000000..139464ec --- /dev/null +++ b/include/osmo-bts/oml.h @@ -0,0 +1,50 @@ +#ifndef _OML_H +#define _OML_H + +#include <osmocom/gsm/protocol/gsm_12_21.h> + +struct gsm_bts; +struct gsm_abis_mo; +struct msgb; +struct gsm_lchan; + + +int oml_init(struct gsm_abis_mo *mo); +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); + +/* Configure LAPDm T200 timers for this lchan according to OML */ +int oml_set_lchan_t200(struct gsm_lchan *lchan); +extern const unsigned int oml_default_t200_ms[7]; + +/* Transmit failure event report */ +void oml_fail_rep(uint16_t cause_value, const char *fmt, ...); + +#endif // _OML_H */ diff --git a/include/osmo-bts/paging.h b/include/osmo-bts/paging.h new file mode 100644 index 00000000..7fc0bf05 --- /dev/null +++ b/include/osmo-bts/paging.h @@ -0,0 +1,52 @@ +#ifndef OSMO_BTS_PAGING_H +#define OSMO_BTS_PAGING_H + +#include <stdint.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +struct paging_state; +struct gsm_bts; + +/* initialize paging code */ +struct paging_state *paging_init(struct gsm_bts *bts, + 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 diff --git a/include/osmo-bts/pcu_if.h b/include/osmo-bts/pcu_if.h new file mode 100644 index 00000000..98efb570 --- /dev/null +++ b/include/osmo-bts/pcu_if.h @@ -0,0 +1,24 @@ +#ifndef _PCU_IF_H +#define _PCU_IF_H + +extern int pcu_direct; + +int pcu_tx_info_ind(void); +int pcu_tx_si13(const struct gsm_bts *bts, bool enable); +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, uint16_t ber10k, int16_t bto, int16_t lqual); +int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, + uint8_t is_11bit, enum ph_burst_type burst_type); +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(const char *path); +void pcu_sock_exit(void); + +bool pcu_connected(void); + +#endif /* _PCU_IF_H */ diff --git a/include/osmo-bts/pcuif_proto.h b/include/osmo-bts/pcuif_proto.h new file mode 100644 index 00000000..b06077c3 --- /dev/null +++ b/include/osmo-bts/pcuif_proto.h @@ -0,0 +1,195 @@ +#ifndef _PCUIF_PROTO_H +#define _PCUIF_PROTO_H + +#include <osmocom/gsm/l1sap.h> + +#define PCU_SOCK_DEFAULT "/tmp/pcu_bts" + +#define PCU_IF_VERSION 0x09 +#define TXT_MAX_LEN 128 + +/* 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_DATA_CNF_DT 0x11 /* confirm (with direct tlli) */ +#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 */ +#define PCU_IF_MSG_TXT_IND 0x70 /* Text indication for BTS */ + +/* 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 */ +#define PCU_IF_SAPI_AGCH_DT 0x08 /* assignment on AGCH but with additional TLLI */ + +/* 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) + +enum gsm_pcu_if_text_type { + PCU_VERSION, + PCU_OML_ALERT, +}; + +struct gsm_pcu_if_txt_ind { + uint8_t type; /* gsm_pcu_if_text_type */ + char text[TXT_MAX_LEN]; /* Text to be transmitted to BTS */ +} __attribute__ ((packed)); + +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; + uint16_t ber10k; /* !< \brief BER in units of 0.01% */ + int16_t ta_offs_qbits; /* !< \brief Burst TA Offset in quarter bits */ + int16_t lqual_cb; /* !< \brief Link quality in centiBel */ +} __attribute__ ((packed)); + +/* data confirmation with direct tlli (instead of raw mac block with tlli) */ +struct gsm_pcu_if_data_cnf_dt { + uint8_t sapi; + uint32_t tlli; + uint32_t fn; + uint16_t arfcn; + uint8_t trx_nr; + uint8_t ts_nr; + uint8_t block_nr; + int8_t rssi; + uint16_t ber10k; /* !< \brief BER in units of 0.01% */ + int16_t ta_offs_qbits; /* !< \brief Burst TA Offset in quarter bits */ + int16_t lqual_cb; /* !< \brief Link quality in centiBel */ +} __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; + uint16_t ra; + int16_t qta; + uint32_t fn; + uint16_t arfcn; + uint8_t is_11bit; + uint8_t burst_type; +} __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; + uint8_t mnc_3_digits; + uint16_t 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_cnf_dt data_cnf_dt; + 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_txt_ind txt_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 */ diff --git a/include/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h new file mode 100644 index 00000000..2472c051 --- /dev/null +++ b/include/osmo-bts/phy_link.h @@ -0,0 +1,175 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include <osmocom/core/linuxlist.h> + +#include <osmo-bts/scheduler.h> + +#include <linux/if_packet.h> +#include "btsconfig.h" + +struct gsm_bts_trx; +struct virt_um_inst; + +enum phy_link_type { + PHY_LINK_T_NONE, + PHY_LINK_T_SYSMOBTS, + PHY_LINK_T_OSMOTRX, + PHY_LINK_T_VIRTUAL, +}; + +enum phy_link_state { + PHY_LINK_SHUTDOWN, + PHY_LINK_CONNECTING, + PHY_LINK_CONNECTED, +}; + +/* A PHY link represents the connection to a given PHYsical layer + * implementation. That PHY link contains 1...N PHY instances, one for + * each TRX */ +struct phy_link { + struct llist_head list; + int num; + enum phy_link_type type; + enum phy_link_state state; + struct llist_head instances; + char *description; + union { + struct { + } sysmobts; + struct { + char *local_ip; + char *remote_ip; + uint16_t base_port_local; + uint16_t base_port_remote; + struct osmo_fd trx_ofd_clk; + bool trx_ta_loop; + bool trx_ms_power_loop; + int8_t trx_target_rssi; + uint32_t clock_advance; + uint32_t rts_advance; + bool use_legacy_setbsic; + } osmotrx; + struct { + char *mcast_dev; /* Network device for multicast */ + char *bts_mcast_group; /* BTS are listening to this group */ + uint16_t bts_mcast_port; + char *ms_mcast_group; /* MS are listening to this group */ + uint16_t ms_mcast_port; + struct virt_um_inst *virt_um; + } virt; + struct { + /* MAC address of the PHY */ + struct sockaddr_ll phy_addr; + /* Network device name */ + char *netdev_name; + + /* configuration */ + uint32_t rf_port_index; +#if OCTPHY_USE_ANTENNA_ID == 1 + uint32_t rx_ant_id; + uint32_t tx_ant_id; +#endif + uint32_t rx_gain_db; + bool tx_atten_flag; + uint32_t tx_atten_db; + bool over_sample_16x; +#if OCTPHY_MULTI_TRX == 1 + /* arfcn used by TRX with id 0 */ + uint16_t center_arfcn; +#endif + + struct octphy_hdl *hdl; + } octphy; + } u; +}; + +struct phy_instance { + /* liked inside phy_link.linstances */ + struct llist_head list; + int num; + char *description; + char version[MAX_VERSION_LENGTH]; + /* pointer to the PHY link to which we belong */ + struct phy_link *phy_link; + + /* back-pointer to the TRX to which we're associated */ + struct gsm_bts_trx *trx; + + union { + struct { + /* configuration */ + uint8_t clk_use_eeprom; + uint32_t dsp_trace_f; + int clk_cal; + uint8_t clk_src; + char *calib_path; + + struct femtol1_hdl *hdl; + } sysmobts; + struct { + struct trx_l1h *hdl; + bool sw_act_reported; + } osmotrx; + struct { + struct l1sched_trx sched; + } virt; + struct { + /* logical transceiver number within one PHY */ + uint32_t trx_id; + /* trx lock state variable */ + int trx_locked; + } octphy; + struct { + /* configuration */ + uint32_t dsp_trace_f; + char *calib_path; + int minTxPower; + int maxTxPower; + struct lc15l1_hdl *hdl; + uint8_t max_cell_size; /* 0:166 qbits*/ + uint8_t diversity_mode; /* 0: SISO A, 1: SISO B, 2: MRC */ + uint8_t pedestal_mode; /* 0: unused TS is OFF, 1: unused TS is in minimum Tx power */ + uint8_t dsp_alive_period; /* DSP alive timer period */ + uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */ + uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */ + } lc15; + struct { + /* configuration */ + uint32_t dsp_trace_f; + char *calib_path; + int minTxPower; + int maxTxPower; + struct oc2gl1_hdl *hdl; + uint8_t max_cell_size; /* 0:166 qbits*/ + uint8_t pedestal_mode; /* 0: unused TS is OFF, 1: unused TS is in minimum Tx power */ + uint8_t dsp_alive_period; /* DSP alive timer period */ + uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */ + uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */ + uint8_t tx_c0_idle_pwr_red; /* C0 idle slot Tx power reduction level in dB */ + } oc2g; + } u; +}; + +struct phy_link *phy_link_by_num(int num); +struct phy_link *phy_link_create(void *ctx, int num); +void phy_link_destroy(struct phy_link *plink); +void phy_link_state_set(struct phy_link *plink, enum phy_link_state state); +int phy_links_open(void); + +struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num); +struct phy_instance *phy_instance_create(struct phy_link *plink, int num); +void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx); +void phy_instance_destroy(struct phy_instance *pinst); +const char *phy_instance_name(struct phy_instance *pinst); + +void phy_user_statechg_notif(struct phy_instance *pinst, enum phy_link_state link_state); + +static inline struct phy_instance *trx_phy_instance(struct gsm_bts_trx *trx) +{ + OSMO_ASSERT(trx); + return trx->role_bts.l1h; +} + +int bts_model_phy_link_open(struct phy_link *plink); diff --git a/include/osmo-bts/power_control.h b/include/osmo-bts/power_control.h new file mode 100644 index 00000000..43d4b591 --- /dev/null +++ b/include/osmo-bts/power_control.h @@ -0,0 +1,7 @@ +#pragma once + +#include <stdint.h> +#include <osmo-bts/gsm_data.h> + +int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, + const uint8_t ms_power, const int rxLevel); diff --git a/include/osmo-bts/rsl.h b/include/osmo-bts/rsl.h new file mode 100644 index 00000000..0361841d --- /dev/null +++ b/include/osmo-bts/rsl.h @@ -0,0 +1,46 @@ +#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, + LCHAN_REL_ACT_REACT, /* remove once auto-activation hack is removed from opstart_compl() */ +}; + +#define LCHAN_FN_DUMMY 0xFFFFFFFF +#define LCHAN_FN_WAIT 0xFFFFFFFE + +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_acknack(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); + +int lchan_deactivate(struct gsm_lchan *lchan); + +/* 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); +int rsl_tx_delete_ind(struct gsm_bts *bts, const uint8_t *ia, uint8_t ia_len); + +void cb_ts_disconnected(struct gsm_bts_trx_ts *ts); +void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc); +void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc); + +#endif // _RSL_H */ diff --git a/include/osmo-bts/scheduler.h b/include/osmo-bts/scheduler.h new file mode 100644 index 00000000..f9d99629 --- /dev/null +++ b/include/osmo-bts/scheduler.h @@ -0,0 +1,227 @@ +#ifndef TRX_SCHEDULER_H +#define TRX_SCHEDULER_H + +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> + +/* 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, + TRXC_CBCH, + _TRX_CHAN_MAX +}; + +extern const struct value_string trx_chan_type_names[]; + +#define GSM_BURST_LEN 148 +#define GPRS_BURST_LEN GSM_BURST_LEN +#define EGPRS_BURST_LEN 444 + +enum trx_burst_type { + TRX_BURST_GMSK, + TRX_BURST_8PSK, +}; + +/* States each channel on a multiframe */ +struct l1sched_chan_state { + /* scheduler */ + uint8_t active; /* Channel is active */ + ubit_t *dl_bursts; /* burst buffer for TX */ + enum trx_burst_type dl_burst_type; /* GMSK or 8PSK burst type */ + 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 */ + int32_t toa256_sum; /* sum of TOA values (1/256 symbol) */ + + /* loss detection */ + uint8_t lost_frames; /* how many L2 frames were lost */ + uint32_t last_tdma_fn; /* last processed TDMA frame number */ + uint32_t proc_tdma_fs; /* how many TDMA frames were processed */ + uint32_t lost_tdma_fs; /* how many TDMA frames were lost */ + + /* 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[MAX_A5_KEY_LEN]; + uint8_t dl_encr_key[MAX_A5_KEY_LEN]; + + /* 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 */ + int32_t toa256_sum; /* sum of TOA values (1/256 symbol) */ + int toa_num; /* number of TOA value */ + } meas; + + /* handover */ + uint8_t ho_rach_detect; /* if rach detection is on */ +}; + +struct l1sched_ts { + uint8_t mf_index; /* selected multiframe index */ + uint8_t mf_period; /* period of multiframe */ + const struct trx_sched_frame *mf_frames; /* pointer to frame layout */ + + struct llist_head dl_prims; /* Queue primitives for TX */ + + /* Channel states for all logical channels */ + struct l1sched_chan_state chan_state[_TRX_CHAN_MAX]; +}; + +struct l1sched_trx { + struct gsm_bts_trx *trx; + struct l1sched_ts ts[TRX_NR_TS]; +}; + +struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn); + +/*! \brief how many frame numbers in advance we should send bursts to PHY */ +extern uint32_t trx_clock_advance; +/*! \brief advance RTS.ind to L2 by that many clocks */ +extern uint32_t trx_rts_advance; +/*! \brief last frame number as received from PHY */ +extern uint32_t transceiver_last_fn; + + +/*! \brief Initialize the scheduler data structures */ +int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx); + +/*! \brief De-initialize the scheduler data structures */ +void trx_sched_exit(struct l1sched_trx *l1t); + +/*! \brief Handle a PH-DATA.req from L2 down to L1 */ +int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap); + +/*! \brief Handle a PH-TCH.req from L2 down to L1 */ +int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap); + +/*! \brief PHY informs us of new (current) GSM frame number */ +int trx_sched_clock(struct gsm_bts *bts, uint32_t fn); + +/*! \brief handle an UL burst received by PHY */ +int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa); + +/*! \brief set multiframe scheduler to given physical channel config */ +int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, + enum gsm_phys_chan_config pchan); + +/*! \brief set all matching logical channels active/inactive */ +int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, + int active); + +/*! \brief set mode of all matching logical channels to given mode(s) */ +int trx_sched_set_mode(struct l1sched_trx *l1t, 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); + +/*! \brief set ciphering on given logical channels */ +int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, + int algo, uint8_t *key, int key_len); + +/* \brief close all logical channels and reset timeslots */ +void trx_sched_reset(struct l1sched_trx *l1t); + + +/* frame structures */ +struct trx_sched_frame { + /*! \brief downlink TRX channel type */ + enum trx_chan_type dl_chan; + /*! \brief downlink block ID */ + uint8_t dl_bid; + /*! \brief uplink TRX channel type */ + enum trx_chan_type ul_chan; + /*! \brief uplink block ID */ + uint8_t ul_bid; +}; + +/* multiframe structure */ +struct trx_sched_multiframe { + /*! \brief physical channel config (channel combination) */ + enum gsm_phys_chan_config pchan; + /*! \brief applies to which timeslots? */ + uint8_t slotmask; + /*! \brief repeats how many frames */ + uint8_t period; + /*! \brief pointer to scheduling structure */ + const struct trx_sched_frame *frames; + /*! \brief human-readable name */ + const char *name; +}; + +int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn); + +/*! Determine if given frame number contains SACCH (true) or other (false) burst */ +bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink); +extern const struct trx_sched_multiframe trx_sched_multiframes[]; + +#endif /* TRX_SCHEDULER_H */ diff --git a/include/osmo-bts/scheduler_backend.h b/include/osmo-bts/scheduler_backend.h new file mode 100644 index 00000000..dbd93195 --- /dev/null +++ b/include/osmo-bts/scheduler_backend.h @@ -0,0 +1,94 @@ +#pragma once + +#define LOGL1S(subsys, level, l1t, tn, chan, fn, fmt, args ...) \ + LOGP(subsys, level, "%s %s %s: " fmt, \ + gsm_fn_as_gsmtime_str(fn), \ + gsm_ts_name(&(l1t)->trx->ts[tn]), \ + chan >=0 ? trx_chan_desc[chan].name : "", ## args) + +typedef int trx_sched_rts_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan); + +typedef ubit_t *trx_sched_dl_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, + uint8_t bid, uint16_t *nbits); + +typedef int trx_sched_ul_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, + uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); + +struct trx_chan_desc { + /*! \brief Is this on a PDCH (PS) ? */ + int pdch; + /*! \brief TRX Channel Type */ + enum trx_chan_type chan; + /*! \brief Channel Number (like in RSL) */ + uint8_t chan_nr; + /*! \brief Link ID (like in RSL) */ + uint8_t link_id; + /*! \brief Human-readable name */ + const char *name; + /*! \brief function to call when we want to generate RTS.req to L2 */ + trx_sched_rts_func *rts_fn; + /*! \brief function to call when DATA.req received from L2 */ + trx_sched_dl_func *dl_fn; + /*! \brief function to call when burst received from PHY */ + trx_sched_ul_func *ul_fn; + /*! \brief is this channel automatically active at start? */ + int auto_active; +}; +extern const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX]; + +extern const ubit_t _sched_tsc[8][26]; +extern const ubit_t _sched_egprs_tsc[8][78]; +const ubit_t _sched_fcch_burst[148]; +const ubit_t _sched_sch_train[64]; + +struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, + enum trx_chan_type chan); + +int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, + uint8_t l2_len, float rssi, + int16_t ta_offs_256bits, int16_t link_qual_cb, + uint16_t ber10k, + enum osmo_ph_pres_info_type presence_info); + +int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len); + +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); + +const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, uint16_t *nbits); +int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn); +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate); diff --git a/include/osmo-bts/signal.h b/include/osmo-bts/signal.h new file mode 100644 index 00000000..01d4099b --- /dev/null +++ b/include/osmo-bts/signal.h @@ -0,0 +1,19 @@ +#ifndef OSMO_BTS_SIGNAL_H +#define OSMO_BTS_SIGNAL_H + +#include <osmocom/core/signal.h> + +enum sig_subsys { + SS_GLOBAL, + SS_FAIL, +}; + +enum signals_global { + S_NEW_SYSINFO, + S_NEW_OP_STATE, + S_NEW_NSE_ATTR, + S_NEW_CELL_ATTR, + S_NEW_NSVC_ATTR, +}; + +#endif diff --git a/include/osmo-bts/tx_power.h b/include/osmo-bts/tx_power.h new file mode 100644 index 00000000..21887c7c --- /dev/null +++ b/include/osmo-bts/tx_power.h @@ -0,0 +1,78 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/core/timer.h> + +/* 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 delta_mdB[1024]; /* gain delta 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); + +int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx); diff --git a/include/osmo-bts/vty.h b/include/osmo-bts/vty.h new file mode 100644 index 00000000..d27acb5f --- /dev/null +++ b/include/osmo-bts/vty.h @@ -0,0 +1,32 @@ +#ifndef OSMOBTS_VTY_H +#define OSMOBTS_VTY_H + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +enum bts_vty_node { + /* PHY_NODE must come before BTS node to ensure the phy + * instances are created at the time the TRX nodes want to refer + * to them */ + PHY_NODE = _LAST_OSMOVTY_NODE + 1, + PHY_INST_NODE, + BTS_NODE, + TRX_NODE, +}; + +extern struct cmd_element ournode_exit_cmd; +extern struct cmd_element ournode_end_cmd; + +extern struct cmd_element cfg_bts_auto_band_cmd; +extern struct cmd_element cfg_bts_no_auto_band_cmd; + +struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr); + +int 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 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..70e4d968 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,22 @@ +SUBDIRS = common osmo-bts-virtual osmo-bts-omldummy + +if ENABLE_SYSMOBTS +SUBDIRS += osmo-bts-sysmo +endif + +if ENABLE_TRX +SUBDIRS += osmo-bts-trx +endif + +if ENABLE_OCTPHY +SUBDIRS += osmo-bts-octphy +endif + +if ENABLE_LC15BTS +SUBDIRS += osmo-bts-litecell15 +endif + +if ENABLE_OC2GBTS +SUBDIRS += osmo-bts-oc2g +endif + diff --git a/src/common/Makefile.am b/src/common/Makefile.am new file mode 100644 index 00000000..113ff2f4 --- /dev/null +++ b/src/common/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOCODEC_LIBS) + +if ENABLE_LC15BTS +AM_CFLAGS += -DENABLE_LC15BTS +endif + +noinst_LIBRARIES = libbts.a libl1sched.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 \ + tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \ + l1sap.c cbch.c power_control.c main.c phy_link.c \ + dtx_dl_amr_fsm.c scheduler_mframe.c + +libl1sched_a_SOURCES = scheduler.c diff --git a/src/common/abis.c b/src/common/abis.c new file mode 100644 index 00000000..84a3a047 --- /dev/null +++ b/src/common/abis.c @@ -0,0 +1,295 @@ +/* Abis/IP interface routines utilizing libosmo-abis (Pablo) */ + +/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "btsconfig.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/macaddr.h> +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipaccess.h> +#include <osmocom/gsm/ipa.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/bts_model.h> + +static struct gsm_bts *g_bts; + +int abis_oml_sendmsg(struct msgb *msg) +{ + struct gsm_bts *bts = msg->trx->bts; + + if (!bts->oml_link) { + llist_add_tail(&msg->list, &bts->oml_queue); + return 0; + } else { + /* 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); + } +} + +static void drain_oml_queue(struct gsm_bts *bts) +{ + struct msgb *msg, *msg2; + + llist_for_each_entry_safe(msg, msg2, &bts->oml_queue, list) { + /* osmo-bts uses msg->trx internally, but libosmo-abis uses + * the signalling link at msg->dst */ + llist_del(&msg->list); + msg->dst = bts->oml_link; + abis_sendmsg(msg); + } +} + +int abis_bts_rsl_sendmsg(struct msgb *msg) +{ + OSMO_ASSERT(msg->trx); + + if (msg->trx->bts->variant == BTS_OSMO_OMLDUMMY) { + msgb_free(msg); + return 0; + } + + /* 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; + struct gsm_bts_trx *trx; + int trx_nr; + + 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); + if (clock_gettime(CLOCK_MONOTONIC, &g_bts->oml_conn_established_timestamp) != 0) + memset(&g_bts->oml_conn_established_timestamp, 0, + sizeof(g_bts->oml_conn_established_timestamp)); + drain_oml_queue(g_bts); + sign_link->trx = g_bts->c0; + bts_link_estab(g_bts); + break; + default: + trx_nr = type - E1INP_SIGN_RSL; + LOGP(DABIS, LOGL_INFO, "RSL Signalling link for TRX%d up\n", + trx_nr); + trx = gsm_bts_trx_num(g_bts, trx_nr); + if (!trx) { + LOGP(DABIS, LOGL_ERROR, "TRX%d does not exist!\n", + trx_nr); + break; + } + e1inp_ts_config_sign(&line->ts[type-1], line); + sign_link = trx->rsl_link = + e1inp_sign_link_create(&line->ts[type-1], + E1INP_SIGN_RSL, NULL, 0, 0); + sign_link->trx = trx; + trx_link_estab(trx); + break; + } + + return sign_link; +} + +static void sign_link_down(struct e1inp_line *line) +{ + struct gsm_bts_trx *trx; + LOGP(DABIS, LOGL_ERROR, "Signalling link down\n"); + + /* First remove the OML signalling link */ + if (g_bts->oml_link) { + struct timespec now; + + e1inp_sign_link_destroy(g_bts->oml_link); + + /* Log a special notice if the OML connection was dropped relatively quickly. */ + if (g_bts->oml_conn_established_timestamp.tv_sec != 0 && clock_gettime(CLOCK_MONOTONIC, &now) == 0 && + g_bts->oml_conn_established_timestamp.tv_sec + OSMO_BTS_OML_CONN_EARLY_DISCONNECT >= now.tv_sec) { + LOGP(DABIS, LOGL_FATAL, "OML link was closed early within %" PRIu64 " seconds. " + "If this situation persists, please check your BTS and BSC configuration files for errors. " + "A common error is a mismatch between unit_id configuration parameters of BTS and BSC.\n", + (uint64_t)(now.tv_sec - g_bts->oml_conn_established_timestamp.tv_sec)); + } + } + g_bts->oml_link = NULL; + memset(&g_bts->oml_conn_established_timestamp, 0, sizeof(g_bts->oml_conn_established_timestamp)); + + /* Then iterate over the RSL signalling links */ + llist_for_each_entry(trx, &g_bts->trx_list, list) { + if (trx->rsl_link) { + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_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; + + struct input_signal_data *isd = signal_data; + DEBUGP(DABIS, "Input Signal %s received for link_type=%s\n", + get_value_string(e1inp_signal_names, signal), e1inp_signtype_name(isd->link_type)); + + 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(&bts->mo); + 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; +} diff --git a/src/common/amr.c b/src/common/amr.c new file mode 100644 index 00000000..05d1aaac --- /dev/null +++ b/src/common/amr.c @@ -0,0 +1,170 @@ +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/logging.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/amr.h> + +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->bts_mode[i].mode, + amr_mrc->bts_mode[i].threshold, + amr_mrc->bts_mode[i].hysteresis); + LOGPC(ss, logl, "\n"); +} + +static inline 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->bts_mode[i].mode == cmi) + return i; + } + return -EINVAL; +} + +static inline uint8_t set_cmr_mode_idx(const struct amr_multirate_conf *amr_mrc, + uint8_t cmr) +{ + int 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 */ + return 0; + } + + 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); + return 0; + } + return rc; +} + +static inline uint8_t set_cmi_mode_idx(const struct amr_multirate_conf *amr_mrc, + uint8_t cmi) +{ + int 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); + return 0; + } + return rc; +} + +void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc, + uint8_t cmi, uint8_t cmr) +{ + data[0] = set_cmi_mode_idx(amr_mrc, cmi); + data[1] = set_cmr_mode_idx(amr_mrc, cmr); +} + +/* 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 unknown\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->bts_mode[j++].mode = i; + } + } + + if (num_codecs >= 2) { + amr_mrc->bts_mode[0].threshold = mr_conf[1] & 0x3F; + amr_mrc->bts_mode[0].hysteresis = mr_conf[2] >> 4; + } + if (num_codecs >= 3) { + amr_mrc->bts_mode[1].threshold = + ((mr_conf[2] & 0xF) << 2) | (mr_conf[3] >> 6); + amr_mrc->bts_mode[1].hysteresis = (mr_conf[3] >> 2) & 0xF; + } + if (num_codecs >= 4) { + amr_mrc->bts_mode[2].threshold = + ((mr_conf[3] & 0x3) << 4) | (mr_conf[4] >> 4); + amr_mrc->bts_mode[2].hysteresis = 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; + } + } +} diff --git a/src/common/bts.c b/src/common/bts.c new file mode 100644 index 00000000..abbaeb46 --- /dev/null +++ b/src/common/bts.c @@ -0,0 +1,759 @@ +/* BTS support code common to all supported BTS models */ + +/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <unistd.h> +#include <stdio.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/trau/osmo_ortp.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */ +#define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */ + +static void bts_update_agch_max_queue_length(struct gsm_bts *bts); + +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; +} + +static const struct rate_ctr_desc bts_ctr_desc[] = { + [BTS_CTR_PAGING_RCVD] = {"paging:rcvd", "Received paging requests (Abis)"}, + [BTS_CTR_PAGING_DROP] = {"paging:drop", "Dropped paging requests (Abis)"}, + [BTS_CTR_PAGING_SENT] = {"paging:sent", "Sent paging requests (Um)"}, + + [BTS_CTR_RACH_RCVD] = {"rach:rcvd", "Received RACH requests (Um)"}, + [BTS_CTR_RACH_DROP] = {"rach:drop", "Dropped RACH requests (Um)"}, + [BTS_CTR_RACH_HO] = {"rach:handover", "Received RACH requests (Handover)"}, + [BTS_CTR_RACH_CS] = {"rach:cs", "Received RACH requests (CS/Abis)"}, + [BTS_CTR_RACH_PS] = {"rach:ps", "Received RACH requests (PS/PCU)"}, + + [BTS_CTR_AGCH_RCVD] = {"agch:rcvd", "Received AGCH requests (Abis)"}, + [BTS_CTR_AGCH_SENT] = {"agch:sent", "Sent AGCH requests (Abis)"}, + [BTS_CTR_AGCH_DELETED] = {"agch:delete", "Sent AGCH DELETE IND (Abis)"}, +}; +static const struct rate_ctr_group_desc bts_ctrg_desc = { + "bts", + "base transceiver station", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(bts_ctr_desc), + bts_ctr_desc +}; + +/* Initialize the BTS data structures, called before config + * file reading */ +int bts_init(struct gsm_bts *bts) +{ + int rc, i; + static int initialized = 0; + void *tall_rtp_ctx; + + /* add to list of BTSs */ + llist_add_tail(&bts->list, &bts_gsmnet.bts_list); + + bts->band = GSM_BAND_1800; + + INIT_LLIST_HEAD(&bts->agch_queue.queue); + bts->agch_queue.length = 0; + + bts->ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); + + /* enable management with default levels, + * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to + * disable this feature. + */ + bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT; + bts->agch_queue.high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT; + bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT; + + /* configurable via VTY */ + bts->paging_state = paging_init(bts, 200, 0); + bts->ul_power_target = -75; /* dBm default */ + bts->rtp_jitter_adaptive = false; + bts->rtp_port_range_start = 16384; + bts->rtp_port_range_end = 17407; + bts->rtp_port_range_next = bts->rtp_port_range_start; + + /* configurable via OML */ + bts->load.ccch.load_ind_period = 112; + load_timer_start(bts); + bts->rtp_jitter_buf_ms = 100; + bts->max_ta = 63; + bts->ny1 = 4; + bts->t3105_ms = 300; + bts->min_qual_rach = MIN_QUAL_RACH; + bts->min_qual_norm = MIN_QUAL_NORM; + bts->max_ber10k_rach = 1707; /* 7 of 41 bits is Eb/N0 of 0 dB = 0.1707 */ + bts->pcu.sock_path = talloc_strdup(bts, PCU_SOCK_DEFAULT); + for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) + bts->t200_ms[i] = oml_default_t200_ms[i]; + + /* default RADIO_LINK_TIMEOUT */ + bts->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); + + /* allocate a talloc pool for ORTP to ensure it doesn't have to go back + * to the libc malloc all the time */ + tall_rtp_ctx = talloc_pool(tall_bts_ctx, 262144); + osmo_rtp_init(tall_rtp_ctx); + + rc = bts_model_init(bts); + if (rc < 0) { + llist_del(&bts->list); + return rc; + } + + /* TRX0 was allocated early during gsm_bts_alloc, not later through VTY */ + bts_trx_init(bts->c0); + bts_gsmnet.num_bts++; + + if (!initialized) { + osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL); + initialized = 1; + } + + INIT_LLIST_HEAD(&bts->smscb_state.queue); + INIT_LLIST_HEAD(&bts->oml_queue); + + /* register DTX DL FSM */ + rc = osmo_fsm_register(&dtx_dl_amr_fsm); + OSMO_ASSERT(rc == 0); + + return rc; +} + +/* Initialize the TRX data structures, called before config + * file reading */ +int bts_trx_init(struct gsm_bts_trx *trx) +{ + /* initialize bts data structure */ + struct trx_power_params *tpp = &trx->power_params; + int rc, 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(0); + tpp->ramp.step_size_mdB = to_mdB(2); + tpp->ramp.step_interval_sec = 1; + + rc = bts_model_trx_init(trx); + if (rc < 0) { + llist_del(&trx->list); + return rc; + } + return 0; +} + +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_reverse(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; + int rc; + + 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) + rc = rsl_tx_rf_res(trx); + else + rc = bts_model_trx_deact_rf(trx); + if (rc < 0) + oml_fail_rep(OSMO_EVT_MAJ_RSL_FAIL, + link ? "Failed to establish RSL link (%d)" : + "Failed to deactivate RF (%d)", rc); + return 0; +} + +/* set the availability of the TRX (used by PHY driver) */ +int trx_set_available(struct gsm_bts_trx *trx, int avail) +{ + int tn; + + LOGP(DSUM, LOGL_INFO, "TRX(%d): Setting available = %d\n", + trx->nr, avail); + if (avail) { + int op_state = trx->rsl_link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED; + oml_mo_state_chg(&trx->mo, op_state, NM_AVSTATE_OK); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) + oml_mo_state_chg(&trx->ts[tn].mo, op_state, NM_AVSTATE_OK); + } else { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_NOT_INSTALLED); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + } + 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); + oml_set_lchan_t200(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; +} + +static void bts_update_agch_max_queue_length(struct gsm_bts *bts) +{ + struct gsm48_system_information_type_3 *si3; + int old_max_length = bts->agch_queue.max_length; + + if (!(bts->si_valid & (1<<SYSINFO_TYPE_3))) + return; + + si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3); + + bts->agch_queue.max_length = + bts_agch_max_queue_length(si3->rach_control.tx_integer, + si3->control_channel_desc.ccch_conf); + + if (bts->agch_queue.max_length != old_max_length) + LOGP(DRSL, LOGL_INFO, "Updated AGCH max queue length to %d\n", + bts->agch_queue.max_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) +{ + int hard_limit = 100; + struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg); + + if (bts->agch_queue.length > hard_limit) { + LOGP(DSUM, LOGL_ERROR, + "AGCH: too many messages in queue, " + "refusing message type %s, length = %d/%d\n", + gsm48_rr_msg_name(((struct gsm48_imm_ass *)msgb_l3(msg))->msg_type), + bts->agch_queue.length, bts->agch_queue.max_length); + + bts->agch_queue.rejected_msgs++; + return -ENOMEM; + } + + if (bts->agch_queue.length > 0) { + struct msgb *last_msg = + llist_entry(bts->agch_queue.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)) { + bts->agch_queue.merged_msgs++; + msgb_free(msg); + return 0; + } + } + + msgb_enqueue(&bts->agch_queue.queue, msg); + bts->agch_queue.length++; + + return 0; +} + +struct msgb *bts_agch_dequeue(struct gsm_bts *bts) +{ + struct msgb *msg = msgb_dequeue(&bts->agch_queue.queue); + if (!msg) + return NULL; + + bts->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 msgb *msg, *msg2; + int max_len, slope, offs; + int level_low = bts->agch_queue.low_level; + int level_high = bts->agch_queue.high_level; + int level_thres = bts->agch_queue.thresh_level; + + max_len = bts->agch_queue.max_length; + + if (max_len == 0) + max_len = 1; + + if (bts->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, &bts->agch_queue.queue, list) { + struct gsm48_imm_ass *imm_ass_cmd = msgb_l3(msg); + int p_drop; + + p_drop = (bts->agch_queue.length - offs) * slope / max_len; + + if ((random() & 0xffff) >= p_drop) + return; + + llist_del(&msg->list); + bts->agch_queue.length--; + rsl_tx_delete_ind(bts, (uint8_t *)imm_ass_cmd, msgb_l3len(msg)); + rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_DELETED); + msgb_free(msg); + + bts->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; + 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(bts->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; + + rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_SENT); + + /* Copy AGCH message */ + memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg)); + rc = msgb_l3len(msg); + msgb_free(msg); + + if (is_ag_res) + bts->agch_queue.agch_msgs++; + else + bts->agch_queue.pch_msgs++; + + return rc; +} + +int bts_supports_cipher(struct gsm_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) +{ + return &bts->gsm_time; +} + +int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, + enum gsm48_chan_mode cm) +{ + enum gsm_bts_features feature = _NUM_BTS_FEAT; + + /* Before the requested pchan/cm combination can be checked, we need to + * convert it to a feature identifier we can check */ + switch (pchan) { + case GSM_PCHAN_TCH_F: + switch(cm) { + case GSM48_CMODE_SPEECH_V1: + feature = BTS_FEAT_SPEECH_F_V1; + break; + case GSM48_CMODE_SPEECH_EFR: + feature = BTS_FEAT_SPEECH_F_EFR; + break; + case GSM48_CMODE_SPEECH_AMR: + feature = BTS_FEAT_SPEECH_F_AMR; + break; + default: + /* Invalid speech codec type => Not supported! */ + return 0; + } + break; + + case GSM_PCHAN_TCH_H: + switch(cm) { + case GSM48_CMODE_SPEECH_V1: + feature = BTS_FEAT_SPEECH_H_V1; + break; + case GSM48_CMODE_SPEECH_AMR: + feature = BTS_FEAT_SPEECH_H_AMR; + break; + default: + /* Invalid speech codec type => Not supported! */ + return 0; + } + break; + + default: + LOGP(DRSL, LOGL_ERROR, "BTS %u: unhandled pchan %s when checking mode %s\n", + bts->nr, gsm_pchan_name(pchan), gsm48_chan_mode_name(cm)); + return 0; + } + + /* Check if the feature is supported by this BTS */ + if (gsm_bts_has_feature(bts, feature)) + return 1; + + return 0; +} diff --git a/src/common/bts_ctrl_commands.c b/src/common/bts_ctrl_commands.c new file mode 100644 index 00000000..4efb4ee3 --- /dev/null +++ b/src/common/bts_ctrl_commands.c @@ -0,0 +1,93 @@ +/* Control Interface for osmo-bts */ + +/* (C) 2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/ctrl/control_cmd.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/tx_power.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/bts.h> + +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; +} + +CTRL_CMD_DEFINE_WO_NOVRF(oml_alert, "oml-alert"); +static int set_oml_alert(struct ctrl_cmd *cmd, void *data) +{ + /* Note: we expect signal dispatch to be synchronous */ + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, cmd->value); + + cmd->reply = "OK"; + + return CTRL_CMD_REPLY; +} + +int bts_ctrl_cmds_install(struct gsm_bts *bts) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_therm_att); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oml_alert); + + return rc; +} diff --git a/src/common/bts_ctrl_lookup.c b/src/common/bts_ctrl_lookup.c new file mode 100644 index 00000000..f0157e9a --- /dev/null +++ b/src/common/bts_ctrl_lookup.c @@ -0,0 +1,115 @@ +/* Control Interface for osmo-bts */ + +/* (C) 2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> + +#include <osmocom/vty/command.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/ports.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/control_if.h> + +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, + const char *bind_addr, uint16_t port) +{ + struct ctrl_handle *hdl; + int rc = 0; + + hdl = ctrl_interface_setup_dynip(bts, bind_addr, port, + bts_ctrl_node_lookup); + if (!hdl) + return NULL; + + rc = bts_ctrl_cmds_install(bts); + if (rc) { + /* FIXME: close control interface */ + return NULL; + } + + rc = bts_model_ctrl_cmds_install(bts); + if (rc) { + /* FIXME: cleanup generic control commands */ + /* FIXME: close control interface */ + return NULL; + } + + return hdl; +} diff --git a/src/common/cbch.c b/src/common/cbch.c new file mode 100644 index 00000000..c628cb5a --- /dev/null +++ b/src/common/cbch.c @@ -0,0 +1,198 @@ +/* Cell Broadcast routines */ + +/* (C) 2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/protocol/gsm_04_12.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/cbch.h> +#include <osmo-bts/logging.h> + +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 gsm412_block_type *block_type; + struct smscb_msg *msg = bts->smscb_state.cur_msg; + + if (!msg) { + /* No message: Send NULL mesage */ + return get_smscb_null_block(out); + } + OSMO_ASSERT(msg->next_seg < 4); + + 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; + OSMO_ASSERT(to_copy >= 0); + + /* 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(bts->smscb_state.cur_msg); + bts->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 smscb_msg *scm; + + if (msg_len > sizeof(scm->msg)) { + LOGP(DLSMS, LOGL_ERROR, + "Cannot process SMSCB of %u bytes (max %zu)\n", + msg_len, sizeof(scm->msg)); + return -EINVAL; + } + + scm = talloc_zero_size(bts, sizeof(*scm)); + if (!scm) + return -1; + + /* 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, &bts->smscb_state.queue); + /* FIXME: limit queue size and optionally send CBCH LOAD Information (overflow) via RSL */ + + return 0; +} + +static struct smscb_msg *select_next_smscb(struct gsm_bts *bts) +{ + struct smscb_msg *msg; + + msg = llist_first_entry_or_null(&bts->smscb_state.queue, struct smscb_msg, list); + if (!msg) { + /* FIXME: send CBCH LOAD Information (underflow) via RSL */ + return NULL; + } + + 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) +{ + 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 */ + bts->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; +} diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c new file mode 100644 index 00000000..38e22c95 --- /dev/null +++ b/src/common/dtx_dl_amr_fsm.c @@ -0,0 +1,477 @@ +/* DTX DL AMR FSM */ + +/* (C) 2016 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/logging.h> + +void dtx_fsm_voice(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_VOICE: + case E_FACCH: + break; + case E_SID_F: + osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0); + break; + case E_SID_U: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + case E_INHIB: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Inexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_sid_f1(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_SID_F: +/* FIXME: what shall we do if we get SID-FIRST _again_ (twice in a row)? + Was observed during testing, let's just ignore it for now */ + break; + case E_SID_U: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_F, 0, 0); + break; + case E_FIRST: + osmo_fsm_inst_state_chg(fi, ST_SID_F2, 0, 0); + break; + case E_ONSET: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_sid_f2(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0); + break; + case E_ONSET: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_V_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_F_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_U_INH_V_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_U_INH_F_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_VOICE: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_VOICE: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_noinh(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0); + break; + case E_VOICE: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_SID_U, 0, 0); + break; + case E_SID_U: + case E_SID_F: +/* FIXME: what shall we do if we get SID-FIRST _after_ sending SID-UPDATE? + Was observed during testing, let's just ignore it for now */ + break; + case E_ONSET: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_sid_upd(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_U_INH_F, 0, 0); + break; + case E_VOICE: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + case E_INHIB: + osmo_fsm_inst_state_chg(fi, ST_U_INH_V, 0, 0); + break; + case E_SID_U: + case E_SID_F: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_v(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_f(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_ONSET_F_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_facch(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_SID_U: + case E_SID_F: + case E_FACCH: + break; + case E_VOICE: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +static struct osmo_fsm_state dtx_dl_amr_fsm_states[] = { + /* default state for non-DTX and DTX when SPEECH is in progress */ + [ST_VOICE] = { + .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_VOICE) | X(E_FACCH) | X(E_INHIB), + .out_state_mask = X(ST_SID_F1) | X(ST_U_NOINH) | X(ST_F1_INH_V), + .name = "Voice", + .action = dtx_fsm_voice, + }, + /* SID-FIRST or SID-FIRST-P1 in case of AMR HR: + start of silence period (might be interrupted in case of AMR HR) */ + [ST_SID_F1]= { + .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_FACCH) | X(E_FIRST) | X(E_ONSET), + .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_SID_F2) | X(ST_ONSET_V), + .name = "SID-FIRST (P1)", + .action = dtx_fsm_sid_f1, + }, + /* SID-FIRST P2 (only for AMR HR): + actual start of silence period in case of AMR HR */ + [ST_SID_F2]= { + .in_event_mask = X(E_COMPL) | X(E_FACCH) | X(E_ONSET), + .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_ONSET_V), + .name = "SID-FIRST (P2)", + .action = dtx_fsm_sid_f2, + }, + /* SID-FIRST Inhibited: incoming SPEECH (only for AMR HR) */ + [ST_F1_INH_V]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_F1_INH_V_REC), + .name = "SID-FIRST (Inh, SPEECH)", + .action = dtx_fsm_f1_inh_v, + }, + /* SID-FIRST Inhibited: incoming FACCH frame (only for AMR HR) */ + [ST_F1_INH_F]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_F1_INH_F_REC), + .name = "SID-FIRST (Inh, FACCH)", + .action = dtx_fsm_f1_inh_f, + }, + /* SID-UPDATE Inhibited: incoming SPEECH (only for AMR HR) */ + [ST_U_INH_V]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_U_INH_V_REC), + .name = "SID-UPDATE (Inh, SPEECH)", + .action = dtx_fsm_u_inh_v, + }, + /* SID-UPDATE Inhibited: incoming FACCH frame (only for AMR HR) */ + [ST_U_INH_F]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_U_INH_F_REC), + .name = "SID-UPDATE (Inh, FACCH)", + .action = dtx_fsm_u_inh_f, + }, + /* SID-UPDATE: Inhibited not allowed (only for AMR HR) */ + [ST_U_NOINH]= { + .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F) | X(E_ONSET), + .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_SID_U) | X(ST_ONSET_V), + .name = "SID-UPDATE (NoInh)", + .action = dtx_fsm_u_noinh, + }, + /* SID-FIRST Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the voice that caused it */ + [ST_F1_INH_V_REC]= { + .in_event_mask = X(E_COMPL) | X(E_VOICE), + .out_state_mask = X(ST_VOICE), + .name = "SID-FIRST (Inh, SPEECH, Rec)", + .action = dtx_fsm_f1_inh_v_rec, + }, + /* SID-FIRST Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the data that caused it */ + [ST_F1_INH_F_REC]= { + .in_event_mask = X(E_COMPL) | X(E_FACCH), + .out_state_mask = X(ST_FACCH), + .name = "SID-FIRST (Inh, FACCH, Rec)", + .action = dtx_fsm_f1_inh_f_rec, + }, + /* SID-UPDATE Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the voice that caused it */ + [ST_U_INH_V_REC]= { + .in_event_mask = X(E_COMPL) | X(E_VOICE), + .out_state_mask = X(ST_VOICE), + .name = "SID-UPDATE (Inh, SPEECH, Rec)", + .action = dtx_fsm_u_inh_v_rec, + }, + /* SID-UPDATE Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the data that caused it */ + [ST_U_INH_F_REC]= { + .in_event_mask = X(E_COMPL) | X(E_FACCH), + .out_state_mask = X(ST_FACCH), + .name = "SID-UPDATE (Inh, FACCH, Rec)", + .action = dtx_fsm_u_inh_f_rec, + }, + /* Silence period with periodic comfort noise data updates */ + [ST_SID_U]= { + .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_INHIB) | X(E_SID_U) | X(E_SID_F), + .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_U_INH_V) | X(ST_U_INH_F) | X(ST_U_NOINH), + .name = "SID-UPDATE (AMR/HR)", + .action = dtx_fsm_sid_upd, + }, + /* ONSET - end of silent period due to incoming SPEECH frame */ + [ST_ONSET_V]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_ONSET_V_REC), + .name = "ONSET (SPEECH)", + .action = dtx_fsm_onset_v, + }, + /* ONSET - end of silent period due to incoming FACCH frame */ + [ST_ONSET_F]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_ONSET_F_REC), + .name = "ONSET (FACCH)", + .action = dtx_fsm_onset_f, + }, + /* ONSET recursion in progress: + ONSET itself was already sent, now have to send the voice that caused it */ + [ST_ONSET_V_REC]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_VOICE), + .name = "ONSET (SPEECH, Rec)", + .action = dtx_fsm_onset_v_rec, + }, + /* ONSET recursion in progress: + ONSET itself was already sent, now have to send the data that caused it */ + [ST_ONSET_F_REC]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_FACCH), + .name = "ONSET (FACCH, Rec)", + .action = dtx_fsm_onset_f_rec, + }, + /* FACCH sending state */ + [ST_FACCH]= { + .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F), + .out_state_mask = X(ST_VOICE) | X(ST_SID_F1), + .name = "FACCH", + .action = dtx_fsm_facch, + }, +}; + +const struct value_string dtx_dl_amr_fsm_event_names[] = { + { E_VOICE, "Voice" }, + { E_ONSET, "ONSET" }, + { E_FACCH, "FACCH" }, + { E_COMPL, "Complete" }, + { E_FIRST, "FIRST P1->P2" }, + { E_INHIB, "Inhibit" }, + { E_SID_F, "SID-FIRST" }, + { E_SID_U, "SID-UPDATE" }, + { 0, NULL } +}; + +struct osmo_fsm dtx_dl_amr_fsm = { + .name = "DTX_DL_AMR_FSM", + .states = dtx_dl_amr_fsm_states, + .num_states = ARRAY_SIZE(dtx_dl_amr_fsm_states), + .event_names = dtx_dl_amr_fsm_event_names, + .log_subsys = DL1C, +}; diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c new file mode 100644 index 00000000..2d9af783 --- /dev/null +++ b/src/common/gsm_data_shared.c @@ -0,0 +1,807 @@ +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include <netinet/in.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/core/statistics.h> + +#include <osmo-bts/gsm_data.h> + +void gsm_abis_mo_reset(struct gsm_abis_mo *mo) +{ + mo->nm_state.operational = NM_OPSTATE_NULL; + mo->nm_state.availability = NM_AVSTATE_POWER_OFF; +} + +static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts, + uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3) +{ + mo->bts = bts; + mo->obj_class = obj_class; + mo->obj_inst.bts_nr = p1; + mo->obj_inst.trx_nr = p2; + mo->obj_inst.ts_nr = p3; + gsm_abis_mo_reset(mo); +} + +const struct value_string bts_attribute_names[] = { + OSMO_VALUE_STRING(BTS_TYPE_VARIANT), + OSMO_VALUE_STRING(BTS_SUB_MODEL), + OSMO_VALUE_STRING(TRX_PHY_VERSION), + { 0, NULL } +}; + +enum bts_attribute str2btsattr(const char *s) +{ + return get_string_value(bts_attribute_names, s); +} + +const char *btsatttr2str(enum bts_attribute v) +{ + return get_value_string(bts_attribute_names, v); +} + +const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { + { BTS_UNKNOWN, "unknown" }, + { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, + { BTS_OSMO_OC2G, "osmo-bts-oc2g" }, + { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, + { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, + { BTS_OSMO_TRX, "omso-bts-trx" }, + { BTS_OSMO_VIRTUAL, "omso-bts-virtual" }, + { BTS_OSMO_OMLDUMMY, "omso-bts-omldummy" }, + { 0, NULL } +}; + +enum gsm_bts_type_variant str2btsvariant(const char *arg) +{ + return get_string_value(osmo_bts_variant_names, arg); +} + +const char *btsvariant2str(enum gsm_bts_type_variant v) +{ + return get_value_string(osmo_bts_variant_names, v); +} + +const struct value_string gsm_bts_features_descs[] = { + { BTS_FEAT_HSCSD, "HSCSD" }, + { BTS_FEAT_GPRS, "GPRS" }, + { BTS_FEAT_EGPRS, "EGPRS" }, + { BTS_FEAT_ECSD, "ECSD" }, + { BTS_FEAT_HOPPING, "Frequency Hopping" }, + { BTS_FEAT_MULTI_TSC, "Multi-TSC" }, + { BTS_FEAT_OML_ALERTS, "OML Alerts" }, + { BTS_FEAT_AGCH_PCH_PROP, "AGCH/PCH proportional allocation" }, + { BTS_FEAT_CBCH, "CBCH" }, + { BTS_FEAT_SPEECH_F_V1, "Fullrate speech V1" }, + { BTS_FEAT_SPEECH_H_V1, "Halfrate speech V1" }, + { BTS_FEAT_SPEECH_F_EFR, "Fullrate speech EFR" }, + { BTS_FEAT_SPEECH_F_AMR, "Fullrate speech AMR" }, + { BTS_FEAT_SPEECH_H_AMR, "Halfrate speech AMR" }, + { 0, NULL } +}; + +const struct value_string gsm_chreq_descs[] = { + { GSM_CHREQ_REASON_EMERG, "emergency call" }, + { GSM_CHREQ_REASON_PAG, "answer to paging" }, + { GSM_CHREQ_REASON_CALL, "call re-establishment" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, + { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, + { GSM_CHREQ_REASON_OTHER, "other" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_names[13] = { + { GSM_PCHAN_NONE, "NONE" }, + { GSM_PCHAN_CCCH, "CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, + { GSM_PCHAN_TCH_F, "TCH/F" }, + { GSM_PCHAN_TCH_H, "TCH/H" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, + { GSM_PCHAN_PDCH, "PDCH" }, + { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, + { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_descs[13] = { + { GSM_PCHAN_NONE, "Physical Channel not configured" }, + { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, + { GSM_PCHAN_CCCH_SDCCH4, + "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" }, + { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" }, + { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" }, + { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" }, + { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" }, + { 0, NULL } +}; + +const char *gsm_pchan_name(enum gsm_phys_chan_config c) +{ + return get_value_string(gsm_pchant_names, c); +} + +enum gsm_phys_chan_config gsm_pchan_parse(const char *name) +{ + return get_string_value(gsm_pchant_names, name); +} + +/* TODO: move to libosmocore, next to gsm_chan_t_names? */ +const char *gsm_lchant_name(enum gsm_chan_t c) +{ + return get_value_string(gsm_chan_t_names, c); +} + +static const struct value_string lchan_s_names[] = { + { LCHAN_S_NONE, "NONE" }, + { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, + { LCHAN_S_ACTIVE, "ACTIVE" }, + { LCHAN_S_INACTIVE, "INACTIVE" }, + { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, + { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, + { 0, NULL } +}; + +const char *gsm_lchans_name(enum gsm_lchan_state s) +{ + return get_value_string(lchan_s_names, s); +} + +static const struct value_string chreq_names[] = { + { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, + { GSM_CHREQ_REASON_PAG, "PAGING" }, + { GSM_CHREQ_REASON_CALL, "CALL" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, + { GSM_CHREQ_REASON_OTHER, "OTHER" }, + { 0, NULL } +}; + +const char *gsm_chreq_name(enum gsm_chreq_reason_t c) +{ + return get_value_string(chreq_names, c); +} + +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) +{ + struct gsm_bts *bts; + + if (num >= net->num_bts) + return NULL; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == num) + return bts; + } + + return NULL; +} + +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); + int k; + + if (!trx) + return NULL; + + trx->bts = bts; + trx->nr = bts->num_trx++; + trx->mo.nm_state.administrative = NM_STATE_UNLOCKED; + + gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, + bts->nr, trx->nr, 0xff); + gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, + bts->nr, trx->nr, 0xff); + + for (k = 0; k < TRX_NR_TS; k++) { + struct gsm_bts_trx_ts *ts = &trx->ts[k]; + int l; + + ts->trx = trx; + ts->nr = k; + ts->pchan = GSM_PCHAN_NONE; + ts->dyn.pchan_is = GSM_PCHAN_NONE; + ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->tsc = -1; + + gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL, + bts->nr, trx->nr, ts->nr); + + ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data); + ts->hopping.arfcns.data = ts->hopping.arfcns_data; + ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data); + ts->hopping.ma.data = ts->hopping.ma_data; + + for (l = 0; l < TS_MAX_LCHAN; l++) { + struct gsm_lchan *lchan; + char *name; + lchan = &ts->lchan[l]; + + lchan->ts = ts; + lchan->nr = l; + lchan->type = GSM_LCHAN_NONE; + + name = gsm_lchan_name_compute(lchan); + lchan->name = talloc_strdup(trx, name); + INIT_LLIST_HEAD(&lchan->sapi_cmds); + } + } + + if (trx->nr != 0) + trx->nominal_power = bts->c0->nominal_power; + + llist_add_tail(&trx->list, &bts->trx_list); + + return trx; +} + + +static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; +static const uint8_t bts_cell_timer_default[] = + { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 }; +static const struct gprs_rlc_cfg rlc_cfg_default = { + .parameter = { + [RLC_T3142] = 20, + [RLC_T3169] = 5, + [RLC_T3191] = 5, + [RLC_T3193] = 160, /* 10ms */ + [RLC_T3195] = 5, + [RLC_N3101] = 10, + [RLC_N3103] = 4, + [RLC_N3105] = 8, + [CV_COUNTDOWN] = 15, + [T_DL_TBF_EXT] = 250 * 10, /* ms */ + [T_UL_TBF_EXT] = 250 * 10, /* ms */ + }, + .paging = { + .repeat_time = 5 * 50, /* ms */ + .repeat_count = 3, + }, + .cs_mask = 0x1fff, + .initial_cs = 2, + .initial_mcs = 6, +}; + +struct gsm_bts *gsm_bts_alloc(void *ctx, uint8_t bts_num) +{ + struct gsm_bts *bts = talloc_zero(ctx, struct gsm_bts); + int i; + + if (!bts) + return NULL; + + bts->nr = bts_num; + bts->num_trx = 0; + INIT_LLIST_HEAD(&bts->trx_list); + bts->ms_max_power = 15; /* dBm */ + + gsm_mo_init(&bts->mo, bts, NM_OC_BTS, + bts->nr, 0xff, 0xff); + gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { + bts->gprs.nsvc[i].bts = bts; + bts->gprs.nsvc[i].id = i; + gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC, + bts->nr, i, 0xff); + } + memcpy(&bts->gprs.nse.timer, bts_nse_timer_default, + sizeof(bts->gprs.nse.timer)); + gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE, + bts->nr, 0xff, 0xff); + memcpy(&bts->gprs.cell.timer, bts_cell_timer_default, + sizeof(bts->gprs.cell.timer)); + gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL, + bts->nr, 0xff, 0xff); + memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default, + sizeof(bts->gprs.cell.rlc_cfg)); + + /* create our primary TRX. It will be initialized during bts_init() */ + bts->c0 = gsm_bts_trx_alloc(bts); + if (!bts->c0) { + talloc_free(bts); + return NULL; + } + bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + + bts->rach_b_thresh = -1; + bts->rach_ldavg_slots = -1; + bts->features.data = &bts->_features_data[0]; + bts->features.data_len = sizeof(bts->_features_data); + + /* si handling */ + bts->bcch_change_mark = 1; + + return bts; +} + +struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num) +{ + struct gsm_bts_trx *trx; + + if (num >= bts->num_trx) + return NULL; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == num) + return trx; + } + + return NULL; +} + +static char ts2str[255]; + +char *gsm_trx_name(const struct gsm_bts_trx *trx) +{ + if (!trx) + snprintf(ts2str, sizeof(ts2str), "(trx=NULL)"); + else + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)", + trx->bts->nr, trx->nr); + + return ts2str; +} + + +char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) +{ + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr); + + return ts2str; +} + +/*! Log timeslot number with full pchan information */ +char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is)); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan)); + break; + } + + return ts2str; +} + +char *gsm_lchan_name_compute(const struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); + + return ts2str; +} + +/* obtain the MO structure for a given object instance */ +struct gsm_abis_mo * +gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + struct gsm_abis_mo *mo = NULL; + + switch (obj_class) { + case NM_OC_BTS: + mo = &bts->mo; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->mo; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bb_transc.mo; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + mo = &trx->ts[obj_inst->ts_nr].mo; + break; + case NM_OC_SITE_MANAGER: + mo = &bts->site_mgr.mo; + break; + case NM_OC_GPRS_NSE: + mo = &bts->gprs.nse.mo; + break; + case NM_OC_GPRS_CELL: + mo = &bts->gprs.cell.mo; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo; + break; + } + return mo; +} + +/* obtain the gsm_nm_state data structure for a given object instance */ +struct gsm_nm_state * +gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_abis_mo *mo; + + mo = gsm_objclass2mo(bts, obj_class, obj_inst); + if (!mo) + return NULL; + + return &mo->nm_state; +} + +/* obtain the in-memory data structure of a given object instance */ +void * +gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + void *obj = NULL; + + switch (obj_class) { + case NM_OC_BTS: + obj = bts; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = trx; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = &trx->bb_transc; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + obj = &trx->ts[obj_inst->ts_nr]; + break; + case NM_OC_SITE_MANAGER: + obj = &bts->site_mgr; + break; + case NM_OC_GPRS_NSE: + obj = &bts->gprs.nse; + break; + case NM_OC_GPRS_CELL: + obj = &bts->gprs.cell; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + obj = &bts->gprs.nsvc[obj_inst->trx_nr]; + break; + } + return obj; +} + +/* See Table 10.5.25 of GSM04.08 */ +uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, + uint8_t ts_nr, uint8_t lchan_nr) +{ + uint8_t cbits, chan_nr; + + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + + switch (pchan) { + case GSM_PCHAN_TCH_F: + OSMO_ASSERT(lchan_nr == 0); + cbits = 0x01; + break; + case GSM_PCHAN_PDCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = RSL_CHAN_OSMO_PDCH >> 3; + break; + case GSM_PCHAN_TCH_H: + OSMO_ASSERT(lchan_nr < 2); + cbits = 0x02; + cbits += lchan_nr; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* + * As a special hack for BCCH, lchan_nr == 4 may be passed + * here. This should never be sent in an RSL message. + * See osmo-bts-xxx/oml.c:opstart_compl(). + */ + if (lchan_nr == CCCH_LCHAN) + cbits = 0x10; /* BCCH */ + else { + OSMO_ASSERT(lchan_nr < 4); + cbits = 0x04; + cbits += lchan_nr; + } + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + OSMO_ASSERT(lchan_nr < 8); + cbits = 0x08; + cbits += lchan_nr; + break; + case GSM_PCHAN_CCCH: + default: + /* OSMO_ASSERT(lchan_nr == 0); + * FIXME: On octphy and litecell, we hit above assertion (see + * Max's comment at https://gerrit.osmocom.org/589 ); disabled + * for BTS until this is clarified; remove the #ifdef when it + * is fixed. Tracked in OS#2906. + */ +#pragma message "fix caller that passes lchan_nr != 0" + cbits = 0x10; + break; + } + + chan_nr = (cbits << 3) | (ts_nr & 0x7); + + return chan_nr; +} + +uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) +{ + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + /* Return chan_nr reflecting the current TS pchan, either a standard TCH kind, or the + * nonstandard value reflecting PDCH for Osmocom style dyn TS. */ + return gsm_lchan_as_pchan2chan_nr(lchan, + lchan->ts->dyn.pchan_is); + case GSM_PCHAN_TCH_F_PDCH: + /* For ip.access style dyn TS, we always want to use the chan_nr as if it was TCH/F. + * We're using custom PDCH ACT and DEACT messages that use the usual chan_nr values. */ + return gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F); + default: + return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); + } +} + +uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, + enum gsm_phys_chan_config as_pchan) +{ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && as_pchan == GSM_PCHAN_PDCH) + return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK); + return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr); +} + +/* return the gsm_lchan for the CBCH (if it exists at all) */ +struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts) +{ + struct gsm_lchan *lchan = NULL; + struct gsm_bts_trx *trx = bts->c0; + + if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + lchan = &trx->ts[0].lchan[2]; + else { + int i; + for (i = 0; i < 8; i++) { + if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { + lchan = &trx->ts[i].lchan[2]; + break; + } + } + } + + return lchan; +} + +/* 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, + int *rc) +{ + 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]; + bool ok = true; + + if (rc) + *rc = -EINVAL; + + 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 && + ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else if ((cbits & 0x1e) == 0x02) { + lch_idx = cbits & 0x1; /* TCH/H */ + if (ts->pchan != GSM_PCHAN_TCH_H && + ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } 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) + ok = false; + } 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) + ok = false; + } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_CCCH && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + /* FIXME: we should not return first sdcch4 !!! */ + } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else + return NULL; + + if (rc && ok) + *rc = 0; + + return &ts->lchan[lch_idx]; +} + +static const uint8_t subslots_per_pchan[] = { + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 0, + [GSM_PCHAN_PDCH] = 0, + [GSM_PCHAN_CCCH_SDCCH4] = 4, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 8, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, those TS are handled according to their dynamic state. + */ +}; + +/*! Return the actual pchan type, also heeding dynamic TS. */ +enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + else + return GSM_PCHAN_TCH_F; + default: + return ts->pchan; + } +} + +/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of + * logical channels available in the timeslot. */ +uint8_t ts_subslots(struct gsm_bts_trx_ts *ts) +{ + return subslots_per_pchan[ts_pchan(ts)]; +} + +static bool pchan_is_tch(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + return true; + default: + return false; + } +} + +bool ts_is_tch(struct gsm_bts_trx_ts *ts) +{ + return pchan_is_tch(ts_pchan(ts)); +} + +const char *gsm_trx_unit_id(struct gsm_bts_trx *trx) +{ + static char buf[23]; + + snprintf(buf, sizeof(buf), "%u/%u/%u", trx->bts->ip_access.site_id, + trx->bts->ip_access.bts_id, trx->nr); + return buf; +} + +const struct value_string lchan_ciph_state_names[] = { + { LCHAN_CIPH_NONE, "NONE" }, + { LCHAN_CIPH_RX_REQ, "RX_REQ" }, + { LCHAN_CIPH_RX_CONF, "RX_CONF" }, + { LCHAN_CIPH_RXTX_REQ, "RXTX_REQ" }, + { LCHAN_CIPH_RX_CONF_TX_REQ, "RX_CONF_TX_REQ" }, + { LCHAN_CIPH_RXTX_CONF, "RXTX_CONF" }, + { 0, NULL } +}; diff --git a/src/common/handover.c b/src/common/handover.c new file mode 100644 index 00000000..54b131ff --- /dev/null +++ b/src/common/handover.c @@ -0,0 +1,164 @@ +/* Paging message encoding + queue management */ + +/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org> + * Andreas Eversberg <jolly@eversberg.eu> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/rsl.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> + +/* 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; + + LOGP(DHO, LOGL_INFO, "%s T3105 timeout (%d resends left)\n", + gsm_lchan_name(lchan), bts->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 >= bts->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, bts->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; + + /* 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; + } + + /* Ignore handover on channels other than DCCH and SACCH */ + if (lchan->type != GSM_LCHAN_SDCCH && lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) { + LOGP(DHO, LOGL_ERROR, "%s handover RACH received on %s?!\n", + gsm_lchan_name(lchan), gsm_lchant_name(lchan->type)); + return; + } + + LOGP(DHO, LOGL_NOTICE, + "%s RACH on dedicated channel type %s received with TA=%u, ref=%u\n", + gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), acc_delay, ra); + + /* 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), bts->t3105_ms); + lchan->ho.t3105.cb = ho_t3105_cb; + lchan->ho.t3105.data = lchan; + osmo_timer_schedule(&lchan->ho.t3105, 0, bts->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; +} diff --git a/src/common/l1sap.c b/src/common/l1sap.c new file mode 100644 index 00000000..dba08dfb --- /dev/null +++ b/src/common/l1sap.c @@ -0,0 +1,1549 @@ +/* L1 SAP primitives */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <inttypes.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/l1sap.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/utils.h> + +#include <osmocom/trau/osmo_ortp.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/power_control.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/cbch.h> + + +#define CB_FCCH -1 +#define CB_SCH -2 +#define CB_BCCH -3 +#define CB_IDLE -4 + +/* according to TS 05.02 Clause 7 Table 3 of 9 an Figure 8a */ +static const int ccch_block_table[51] = { + CB_FCCH, CB_SCH,/* 0..1 */ + CB_BCCH, CB_BCCH, CB_BCCH, CB_BCCH, /* 2..5: BCCH */ + 0, 0, 0, 0, /* 6..9: B0 */ + CB_FCCH, CB_SCH,/* 10..11 */ + 1, 1, 1, 1, /* 12..15: B1 */ + 2, 2, 2, 2, /* 16..19: B2 */ + CB_FCCH, CB_SCH,/* 20..21 */ + 3, 3, 3, 3, /* 22..25: B3 */ + 4, 4, 4, 4, /* 26..29: B4 */ + CB_FCCH, CB_SCH,/* 30..31 */ + 5, 5, 5, 5, /* 32..35: B5 */ + 6, 6, 6, 6, /* 36..39: B6 */ + CB_FCCH, CB_SCH,/* 40..41 */ + 7, 7, 7, 7, /* 42..45: B7 */ + 8, 8, 8, 8, /* 46..49: B8 */ + -4 /* 50: Idle */ +}; + +/* determine the CCCH block number based on the frame number */ +unsigned int l1sap_fn2ccch_block(uint32_t fn) +{ + int rc = ccch_block_table[fn%51]; + /* if FN is negative, we were called for something that's not CCCH! */ + OSMO_ASSERT(rc >= 0); + return rc; +} + +struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx, + unsigned int chan_nr) +{ + unsigned int tn, ss; + + tn = L1SAP_CHAN2TS(chan_nr); + OSMO_ASSERT(tn < ARRAY_SIZE(trx->ts)); + + if (L1SAP_IS_CHAN_CBCH(chan_nr)) + ss = 2; /* CBCH is always on sub-slot 2 */ + else + ss = l1sap_chan2ss(chan_nr); + OSMO_ASSERT(ss < ARRAY_SIZE(trx->ts[tn].lchan)); + + return &trx->ts[tn].lchan[ss]; +} + +static struct gsm_lchan * +get_active_lchan_by_chan_nr(struct gsm_bts_trx *trx, unsigned int chan_nr) +{ + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + + if (lchan && lchan->state != LCHAN_S_ACTIVE) { + LOGP(DL1P, LOGL_NOTICE, "%s: assuming active lchan, but " + "state is %s\n", gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + return NULL; + } + return lchan; +} + +static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); + +static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan) +{ + uint32_t samples_passed, r; + + if (lchan->tch.last_fn != LCHAN_FN_DUMMY) { + /* 12/13 frames usable for audio in TCH, + 160 samples per RTP packet, + 1 RTP packet per 4 frames */ + samples_passed = (fn - lchan->tch.last_fn) * 12 * 160 / (13 * 4); + /* round number of samples to the nearest multiple of + GSM_RTP_DURATION */ + r = samples_passed + GSM_RTP_DURATION / 2; + r -= r % GSM_RTP_DURATION; + + if (r != GSM_RTP_DURATION) + LOGP(DRTP, LOGL_ERROR, "RTP clock out of sync with lower layer:" + " %"PRIu32" vs %d (%"PRIu32"->%"PRIu32")\n", + r, GSM_RTP_DURATION, lchan->tch.last_fn, fn); + } + return GSM_RTP_DURATION; +} + +/*! limit number of queue entries to %u; drops any surplus messages */ +static void queue_limit_to(const char *prefix, struct llist_head *queue, unsigned int limit) +{ + unsigned int count = llist_count(queue); + + if (count > limit) + LOGP(DL1P, LOGL_NOTICE, "%s: freeing %d queued frames\n", prefix, count-limit); + while (count > limit) { + struct msgb *tmp = msgb_dequeue(queue); + msgb_free(tmp); + count--; + } +} + +/* 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) +{ + int headroom = 128; + int size = headroom + sizeof(struct osmo_phsap_prim) + l2_len; + struct msgb *msg = msgb_alloc_headroom(size, headroom, "l1sap_prim"); + + if (!msg) + return NULL; + + msg->l1h = msgb_put(msg, sizeof(struct osmo_phsap_prim)); + + return msg; +} + +int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg, + struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn, + uint16_t ber10k, int16_t lqual_cb) +{ + struct osmo_phsap_prim *l1sap; + + LOGP(DL1P, LOGL_DEBUG, "%s Rx -> RTP: %s\n", + gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len)); + + rmsg->l2h = rmsg->data; + rmsg->l1h = msgb_push(rmsg, sizeof(*l1sap)); + 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; + l1sap->u.tch.fn = fn; + l1sap->u.tch.ber10k = ber10k; + l1sap->u.tch.lqual_cb = lqual_cb; + + return l1sap_up(trx, l1sap); +} + +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) +{ + 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; + + l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 0, 1); + + return 1; +} + +/* public helpers for the test */ +int bts_check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan, + uint8_t chan_nr) +{ + return check_for_ciph_cmd(msg, lchan, chan_nr); +} + +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 *ss, uint32_t fn, uint8_t **data, unsigned int *len, + uint8_t num_agch) +{ + struct msgb *msg = l1sap->oph.msg; + uint8_t chan_nr, link_id; + + *data = msgb_l2(msg); + *len = msgb_l2len(msg); + 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)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + if (l1sap_fn2ccch_block(fn) >= num_agch) + *chan_type = GSMTAP_CHANNEL_PCH; + else + *chan_type = GSMTAP_CHANNEL_AGCH; + } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) { + *chan_type = GSMTAP_CHANNEL_CBCH51; + } else if (L1SAP_IS_CHAN_PDCH(chan_nr)) { + *chan_type = GSMTAP_CHANNEL_PDTCH; + } + 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 *ss, uint32_t fn, uint8_t **data, unsigned int *len) +{ + struct msgb *msg = l1sap->oph.msg; + + *data = msgb_l2(msg); + *len = msgb_l2len(msg); + + if (L1SAP_IS_PTCCH(fn)) { + *chan_type = GSMTAP_CHANNEL_PTCCH; + *ss = L1SAP_FN2PTCCHBLOCK(fn); + if (l1sap->oph.primitive == PRIM_OP_INDICATION) { + OSMO_ASSERT(len > 0); + 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, unsigned 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 = (uint8_t *)&l1sap->u.rach_ind.ra; + *len = 1; + + return 0; +} + +/* Paging Request 1 with "no identity" content, i.e. empty/dummy paging */ +static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = { + 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b }; + +static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int len) +{ + switch (chan_type) { + case GSMTAP_CHANNEL_AGCH: + if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN)) + return true; + break; + case GSMTAP_CHANNEL_PCH: + if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN)) + return true; + break; + /* don't use 'default' case here as the above only conditionally return true */ + } + return false; +} + +static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + uint8_t *data; + unsigned 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): + fn = l1sap->u.data.fn; + tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr); + if (ts_is_pdch(&trx->ts[tn])) + rc = gsmtap_pdch(l1sap, &chan_type, &ss, fn, &data, + &len); + else + rc = gsmtap_ph_data(l1sap, &chan_type, &ss, fn, &data, + &len, num_agch(trx, "GSMTAP")); + 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; + } + + /* don't log fill frames via GSMTAP; they serve no purpose other than + * to clog up your logs */ + if (is_fill_frame(chan_type, data, len)) + return 0; + + gsmtap_send(gsmtap, trx->arfcn | uplink, tn, chan_type, ss, fn, 0, 0, + data, len); + + return 0; +} + +/* Calculate the number of RACH slots that expire in a certain GSM frame + * See also 3GPP TS 05.02 Clause 7 Table 5 of 9 */ +static unsigned int calc_exprd_rach_frames(struct gsm_bts *bts, uint32_t fn) +{ + int rach_frames_expired = 0; + uint8_t ccch_conf; + struct gsm48_system_information_type_3 *si3; + unsigned int blockno; + + si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3); + ccch_conf = si3->control_channel_desc.ccch_conf; + + if (ccch_conf == RSL_BCCH_CCCH_CONF_1_C) { + /* It is possible to combine a CCCH with an SDCCH4, in this + * case the CCCH will have to share the available frames with + * the other channel, this results in a limited number of + * available rach slots */ + blockno = fn % 51; + if (blockno == 4 || blockno == 5 + || (blockno >= 15 && blockno <= 36) || blockno == 45 + || blockno == 46) + rach_frames_expired = 1; + } else { + /* It is possible to have multiple CCCH channels on + * different physical channels (large cells), this + * also multiplies the available/expired RACH channels. + * See also TS 04.08, Chapter 10.5.2.11, table 10.29 */ + if (ccch_conf == RSL_BCCH_CCCH_CONF_2_NC) + rach_frames_expired = 2; + else if (ccch_conf == RSL_BCCH_CCCH_CONF_3_NC) + rach_frames_expired = 3; + else if (ccch_conf == RSL_BCCH_CCCH_CONF_4_NC) + rach_frames_expired = 4; + else + rach_frames_expired = 1; + } + + /* Each Frame has room for 4 RACH slots, since RACH + * slots are short enough to fit into a single radio + * burst, so we need to multiply the final result by 4 */ + return rach_frames_expired * 4; +} + +/* 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) +{ + int frames_expired; + + DEBUGPFN(DL1P, info_time_ind->fn, "Rx MPH_INFO time ind\n"); + + /* Calculate and check frame difference */ + frames_expired = info_time_ind->fn - bts->gsm_time.fn; + if (frames_expired > 1) { + if (bts->gsm_time.fn) + LOGPFN(DL1P, LOGL_ERROR, info_time_ind->fn, + "Invalid condition detected: Frame difference is %"PRIu32"-%"PRIu32"=%d > 1!\n", + info_time_ind->fn, bts->gsm_time.fn, frames_expired); + } + + /* Update our data structures with the current GSM time */ + gsm_fn2gsmtime(&bts->gsm_time, info_time_ind->fn); + + /* Update time on PCU interface */ + pcu_tx_time_ind(info_time_ind->fn); + + /* increment number of RACH slots that have passed by since the + * last time indication */ + bts->load.rach.total += + calc_exprd_rach_frames(bts, info_time_ind->fn) * frames_expired; + + return 0; +} + +static inline void set_ms_to_data(struct gsm_lchan *lchan, int16_t data, bool set_ms_to) +{ + if (!lchan) + return; + + if (data + 63 > 255) { /* According to 3GPP TS 48.058 §9.3.37 Timing Offset field cannot exceed 255 */ + LOGP(DL1P, LOGL_ERROR, "Attempting to set invalid Timing Offset value %d (MS TO = %u)!\n", + data, set_ms_to); + return; + } + + if (set_ms_to) { + lchan->ms_t_offs = data + 63; + lchan->p_offs = -1; + } else { + lchan->p_offs = data + 63; + lchan->ms_t_offs = -1; + } +} + +/* 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; + + lchan = get_active_lchan_by_chan_nr(trx, info_meas_ind->chan_nr); + if (!lchan) { + LOGPFN(DL1P, LOGL_ERROR, info_meas_ind->fn, + "No lchan for MPH INFO MEAS IND (chan_nr=%s)\n", rsl_chan_nr_str(info_meas_ind->chan_nr)); + return 0; + } + + DEBUGPFN(DL1P, info_meas_ind->fn, + "%s MPH_INFO meas ind, ta_offs_256bits=%d, ber10k=%d, inv_rssi=%u\n", + gsm_lchan_name(lchan), info_meas_ind->ta_offs_256bits, + info_meas_ind->ber10k, info_meas_ind->inv_rssi); + + /* 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_256bits = info_meas_ind->ta_offs_256bits; + ulm.ber10k = info_meas_ind->ber10k; + ulm.inv_rssi = info_meas_ind->inv_rssi; + ulm.is_sub = info_meas_ind->is_sub; + + /* we assume that symbol period is 1 bit: */ + set_ms_to_data(lchan, info_meas_ind->ta_offs_256bits / 256, true); + + lchan_meas_process_measurement(lchan, &ulm, info_meas_ind->fn); + + 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: + if (trx != trx->bts->c0) { + LOGPFN(DL1P, LOGL_NOTICE, info->u.time_ind.fn, + "BTS model is sending us PRIM_INFO_TIME for TRX %u, please fix it\n", + trx->nr); + rc = -1; + } else + 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(DL1C, LOGL_INFO, "activate confirm chan_nr=%s trx=%d\n", + rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr); + + lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr); + + rsl_tx_chan_act_acknack(lchan, info_act_cnf->cause); + + /* During PDCH ACT, this is where we know that the PCU is done + * activating a PDCH, and PDCH switchover is complete. See + * rsl_rx_dyn_pdch() */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && (lchan->ts->flags & TS_F_PDCH_ACT_PENDING)) + ipacc_dyn_pdch_complete(lchan->ts, + info_act_cnf->cause? -EIO : 0); + + 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(DL1C, LOGL_INFO, "deactivate confirm chan_nr=%s trx=%d\n", + rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr); + + lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr); + + rsl_tx_rf_rel_ack(lchan); + + /* During PDCH DEACT, this marks the deactivation of the PDTCH as + * requested by the PCU. Next up, we disconnect the TS completely and + * call back to cb_ts_disconnected(). See rsl_rx_dyn_pdch(). */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && (lchan->ts->flags & TS_F_PDCH_DEACT_PENDING)) + bts_model_ts_disconnect(lchan->ts); + + 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(DL1C, LOGL_NOTICE, "unknown MPH_INFO cnf type %d\n", + info->type); + break; + } + + return rc; +} + +/*! handling for PDTCH loopback mode, used for BER testing + * \param[in] lchan logical channel on which we operate + * \param[in] rts_ind PH-RTS.ind from PHY which we process + * \param[out] msg Message buffer to which we write data + * + * The function will fill \a msg, from which the caller can then + * subsequently build a PH-DATA.req */ +static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan, + const struct ph_data_param *rts_ind, + struct msgb *msg, const struct gsm_time *tm) +{ + struct msgb *loop_msg; + uint8_t *p; + + /* de-queue response message (loopback) */ + loop_msg = msgb_dequeue(&lchan->dl_tch_queue); + if (!loop_msg) { + LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: no looped PDTCH message, sending empty\n", + gsm_lchan_name(lchan)); + /* empty downlink message */ + p = msgb_put(msg, GSM_MACBLOCK_LEN); + memset(p, 0, GSM_MACBLOCK_LEN); + } else { + LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: looped PDTCH message of %u bytes\n", + gsm_lchan_name(lchan), msgb_l2len(loop_msg)); + /* copy over data from queued response message */ + p = msgb_put(msg, msgb_l2len(loop_msg)); + memcpy(p, msgb_l2(loop_msg), msgb_l2len(loop_msg)); + msgb_free(loop_msg); + } + return 0; +} + +/* Check if given CCCH frame number is for a PCH or for an AGCH (this function is + * only used internally, it is public to call it from unit-tests) */ +int is_ccch_for_agch(struct gsm_bts_trx *trx, uint32_t fn) { + /* Note: The number of available access grant channels is set by the + * parameter BS_AG_BLKS_RES via system information type 3. This SI is + * transfered to osmo-bts via RSL */ + return l1sap_fn2ccch_block(fn) < num_agch(trx, "PH-RTS-IND"); +} + +/* PH-RTS-IND prim received 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; + uint32_t fn; + uint8_t *p, *si; + struct lapdm_entity *le; + struct osmo_phsap_prim pp; + bool dtxd_facch = false; + int rc; + int is_ag_res; + + 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); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind chan_nr=%s link_id=0x%02xd\n", rsl_chan_nr_str(chan_nr), link_id); + + /* reuse PH-RTS.ind for PH-DATA.req */ + if (!msg) { + LOGPGT(DL1P, LOGL_FATAL, &g_time, "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 (ts_is_pdch(&trx->ts[tn])) { + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (lchan && lchan->loopback) { + if (!L1SAP_IS_PTCCH(rts_ind->fn)) + lchan_pdtch_ph_rts_ind_loop(lchan, rts_ind, msg, &g_time); + /* continue below like for SACCH/FACCH/... */ + } else { + /* forward RTS.ind to PCU */ + if (L1SAP_IS_PTCCH(rts_ind->fn)) { + pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */, + L1SAP_FN2PTCCHBLOCK(fn)); + } else { + pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */, + L1SAP_FN2MACBLOCK(fn)); + } + /* return early, PCU takes care of rest */ + return 0; + } + } else 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 (L1SAP_IS_CHAN_CBCH(chan_nr)) { + p = msgb_put(msg, GSM_MACBLOCK_LEN); + bts_cbch_get(trx->bts, p, &g_time); + } else if (!(chan_nr & 0x80)) { /* only TCH/F, TCH/H, SDCCH/4 and SDCCH/8 have C5 bit cleared */ + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%s)\n", + rsl_chan_nr_str(chan_nr)); + return 0; + } + 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 { + if (lchan->ts->trx->bts->dtxd) + dtxd_facch = true; + 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_SDCCH4(chan_nr) || L1SAP_IS_CHAN_SDCCH8(chan_nr) || + (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN && !lchan->ts->trx->bts->dtxd)) { + /* + * SDCCH or TCH in signalling mode without DTX. + * + * Send fill frame according to GSM 05.08, section 8.3: "On the SDCCH and on the + * half rate speech traffic channel in signalling only mode DTX is not allowed. + * In these cases and during signalling on the TCH when DTX is not used, the same + * L2 fill frame shall be transmitted in case there is nothing else to transmit." + */ + 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); + if (dtxd_facch) + dtx_dispatch(lchan, E_FACCH); + } + msgb_free(pp.oph.msg); + } + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + p = msgb_put(msg, GSM_MACBLOCK_LEN); + is_ag_res = is_ccch_for_agch(trx, fn); + rc = bts_ccch_copy_msg(trx->bts, p, &g_time, is_ag_res); + if (rc <= 0) + memcpy(p, fill_frame, GSM_MACBLOCK_LEN); + } + + DEBUGPGT(DL1P, &g_time, "Tx PH-DATA.req chan_nr=%s link_id=0x%02x\n", rsl_chan_nr_str(chan_nr), link_id); + + l1sap_down(trx, l1sap); + + /* don't free, because we forwarded data */ + return 1; +} + +static bool rtppayload_is_octet_aligned(const uint8_t *rtp_pl, uint8_t payload_len) +{ + /* + * Logic: If 1st bit padding is not zero, packet is either: + * - bandwidth-efficient AMR payload. + * - malformed packet. + * However, Bandwidth-efficient AMR 4,75 frame last in payload(F=0, FT=0) + * with 4th,5ht,6th AMR payload to 0 matches padding==0. + * Furthermore, both AMR 4,75 bw-efficient and octet alignment are 14 bytes long (AMR 4,75 encodes 95b): + * bw-efficient: 95b, + 4b hdr + 6b ToC = 105b, + padding = 112b = 14B. + * octet-aligned: 1B hdr + 1B ToC + 95b = 111b, + padding = 112b = 14B. + * We cannot use other fields to match since they are inside the AMR + * payload bits which are unknown. + * As a result, this function may return false positive (true) for some AMR + * 4,75 AMR frames, but given the length, CMR and FT read is the same as a + * consequence, the damage in here is harmless other than being unable to + * decode the audio at the other side. + */ + #define AMR_PADDING1(rtp_pl) (rtp_pl[0] & 0x0f) + #define AMR_PADDING2(rtp_pl) (rtp_pl[1] & 0x03) + + if(payload_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl)) + return false; + + return true; +} + +static bool rtppayload_is_valid(struct gsm_lchan *lchan, struct msgb *resp_msg) +{ + /* Avoid sending bw-efficient AMR to lower layers, most bts models + * don't support it. */ + if(lchan->tch_mode == GSM48_CMODE_SPEECH_AMR && + !rtppayload_is_octet_aligned(resp_msg->data, resp_msg->len)) { + LOGP(DL1P, LOGL_NOTICE, + "%s RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n", + gsm_lchan_name(lchan), osmo_hexdump(resp_msg->data, resp_msg->len)); + return false; + } + return true; +} + +/* 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, marker = 0; + uint32_t fn; + int rc; + + chan_nr = rts_ind->chan_nr; + fn = rts_ind->fn; + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx TCH-RTS.ind chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); + + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%s)\n", rsl_chan_nr_str(chan_nr)); + return 0; + } + + 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) { + DEBUGPGT(DL1P, &g_time, "%s DL TCH Tx queue underrun\n", gsm_lchan_name(lchan)); + resp_l1sap = &empty_l1sap; + } else if(!rtppayload_is_valid(lchan, resp_msg)) { + msgb_free(resp_msg); + resp_msg = NULL; + resp_l1sap = &empty_l1sap; + } else { + /* Obtain RTP header Marker bit from control buffer */ + marker = rtpmsg_marker_bit(resp_msg); + + 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); + } + + /* check for pending REL_IND */ + if (lchan->pending_rel_ind_msg) { + LOGPGT(DRSL, LOGL_INFO, &g_time, "%s Forward REL_IND to L3\n", gsm_lchan_name(lchan)); + /* Forward it to L3 */ + rc = abis_bts_rsl_sendmsg(lchan->pending_rel_ind_msg); + lchan->pending_rel_ind_msg = NULL; + if (rc < 0) + return rc; + } + + 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; + resp_l1sap->u.tch.marker = marker; + + DEBUGPGT(DL1P, &g_time, "Tx TCH.req chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); + + l1sap_down(trx, resp_l1sap); + + return 0; +} + +/* process radio link timeout counter S. Follows TS 05.08 Section 5.2 + * "MS Procedure" as the "BSS Procedure [...] shall be determined by the + * network operator." */ +static void radio_link_timeout(struct gsm_lchan *lchan, int bad_frame) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + /* Bypass radio link timeout if set to -1 */ + if (bts->radio_link_timeout < 0) + return; + + /* 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 < bts->radio_link_timeout) { + /* count up radio link counter S */ + lchan->s += 2; + if (lchan->s > bts->radio_link_timeout) + lchan->s = bts->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_r; + + /* 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_r = data[1] >> 5; + if (lchan->ciph_ns != n_r) + 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; + uint32_t fn; + int8_t rssi; + enum osmo_ph_pres_info_type pr_info = data_ind->pdch_presence_info; + + 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); + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind chan_nr=%s link_id=0x%02x len=%d\n", + rsl_chan_nr_str(chan_nr), link_id, len); + + if (ts_is_pdch(&trx->ts[tn])) { + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); + if (lchan && lchan->loopback && !L1SAP_IS_PTCCH(fn)) { + /* we are in loopback mode (for BER testing) + * mode and need to enqeue the frame to be + * returned in downlink */ + queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); + msgb_enqueue(&lchan->dl_tch_queue, msg); + + /* Return 1 to signal that we're still using msg + * and it should not be freed */ + return 1; + } + + /* don't send bad frames to PCU */ + if (len == 0) + return -EINVAL; + if (L1SAP_IS_PTCCH(fn)) { + pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PTCCH, fn, + 0 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn), + data, len, rssi, data_ind->ber10k, + data_ind->ta_offs_256bits/64, + data_ind->lqual_cb); + } else { + /* drop incomplete UL block */ + if (pr_info != PRES_INFO_BOTH) + return 0; + /* PDTCH / PACCH frame handling */ + pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PDTCH, fn, 0 /* ARFCN */, + L1SAP_FN2MACBLOCK(fn), data, len, rssi, data_ind->ber10k, + data_ind->ta_offs_256bits/64, data_ind->lqual_cb); + } + return 0; + } + + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); + return 0; + } + + /* 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) { + LOGPGT(DL1P, LOGL_NOTICE, &g_time, "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 gsm_bts *bts = trx->bts; + struct msgb *msg = l1sap->oph.msg; + struct gsm_time g_time; + struct gsm_lchan *lchan; + uint8_t chan_nr; + uint32_t fn; + + chan_nr = tch_ind->chan_nr; + fn = tch_ind->fn; + + gsm_fn2gsmtime(&g_time, fn); + + LOGPGT(DL1P, LOGL_INFO, &g_time, "Rx TCH.ind chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); + + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for TCH.ind (chan_nr=%s)\n", rsl_chan_nr_str(chan_nr)); + return 0; + } + + msgb_pull(msg, sizeof(*l1sap)); + + /* Low level layers always call us when TCH content is expected, even if + * the content is not available due to decoding issues. Content not + * available is expected as empty payload. We also check if quality is + * good enough. */ + if (msg->len && tch_ind->lqual_cb / 10 >= bts->min_qual_norm) { + /* hand msg to RTP code for transmission */ + if (lchan->abis_ip.rtp_socket) + osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket, + msg->data, msg->len, fn_ms_adj(fn, lchan), lchan->rtp_tx_marker); + /* if loopback is enabled, also queue received RTP data */ + if (lchan->loopback) { + /* make sure the queue doesn't get too long */ + queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); + /* add new frame to queue */ + msgb_enqueue(&lchan->dl_tch_queue, msg); + /* Return 1 to signal that we're still using msg and it should not be freed */ + return 1; + } + /* Only clear the marker bit once we have sent a RTP packet with it */ + lchan->rtp_tx_marker = false; + } else { + DEBUGPGT(DRTP, &g_time, "Skipping RTP frame with lost payload (chan_nr=0x%02x)\n", + chan_nr); + if (lchan->abis_ip.rtp_socket) + osmo_rtp_skipped_frame(lchan->abis_ip.rtp_socket, fn_ms_adj(fn, lchan)); + lchan->rtp_tx_marker = true; + } + + lchan->tch.last_fn = fn; + return 0; +} + +#define RACH_MIN_TOA256 -2 * 256 + +static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts) +{ + int16_t toa256 = rach_ind->acc_delay_256bits; + + /* Check for RACH exceeding BER threshold (ghost RACH) */ + if (rach_ind->ber10k > bts->max_ber10k_rach) { + LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " + "BER10k(%u) > BER10k_MAX(%u)\n", + rach_ind->ber10k, bts->max_ber10k_rach); + return false; + } + + /** + * Make sure that ToA (Timing of Arrival) is acceptable. + * We allow early arrival up to 2 symbols, and delay + * according to maximal allowed Timing Advance value. + */ + if (toa256 < RACH_MIN_TOA256 || toa256 > bts->max_ta * 256) { + LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " + "ToA(%d) exceeds the allowed range (%d..%d)\n", + toa256, RACH_MIN_TOA256, bts->max_ta * 256); + return false; + } + + return true; +} + +/* 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) +{ + /* Filter out noise / interference / ghosts */ + if (!rach_pass_filter(rach_ind, trx->bts)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP); + return 0; + } + + handover_rach(get_lchan_by_chan_nr(trx, rach_ind->chan_nr), + rach_ind->ra, rach_ind->acc_delay); + + /* must return 0, so in case of msg at l1sap, it will be freed */ + 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 lapdm_channel *lc; + + DEBUGPFN(DL1P, rach_ind->fn, "Rx PH-RA.ind"); + + /* check for handover access burst on dedicated channels */ + if (!L1SAP_IS_CHAN_RACH(rach_ind->chan_nr)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_HO); + return l1sap_handover_rach(trx, l1sap, rach_ind); + } + + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_RCVD); + + /* increment number of busy RACH slots, if required */ + if (rach_ind->rssi >= bts->load.rach.busy_thresh) + bts->load.rach.busy++; + + /* Filter out noise / interference / ghosts */ + if (!rach_pass_filter(rach_ind, bts)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP); + return 0; + } + + /* increment number of RACH slots with valid non-handover RACH burst */ + bts->load.rach.access++; + + lc = &trx->ts[0].lchan[CCCH_LCHAN].lapdm_ch; + + /* According to 3GPP TS 48.058 § 9.3.17 Access Delay is expressed same way as TA (number of symbols) */ + set_ms_to_data(get_lchan_by_chan_nr(trx, rach_ind->chan_nr), + rach_ind->acc_delay, false); + + /* check for packet access */ + if ((trx == bts->c0 && L1SAP_IS_PACKET_RACH(rach_ind->ra)) || + (trx == bts->c0 && rach_ind->is_11bit)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_PS); + + LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for packet access (toa=%d, ra=%d)\n", + rach_ind->acc_delay, rach_ind->ra); + + pcu_tx_rach_ind(bts, rach_ind->acc_delay << 2, + rach_ind->ra, rach_ind->fn, + rach_ind->is_11bit, rach_ind->burst_type); + return 0; + } + + LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for RR access (toa=%d, ra=%d)\n", + rach_ind->acc_delay, rach_ind->ra); + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_CS); + lapdm_phsap_up(&l1sap->oph, &lc->lapdm_dcch); + + return 0; +} + +/* Process any L1 prim received from bts model. + * + * This function takes ownership of the msgb. + * If l1sap contains a msgb, it assumes that msgb->l2h was set by lower layer. + */ +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); + oml_fail_rep(OSMO_EVT_MAJ_UKWN_MSG, "unknown prim %d op %d", + 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 %s is_ptcch=%d trx=%d ts=%d " + "block_nr=%d, arfcn=%d, len=%d\n", osmo_dump_gsmtime(&g_time), + 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 = RSL_CHAN_OSMO_PDCH | 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, uint16_t seq_number, + uint32_t timestamp, bool marker) +{ + struct gsm_lchan *lchan = rs->priv; + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* if we're in loopback mode, we don't accept frames from the + * RTP socket anymore */ + if (lchan->loopback) + return; + + 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)); + + /* Store RTP header Marker bit in control buffer */ + rtpmsg_marker_bit(msg) = marker; + /* Store RTP header Sequence Number in control buffer */ + rtpmsg_seq(msg) = seq_number; + /* Store RTP header Timestamp in control buffer */ + rtpmsg_ts(msg) = timestamp; + + /* make sure the queue doesn't get too long */ + queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); + + 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_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + struct gsm48_chan_desc *cd; + int rc; + + LOGP(DL1C, LOGL_INFO, "activating channel chan_nr=%s trx=%d\n", + rsl_chan_nr_str(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(DL1C, 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 = lchan->ts->trx->bts->radio_link_timeout; + + rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); + if (rc) + return -RSL_ERR_EQUIPMENT_FAIL; + + /* Init DTX DL FSM if necessary */ + if (trx->bts->dtxd && lchan->type != GSM_LCHAN_SDCCH) { + char name[32]; + snprintf(name, sizeof(name), "bts%u-trx%u-ts%u-ss%u", lchan->ts->trx->bts->nr, + lchan->ts->trx->nr, lchan->ts->nr, lchan->nr); + lchan->tch.dtx.dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm, + tall_bts_ctx, + lchan, + LOGL_DEBUG, + name); + if (!lchan->tch.dtx.dl_amr_fsm) { + l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); + return -RSL_ERR_EQUIPMENT_FAIL; + } + } + return 0; +} + +int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr) +{ + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + LOGP(DL1C, LOGL_INFO, "deactivating channel chan_nr=%s trx=%d\n", + rsl_chan_nr_str(chan_nr), trx->nr); + + if (lchan->tch.dtx.dl_amr_fsm) { + osmo_fsm_inst_free(lchan->tch.dtx.dl_amr_fsm); + lchan->tch.dtx.dl_amr_fsm = NULL; + } + + 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 = get_lchan_by_chan_nr(trx, chan_nr); + + LOGP(DL1C, LOGL_INFO, "deactivating sacch chan_nr=%s trx=%d\n", + rsl_chan_nr_str(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(DL1C, LOGL_INFO, "modifying channel chan_nr=%s trx=%d\n", + rsl_chan_nr_str(chan_nr), trx->nr); + + return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0); +} diff --git a/src/common/lchan.c b/src/common/lchan.c new file mode 100644 index 00000000..9e98166d --- /dev/null +++ b/src/common/lchan.c @@ -0,0 +1,49 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/logging.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state) +{ + DEBUGP(DL1C, "%s state %s -> %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state), + gsm_lchans_name(state)); + lchan->state = state; +} + +bool ts_is_pdch(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_PDCH: + return true; + case GSM_PCHAN_TCH_F_PDCH: + return (ts->flags & TS_F_PDCH_ACTIVE) + && !(ts->flags & TS_F_PDCH_PENDING_MASK); + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is == GSM_PCHAN_PDCH + && ts->dyn.pchan_want == ts->dyn.pchan_is; + default: + return false; + } +} diff --git a/src/common/load_indication.c b/src/common/load_indication.c new file mode 100644 index 00000000..e91f6d49 --- /dev/null +++ b/src/common/load_indication.c @@ -0,0 +1,94 @@ +/* Support for generating RSL Load Indication */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> + +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/paging.h> + +static void reset_load_counters(struct gsm_bts *bts) +{ + /* re-set the counters */ + bts->load.ccch.pch_used = bts->load.ccch.pch_total = 0; +} + +static void load_timer_cb(void *data) +{ + struct gsm_bts *bts = data; + unsigned int pch_percent, rach_percent; + + /* compute percentages */ + if (bts->load.ccch.pch_total == 0) + pch_percent = 0; + else + pch_percent = (bts->load.ccch.pch_used * 100) / + bts->load.ccch.pch_total; + + if (pch_percent >= bts->load.ccch.load_ind_thresh) { + /* send RSL load indication message to BSC */ + uint16_t buffer_space = paging_buffer_space(bts->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 (bts->load.rach.total == 0) + rach_percent = 0; + else + rach_percent = (bts->load.rach.busy * 100) / + bts->load.rach.total; + + if (rach_percent >= bts->load.ccch.load_ind_thresh) { + /* send RSL load indication message to BSC */ + rsl_tx_ccch_load_ind_rach(bts, bts->load.rach.total, + bts->load.rach.busy, + bts->load.rach.access); + } + + reset_load_counters(bts); + + /* re-schedule the timer */ + osmo_timer_schedule(&bts->load.ccch.timer, + bts->load.ccch.load_ind_period, 0); +} + +void load_timer_start(struct gsm_bts *bts) +{ + if (!bts->load.ccch.timer.data) { + bts->load.ccch.timer.data = bts; + bts->load.ccch.timer.cb = load_timer_cb; + reset_load_counters(bts); + } + osmo_timer_schedule(&bts->load.ccch.timer, bts->load.ccch.load_ind_period, 0); +} + +void load_timer_stop(struct gsm_bts *bts) +{ + osmo_timer_del(&bts->load.ccch.timer); +} diff --git a/src/common/logging.c b/src/common/logging.c new file mode 100644 index 00000000..3315a019 --- /dev/null +++ b/src/common/logging.c @@ -0,0 +1,150 @@ +/* libosmocore logging support */ + +/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +#include <errno.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/application.h> +#include <osmocom/core/utils.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> + +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 Control (MPH)", + .loglevel = LOGL_INFO, + .enabled = 1, + }, + [DL1P] = { + .name = "DL1P", + .description = "Layer 1 Primitives (PH)", + .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 + [DSUM] = { + .name = "DSUM", + .description = "DSUM", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, +}; + +const struct log_info bts_log_info = { + .cat = bts_log_info_cat, + .num_cat = ARRAY_SIZE(bts_log_info_cat), +}; diff --git a/src/common/main.c b/src/common/main.c new file mode 100644 index 00000000..f90a4d4d --- /dev/null +++ b/src/common/main.c @@ -0,0 +1,358 @@ +/* Main program for Osmocom BTS */ + +/* (C) 2011-2016 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/control_if.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/ports.h> +#include <osmocom/ctrl/control_vty.h> +#include <osmo-bts/oml.h> + +int quit = 0; +static const char *config_file = "osmo-bts.cfg"; +static int daemonize = 0; +static int rt_prio = -1; +static char *gsmtap_ip = 0; +extern int g_vty_port_num; + +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" + " -r --realtime PRIO Use SCHED_RR with the specified priority\n" + " -i --gsmtap-ip The destination IP used for GSMTAP.\n" + ); + bts_model_print_help(); +} + +/* FIXME: finally get some option parsing code into libosmocore */ +static void handle_options(int argc, char **argv) +{ + char *argv_out[argc]; + int argc_out = 0; + + argv_out[argc_out++] = argv[0]; + + /* disable generation of error messages on encountering unknown + * options */ + opterr = 0; + + 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' }, + /* FIXME: generic BTS app options */ + { "gsmtap-ip", 1, 0, 'i' }, + { "trx-num", 1, 0, 't' }, + { "realtime", 1, 0, 'r' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "-hc:d:Dc:sTVe:i:t:r:", + 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 'V': + print_version(1); + exit(0); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'r': + rt_prio = atoi(optarg); + break; + case 'i': + gsmtap_ip = optarg; + break; + case 't': + fprintf(stderr, "Parameter -t is deprecated and does nothing, " + "TRX num is calculated from VTY\n"); + break; + case '?': + case 1: + /* prepare argv[] for bts_model */ + argv_out[argc_out++] = argv[optind-1]; + break; + default: + break; + } + } + + /* re-set opt-ind for new parsig round */ + optind = 1; + /* enable error-checking for the following getopt call */ + opterr = 1; + if (bts_model_handle_options(argc_out, argv_out)) { + print_help(); + exit(1); + } +} + +static struct gsm_bts *bts; + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + if (!quit) { + oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP, + "BTS: SIGINT received -> shutdown"); + bts_shutdown(bts, "SIGINT"); + } + quit++; + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP, + "BTS: signal %d (%s) received", signal, + strsignal(signal)); + 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 bts_main(int argc, char **argv) +{ + struct gsm_bts_trx *trx; + struct e1inp_line *line; + int rc; + + printf("((*))\n |\n / \\ OsmoBTS\n"); + + /* Track the use of talloc NULL memory contexts */ + talloc_enable_null_tracking(); + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 100*1024); + bts_vty_info.tall_ctx = tall_bts_ctx; + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + vty_init(&bts_vty_info); + ctrl_vty_init(tall_bts_ctx); + rate_ctr_init(tall_bts_ctx); + + handle_options(argc, argv); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + + e1inp_vty_init(); + bts_vty_init(bts, &bts_log_info); + + /* enable realtime priority for us */ + if (rt_prio != -1) { + struct sched_param param; + + 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); + } + + 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 (!phy_link_by_num(0)) { + fprintf(stderr, "You need to configure at least phy0\n"); + exit(1); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx->role_bts.l1h) { + fprintf(stderr, "TRX %u has no associated PHY instance\n", + trx->nr); + exit(1); + } + } + + write_pid_file("osmo-bts"); + + bts_controlif_setup(bts, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_BTS); + + rc = telnet_init_dynif(tall_bts_ctx, NULL, vty_get_bind_addr(), + g_vty_port_num); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + if (pcu_sock_init(bts->pcu.sock_path)) { + fprintf(stderr, "PCU L1 socket failed\n"); + exit(1); + } + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + //signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (!bts->bsc_oml_host) { + fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n"); + exit(1); + } + + line = abis_open(bts, bts->bsc_oml_host, "sysmoBTS"); + if (!line) { + fprintf(stderr, "unable to connect to BSC\n"); + exit(2); + } + + rc = phy_links_open(); + if (rc < 0) { + fprintf(stderr, "unable to open PHY link(s)\n"); + exit(2); + } + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (quit < 2) { + log_reset_context(); + osmo_select_main(0); + } + + return EXIT_SUCCESS; +} diff --git a/src/common/measurement.c b/src/common/measurement.c new file mode 100644 index 00000000..c2001dae --- /dev/null +++ b/src/common/measurement.c @@ -0,0 +1,723 @@ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/rsl.h> + +/* Tables as per TS 45.008 Section 8.3 */ +static const uint8_t ts45008_83_tch_f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }; +static const uint8_t ts45008_83_tch_hs0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }; +static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; + +/* In cases where we less measurements than we expect we must assume that we + * just did not receive the block because it was lost due to bad channel + * conditions. We set up a dummy measurement result here that reflects the + * worst possible result. In our* calculation we will use this dummy to replace + * the missing measurements */ +#define MEASUREMENT_DUMMY_BER 10000 /* 100% BER */ +#define MEASUREMENT_DUMMY_IRSSI 109 /* noise floor in -dBm */ +static const struct bts_ul_meas measurement_dummy = (struct bts_ul_meas) { + .ber10k = MEASUREMENT_DUMMY_BER, + .ta_offs_256bits = 0, + .c_i = 0, + .is_sub = 0, + .inv_rssi = MEASUREMENT_DUMMY_IRSSI +}; + +/* find out if an array contains a given key as element */ +#define ARRAY_CONTAINS(arr, val) array_contains(arr, ARRAY_SIZE(arr), val) +static bool array_contains(const uint8_t *arr, unsigned int len, uint8_t val) { + int i; + for (i = 0; i < len; i++) { + if (arr[i] == val) + return true; + } + return false; +} + +/* Decide if a given frame number is part of the "-SUB" measurements (true) or not (false) + * (this function is only used internally, it is public to call it from unit-tests) */ +bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn, bool is_amr_sid_update) +{ + uint32_t fn104 = fn % 104; + + /* See TS 45.008 Sections 8.3 and 8.4 for a detailed descriptions of the rules + * implemented here. We only implement the logic for Voice, not CSD */ + + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + if (ARRAY_CONTAINS(ts45008_83_tch_f, fn104)) + return true; + break; + case GSM48_CMODE_SPEECH_AMR: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + if (is_amr_sid_update) + return true; + break; + default: + LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n", + gsm_lchan_name(lchan), lchan->tch_mode); + break; + } + break; + case GSM_LCHAN_TCH_H: + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + switch (lchan->nr) { + case 0: + if (ARRAY_CONTAINS(ts45008_83_tch_hs0, fn104)) + return true; + break; + case 1: + if (ARRAY_CONTAINS(ts45008_83_tch_hs1, fn104)) + return true; + break; + default: + OSMO_ASSERT(0); + } + break; + case GSM48_CMODE_SPEECH_AMR: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + if (is_amr_sid_update) + return true; + break; + case GSM48_CMODE_SIGN: + /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are + * SUB */ + return true; + default: + LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n", + gsm_lchan_name(lchan), lchan->tch_mode); + break; + } + break; + case GSM_LCHAN_SDCCH: + /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are SUB */ + return true; + default: + break; + } + return false; +} + +/* Measurement reporting period and mapping of SACCH message block for TCHF + * and TCHH chan As per in 3GPP TS 45.008, section 8.4.1. + * + * Timeslot number (TN) TDMA frame number (FN) modulo 104 + * Half rate, Half rate, Reporting SACCH + * Full Rate subch.0 subch.1 period Message block + * 0 0 and 1 0 to 103 12, 38, 64, 90 + * 1 0 and 1 13 to 12 25, 51, 77, 103 + * 2 2 and 3 26 to 25 38, 64, 90, 12 + * 3 2 and 3 39 to 38 51, 77, 103, 25 + * 4 4 and 5 52 to 51 64, 90, 12, 38 + * 5 4 and 5 65 to 64 77, 103, 25, 51 + * 6 6 and 7 78 to 77 90, 12, 38, 64 + * 7 6 and 7 91 to 90 103, 25, 51, 77 + * + * Note: The array index of the following three lookup tables refes to a + * timeslot number. */ + +static const uint8_t tchf_meas_rep_fn104_by_ts[] = { + [0] = 90, + [1] = 103, + [2] = 12, + [3] = 25, + [4] = 38, + [5] = 51, + [6] = 64, + [7] = 77, +}; +static const uint8_t tchh0_meas_rep_fn104_by_ts[] = { + [0] = 90, + [1] = 90, + [2] = 12, + [3] = 12, + [4] = 38, + [5] = 38, + [6] = 64, + [7] = 64, +}; +static const uint8_t tchh1_meas_rep_fn104_by_ts[] = { + [0] = 103, + [1] = 103, + [2] = 25, + [3] = 25, + [4] = 51, + [5] = 51, + [6] = 77, + [7] = 77, +}; + +/* Measurement reporting period for SDCCH8 and SDCCH4 chan + * As per in 3GPP TS 45.008, section 8.4.2. + * + * Logical Chan TDMA frame number + * (FN) modulo 102 + * + * SDCCH/8 12 to 11 + * SDCCH/4 37 to 36 + * + * + * Note: The array index of the following three lookup tables refes to a + * subslot number. */ + +/* FN of the first burst whose block completes before reaching fn%102=11 */ +static const uint8_t sdcch8_meas_rep_fn102_by_ss[] = { + [0] = 66, /* 15(SDCCH), 47(SACCH), 66(SDCCH) */ + [1] = 70, /* 19(SDCCH), 51(SACCH), 70(SDCCH) */ + [2] = 74, /* 23(SDCCH), 55(SACCH), 74(SDCCH) */ + [3] = 78, /* 27(SDCCH), 59(SACCH), 78(SDCCH) */ + [4] = 98, /* 31(SDCCH), 98(SACCH), 82(SDCCH) */ + [5] = 0, /* 35(SDCCH), 0(SACCH), 86(SDCCH) */ + [6] = 4, /* 39(SDCCH), 4(SACCH), 90(SDCCH) */ + [7] = 8, /* 43(SDCCH), 8(SACCH), 94(SDCCH) */ +}; + +/* FN of the first burst whose block completes before reaching fn%102=37 */ +static const uint8_t sdcch4_meas_rep_fn102_by_ss[] = { + [0] = 88, /* 37(SDCCH), 57(SACCH), 88(SDCCH) */ + [1] = 92, /* 41(SDCCH), 61(SACCH), 92(SDCCH) */ + [2] = 6, /* 6(SACCH), 47(SDCCH), 98(SDCCH) */ + [3] = 10 /* 10(SACCH), 0(SDCCH), 51(SDCCH) */ +}; + +/* Note: The reporting of the measurement results is done via the SACCH channel. + * The measurement interval is not aligned with the interval in which the + * SACCH is transmitted. When we receive the measurement indication with the + * SACCH block, the corresponding measurement interval will already have ended + * and we will get the results late, but on spot with the beginning of the + * next measurement interval. + * + * For example: We get a measurement indication on FN%104=38 in TS=2. Then we + * will have to look at 3GPP TS 45.008, section 8.4.1 (or 3GPP TS 05.02 Clause 7 + * Table 1 of 9) what value we need to feed into the lookup tables in order to + * detect the measurement period ending. In this example the "real" ending + * was on FN%104=12. This is the value we have to look for in + * tchf_meas_rep_fn104_by_ts to know that a measurement period has just ended. */ + +/* See also 3GPP TS 05.02 Clause 7 Table 1 of 9: + * Mapping of logical channels onto physical channels (see subclauses 6.3, 6.4, 6.5) */ +static uint8_t translate_tch_meas_rep_fn104(uint8_t fn_mod) +{ + switch (fn_mod) { + case 25: + return 103; + case 38: + return 12; + case 51: + return 25; + case 64: + return 38; + case 77: + return 51; + case 90: + return 64; + case 103: + return 77; + case 12: + return 90; + } + + /* Invalid / not of interest */ + return 0; +} + +/* determine if a measurement period ends at the given frame number + * (this function is only used internally, it is public to call it from + * unit-tests) */ +int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn) +{ + unsigned int fn_mod = -1; + const uint8_t *tbl; + int rc = 0; + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + if (lchan->ts->nr >= 8) + return -EINVAL; + if (pchan >= _GSM_PCHAN_MAX) + return -EINVAL; + + switch (pchan) { + case GSM_PCHAN_TCH_F: + fn_mod = translate_tch_meas_rep_fn104(fn % 104); + if (tchf_meas_rep_fn104_by_ts[lchan->ts->nr] == fn_mod) + rc = 1; + break; + case GSM_PCHAN_TCH_H: + fn_mod = translate_tch_meas_rep_fn104(fn % 104); + if (lchan->nr == 0) + tbl = tchh0_meas_rep_fn104_by_ts; + else + tbl = tchh1_meas_rep_fn104_by_ts; + if (tbl[lchan->ts->nr] == fn_mod) + rc = 1; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + fn_mod = fn % 102; + if (sdcch8_meas_rep_fn102_by_ss[lchan->nr] == fn_mod) + rc = 1; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + fn_mod = fn % 102; + if (sdcch4_meas_rep_fn102_by_ss[lchan->nr] == fn_mod) + rc = 1; + break; + default: + rc = 0; + break; + } + + if (rc == 1) { + DEBUGP(DMEAS, + "%s meas period end fn:%u, fn_mod:%i, status:%d, pchan:%s\n", + gsm_lchan_name(lchan), fn, fn_mod, rc, gsm_pchan_name(pchan)); + } + + return rc; +} + +/* determine the measurement interval modulus by a given lchan */ +static uint8_t modulus_by_lchan(struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + return 104; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + return 102; + break; + default: + /* Invalid */ + return 1; + break; + } +} + +/* receive a L1 uplink measurement from L1 (this function is only used + * internally, it is public to call it from unit-tests) */ +int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn) +{ + uint32_t fn_mod = fn % modulus_by_lchan(lchan); + + if (lchan->state != LCHAN_S_ACTIVE) { + LOGPFN(DMEAS, LOGL_NOTICE, fn, + "%s measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), + lchan->meas.num_ul_meas, fn_mod); + } + + if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) { + LOGPFN(DMEAS, LOGL_NOTICE, fn, + "%s no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n", + gsm_lchan_name(lchan), lchan->meas.num_ul_meas, fn_mod); + return -ENOSPC; + } + + /* We expect the lower layers to mark AMR SID_UPDATE frames already as such. + * In this function, we only deal with the comon logic as per the TS 45.008 tables */ + if (!ulm->is_sub) + ulm->is_sub = ts45008_83_is_sub(lchan, fn, false); + + DEBUGPFN(DMEAS, fn, "%s adding measurement (is_sub=%u), num_ul_meas=%d, fn_mod=%u\n", + gsm_lchan_name(lchan), ulm->is_sub, lchan->meas.num_ul_meas, fn_mod); + + memcpy(&lchan->meas.uplink[lchan->meas.num_ul_meas++], ulm, + sizeof(*ulm)); + + lchan->meas.last_fn = fn; + + return 0; +} + +/* input: BER in steps of .01%, i.e. percent/100 */ +static uint8_t ber10k_to_rxqual(uint32_t ber10k) +{ + /* Eight levels of Rx quality are defined and are mapped to the + * equivalent BER before channel decoding, as per in 3GPP TS 45.008, + * secton 8.2.4. + * + * RxQual: BER Range: + * RXQUAL_0 BER < 0,2 % Assumed value = 0,14 % + * RXQUAL_1 0,2 % < BER < 0,4 % Assumed value = 0,28 % + * RXQUAL_2 0,4 % < BER < 0,8 % Assumed value = 0,57 % + * RXQUAL_3 0,8 % < BER < 1,6 % Assumed value = 1,13 % + * RXQUAL_4 1,6 % < BER < 3,2 % Assumed value = 2,26 % + * RXQUAL_5 3,2 % < BER < 6,4 % Assumed value = 4,53 % + * RXQUAL_6 6,4 % < BER < 12,8 % Assumed value = 9,05 % + * RXQUAL_7 12,8 % < BER Assumed value = 18,10 % */ + + 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; +} + +/* Get the number of measurements that we expect for a specific lchan. + * (This is a static number that is defined by the specific slot layout of + * the channel) */ +static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + switch (pchan) { + case GSM_PCHAN_TCH_F: + /* 24 for TCH + 1 for SACCH */ + return 25; + case GSM_PCHAN_TCH_H: + /* 24 half-blocks for TCH + 1 for SACCH */ + return 25; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* 2 for SDCCH + 1 for SACCH */ + return 3; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* 2 for SDCCH + 1 for SACCH */ + return 3; + default: + return lchan->meas.num_ul_meas; + } +} + +/* In DTX a subset of blocks must always be transmitted + * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX) */ +static unsigned int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + /* AMR is using a more elaborated model with a dymanic amount of + * DTX blocks so this function is not applicable to determine the + * amount of expected SUB Measurements when AMR is used */ + OSMO_ASSERT(lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) + + switch (pchan) { + case GSM_PCHAN_TCH_F: + /* 1 block SDCCH, 2 blocks TCH */ + return 3; + case GSM_PCHAN_TCH_H: + /* 1 block SDCCH, 4 half-blocks TCH */ + return 5; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* no DTX here, all blocks must be present! */ + return 3; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* no DTX here, all blocks must be present! */ + return 3; + default: + return 0; + } +} + +/* if we clip the TOA value to 12 bits, i.e. toa256=3200, + * -> the maximum deviation can be 2*3200 = 6400 + * -> the maximum squared deviation can be 6400^2 = 40960000 + * -> the maximum sum of squared deviations can be 104*40960000 = 4259840000 + * and hence fit into uint32_t + * -> once the value is divided by 104, it's again below 40960000 + * leaving 6 MSBs of freedom, i.e. we could extend by 64, resulting in 2621440000 + * -> as a result, the standard deviation could be communicated with up to six bits + * of fractional fixed-point number. + */ + +/* compute Osmocom extended measurements for the given lchan */ +static void lchan_meas_compute_extended(struct gsm_lchan *lchan) +{ + unsigned int num_ul_meas; + unsigned int num_ul_meas_excess = 0; + unsigned int num_ul_meas_expect; + + /* we assume that lchan_meas_check_compute() has already computed the mean value + * and we can compute the min/max/variance/stddev from this */ + int i; + + /* each measurement is an int32_t, so the squared difference value must fit in 32bits */ + /* the sum of the squared values (each up to 32bit) can very easily exceed 32 bits */ + u_int64_t sq_diff_sum = 0; + + /* In case we do not have any measurement values collected there is no + * computation possible. We just skip the whole computation here, the + * lchan->meas.flags will not get the LC_UL_M_F_OSMO_EXT_VALID flag set + * so no extended measurement results will be reported back via RSL. + * this is ok, since we have nothing to report anyway and apart of that + * we also just lost the signal (otherwise we would have at least some + * measurements). */ + if (!lchan->meas.num_ul_meas) + return; + + /* initialize min/max values with their counterpart */ + lchan->meas.ext.toa256_min = INT16_MAX; + lchan->meas.ext.toa256_max = INT16_MIN; + + /* Determine the number of measurement values we need to take into the + * computation. In this case we only compute over the measurements we + * have indeed received. Since this computation is about timing + * information it does not make sense to approach missing measurement + * samples the TOA with 0. This would bend the average towards 0. What + * counts is the average TOA of the properly received blocks so that + * the TA logic can make a proper decision. */ + num_ul_meas_expect = lchan_meas_num_expected(lchan); + if (lchan->meas.num_ul_meas > num_ul_meas_expect) { + num_ul_meas = num_ul_meas_expect; + num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; + } + else + num_ul_meas = lchan->meas.num_ul_meas; + + /* all computations are done on the relative arrival time of the burst, relative to the + * beginning of its slot. This is of course excluding the TA value that the MS has already + * compensated/pre-empted its transmission */ + + /* step 1: compute the sum of the squared difference of each value to mean */ + for (i = 0; i < num_ul_meas; i++) { + const struct bts_ul_meas *m; + + OSMO_ASSERT(i < lchan->meas.num_ul_meas); + m = &lchan->meas.uplink[i+num_ul_meas_excess]; + + int32_t diff = (int32_t)m->ta_offs_256bits - (int32_t)lchan->meas.ms_toa256; + /* diff can now be any value of +65535 to -65535, so we can safely square it, + * but only in unsigned math. As squaring looses the sign, we can simply drop + * it before squaring, too. */ + uint32_t diff_abs = labs(diff); + uint32_t diff_squared = diff_abs * diff_abs; + sq_diff_sum += diff_squared; + + /* also use this loop iteration to compute min/max values */ + if (m->ta_offs_256bits > lchan->meas.ext.toa256_max) + lchan->meas.ext.toa256_max = m->ta_offs_256bits; + if (m->ta_offs_256bits < lchan->meas.ext.toa256_min) + lchan->meas.ext.toa256_min = m->ta_offs_256bits; + } + /* step 2: compute the variance (mean of sum of squared differences) */ + sq_diff_sum = sq_diff_sum / num_ul_meas; + /* as the individual summed values can each not exceed 2^32, and we're + * dividing by the number of summands, the resulting value can also not exceed 2^32 */ + OSMO_ASSERT(sq_diff_sum <= UINT32_MAX); + /* step 3: compute the standard deviation from the variance */ + lchan->meas.ext.toa256_std_dev = osmo_isqrt32(sq_diff_sum); + lchan->meas.flags |= LC_UL_M_F_OSMO_EXT_VALID; +} + +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 ta256b_sum = 0; + unsigned int num_meas_sub = 0; + unsigned int num_meas_sub_actual = 0; + unsigned int num_meas_sub_subst = 0; + unsigned int num_meas_sub_expect; + unsigned int num_ul_meas; + unsigned int num_ul_meas_actual = 0; + unsigned int num_ul_meas_subst = 0; + unsigned int num_ul_meas_expect; + unsigned int num_ul_meas_excess = 0; + int i; + + /* if measurement period is not complete, abort */ + if (!is_meas_complete(lchan, fn)) + return 0; + + LOGP(DMEAS, LOGL_DEBUG, "%s Calculating measurement results for physical channel:%s\n", + gsm_lchan_name(lchan), gsm_pchan_name(ts_pchan(lchan->ts))); + + /* Note: Some phys will send no measurement indication at all + * when a block is lost. Also in DTX mode blocks are left out + * intentionally to save energy. It is not necessarly an error + * when we get less measurements as we expect. */ + num_ul_meas_expect = lchan_meas_num_expected(lchan); + + if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) + num_meas_sub_expect = lchan_meas_sub_num_expected(lchan); + else { + /* FIXME: the amount of SUB Measurements is a dynamic parameter + * in AMR and can not be determined by using a lookup table. + * See also: OS#2978 */ + num_meas_sub_expect = 0; + } + + if (lchan->meas.num_ul_meas > num_ul_meas_expect) + num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; + num_ul_meas = num_ul_meas_expect; + + LOGP(DMEAS, LOGL_DEBUG, "%s received %u UL measurements, expected %u\n", gsm_lchan_name(lchan), + lchan->meas.num_ul_meas, num_ul_meas_expect); + if (num_ul_meas_excess) + LOGP(DMEAS, LOGL_DEBUG, "%s received %u excess UL measurements\n", gsm_lchan_name(lchan), + num_ul_meas_excess); + + /* Measurement computation step 1: add up */ + for (i = 0; i < num_ul_meas; i++) { + const struct bts_ul_meas *m; + bool is_sub = false; + + /* Note: We will always compute over a full measurement, + * interval even when not enough measurement samples are in + * the buffer. As soon as we run out of measurement values + * we continue the calculation using dummy values. This works + * well for the BER, since there we can safely assume 100% + * since a missing measurement means that the data (block) + * is lost as well (some phys do not give us measurement + * reports for lost blocks or blocks that are spaced out for + * DTX). However, for RSSI and TA this does not work since + * there we would distort the calculation if we would replace + * them with a made up number. This means for those values we + * only compute over the data we have actually received. */ + + if (i < lchan->meas.num_ul_meas) { + m = &lchan->meas.uplink[i + num_ul_meas_excess]; + if (m->is_sub) { + irssi_sub_sum += m->inv_rssi; + num_meas_sub_actual++; + is_sub = true; + } + irssi_full_sum += m->inv_rssi; + ta256b_sum += m->ta_offs_256bits; + + num_ul_meas_actual++; + } else { + m = &measurement_dummy; + if (num_ul_meas_expect - i <= num_meas_sub_expect - num_meas_sub) { + num_meas_sub_subst++; + is_sub = true; + } + + num_ul_meas_subst++; + } + + ber_full_sum += m->ber10k; + if (is_sub) { + num_meas_sub++; + ber_sub_sum += m->ber10k; + } + } + + LOGP(DMEAS, LOGL_DEBUG, "%s received UL measurements contain %u SUB measurements, expected %u\n", + gsm_lchan_name(lchan), num_meas_sub_actual, num_meas_sub_expect); + LOGP(DMEAS, LOGL_DEBUG, "%s replaced %u measurements with dummy values, from which %u were SUB measurements\n", + gsm_lchan_name(lchan), num_ul_meas_subst, num_meas_sub_subst); + + if (num_meas_sub != num_meas_sub_expect) { + LOGP(DMEAS, LOGL_ERROR, "%s Incorrect number of SUB measurements detected! (%u vs exp %u)\n", + gsm_lchan_name(lchan), num_meas_sub, num_meas_sub_expect); + /* Normally the logic above should make sure that there is + * always the exact amount of SUB measurements taken into + * account. If not then the logic that decides tags the received + * measurements as is_sub works incorrectly. Since the logic + * above only adds missing measurements during the calculation + * it can not remove excess SUB measurements or add missing SUB + * measurements when there is no more room in the interval. */ + } + + /* Measurement computation step 2: divide */ + ber_full_sum = ber_full_sum / num_ul_meas; + + if (!irssi_full_sum) + ber_full_sum = MEASUREMENT_DUMMY_IRSSI; + else + irssi_full_sum = irssi_full_sum / num_ul_meas_actual; + + if (!num_ul_meas_actual) + ta256b_sum = lchan->meas.ms_toa256; + else + ta256b_sum = ta256b_sum / num_ul_meas_actual; + + if (!num_meas_sub) + ber_sub_sum = MEASUREMENT_DUMMY_BER; + else + ber_sub_sum = ber_sub_sum / num_meas_sub; + + if (!num_meas_sub_actual) + irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI; + else + irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual; + + LOGP(DMEAS, LOGL_INFO, "%s Computed TA256(% 4d) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), " + "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan), + ta256b_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.ms_toa256 = ta256b_sum; + + LOGP(DMEAS, LOGL_INFO, "%s UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u)," + "RXQUAL_FULL(%u), RXQUAL_SUB(%u), num_meas_sub(%u), num_ul_meas(%u) \n", + gsm_lchan_name(lchan), + mru->full.rx_lev, mru->sub.rx_lev, mru->full.rx_qual, mru->sub.rx_qual, num_meas_sub, num_ul_meas_expect); + + lchan->meas.flags |= LC_UL_M_F_RES_VALID; + + lchan_meas_compute_extended(lchan); + + lchan->meas.num_ul_meas = 0; + + /* return 1 to indicte that the computation has been done and the next + * interval begins. */ + return 1; +} + +/* Process a single uplink measurement sample. This function is called from + * l1sap.c every time a measurement indication is received. It collects the + * measurement samples and automatically detects the end of the measurement + * interval. */ +int lchan_meas_process_measurement(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn) +{ + lchan_new_ul_meas(lchan, ulm, fn); + return lchan_meas_check_compute(lchan, fn); +} + +/* Reset all measurement related struct members to their initial values. This + * function will be called every time an lchan is activated to ensure the + * measurement process starts with a defined state. */ +void lchan_meas_reset(struct gsm_lchan *lchan) +{ + memset(&lchan->meas, 0, sizeof(lchan->meas)); + lchan->meas.last_fn = LCHAN_FN_DUMMY; +} diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c new file mode 100644 index 00000000..f936c983 --- /dev/null +++ b/src/common/msg_utils.c @@ -0,0 +1,603 @@ +/* (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/rsl.h> + +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/fsm.h> +#include <osmocom/trau/osmo_ortp.h> + +#include <arpa/inet.h> +#include <errno.h> + +#define STI_BIT_MASK 16 + +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; +} + +/* check that DTX is in the middle of silence */ +static inline bool dtx_is_update(const struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return false; + if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH) + return true; + return false; +} + +/* check that DTX is in the beginning of silence for AMR HR */ +bool dtx_is_first_p1(const struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return false; + if ((lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1)) + return true; + return false; +} + +/* update lchan SID status */ +void lchan_set_marker(bool t, struct gsm_lchan *lchan) +{ + if (t) + lchan->tch.dtx.ul_sid = true; + else if (lchan->tch.dtx.ul_sid) { + lchan->tch.dtx.ul_sid = false; + lchan->rtp_tx_marker = true; + } +} + +/*! \brief Store the last SID frame in lchan context + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] l1_payload buffer with SID data + * \param[in] length length of l1_payload + * \param[in] fn Frame Number for which we check scheduling + * \param[in] update 0 if SID_FIRST, 1 if SID_UPDATE, -1 if not AMR SID + */ +void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload, + size_t length, uint32_t fn, int update) +{ + size_t amr = (update < 0) ? 0 : 2, + copy_len = OSMO_MIN(length, + ARRAY_SIZE(lchan->tch.dtx.cache) - amr); + + lchan->tch.dtx.len = copy_len + amr; + /* SID FIRST is special because it's both sent and cached: */ + if (update == 0) { + lchan->tch.dtx.is_update = false; /* Mark SID FIRST explicitly */ + /* for non-AMR case - always update FN for incoming SID FIRST */ + if (!amr || !dtx_is_update(lchan)) + lchan->tch.dtx.fn = fn; + /* for AMR case - do not update FN if SID FIRST arrives in a + middle of silence: this should not be happening according to + the spec */ + } + + memcpy(lchan->tch.dtx.cache + amr, l1_payload, copy_len); +} + +/*! \brief Check current state of DTX DL AMR FSM and dispatch necessary events + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] rtp_pl buffer with RTP data + * \param[in] rtp_pl_len length of rtp_pl + * \param[in] fn Frame Number for which we check scheduling + * \param[in] l1_payload buffer where CMR and CMI prefix should be added + * \param[in] marker RTP Marker bit + * \param[out] len Length of expected L1 payload + * \param[out] ft_out Frame Type to be populated after decoding + * \returns 0 in case of success; negative on error + */ +int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl, + size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload, + bool marker, uint8_t *len, uint8_t *ft_out) +{ + uint8_t cmr; + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + int8_t sti, cmi; + int rc; + + if (dtx_dl_amr_enabled(lchan)) { + if (lchan->type == GSM_LCHAN_TCH_H && !rtp_pl) { + /* we're called by gen_empty_tch_msg() to handle states + specific to AMR HR DTX */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_SID_F2: + *len = 3; /* SID-FIRST P1 -> P2 completion */ + memcpy(l1_payload, lchan->tch.dtx.cache, 2); + rc = 0; + dtx_dispatch(lchan, E_COMPL); + break; + case ST_SID_U: + rc = -EBADMSG; + dtx_dispatch(lchan, E_SID_U); + break; + default: + rc = -EBADMSG; + } + return rc; + } + } + + if (!rtp_pl_len) + return -EBADMSG; + + rc = osmo_amr_rtp_dec(rtp_pl, rtp_pl_len, &cmr, &cmi, &ft, &bfi, &sti); + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "failed to decode AMR RTP (length %zu, " + "%p)\n", rtp_pl_len, rtp_pl); + return rc; + } + + /* only needed for old sysmo firmware: */ + *ft_out = ft; + + /* CMI in downlink tells the L1 encoder which encoding function + * it will use, so we have to use the frame type */ + if (osmo_amr_is_speech(ft)) + cmi = ft; + + /* populate L1 payload with CMR/CMI - might be ignored by caller: */ + amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi, cmr); + + /* populate DTX cache with CMR/CMI - overwrite cache which will be + either updated or invalidated by caller anyway: */ + amr_set_mode_pref(lchan->tch.dtx.cache, &lchan->tch.amr_mr, cmi, cmr); + *len = 3 + rtp_pl_len; + + /* DTX DL is not enabled, move along */ + if (!lchan->ts->trx->bts->dtxd) + return 0; + + if (osmo_amr_is_speech(ft)) { + /* AMR HR - SID-FIRST_P1 Inhibition */ + if (marker && lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_INHIB, (void *)lchan); + + /* AMR HR - SID-UPDATE Inhibition */ + if (marker && lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_INHIB, (void *)lchan); + + /* AMR FR & HR - generic */ + if (marker && (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1 || + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2 || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH)) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_ONSET, (void *)lchan); + + if (lchan->tch.dtx.dl_amr_fsm->state != ST_VOICE) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_VOICE, (void *)lchan); + + return 0; + } + + if (ft == AMR_SID) { + if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) { + /* SID FIRST/UPDATE scheduling logic relies on SID FIRST + being sent first hence we have to force caching of SID + as FIRST regardless of actually decoded type */ + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, false); + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + sti ? E_SID_U : E_SID_F, + (void *)lchan); + } else if (lchan->tch.dtx.dl_amr_fsm->state != ST_FACCH) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, sti); + if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_COMPL, (void *)lchan); + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + sti ? E_SID_U : E_SID_F, + (void *)lchan); + } + + if (ft != AMR_NO_DATA) { + LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft); + return -ENOTSUP; + } + + if (marker) + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, E_VOICE, + (void *)lchan); + *len = 0; + return 0; +} + +/* STI is located in payload byte 6, cache contains 2 byte prefix (CMR/CMI) + * STI set = SID UPDATE, STI unset = SID FIRST + */ +static inline void dtx_sti_set(struct gsm_lchan *lchan) +{ + lchan->tch.dtx.cache[6 + 2] |= STI_BIT_MASK; +} + +static inline void dtx_sti_unset(struct gsm_lchan *lchan) +{ + lchan->tch.dtx.cache[6 + 2] &= ~STI_BIT_MASK; +} + +/*! \brief Check if enough time has passed since last SID (if any) to repeat it + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] fn Frame Number for which we check scheduling + * \returns true if transmission can be omitted, false otherwise + */ +static inline bool dtx_amr_sid_optional(struct gsm_lchan *lchan, uint32_t fn) +{ + if (!dtx_dl_amr_enabled(lchan)) + return true; + + /* Compute approx. time delta x26 based on Fn duration */ + uint32_t dx26 = 120 * (fn - lchan->tch.dtx.fn); + + /* We're resuming after FACCH interruption */ + if (lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* force STI bit to 0 so cache is treated as SID FIRST */ + dtx_sti_unset(lchan); + lchan->tch.dtx.is_update = false; + /* check that this FN has not been used for FACCH message + already: we rely here on the order of RTS arrival from L1 - we + expect that PH-DATA.req ALWAYS comes before PH-TCH.req for the + same FN */ + if(lchan->type == GSM_LCHAN_TCH_H) { + if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY && + lchan->tch.dtx.fn != LCHAN_FN_WAIT) { + /* FACCH interruption is over */ + dtx_dispatch(lchan, E_COMPL); + return false; + } else if(lchan->tch.dtx.fn == LCHAN_FN_DUMMY) { + lchan->tch.dtx.fn = LCHAN_FN_WAIT; + } else + lchan->tch.dtx.fn = fn; + } else if(lchan->type == GSM_LCHAN_TCH_F) { + if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY) { + /* FACCH interruption is over */ + dtx_dispatch(lchan, E_COMPL); + return false; + } else + lchan->tch.dtx.fn = fn; + } + /* this FN was already used for FACCH or ONSET message so we just + prepare things for next one */ + return true; + } + + if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) + return true; + + /* according to 3GPP TS 26.093 A.5.1.1: + (*26) to avoid float math, add 1 FN tolerance (-120) */ + if (lchan->tch.dtx.is_update) { /* SID UPDATE: every 8th RTP frame */ + if (dx26 < GSM_RTP_FRAME_DURATION_MS * 8 * 26 - 120) + return true; + return false; + } + /* 3rd frame after SID FIRST should be SID UPDATE */ + if (dx26 < GSM_RTP_FRAME_DURATION_MS * 3 * 26 - 120) + return true; + return false; +} + +static inline bool fn_chk(const uint8_t *t, uint32_t fn, uint8_t len) +{ + uint8_t i; + for (i = 0; i < len; i++) + if (fn % 104 == t[i]) + return false; + return true; +} + +/*! \brief Check if TX scheduling is optional for a given FN in case of DTX + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] fn Frame Number for which we check scheduling + * \returns true if transmission can be omitted, false otherwise + */ +static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn) +{ + /* According to 3GPP TS 45.008 § 8.3: */ + static const uint8_t f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }, + h0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }, + h1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; + if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1) { + if (lchan->type == GSM_LCHAN_TCH_F) + return fn_chk(f, fn, ARRAY_SIZE(f)); + else + return fn_chk(lchan->nr ? h1 : h0, fn, + lchan->nr ? ARRAY_SIZE(h1) : + ARRAY_SIZE(h0)); + } + return false; +} + +/*! \brief Check if DTX DL AMR is enabled for a given lchan (it have proper type, + * FSM is allocated etc.) + * \param[in] lchan Logical channel on which we check scheduling + * \returns true if DTX DL AMR is enabled, false otherwise + */ +bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan) +{ + if (lchan->ts->trx->bts->dtxd && + lchan->tch.dtx.dl_amr_fsm && + lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + return true; + return false; +} + +/*! \brief Check if DTX DL AMR FSM state is recursive: requires secondary + * response to a single RTS request from L1. + * \param[in] lchan Logical channel on which we check scheduling + * \returns true if DTX DL AMR FSM state is recursive, false otherwise + */ +bool dtx_recursion(const struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return false; + + if (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V_REC) + return true; + + return false; +} + +/*! \brief Send signal to FSM: with proper check if DIX is enabled for this lchan + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] e DTX DL AMR FSM Event + */ +void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e) +{ + if (dtx_dl_amr_enabled(lchan)) + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, e, + (void *)lchan); +} + +/*! \brief Send internal signal to FSM: check that DTX is enabled for this chan, + * check that current FSM and lchan states are permitting such signal. + * Note: this should be the only way to dispatch E_COMPL to FSM from + * BTS code. + * \param[in] lchan Logical channel on which we check scheduling + */ +void dtx_int_signal(struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return; + + if (dtx_is_first_p1(lchan) || dtx_recursion(lchan)) + dtx_dispatch(lchan, E_COMPL); +} + +/*! \brief Repeat last SID if possible in case of DTX + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] dst Buffer to copy last SID into + * \returns Number of bytes copied + 1 (to accommodate for extra byte with + * payload type), 0 if there's nothing to copy + */ +uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn) +{ + /* FIXME: add EFR support */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) + return 0; + + if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) { + if (dtx_sched_optional(lchan, fn)) + return 0; + } else + if (dtx_amr_sid_optional(lchan, fn)) + return 0; + + if (lchan->tch.dtx.len) { + if (dtx_dl_amr_enabled(lchan)) { + if ((lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2) || + (lchan->type == GSM_LCHAN_TCH_F && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1)) { + /* advance FSM in case we've just sent SID FIRST + to restore silence after FACCH interruption */ + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_SID_U, (void *)lchan); + dtx_sti_unset(lchan); + } else if (dtx_is_update(lchan)) { + /* enforce SID UPDATE for next repetition: it + might have been altered by FACCH handling */ + dtx_sti_set(lchan); + if (lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == + ST_U_NOINH) + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_COMPL, + (void *)lchan); + lchan->tch.dtx.is_update = true; + } + } + memcpy(dst, lchan->tch.dtx.cache, lchan->tch.dtx.len); + lchan->tch.dtx.fn = fn; + return lchan->tch.dtx.len + 1; + } + + LOGP(DL1C, LOGL_DEBUG, "Have to send %s frame on TCH but SID buffer " + "is empty - sent nothing\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode)); + + return 0; +} + +/** + * 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 %zu\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 %zu\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 %zu\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; +} diff --git a/src/common/oml.c b/src/common/oml.c new file mode 100644 index 00000000..41debc1b --- /dev/null +++ b/src/common/oml.c @@ -0,0 +1,1495 @@ +/* GSM TS 12.21 O&M / OML, BTS side */ + +/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +/* + * Operation and Maintenance Messages + */ + +#include "btsconfig.h" + +#include <errno.h> +#include <stdarg.h> +#include <string.h> +#include <sys/types.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipaccess.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/phy_link.h> + +static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg); + +static struct tlv_definition abis_nm_att_tlvdef_ipa_local = {}; + +/* + * support + */ + +static int oml_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len) +{ + return tlv_parse(tp, &abis_nm_att_tlvdef_ipa_local, buf, len, 0, 0); +} + +struct msgb *oml_msgb_alloc(void) +{ + return msgb_alloc_headroom(1024, 128, "OML"); +} + +/* 3GPP TS 12.21 § 8.8.2 */ +static int oml_tx_failure_event_rep(struct gsm_abis_mo *mo, uint16_t cause_value, + const char *fmt, ...) +{ + struct msgb *nmsg; + va_list ap; + + LOGP(DOML, LOGL_NOTICE, "Sending %s to BSC: ", get_value_string(abis_mm_event_cause_names, cause_value)); + va_start(ap, fmt); + osmo_vlogp(DOML, LOGL_NOTICE, __FILE__, __LINE__, 1, fmt, ap); + nmsg = abis_nm_fail_evt_vrep(NM_EVT_PROC_FAIL, NM_SEVER_CRITICAL, + NM_PCAUSE_T_MANUF, cause_value, fmt, ap); + va_end(ap); + LOGPC(DOML, LOGL_NOTICE, "\n"); + + if (!nmsg) + return -ENOMEM; + + return oml_mo_send_msg(mo, nmsg, NM_MT_FAILURE_EVENT_REP); +} + +void oml_fail_rep(uint16_t cause_value, const char *fmt, ...) +{ + va_list ap; + char *rep; + + va_start(ap, fmt); + rep = talloc_asprintf(tall_bts_ctx, fmt, ap); + va_end(ap); + + osmo_signal_dispatch(SS_FAIL, cause_value, rep); + /* signal dispatch is synchronous so all the signal handlers are + finished already: we're free to free */ + talloc_free(rep); +} + +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; +} + +static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts) +{ + abis_nm_put_sw_file(msg, "osmobts", PACKAGE_VERSION, true); + abis_nm_put_sw_file(msg, btsatttr2str(BTS_TYPE_VARIANT), btsvariant2str(bts->variant), true); + + if (strlen(bts->sub_model)) + abis_nm_put_sw_file(msg, btsatttr2str(BTS_SUB_MODEL), bts->sub_model, true); +} + +/* Add BTS features as 3GPP TS 52.021 §9.4.30 Manufacturer Id */ +static inline void add_bts_feat(struct msgb *msg, const struct gsm_bts *bts) +{ + msgb_tl16v_put(msg, NM_ATT_MANUF_ID, _NUM_BTS_FEAT/8 + 1, bts->_features_data); +} + +static inline void add_trx_attr(struct msgb *msg, struct gsm_bts_trx *trx) +{ + const struct phy_instance *pinst = trx_phy_instance(trx); + + abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), pinst && strlen(pinst->version) ? pinst->version : "Unknown", + true); +} + +/* The number of attributes in §9.4.26 List of Required Attributes is 2 bytes, + but the Count of not-reported attributes from §9.4.64 is 1 byte */ +static inline uint8_t pack_num_unreported_attr(uint16_t attrs) +{ + if (attrs > 255) { + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes, Count of not-reported attributes is too big: %u\n", + attrs); + return 255; + } + return attrs; /* Return number of unhandled attributes */ +} + +/* copy all the attributes accumulated in msg to out and return the total length of out buffer */ +static inline int cleanup_attr_msg(uint8_t *out, int out_offset, struct msgb *msg) +{ + int len = 0; + + out[0] = pack_num_unreported_attr(out_offset - 1); + + if (msg) { + memcpy(out + out_offset, msgb_data(msg), msg->len); + len = msg->len; + msgb_free(msg); + } + + return len + out_offset + 1; +} + +static inline int handle_attrs_trx(uint8_t *out, struct gsm_bts_trx *trx, const uint8_t *attr, uint16_t attr_len) +{ + uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */ + struct msgb *attr_buf = oml_msgb_alloc(); + + if (!attr_buf) + return -NM_NACK_CANT_PERFORM; + + for (i = 0; i < attr_len; i++) { + bool processed = false; + switch (attr[i]) { + case NM_ATT_SW_CONFIG: + if (trx) { + add_trx_attr(attr_buf, trx); + processed = true; + } else + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unhandled due to missing TRX.\n", + i, get_value_string(abis_nm_att_names, attr[i])); + break; + default: + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by TRX.\n", i, + get_value_string(abis_nm_att_names, attr[i])); + } + /* assemble values of supported attributes and list of unsupported ones */ + if (!processed) { + out[attr_out_index] = attr[i]; + attr_out_index++; + } + } + + return cleanup_attr_msg(out, attr_out_index, attr_buf); +} + +static inline int handle_attrs_bts(uint8_t *out, const struct gsm_bts *bts, const uint8_t *attr, uint16_t attr_len) +{ + uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */ + struct msgb *attr_buf = oml_msgb_alloc(); + + if (!attr_buf) + return -NM_NACK_CANT_PERFORM; + + for (i = 0; i < attr_len; i++) { + switch (attr[i]) { + case NM_ATT_SW_CONFIG: + add_bts_attrs(attr_buf, bts); + break; + case NM_ATT_MANUF_ID: + add_bts_feat(attr_buf, bts); + break; + default: + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by BTS.\n", i, + get_value_string(abis_nm_att_names, attr[i])); + out[attr_out_index] = attr[i]; /* assemble values of supported attributes and list of unsupported ones */ + attr_out_index++; + } + } + + return cleanup_attr_msg(out, attr_out_index, attr_buf); +} + +/* send 3GPP TS 52.021 §8.11.2 Get Attribute Response */ +static int oml_tx_attr_resp(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, const uint8_t *attr, + uint16_t attr_len) +{ + struct msgb *nmsg = oml_msgb_alloc(); + uint8_t resp[MAX_VERSION_LENGTH * attr_len * 2]; /* heuristic for Attribute Response Info space requirements */ + int len; + + LOGP(DOML, LOGL_INFO, "%s Tx Get Attribute Response\n", + get_value_string(abis_nm_obj_class_names, foh->obj_class)); + + if (!nmsg) + return -NM_NACK_CANT_PERFORM; + + switch (foh->obj_class) { + case NM_OC_BTS: + len = handle_attrs_bts(resp, bts, attr, attr_len); + break; + case NM_OC_BASEB_TRANSC: + len = handle_attrs_trx(resp, gsm_bts_trx_num(bts, foh->obj_inst.trx_nr), attr, attr_len); + break; + default: + LOGP(DOML, LOGL_ERROR, "Unsupported MO class %s in Get Attribute Response\n", + get_value_string(abis_nm_obj_class_names, foh->obj_class)); + len = -NM_NACK_RES_NOTIMPL; + } + + if (len < 0) { + LOGP(DOML, LOGL_ERROR, "Tx Get Attribute Response FAILED with %d\n", len); + msgb_free(nmsg); + return len; + } + + /* §9.4.64 Get Attribute Response Info */ + msgb_tl16v_put(nmsg, NM_ATT_GET_ARI, len, resp); + + len = oml_mo_send_msg(&bts->mo, nmsg, NM_MT_GET_ATTR_RESP); + return (len < 0) ? -NM_NACK_CANT_PERFORM : len; +} + +/* 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); + + /* 9.4.4 Administrative Status -- not in spec but also sent by nanobts */ + msgb_tv_put(nmsg, NM_ATT_ADM_STATE, mo->nm_state.administrative); + + 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; + int rc = 0; + + msg = oml_msgb_alloc(); + if (!msg) + return -ENOMEM; + + msgb_tv_put(msg, NM_ATT_ADM_STATE, mo->nm_state.administrative); + + rc = oml_mo_send_msg(mo, msg, NM_MT_CHG_ADM_STATE_ACK); + if (rc != 0) + return rc; + + /* Emulate behaviour of ipaccess nanobts: Send a 'State Changed Event Report' as well. */ + return oml_tx_state_changed(mo); +} + +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); +} + +/* The defaults below correspond to the libosmocore default of 1s for + * DCCH and 2s for ACCH. The BSC should override this via OML anyway. */ +const unsigned int oml_default_t200_ms[7] = { + [T200_SDCCH] = 1000, + [T200_FACCH_F] = 1000, + [T200_FACCH_H] = 1000, + [T200_SACCH_TCH_SAPI0] = 2000, + [T200_SACCH_SDCCH] = 2000, + [T200_SDCCH_SAPI3] = 1000, + [T200_SACCH_TCH_SAPI3] = 2000, +}; + +static void dl_set_t200(struct lapdm_datalink *dl, unsigned int t200_msec) +{ + dl->dl.t200_sec = t200_msec / 1000; + dl->dl.t200_usec = (t200_msec % 1000) * 1000; +} + +/* Configure LAPDm T200 timers for this lchan according to OML */ +int oml_set_lchan_t200(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + struct lapdm_channel *lc = &lchan->lapdm_ch; + unsigned int t200_dcch, t200_dcch_sapi3, t200_acch, t200_acch_sapi3; + + /* set T200 for main and associated channel */ + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + t200_dcch = bts->t200_ms[T200_SDCCH]; + t200_dcch_sapi3 = bts->t200_ms[T200_SDCCH_SAPI3]; + t200_acch = bts->t200_ms[T200_SACCH_SDCCH]; + t200_acch_sapi3 = bts->t200_ms[T200_SACCH_SDCCH]; + break; + case GSM_LCHAN_TCH_F: + t200_dcch = bts->t200_ms[T200_FACCH_F]; + t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_F]; + t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0]; + t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3]; + break; + case GSM_LCHAN_TCH_H: + t200_dcch = bts->t200_ms[T200_FACCH_H]; + t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_H]; + t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0]; + t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3]; + break; + default: + return -1; + } + + DEBUGP(DLLAPD, "%s: Setting T200 D0=%u, D3=%u, S0=%u, S3=%u" + "(all in ms)\n", gsm_lchan_name(lchan), t200_dcch, + t200_dcch_sapi3, t200_acch, t200_acch_sapi3); + + dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI0], t200_dcch); + dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI3], t200_dcch_sapi3); + dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI0], t200_acch); + dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI3], t200_acch_sapi3); + + return 0; +} + +/* 3GPP TS 52.021 §8.11.1 Get Attributes has been received */ +static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct tlv_parsed tp; + int rc; + + if (!foh || !bts) + return -EINVAL; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx GET ATTR\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute parsing failure"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + if (!TLVP_PRES_LEN(&tp, NM_ATT_LIST_REQ_ATTR, 1)) { + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes message without Attribute List?!\n"); + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute without Attribute List"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + rc = oml_tx_attr_resp(bts, foh, TLVP_VAL(&tp, NM_ATT_LIST_REQ_ATTR), TLVP_LEN(&tp, NM_ATT_LIST_REQ_ATTR)); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "responding to O&M Get Attributes message with NACK 0%x\n", -rc); + return oml_fom_ack_nack(msg, -rc); + } + + return 0; +} + +/* 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; + 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) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for Attribute not supported"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + /* Test for globally unsupported stuff here */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) { + uint16_t arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); + if (arfcn > 1024) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_WARN_SW_WARN, + "Given ARFCN %u is not supported", + arfcn); + 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)) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "NM_ATT_START_TIME Attribute not " + "supported"); + return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); + } + + /* merge existing BTS attributes with new attributes */ + tp_merged = osmo_tlvp_copy(bts->mo.nm_attr, bts); + osmo_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_PRES_LEN(&tp, NM_ATT_INTERF_BOUND, 6)) { + payload = TLVP_VAL(&tp, NM_ATT_INTERF_BOUND); + for (i = 0; i < 6; i++) { + int16_t boundary = *payload; + bts->interference.boundary[i] = -1 * boundary; + } + } + /* 9.4.24 Intave Parameter */ + if (TLVP_PRES_LEN(&tp, NM_ATT_INTAVE_PARAM, 1)) + bts->interference.intave = *TLVP_VAL(&tp, NM_ATT_INTAVE_PARAM); + + /* 9.4.14 Connection Failure Criterion */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CONN_FAIL_CRIT, 1)) { + const uint8_t *val = TLVP_VAL(&tp, NM_ATT_CONN_FAIL_CRIT); + + switch (val[0]) { + case 0xFF: /* Osmocom specific Extension of TS 12.21 */ + LOGP(DOML, LOGL_NOTICE, "WARNING: Radio Link Timeout " + "explicitly disabled, only use this for lab testing!\n"); + bts->radio_link_timeout = -1; + break; + case 0x01: /* Based on uplink SACCH (radio link timeout) */ + if (TLVP_LEN(&tp, NM_ATT_CONN_FAIL_CRIT) >= 2 && + val[1] >= 4 && val[1] <= 64) { + bts->radio_link_timeout = val[1]; + break; + } + /* fall-through */ + case 0x02: /* Based on RXLEV/RXQUAL measurements */ + default: + LOGP(DOML, LOGL_NOTICE, "Given Conn. Failure Criterion " + "not supported. Please use criterion 0x01 with " + "RADIO_LINK_TIMEOUT value of 4..64\n"); + return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); + } + } + + /* 9.4.53 T200 */ + if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_ms))) { + payload = TLVP_VAL(&tp, NM_ATT_T200); + for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) { + uint32_t t200_ms = payload[i] * abis_nm_t200_ms[i]; +#if 0 + bts->t200_ms[i] = t200_ms; + DEBUGP(DOML, "T200[%u]: OML=%u, mult=%u => %u ms\n", + i, payload[i], abis_nm_t200_mult[i], + bts->t200_ms[i]); +#else + /* we'd rather use the 1s/2s (long) defaults by + * libosmocore, as we appear to have some bug(s) + * related to handling T200 expiration in + * libosmogsm lapd(m) code? */ + LOGP(DOML, LOGL_NOTICE, "Ignoring T200[%u] (%u ms) " + "as sent by BSC due to suspected LAPDm bug!\n", + i, t200_ms); +#endif + } + } + + /* 9.4.31 Maximum Timing Advance */ + if (TLVP_PRES_LEN(&tp, NM_ATT_MAX_TA, 1)) + bts->max_ta = *TLVP_VAL(&tp, NM_ATT_MAX_TA); + + /* 9.4.39 Overload Period */ + if (TLVP_PRES_LEN(&tp, NM_ATT_OVERL_PERIOD, 1)) + bts->load.overload_period = *TLVP_VAL(&tp, NM_ATT_OVERL_PERIOD); + + /* 9.4.12 CCCH Load Threshold */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_T, 1)) + bts->load.ccch.load_ind_thresh = *TLVP_VAL(&tp, NM_ATT_CCCH_L_T); + + /* 9.4.11 CCCH Load Indication Period */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_I_P, 1)) { + bts->load.ccch.load_ind_period = *TLVP_VAL(&tp, NM_ATT_CCCH_L_I_P); + load_timer_start(bts); + } + + /* 9.4.44 RACH Busy Threshold */ + if (TLVP_PRES_LEN(&tp, NM_ATT_RACH_B_THRESH, 1)) { + int16_t thresh = *TLVP_VAL(&tp, NM_ATT_RACH_B_THRESH); + bts->load.rach.busy_thresh = -1 * thresh; + } + + /* 9.4.45 RACH Load Averaging Slots */ + if (TLVP_PRES_LEN(&tp, NM_ATT_LDAVG_SLOTS, 2)) { + bts->load.rach.averaging_slots = + ntohs(tlvp_val16_unal(&tp, NM_ATT_LDAVG_SLOTS)); + } + + /* 9.4.10 BTS Air Timer */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BTS_AIR_TIMER, 1)) { + 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); + } + bts->t3105_ms = t3105 * 10; + } + + /* 9.4.37 NY1 */ + if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1)) + bts->ny1 = *TLVP_VAL(&tp, NM_ATT_NY1); + + /* 9.4.8 BCCH ARFCN */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) + bts->c0->arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); + + /* 9.4.9 BSIC */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BSIC, 1)) + 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) { + oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for Set Radio Attribute not" + " supported"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + /* merge existing BTS attributes with new attributes */ + tp_merged = osmo_tlvp_copy(trx->mo.nm_attr, trx->bts); + osmo_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_PRES_LEN(&tp, NM_ATT_RF_MAXPOWR_R, 1)) { + 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"); + return oml_fom_ack_nack(msg, NM_NACK_MSGINCONSIST_PHYSCFG); + } + memcpy(&_value, value, 2); + arfcn = ntohs(_value); + value += 2; + if (arfcn > 1024) { + oml_tx_failure_event_rep(&trx->bts->mo, + OSMO_EVT_WARN_SW_WARN, + "Given ARFCN %u is unsupported", + arfcn); + LOGP(DOML, LOGL_NOTICE, + "Given ARFCN %u is unsupported.\n", arfcn); + 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(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + + /* RSL_MT_IPAC_PDCH_ACT style dyn PDCH */ + if (pchan == GSM_PCHAN_TCH_F_PDCH) + pchan = ts->flags & TS_F_PDCH_ACTIVE? GSM_PCHAN_PDCH + : GSM_PCHAN_TCH_F; + + /* Osmocom RSL CHAN ACT style dyn TS */ + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + pchan = ts->dyn.pchan_is; + + /* If the dyn TS doesn't have a pchan yet, do nothing. */ + if (pchan == GSM_PCHAN_NONE) + return 0; + } + + return conf_lchans_as_pchan(ts, pchan); +} + +int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan) +{ + struct gsm_lchan *lchan; + unsigned int i; + + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* fallthrough */ + case GSM_PCHAN_CCCH_SDCCH4: + for (i = 0; i < 4; i++) { + lchan = &ts->lchan[i]; + if (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[CCCH_LCHAN]; + 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 (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: + LOGP(DOML, LOGL_ERROR, "Unknown/unhandled PCHAN type: %u %s\n", + ts->pchan, gsm_pchan_name(ts->pchan)); + return -NM_NACK_PARAM_RANGE; + } + 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) { + oml_tx_failure_event_rep(&ts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for Set Channel Attribute " + "not supported"); + 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 = osmo_tlvp_copy(ts->mo.nm_attr, bts); + osmo_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) { + LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid attribute value, rc=%d\n", rc); + 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_PRES_LEN(&tp, NM_ATT_CHAN_COMB, 1)) { + uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB); + ts->pchan = abis_nm_pchan4chcomb(comb); + rc = conf_lchans(ts); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid Chan Comb 0x%x" + " (pchan=%s, conf_lchans()->%d)\n", + comb, gsm_pchan_name(ts->pchan), rc); + talloc_free(tp_merged); + /* Send NACK */ + return oml_fom_ack_nack(msg, -rc); + } + } + + /* 9.4.5 ARFCN List */ + + /* 9.4.60 TSC */ + if (TLVP_PRES_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 = BSIC2BCC(bts->bsic); + } + LOGP(DOML, LOGL_INFO, "%s SET CHAN ATTR (TSC=%u pchan=%s)\n", + gsm_abis_mo_name(&ts->mo), ts->tsc, gsm_pchan_name(ts->pchan)); + + /* 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); +} + +/* Check and report if the BTS number received via OML is incorrect: + according to 3GPP TS 52.021 §9.3 BTS number is used to distinguish between different BTS of the same Site Manager. + As we always have only single BTS per Site Manager (in case of Abis/IP with each BTS having dedicated OML connection + to BSC), the only valid values are 0 and 0xFF (means all BTS' of a given Site Manager). */ +static inline bool report_bts_number_incorrect(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, bool is_formatted) +{ + struct gsm_bts_trx *trx; + struct gsm_abis_mo *mo = &bts->mo; + const char *form = is_formatted ? + "Unexpected BTS %d in formatted O&M %s (exp. 0 or 0xFF)" : + "Unexpected BTS %d in manufacturer O&M %s (exp. 0 or 0xFF)"; + + if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) { + LOGP(DOML, LOGL_ERROR, form, foh->obj_inst.bts_nr, get_value_string(abis_nm_msgtype_names, + foh->msg_type)); + LOGPC(DOML, LOGL_ERROR, "\n"); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (trx) { + trx->mo.obj_inst.bts_nr = 0; + trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr; + trx->mo.obj_inst.ts_nr = 0xff; + mo = &trx->mo; + } + oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UKWN_MSG, form, foh->obj_inst.bts_nr, + get_value_string(abis_nm_msgtype_names, foh->msg_type)); + + return true; + } + + return false; +} + +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"); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (trx) { + trx->mo.obj_inst.bts_nr = 0; + trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr; + trx->mo.obj_inst.ts_nr = 0xff; + oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG, + "Formatted O&M message too short"); + } + return -EIO; + } + + if (report_bts_number_incorrect(bts, foh, true)) + 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; + case NM_MT_GET_ATTR: + ret = oml_rx_get_attr(bts, msg); + break; + default: + LOGP(DOML, LOGL_INFO, "unknown Formatted O&M msg_type 0x%02x\n", + foh->msg_type); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (trx) { + trx->mo.obj_inst.bts_nr = 0; + trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr; + trx->mo.obj_inst.ts_nr = 0xff; + oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG, + "unknown Formatted O&M " + "msg_type 0x%02x", + foh->msg_type); + } else + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UKWN_MSG, + "unknown Formatted O&M " + "msg_type 0x%02x", + foh->msg_type); + ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL); + } + + return ret; +} + +/* + * manufacturer related messages + */ + +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 = + ntohs(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) { + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + if (!mo) + return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for IPAC Set Attribute not " + "supported\n"); + 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_PRES_LEN(tp, NM_ATT_IPACC_DST_IP, 4)) { + ip = ntohl(tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP)); + } + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP_PORT, 2)) { + port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT)); + } + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_STREAM_ID, 1)) { + 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); + + if (trx->bts->variant == BTS_OSMO_OMLDUMMY) { + rc = 0; + LOGP(DOML, LOGL_NOTICE, "Not connecting RSL in OML-DUMMY!\n"); + } else + rc = e1inp_ipa_bts_rsl_connect_n(oml_link->ts->line, inet_ntoa(in), port, trx->nr); + 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 (report_bts_number_incorrect(bts, foh, false)) + 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; +} + +static int handle_fail_sig(unsigned int subsys, unsigned int signal, void *handle, + void *signal_data) +{ + if (signal_data) + oml_tx_failure_event_rep(handle, signal, "%s", signal_data); + else + oml_tx_failure_event_rep(handle, signal, ""); + + return 0; +} + +int oml_init(struct gsm_abis_mo *mo) +{ + DEBUGP(DOML, "Initializing OML attribute definitions\n"); + tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef_ipa); + tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef); + osmo_signal_register_handler(SS_FAIL, handle_fail_sig, mo); + + return 0; +} diff --git a/src/common/paging.c b/src/common/paging.c new file mode 100644 index 00000000..aa604e72 --- /dev/null +++ b/src/common/paging.c @@ -0,0 +1,630 @@ +/* Paging message encoding + queue management */ + +/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +/* TODO: + * eMLPP priprity + * add P1/P2/P3 rest octets + */ + +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <time.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/gsm/gsm48.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/pcu_if.h> + +#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 *bts; + + /* 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]; + int blocks = gsm48_number_of_paging_subchannels(&ps->chan_desc); + struct paging_record *pr; + + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_RCVD); + + if (paging_group >= blocks) { + LOGP(DPAG, LOGL_ERROR, "BSC Send PAGING for group %u, but number of paging " + "sub-channels is only %u\n", paging_group, blocks); + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP); + return -EINVAL; + } + + if (ps->num_paging >= ps->num_paging_max) { + LOGP(DPAG, LOGL_NOTICE, "Dropping paging, queue full (%u)\n", + ps->num_paging); + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP); + 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 = tlv_put(cur, GSM48_IE_MOBILE_ID, 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 = tlv_put(pt2->data, GSM48_IE_MOBILE_ID, 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, uint8_t cneed3, + const uint8_t *tmsi4_lv, uint8_t cneed4) +{ + struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf; + + 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); + + /* The structure definition in libosmocore is wrong. It includes as last + * byte some invalid definition of chneed3/chneed4, so we must do this by hand + * here and cannot rely on sizeof(*pt3) */ + out_buf[20] = (0x23 & ~0xf8) | 0x80 | (cneed3 & 3) << 5 | (cneed4 & 3) << 3; + + return 21; +} + +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->bts->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->bts->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[2]->u.paging.chan_needed, + pr[3]->u.paging.identity_lv, + pr[3]->u.paging.chan_needed); + } 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; + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_SENT); + /* 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 paging_state *ps = bts->paging_state; + struct gsm48_system_information_type_3 *si3 = (void *) bts->si_buf[SYSINFO_TYPE_3]; + + paging_si_update(ps, &si3->control_channel_desc); + } + return 0; +} + +static int initialized = 0; + +struct paging_state *paging_init(struct gsm_bts *bts, + unsigned int num_paging_max, + unsigned int paging_lifetime) +{ + struct paging_state *ps; + unsigned int i; + + ps = talloc_zero(bts, struct paging_state); + if (!ps) + return NULL; + + ps->bts = bts; + 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; +} diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c new file mode 100644 index 00000000..60e0f7a7 --- /dev/null +++ b/src/common/pcu_sock.c @@ -0,0 +1,982 @@ +/* pcu_sock.c: Connect from PCU via unix domain socket */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu> + * (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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <inttypes.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/gsm/gsm23003.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/l1sap.h> + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); + +extern struct gsm_network bts_gsmnet; +int pcu_direct = 0; +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); + +/* + * 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; +} + +static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { + if (ts->pchan == GSM_PCHAN_PDCH) + return true; + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + /* When we're busy deactivating the PDCH, we first set + * DEACT_PENDING, tell the PCU about it and wait for a + * response. So DEACT_PENDING means "no PDCH" to the PCU. + * Similarly, when we're activating PDCH, we set the + * ACT_PENDING and wait for an activation response from the + * PCU, so ACT_PENDING means "is PDCH". */ + if (ts->flags & TS_F_PDCH_ACTIVE) + return !(ts->flags & TS_F_PDCH_DEACT_PENDING); + else + return (ts->flags & TS_F_PDCH_ACT_PENDING); + } + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + /* + * When we're busy de-/activating the PDCH, we first set + * ts->dyn.pchan_want, tell the PCU about it and wait for a + * response. So only care about dyn.pchan_want here. + */ + return ts->dyn.pchan_want == GSM_PCHAN_PDCH; + } + return false; +} + +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->plmn.mcc; + info_ind->mnc = net->plmn.mnc; + info_ind->mnc_3_digits = net->plmn.mnc_3_digits; + 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 = gsm_bts_trx_num(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_should_be_pdch(ts)) { + info_ind->trx[i].pdch_mask |= (1 << j); + info_ind->trx[i].tsc[j] = gsm_ts_tsc(ts); + + 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]; + osmo_plmn_from_bcd(si3->lai.digits, &net->plmn); + 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 sapi, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len, + int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual) +{ + 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: sapi=%s arfcn=%d block=%d data=%s\n", + sapi_string[sapi], arfcn, block_nr, osmo_hexdump(data, len)); + + if (lqual / 10 < bts->min_qual_norm) { + LOGP(DPCU, LOGL_DEBUG, "Link quality %"PRId16" is below threshold %f, dropping packet\n", + lqual, bts->min_qual_norm); + return 0; + } + + 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 = sapi; + 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->ber10k = ber10k; + data_ind->ta_offs_qbits = bto; + data_ind->lqual_cb = lqual; + 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, uint16_t ra, uint32_t fn, + uint8_t is_11bit, enum ph_burst_type burst_type) +{ + 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; + rach_ind->is_11bit = is_11bit; + rach_ind->burst_type = burst_type; + + 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_PCH: + paging_add_imm_ass(bts->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 = gsm_bts_trx_num(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; + } + if (data_req->ts_nr >= ARRAY_SIZE(trx->ts)) { + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "not existing TS %u\n", data_req->ts_nr); + rc = -EINVAL; + break; + } + ts = &trx->ts[data_req->ts_nr]; + if (!ts_should_be_pdch(ts)) { + LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for non-PDCH TS\n", + gsm_ts_name(ts)); + rc = -EINVAL; + break; + } + if (ts->lchan[0].state != LCHAN_S_ACTIVE) { + LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for inactive lchan\n", + gsm_ts_name(ts)); + rc = -EINVAL; + break; + } + 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_pag_req(struct gsm_bts *bts, uint8_t msg_type, + struct gsm_pcu_if_pag_req *pag_req) +{ + int rc = 0; + + OSMO_ASSERT(msg_type == PCU_IF_MSG_PAG_REQ); + + /* FIXME: Add function to schedule paging request. + * At present, osmo-pcu sends paging requests in PCU_IF_MSG_DATA_REQ + * messages which are processed by pcu_rx_data_req(). + * This code path is not triggered in practice. */ + LOGP(DPCU, LOGL_NOTICE, "Paging request received: chan_needed=%d length=%d " + "(dropping message because support for PCU_IF_MSG_PAG_REQ is not yet implemented)\n", + pag_req->chan_needed, pag_req->identity_lv[0]); + + return rc; +} + +int pcu_tx_si13(const struct gsm_bts *bts, bool enable) +{ + /* the SI is per-BTS so it doesn't matter which TRX we use */ + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 0); + + /* The low-level data like FN, ARFCN etc will be ignored but we have to set lqual high enough to bypass + the check at lower levels */ + int rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, GSM_BTS_SI(bts, SYSINFO_TYPE_13), + enable ? GSM_MACBLOCK_LEN : 0, 0, 0, 0, INT16_MAX); + if (rc < 0) + LOGP(DPCU, LOGL_NOTICE, "Failed to send SI13 to PCU: %d\n", rc); + + return rc; +} + +static int pcu_rx_txt_ind(struct gsm_bts *bts, + struct gsm_pcu_if_txt_ind *txt) +{ + switch (txt->type) { + case PCU_VERSION: + LOGP(DPCU, LOGL_INFO, "OsmoPCU version %s connected\n", + txt->text); + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, txt->text); + osmo_strlcpy(bts->pcu_version, txt->text, MAX_VERSION_LENGTH); + + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) + return pcu_tx_si13(bts, true); + + LOGP(DPCU, LOGL_INFO, "SI13 is not available on PCU connection\n"); + break; + case PCU_OML_ALERT: + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, txt->text); + break; + default: + LOGP(DPCU, LOGL_ERROR, "Unknown TXT_IND type %u received\n", + txt->type); + return -EINVAL; + } + + return 0; +} + +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 = gsm_bts_trx_num(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, + "%s request, but lchan is not of type PDTCH (is %s)\n", + (act_req->activate) ? "Activate" : "Deactivate", + gsm_lchant_name(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 */ + if (pcu_prim->bts_nr != 0) { + LOGP(DPCU, LOGL_ERROR, "Received PCU Prim for non-existent BTS %u\n", pcu_prim->bts_nr); + return -EINVAL; + } + bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + + switch (msg_type) { + case PCU_IF_MSG_DATA_REQ: + rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); + break; + case PCU_IF_MSG_PAG_REQ: + rc = pcu_rx_pag_req(bts, msg_type, &pcu_prim->u.pag_req); + break; + case PCU_IF_MSG_ACT_REQ: + rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req); + break; + case PCU_IF_MSG_TXT_IND: + rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); + 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"); + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, NULL); + bts->pcu_version[0] = '\0'; + + 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 = gsm_bts_trx_num(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[0].rel_act_kind = LCHAN_REL_ACT_PCU; + l1sap_chan_rel(trx, + gsm_lchan2chan_nr(&ts->lchan[0])); + } + } + } + + /* 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; + } + + if (rc < sizeof(*pcu_prim)) { + LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive size " + "is %lu, discarding\n", rc, sizeof(*pcu_prim)); + return 0; + } + + 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(const char *path) +{ + 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; + + bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, + OSMO_SOCK_F_BIND); + if (bfd->fd < 0) { + LOGP(DPCU, LOGL_ERROR, "Could not create %s unix socket: %s\n", + path, strerror(errno)); + talloc_free(state); + return -1; + } + + 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; + + LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path); + + 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; +} + +bool pcu_connected(void) { + struct gsm_network *net = &bts_gsmnet; + struct pcu_sock_state *state = net->pcu_state; + + if (!state) + return false; + if (state->conn_bfd.fd <= 0) + return false; + return true; +} diff --git a/src/common/phy_link.c b/src/common/phy_link.c new file mode 100644 index 00000000..588fcc91 --- /dev/null +++ b/src/common/phy_link.c @@ -0,0 +1,163 @@ +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts_model.h> + +static LLIST_HEAD(g_phy_links); + +struct phy_link *phy_link_by_num(int num) +{ + struct phy_link *plink; + + llist_for_each_entry(plink, &g_phy_links, list) { + if (plink->num == num) + return plink; + } + + return NULL; +} + +struct phy_link *phy_link_create(void *ctx, int num) +{ + struct phy_link *plink; + + if (phy_link_by_num(num)) + return NULL; + + plink = talloc_zero(ctx, struct phy_link); + plink->num = num; + plink->state = PHY_LINK_SHUTDOWN; + INIT_LLIST_HEAD(&plink->instances); + llist_add_tail(&plink->list, &g_phy_links); + + bts_model_phy_link_set_defaults(plink); + + return plink; +} + +const struct value_string phy_link_state_vals[] = { + { PHY_LINK_SHUTDOWN, "shutdown" }, + { PHY_LINK_CONNECTING, "connecting" }, + { PHY_LINK_CONNECTED, "connected" }, + { 0, NULL } +}; + +void phy_link_state_set(struct phy_link *plink, enum phy_link_state state) +{ + struct phy_instance *pinst; + + LOGP(DL1C, LOGL_INFO, "PHY link state change %s -> %s\n", + get_value_string(phy_link_state_vals, plink->state), + get_value_string(phy_link_state_vals, state)); + + /* notify all TRX associated with this phy */ + llist_for_each_entry(pinst, &plink->instances, list) { + struct gsm_bts_trx *trx = pinst->trx; + if (!trx) + continue; + + switch (state) { + case PHY_LINK_CONNECTED: + LOGP(DL1C, LOGL_INFO, "trx_set_avail(1)\n"); + trx_set_available(trx, 1); + break; + case PHY_LINK_SHUTDOWN: + LOGP(DL1C, LOGL_INFO, "trx_set_avail(0)\n"); + trx_set_available(trx, 0); + break; + case PHY_LINK_CONNECTING: + /* nothing to do */ + break; + } + } + + plink->state = state; +} + +struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num) +{ + struct phy_instance *pinst; + + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->num == num) + return pinst; + } + return NULL; +} + +struct phy_instance *phy_instance_create(struct phy_link *plink, int num) +{ + struct phy_instance *pinst; + + if (phy_instance_by_num(plink, num)) + return NULL; + + pinst = talloc_zero(plink, struct phy_instance); + pinst->num = num; + pinst->phy_link = plink; + llist_add_tail(&pinst->list, &plink->instances); + + bts_model_phy_instance_set_defaults(pinst); + + return pinst; +} + +void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx) +{ + trx->role_bts.l1h = pinst; + pinst->trx = trx; +} + +void phy_instance_destroy(struct phy_instance *pinst) +{ + /* remove from list of instances in the link */ + llist_del(&pinst->list); + + /* remove reverse link from TRX */ + OSMO_ASSERT(pinst->trx->role_bts.l1h == pinst); + pinst->trx->role_bts.l1h = NULL; + pinst->trx = NULL; + + talloc_free(pinst); +} + +void phy_link_destroy(struct phy_link *plink) +{ + struct phy_instance *pinst, *pinst2; + + llist_for_each_entry_safe(pinst, pinst2, &plink->instances, list) + phy_instance_destroy(pinst); + + talloc_free(plink); +} + +int phy_links_open(void) +{ + struct phy_link *plink; + + llist_for_each_entry(plink, &g_phy_links, list) { + int rc; + + rc = bts_model_phy_link_open(plink); + if (rc < 0) + return rc; + } + + return 0; +} + +const char *phy_instance_name(struct phy_instance *pinst) +{ + static char buf[32]; + + snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num, + pinst->num); + return buf; +} diff --git a/src/common/power_control.c b/src/common/power_control.c new file mode 100644 index 00000000..b1728705 --- /dev/null +++ b/src/common/power_control.c @@ -0,0 +1,89 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> + +/* + * 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; + 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? */ + rx = bts->ul_power_target - rxLevel; + + 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; +} diff --git a/src/common/rsl.c b/src/common/rsl.c new file mode 100644 index 00000000..507e8aaf --- /dev/null +++ b/src/common/rsl.c @@ -0,0 +1,2996 @@ +/* GSM TS 08.58 RSL, BTS Side */ + +/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2011-2017 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "btsconfig.h" /* for PACKAGE_VERSION */ + +#include <stdio.h> +#include <errno.h> +#include <netdb.h> +#include <stdbool.h> +#include <sys/types.h> +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/trau/osmo_ortp.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/cbch.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcuif_proto.h> + +//#define FAKE_CIPH_MODE_COMPL + + +static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uint8_t *chan_nr, + const uint8_t *link_id, const struct msgb *orig_msg); + +/* 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; + lchan->ts->trx->bts->dtxd = (cm->dtx_dtu & RSL_CMOD_DTXd) ? true : false; + + 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 + */ + +/* Is this channel number for a dedicated channel (true) or not (false) */ +static bool chan_nr_is_dchan(uint8_t chan_nr) +{ + /* See TS 48.058 9.3.1 + Osmocom extension for RSL_CHAN_OSMO_PDCH */ + if ((chan_nr & 0xc0) == 0x80) + return false; + else + return true; +} + +static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + const char *log_name) +{ + int rc; + struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc); + + if (!lchan) { + LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n", log_name, + chan_nr); + return NULL; + } + + if (rc < 0) { + LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n", + gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr); + return NULL; + } + 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, const uint8_t *chan_nr, + const uint8_t *link_id, const struct msgb *orig_msg) +{ + unsigned int len = sizeof(struct abis_rsl_common_hdr); + struct msgb *nmsg; + + LOGP(DRSL, LOGL_NOTICE, "Tx RSL Error Report: cause = 0x%02x\n", cause); + + if (orig_msg) + len += 2 + 3+msgb_l2len(orig_msg); /* chan_nr + TLV(orig_msg) */ + if (chan_nr) + len += 2; + if (link_id) + len += 2; + + nmsg = rsl_msgb_alloc(len); + if (!nmsg) + return -ENOMEM; + msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause); + if (orig_msg && msgb_l2len(orig_msg) >= sizeof(struct abis_rsl_common_hdr)) { + struct abis_rsl_common_hdr *ch = (struct abis_rsl_common_hdr *) msgb_l2(orig_msg); + msgb_tv_put(nmsg, RSL_IE_MSG_ID, ch->msg_type); + } + if (chan_nr) + msgb_tv_put(nmsg, RSL_IE_CHAN_NR, *chan_nr); + if (link_id) + msgb_tv_put(nmsg, RSL_IE_LINK_IDENT, *link_id); + if (orig_msg) + msgb_tlv_put(nmsg, RSL_IE_ERR_MSG, msgb_l2len(orig_msg), msgb_l2(orig_msg)); + + 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 + msgb_tlv_put(nmsg, RSL_IE_RESOURCE_INFO, 0, NULL); + 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, count; + enum osmo_sysinfo_type osmo_si; + struct gsm48_system_information_type_2quater *si2q; + struct bitvec bv; + 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, NULL, NULL, msg); + + 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, NULL, NULL, msg); + + 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, NULL, NULL, msg); + } + /* 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)) { + LOGP(DRSL, LOGL_ERROR, "Truncating received Full BCCH Info (%u -> %zu) for SI%s\n", + len, sizeof(sysinfo_buf_t), get_value_string(osmo_sitype_strs, osmo_si)); + len = sizeof(sysinfo_buf_t); + } + + LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s, %u bytes)\n", + get_value_string(osmo_sitype_strs, osmo_si), len); + + if (SYSINFO_TYPE_2quater == osmo_si) { + si2q = (struct gsm48_system_information_type_2quater *) TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO); + bv.data = si2q->rest_octets; + bv.data_len = GSM_MACBLOCK_LEN; + bv.cur_bit = 3; + bts->si2q_index = (uint8_t) bitvec_get_uint(&bv, 4); + + count = (uint8_t) bitvec_get_uint(&bv, 4); + if (bts->si2q_count && bts->si2q_count != count) { + LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI2quater count updated: %u -> %d\n", + bts->si2q_count, count); + } + + bts->si2q_count = count; + if (bts->si2q_index > bts->si2q_count) { + LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with index %u > count %u\n", + bts->si2q_index, bts->si2q_count); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + } + + if (bts->si2q_index > SI2Q_MAX_NUM || bts->si2q_count > SI2Q_MAX_NUM) { + LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with impossible parameters: index %u, count %u" + "should be <= %u\n", bts->si2q_index, bts->si2q_count, SI2Q_MAX_NUM); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + } + + memset(GSM_BTS_SI2Q(bts, bts->si2q_index), GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); + memcpy(GSM_BTS_SI2Q(bts, bts->si2q_index), TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); + } else { + memset(bts->si_buf[osmo_si], GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); + memcpy(bts->si_buf[osmo_si], TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); + } + + bts->si_valid |= (1 << osmo_si); + + if (SYSINFO_TYPE_3 == osmo_si && trx->nr == 0 && + num_agch(trx, "RSL") != 1) { + lchan_deactivate(&trx->bts->c0->ts[0].lchan[CCCH_LCHAN]); + /* will be reactivated by sapi_deactivate_cb() */ + trx->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_REACT; + } + + if (SYSINFO_TYPE_13 == osmo_si) + pcu_tx_si13(trx->bts, true); + + } 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)); + if (SYSINFO_TYPE_13 == osmo_si) + pcu_tx_si13(trx->bts, false); + } + 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.4 DELETE INDICATION */ +int rsl_tx_delete_ind(struct gsm_bts *bts, const uint8_t *ia, uint8_t ia_len) +{ + struct msgb *msg; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); + if (!msg) + return -ENOMEM; + rsl_cch_push_hdr(msg, RSL_MT_DELETE_IND, RSL_CHAN_PCH_AGCH); + msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, ia_len, ia); + 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 tlv_parsed tp; + struct gsm_bts *bts = trx->bts; + 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, NULL, NULL, msg); + + paging_group = *TLVP_VAL(&tp, RSL_IE_PAGING_GROUP); + identity_lv = TLVP_VAL(&tp, RSL_IE_MS_IDENTITY)-1; + + if (TLVP_PRES_LEN(&tp, RSL_IE_CHAN_NEEDED, 1)) + chan_needed = *TLVP_VAL(&tp, RSL_IE_CHAN_NEEDED); + + rc = paging_add_identity(bts->paging_state, paging_group, identity_lv, chan_needed); + if (rc < 0) { + /* FIXME: notfiy the BSC on other errors? */ + if (rc == -ENOSPC) + oml_fail_rep(OSMO_EVT_MIN_PAG_TAB_FULL, + "BTS paging table is full"); + } + + 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, NULL, NULL, msg); + + 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)); +} + +/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given output buffer. + * \param[out] buf Output buffer, must be caller-allocated and hold at least len + 2 or sizeof(sysinfo_buf_t) bytes + * \param[out] valid pointer to bit-mask of 'valid' System information types + * \param[in] current input data (L3 without L2/L1 header) + * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] len length of \a current in octets */ +static inline void lapdm_ui_prefix(uint8_t *buf, uint32_t *valid, const uint8_t *current, uint8_t osmo_si, uint16_t len) +{ + /* We have to pre-fix with the two-byte LAPDM UI header */ + if (len > sizeof(sysinfo_buf_t) - 2) { + LOGP(DRSL, LOGL_ERROR, "Truncating received SI%s (%u -> %zu) to prepend LAPDM UI header (2 bytes)\n", + get_value_string(osmo_sitype_strs, osmo_si), len, sizeof(sysinfo_buf_t) - 2); + len = sizeof(sysinfo_buf_t) - 2; + } + + (*valid) |= (1 << osmo_si); + buf[0] = 0x03; /* C/R + EA */ + buf[1] = 0x03; /* UI frame */ + + memset(buf + 2, GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t) - 2); + memcpy(buf + 2, current, len); +} + +/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given BTS SACCH buffer + * \param[out] bts BTS in whose System Information State we shall store + * \param[in] current input data (L3 without L2/L1 header) + * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] len length of \a current in octets */ +static inline void lapdm_ui_prefix_bts(struct gsm_bts *bts, const uint8_t *current, uint8_t osmo_si, uint16_t len) +{ + lapdm_ui_prefix(GSM_BTS_SI(bts, osmo_si), &bts->si_valid, current, osmo_si, len); +} + +/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given lchan SACCH buffer + * \param[out] lchan Logical Channel in whose System Information State we shall store + * \param[in] current input data (L3 without L2/L1 header) + * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] len length of \a current in octets */ +static inline void lapdm_ui_prefix_lchan(struct gsm_lchan *lchan, const uint8_t *current, uint8_t osmo_si, uint16_t len) +{ + lapdm_ui_prefix(GSM_LCHAN_SI(lchan, osmo_si), &lchan->si.valid, current, osmo_si, len); +} + +/* 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, NULL, NULL, msg); + + 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, NULL, NULL, msg); + + 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, NULL, NULL, msg); + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); + struct gsm_bts_trx *t; + + lapdm_ui_prefix_bts(bts, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len); + + /* Propagate SI change to all lchans which adhere to BTS-global default. */ + llist_for_each_entry(t, &bts->trx_list, list) { + int i, j; + for (i = 0; i < ARRAY_SIZE(t->ts); i++) { + struct gsm_bts_trx_ts *ts = &t->ts[i]; + for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + if (lchan->state == LCHAN_S_NONE || (lchan->si.overridden & (1 << osmo_si))) + continue; + lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len); + } + } + } + + LOGP(DRSL, LOGL_INFO, " Rx RSL SACCH FILLING (SI%s, %u bytes)\n", + get_value_string(osmo_sitype_strs, osmo_si), len); + } else { + struct gsm_bts_trx *t; + + bts->si_valid &= ~(1 << osmo_si); + + /* Propagate SI change to all lchans which adhere to BTS-global default. */ + llist_for_each_entry(t, &bts->trx_list, list) { + int i, j; + for (i = 0; i < ARRAY_SIZE(t->ts); i++) { + struct gsm_bts_trx_ts *ts = &t->ts[i]; + for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + if (lchan->state == LCHAN_S_NONE || (lchan->si.overridden & (1 << osmo_si))) + continue; + lchan->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, NULL, NULL, msg); + + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_RCVD); + + /* 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 */ + rsl_tx_delete_ind(trx->bts, TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO), + TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO)); + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_DELETED); + msgb_free(msg); + } + + /* return 1 means: don't msgb_free() the msg */ + return 1; +} + +/* + * dedicated channel related messages + */ + +/* Send an RF CHANnel RELease ACKnowledge with the given chan_nr. This chan_nr may mismatch the current + * lchan state, if we received a CHANnel RELease for an already released channel, and we're just acking + * what we got without taking any action. */ +static int tx_rf_rel_ack(struct gsm_lchan *lchan, uint8_t chan_nr) +{ + struct msgb *msg; + + 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.19 sending RF CHANnel RELease ACKnowledge */ +int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan) +{ + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + bool send_rel_ack; + + switch (lchan->rel_act_kind) { + case LCHAN_REL_ACT_RSL: + send_rel_ack = true; + break; + + case LCHAN_REL_ACT_PCU: + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (lchan->ts->dyn.pchan_is != GSM_PCHAN_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s (ss=%d) PDCH release: not in PDCH mode\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr); + /* well, what to do about it ... carry on and hope it's fine. */ + } + /* remember the fact that the TS is now released */ + lchan->ts->dyn.pchan_is = GSM_PCHAN_NONE; + /* Continue to ack the release below. (This is a non-standard rel ack invented + * specifically for GSM_PCHAN_TCH_F_TCH_H_PDCH). */ + send_rel_ack = true; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* GSM_PCHAN_TCH_F_PDCH, does not require a rel ack. The caller + * l1sap_info_rel_cnf() will continue with bts_model_ts_disconnect(). */ + send_rel_ack = false; + break; + default: + LOGP(DRSL, LOGL_ERROR, "%s PCU rel ack for unexpected lchan kind\n", + gsm_lchan_name(lchan)); + /* Release certainly was not requested by the BSC via RSL, so don't ack. */ + send_rel_ack = false; + break; + } + break; + + default: + /* A rel that was not requested by the BSC via RSL, hence not sending a rel ack to the + * BSC. */ + send_rel_ack = false; + break; + } + + if (!send_rel_ack) { + LOGP(DRSL, LOGL_NOTICE, "%s not sending REL ACK\n", + gsm_lchan_name(lchan)); + return 0; + } + + LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN REL ACK\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr, + gsm_lchant_name(lchan->type)); + + /* + * Free the LAPDm resources now that the BTS + * has released all the resources. + */ + lapdm_channel_exit(&lchan->lapdm_ch); + + return tx_rf_rel_ack(lchan, chan_nr); +} + +/* 8.4.2 sending CHANnel ACTIVation ACKnowledge */ +static 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]; + + LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN ACT ACK\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr, + gsm_lchant_name(lchan->type)); + + 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_reset(lchan); + + 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 */ +static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + + if (lchan) + LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan)); + else + LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr); + LOGPC(DRSL, LOGL_NOTICE, "Sending Channel Activated NACK: cause = 0x%02x\n", 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 = trx; + + return abis_bts_rsl_sendmsg(msg); +} +static int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause) { + return _rsl_tx_chan_act_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan); +} + +/* Send an RSL Channel Activation Ack if cause is zero, a Nack otherwise. */ +int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause) +{ + if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) { + LOGP(DRSL, LOGL_NOTICE, "%s not sending CHAN ACT %s\n", + gsm_lchan_name(lchan), cause ? "NACK" : "ACK"); + return 0; + } + + if (cause) + return rsl_tx_chan_act_nack(lchan, cause); + return rsl_tx_chan_act_ack(lchan); +} + +/* 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]; + int osmo_si = osmo_rsl2sitype(rsl_si); + uint32_t osmo_si_shifted = (1 << osmo_si); + osmo_static_assert(_MAX_SYSINFO_TYPE <= sizeof(osmo_si_shifted) * 8, + si_enum_vals_fit_in_bit_mask); + + 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(GSM_LCHAN_SI(lchan, osmo_si), GSM_BTS_SI(bts, 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 *bts = lchan->ts->trx->bts; + const char *ciph_name = get_value_string(gsm0808_chosen_enc_alg_names, *val); + + /* check if the encryption algorithm sent by BSC is supported! */ + rc = bts_supports_cipher(bts, *val); + if (rc != 1) { + LOGP(DRSL, LOGL_ERROR, "%s: BTS doesn't support cipher %s\n", + gsm_lchan_name(lchan), ciph_name); + return -EINVAL; + } + + /* length can be '1' in case of no ciphering */ + if (len < 1) { + LOGP(DRSL, LOGL_ERROR, "%s: Encryption Info cannot have len=%d\n", + gsm_lchan_name(lchan), len); + 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); + DEBUGP(DRSL, "%s: Setting lchan cipher algorithm %s\n", + gsm_lchan_name(lchan), ciph_name); + + return 0; +} + +/* Make sure no state from TCH use remains. */ +static void clear_lchan_for_pdch_activ(struct gsm_lchan *lchan) +{ + /* These values don't apply to PDCH, just clear them. Particularly the encryption must be + * cleared, or we would enable encryption on PDCH with parameters remaining from the TCH. */ + 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; + lchan->rsl_cmode = 0; + lchan->tch_mode = 0; + memset(&lchan->encr, 0, sizeof(lchan->encr)); + memset(&lchan->ho, 0, sizeof(lchan->ho)); + lchan->bs_power = 0; + lchan->ms_power = 0; + memset(&lchan->ms_power_ctrl, 0, sizeof(lchan->ms_power_ctrl)); + lchan->rqd_ta = 0; + copy_sacch_si_to_lchan(lchan); + memset(&lchan->tch, 0, sizeof(lchan->tch)); +} + +/*! + * Store the CHAN_ACTIV msg, connect the L1 timeslot in the proper type and + * then invoke rsl_rx_chan_activ() with msg. + */ +static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg) +{ + DEBUGP(DRSL, "%s dyn_ts_l1_reconnect\n", gsm_ts_and_pchan_name(ts)); + + switch (ts->dyn.pchan_want) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + break; + case GSM_PCHAN_PDCH: + /* Only the first lchan matters for PDCH */ + clear_lchan_for_pdch_activ(ts->lchan); + break; + default: + LOGP(DRSL, LOGL_ERROR, + "%s Cannot reconnect as pchan %s\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(ts->dyn.pchan_want)); + return -EINVAL; + } + + /* We will feed this back to rsl_rx_chan_activ() later */ + ts->dyn.pending_chan_activ = msg; + + /* Disconnect, continue connecting from cb_ts_disconnected(). */ + DEBUGP(DRSL, "%s Disconnect\n", gsm_ts_and_pchan_name(ts)); + return bts_model_ts_disconnect(ts); +} + +static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr) +{ + uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK; + switch (cbits) { + case RSL_CHAN_Bm_ACCHs: + return GSM_PCHAN_TCH_F; + case RSL_CHAN_Lm_ACCHs: + case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1): + return GSM_PCHAN_TCH_H; + case RSL_CHAN_OSMO_PDCH: + return GSM_PCHAN_PDCH; + default: + LOGP(DRSL, LOGL_ERROR, + "chan nr 0x%x not covered by dyn_pchan_from_chan_nr()\n", + chan_nr); + return GSM_PCHAN_UNKNOWN; + } +} + +/* 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 gsm_bts_trx_ts *ts = lchan->ts; + 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, but in state: %s.\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL); + } + + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + ts->dyn.pchan_want = dyn_pchan_from_chan_nr(dch->chan_nr); + DEBUGP(DRSL, "%s rx chan activ\n", gsm_ts_and_pchan_name(ts)); + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + /* + * The phy has the timeslot connected in a different + * mode than this activation needs it to be. + * Re-connect, then come back to rsl_rx_chan_activ(). + */ + rc = dyn_ts_l1_reconnect(ts, msg); + if (rc) + return rsl_tx_chan_act_nack(lchan, RSL_ERR_NORMAL_UNSPEC); + /* indicate that the msgb should not be freed. */ + return 1; + } + } + + LOGP(DRSL, LOGL_DEBUG, "%s: rx Channel Activation in state: %s.\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); + + /* 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 (type != RSL_ACT_OSMO_PDCH) { + 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) { + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_ENCR_UNIMPL); + } + } 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_PRES_LEN(&tp, RSL_IE_HANDO_REF, 1)) { + lchan->ho.active = HANDOVER_ENABLED; + lchan->ho.ref = *TLVP_VAL(&tp, RSL_IE_HANDO_REF); + } + + /* 9.3.4 BS Power */ + if (TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1)) + lchan->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + /* 9.3.13 MS Power */ + if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) { + 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_PRES_LEN(&tp, RSL_IE_TIMING_ADVANCE, 1)) + 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; + + if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) { + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, + &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, 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); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, + NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT); + } + + lapdm_ui_prefix_lchan(lchan, cur, osmo_si, si_len); + + cur += si_len; + if (cur >= val + tot_len) { + LOGP(DRSL, LOGL_ERROR, "Error parsing SACCH INFO IE\n"); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, + NULL, msg); + return rsl_tx_chan_act_acknack(lchan, 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"); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, 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, "%s: chan_nr=%s type=0x%02x mode=%s\n", + gsm_lchan_name(lchan), rsl_chan_nr_str(dch->chan_nr), type, + gsm48_chan_mode_name(lchan->tch_mode)); + + /* Connecting PDCH on dyn TS goes via PCU instead. */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && ts->dyn.pchan_want == GSM_PCHAN_PDCH) { + /* + * We ack the activation to the BSC right away, regardless of + * the PCU succeeding or not; if a dynamic timeslot fails to go + * to PDCH mode for any reason, the BSC should still be able to + * switch it back to TCH modes and should not put the time slot + * in an error state. So for operating dynamic TS, the BSC + * would not take any action if the PDCH mode failed, e.g. + * because the PCU is not yet running. Even if alerting the + * core network of broken GPRS service is desired, this only + * makes sense when the PCU has not shown up for some time. + * It's easiest to not forward activation delays to the BSC: if + * the BSC tells us to do PDCH, we do our best, and keep the + * details on the BTS and PCU level. This is kind of analogous + * to how plain PDCH TS operate. Directly call + * rsl_tx_chan_act_ack() instead of rsl_tx_chan_act_acknack() + * because we don't want/need to decide whether to drop due to + * lchan->rel_act_kind. + */ + rc = rsl_tx_chan_act_ack(lchan); + if (rc < 0) + LOGP(DRSL, LOGL_ERROR, "%s Cannot send act ack: %d\n", + gsm_ts_and_pchan_name(ts), rc); + + /* + * pcu_tx_info_ind() will pick up the ts->dyn.pchan_want. If + * the PCU is not connected yet, ignore for now; the PCU will + * catch up (and send the RSL ack) once it connects. + */ + if (pcu_connected()) { + DEBUGP(DRSL, "%s Activate via PCU\n", gsm_ts_and_pchan_name(ts)); + rc = pcu_tx_info_ind(); + } + else { + DEBUGP(DRSL, "%s Activate via PCU when PCU connects\n", + gsm_ts_and_pchan_name(ts)); + rc = 0; + } + if (rc) { + rsl_tx_error_report(msg->trx, RSL_ERR_NORMAL_UNSPEC, &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_NORMAL_UNSPEC); + } + return 0; + } + + /* Remember to send an RSL ACK once the lchan is active */ + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + + /* actually activate the channel in the BTS */ + rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr, &tp); + if (rc < 0) + return rsl_tx_chan_act_acknack(lchan, -rc); + + return 0; +} + +static int dyn_ts_pdch_release(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, "%s: PDCH release requested but already" + " in switchover\n", gsm_ts_and_pchan_name(ts)); + return -EINVAL; + } + + /* + * Indicate PDCH Disconnect in dyn_pdch.want, let pcu_tx_info_ind() + * pick it up and wait for PCU to disable the channel. + */ + ts->dyn.pchan_want = GSM_PCHAN_NONE; + + if (!pcu_connected()) { + /* PCU not connected yet. Just record the new type and done, + * the PCU will pick it up once connected. */ + ts->dyn.pchan_is = GSM_PCHAN_NONE; + return 1; + } + + return pcu_tx_info_ind(); +} + +/* 8.4.14 RF CHANnel RELease is received */ +static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr) +{ + int rc; + + if (lchan->state == LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, + "%s ss=%d state=%s Rx RSL RF Channel Release, but is already inactive;" + " just ACKing the release\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr, + gsm_lchans_name(lchan->state)); + /* Just ack the release and ignore. Make sure to reflect the same chan_nr we received, + * not necessarily reflecting the current lchan state. */ + return tx_rf_rel_ack(lchan, chan_nr); + } + + if (lchan->abis_ip.rtp_socket) { + rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC); + osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, + "Closing RTP socket on Channel Release "); + 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; + + /* Dynamic channel in PDCH mode is released via PCU */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH) { + rc = dyn_ts_pdch_release(lchan); + if (rc == 1) { + /* If the PCU is not connected, continue to rel ack right away. */ + lchan->rel_act_kind = LCHAN_REL_ACT_PCU; + return rsl_tx_rf_rel_ack(lchan); + } + /* Waiting for PDCH release */ + return rc; + } + + 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 <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm48.h> +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, &dch->chan_nr, NULL, msg); + } + + 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, &dch->chan_nr, NULL, msg); + } + + /* 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, &dch->chan_nr, + NULL, msg); + } + } + + /* 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_bts_trx *trx, uint8_t chan_nr, uint8_t cause, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + + if (lchan) + LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan)); + else + LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr); + LOGPC(DRSL, LOGL_NOTICE, "Tx MODE MODIFY NACK (cause = 0x%02x)\n", 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->trx = trx; + + return abis_bts_rsl_sendmsg(msg); +} +static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause) +{ + return _rsl_tx_mode_modif_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan); +} + + +/* 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"); + 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); + + if (bts_supports_cm(lchan->ts->trx->bts, ts_pchan(lchan->ts), lchan->tch_mode) != 1) { + LOGP(DRSL, LOGL_ERROR, + "%s %s: invalid mode: %s (wrong BSC configuration?)\n", + gsm_ts_and_pchan_name(lchan->ts), gsm_lchan_name(lchan), + gsm48_chan_mode_name(lchan->tch_mode)); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_SERV_OPT_UNAVAIL); + } + + /* 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) { + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_ENCR_UNIMPL); + } + } + + /* 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"); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_mode_modif_nack(lchan, 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_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) { + 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 abis_rsl_dchan_hdr *dch = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + 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, &dch->chan_nr, NULL, msg); + } + + /* 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, &dch->chan_nr, NULL, msg); + + 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, &dch->chan_nr, NULL, msg); + + 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, &dch->chan_nr, NULL, msg); + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); + + lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len); + if (memcmp(GSM_BTS_SI(bts, osmo_si), TLVP_VAL(&tp, RSL_IE_L3_INFO), sizeof(sysinfo_buf_t) != 0)) + lchan->si.overridden |= (1 << osmo_si); + else + lchan->si.overridden &= ~(1 << osmo_si); + + 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)); + + if (lchan->abis_ip.rtp_socket) + 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 = %s\n", + gsm_lchan_name(lchan), rsl_err_name(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_tv16_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 bind_rtp(struct gsm_bts *bts, struct osmo_rtp_socket *rs, const char *ip) +{ + int rc; + unsigned int i; + unsigned int tries; + + tries = (bts->rtp_port_range_end - bts->rtp_port_range_start) / 2; + for (i = 0; i < tries; i++) { + + if (bts->rtp_port_range_next >= bts->rtp_port_range_end) + bts->rtp_port_range_next = bts->rtp_port_range_start; + + rc = osmo_rtp_socket_bind(rs, ip, bts->rtp_port_range_next); + + bts->rtp_port_range_next += 2; + + if (rc == 0) + return 0; + } + + return -1; +} + +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 *bts = 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; + struct in_addr addr; + + 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); + + LOGP(DRSL, LOGL_DEBUG, "%s IPAC_%s: ", gsm_lchan_name(lchan), name); + if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_IP, 4)) { + connect_ip = tlvp_val32_unal(&tp, RSL_IE_IPAC_REMOTE_IP); + addr.s_addr = connect_ip; + LOGPC(DRSL, LOGL_DEBUG, "connect_ip=%s ", inet_ntoa(addr)); + } + + if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_PORT, 2)) { + connect_port = tlvp_val16_unal(&tp, RSL_IE_IPAC_REMOTE_PORT); + LOGPC(DRSL, LOGL_DEBUG, "connect_port=%u ", + ntohs(connect_port)); + } + + speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE); + if (speech_mode) + LOGPC(DRSL, LOGL_DEBUG, "speech_mode=%u ", *speech_mode); + + payload_type = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD); + if (payload_type) + LOGPC(DRSL, LOGL_DEBUG, "payload_type=%u ", *payload_type); + + LOGPC(DRSL, LOGL_DEBUG, "\n"); + + payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2); + + 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 cname[32]; + 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->tch.last_fn = LCHAN_FN_DUMMY; + lchan->abis_ip.rtp_socket = osmo_rtp_socket_create(lchan->ts->trx, + OSMO_RTP_F_POLL); + if (!lchan->abis_ip.rtp_socket) { + LOGP(DRTP, LOGL_ERROR, + "%s IPAC Failed to create RTP/RTCP sockets\n", + gsm_lchan_name(lchan)); + oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT, + "%s IPAC Failed to create RTP/RTCP sockets", + gsm_lchan_name(lchan)); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, + bts->rtp_jitter_adaptive ? + OSMO_RTP_P_JIT_ADAP : + OSMO_RTP_P_JITBUF, + bts->rtp_jitter_buf_ms); + if (rc < 0) + LOGP(DRTP, LOGL_ERROR, + "%s IPAC Failed to set RTP socket parameters: %s\n", + gsm_lchan_name(lchan), strerror(-rc)); + else + LOGP(DRTP, LOGL_INFO, + "%s IPAC set RTP socket parameters: %d\n", + gsm_lchan_name(lchan), rc); + 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 = bind_rtp(bts, lchan->abis_ip.rtp_socket, ipstr); + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, + "%s IPAC Failed to bind RTP/RTCP sockets\n", + gsm_lchan_name(lchan)); + oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT, + "%s IPAC Failed to bind RTP/RTCP sockets", + 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); + } + /* Ensure RTCP SDES contains some useful information */ + snprintf(cname, sizeof(cname), "bts@%s", ipstr); + osmo_rtp_set_source_desc(lchan->abis_ip.rtp_socket, cname, + gsm_lchan_name(lchan), NULL, NULL, + gsm_trx_unit_id(lchan->ts->trx), + "OsmoBTS-" PACKAGE_VERSION, NULL); + /* 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(DRTP, 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(DRTP, 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); + if (lchan->abis_ip.rtp_socket) { + osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, + "Closing RTP socket on DLCX "); + osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + msgb_queue_flush(&lchan->dl_tch_queue); + } + return rc; +} + +/* + * dynamic TCH/F_PDCH related messages, originally ip.access specific but + * reused for other BTS models (sysmo-bts, ...) + */ + +/* PDCH ACT/DEACT ACKNOWLEDGE */ +static int rsl_tx_dyn_pdch_ack(struct gsm_lchan *lchan, bool pdch_act) +{ + struct gsm_time *gtime = get_time(lchan->ts->trx->bts); + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + struct msgb *msg; + uint8_t ie[2]; + + LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s ACK\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT"); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + if (pdch_act) { + gsm48_gen_starting_time(ie, gtime); + msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie); + } + rsl_dch_push_hdr(msg, + pdch_act? RSL_MT_IPAC_PDCH_ACT_ACK + : RSL_MT_IPAC_PDCH_DEACT_ACK, + chan_nr); + msg->lchan = lchan; + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* PDCH ACT/DEACT NEGATIVE ACKNOWLEDGE */ +static int rsl_tx_dyn_pdch_nack(struct gsm_lchan *lchan, bool pdch_act, + uint8_t cause) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s NACK (cause = 0x%02x)\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", 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, + pdch_act? RSL_MT_IPAC_PDCH_ACT_NACK + : RSL_MT_IPAC_PDCH_DEACT_NACK, + chan_nr); + msg->lchan = lchan; + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* + * Starting point for dynamic PDCH switching. See osmo-gsm-manuals.git for a + * diagram of what will happen here. The implementation is as follows: + * + * PDCH ACT == TCH/F -> PDCH: + * 1. call bts_model_ts_disconnect() to disconnect TCH/F; + * 2. cb_ts_disconnected() is called when done; + * 3. call bts_model_ts_connect() to connect as PDTCH; + * 4. cb_ts_connected(rc) is called when done; + * 5. instruct the PCU to enable PDTCH; + * 6. the PCU will call back with an activation request; + * 7. l1sap_info_act_cnf() will call ipacc_dyn_pdch_complete() when SAPI + * activations are done; + * 8. send a PDCH ACT ACK. + * + * PDCH DEACT == PDCH -> TCH/F: + * 1. instruct the PCU to disable PDTCH; + * 2. the PCU will call back with a deactivation request; + * 3. l1sap_info_rel_cnf() will call bts_model_ts_disconnect() when SAPI + * deactivations are done; + * 4. cb_ts_disconnected() is called when done; + * 5. call bts_model_ts_connect() to connect as TCH/F; + * 6. cb_ts_connected(rc) is called when done; + * 7. directly call ipacc_dyn_pdch_complete(), since no further action required + * for TCH/F; + * 8. send a PDCH DEACT ACK. + * + * When an error happens along the way, a PDCH DE/ACT NACK is sent. + * TODO: may need to be made more waterproof in all stages, to send a NACK and + * clear the PDCH pending flags from ts->flags. + */ +static void rsl_rx_dyn_pdch(struct msgb *msg, bool pdch_act) +{ + int rc; + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts_trx_ts *ts = lchan->ts; + bool is_pdch_act = (ts->flags & TS_F_PDCH_ACTIVE); + + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + /* Only one of the pending flags should ever be set at the same + * time, but just log both in case both should be set. */ + LOGP(DRSL, LOGL_ERROR, + "%s Request to PDCH %s, but PDCH%s%s is still pending\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", + (ts->flags & TS_F_PDCH_ACT_PENDING)? " ACT" : "", + (ts->flags & TS_F_PDCH_DEACT_PENDING)? " DEACT" : ""); + rsl_tx_dyn_pdch_nack(lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC); + return; + } + + if (lchan->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_NOTICE, + "%s Request to PDCH %s, but lchan is still in state %s\n", + gsm_ts_and_pchan_name(ts), pdch_act? "ACT" : "DEACT", + gsm_lchans_name(lchan->state)); + } + + ts->flags |= pdch_act? TS_F_PDCH_ACT_PENDING + : TS_F_PDCH_DEACT_PENDING; + + /* ensure that this is indeed a dynamic-PDCH channel */ + if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s Attempt to PDCH %s on TS that is not a TCH/F_PDCH (is %s)\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", + gsm_pchan_name(ts->pchan)); + ipacc_dyn_pdch_complete(ts, -EINVAL); + return; + } + + if (is_pdch_act == pdch_act) { + LOGP(DL1C, LOGL_NOTICE, + "%s Request to PDCH %s, but is already so\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT"); + ipacc_dyn_pdch_complete(ts, 0); + return; + } + + if (pdch_act) { + /* Clear TCH state. Only first lchan matters for PDCH */ + clear_lchan_for_pdch_activ(ts->lchan); + + /* First, disconnect the TCH channel, to connect PDTCH later */ + rc = bts_model_ts_disconnect(ts); + } else { + /* First, deactivate PDTCH through the PCU, to connect TCH + * later. + * pcu_tx_info_ind() will pick up TS_F_PDCH_DEACT_PENDING and + * trigger a deactivation. + * Except when the PCU is not connected yet, then trigger + * disconnect immediately from here. The PCU will catch up when + * it connects. */ + /* TODO: timeout on channel connect / disconnect request from PCU? */ + if (pcu_connected()) + rc = pcu_tx_info_ind(); + else + rc = bts_model_ts_disconnect(ts); + } + + /* Error? then NACK right now. */ + if (rc) + ipacc_dyn_pdch_complete(ts, rc); +} + +static void ipacc_dyn_pdch_ts_disconnected(struct gsm_bts_trx_ts *ts) +{ + int rc; + enum gsm_phys_chan_config as_pchan; + + if (ts->flags & TS_F_PDCH_DEACT_PENDING) { + LOGP(DRSL, LOGL_DEBUG, + "%s PDCH DEACT operation: channel disconnected, will reconnect as TCH\n", + gsm_lchan_name(ts->lchan)); + as_pchan = GSM_PCHAN_TCH_F; + } else if (ts->flags & TS_F_PDCH_ACT_PENDING) { + LOGP(DRSL, LOGL_DEBUG, + "%s PDCH ACT operation: channel disconnected, will reconnect as PDTCH\n", + gsm_lchan_name(ts->lchan)); + as_pchan = GSM_PCHAN_PDCH; + } else + /* No reconnect pending. */ + return; + + rc = conf_lchans_as_pchan(ts, as_pchan); + if (rc) + goto error_nack; + + bts_model_ts_connect(ts, as_pchan); + return; + +error_nack: + /* Error? then NACK right now. */ + if (rc) + ipacc_dyn_pdch_complete(ts, rc); +} + +static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts) +{ + DEBUGP(DRSL, "%s Disconnected\n", gsm_ts_and_pchan_name(ts)); + ts->dyn.pchan_is = GSM_PCHAN_NONE; + + switch (ts->dyn.pchan_want) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + break; + default: + LOGP(DRSL, LOGL_ERROR, + "%s Dyn TS disconnected, but invalid desired pchan: %s\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(ts->dyn.pchan_want)); + ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* TODO: how would this recover? */ + return; + } + + conf_lchans_as_pchan(ts, ts->dyn.pchan_want); + DEBUGP(DRSL, "%s Connect\n", gsm_ts_and_pchan_name(ts)); + bts_model_ts_connect(ts, ts->dyn.pchan_want); +} + +void cb_ts_disconnected(struct gsm_bts_trx_ts *ts) +{ + OSMO_ASSERT(ts); + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + return ipacc_dyn_pdch_ts_disconnected(ts); + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return osmo_dyn_ts_disconnected(ts); + default: + return; + } +} + +static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts, int rc) +{ + if (rc) { + LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT IPA operation failed (%d) in bts model\n", + gsm_lchan_name(ts->lchan), rc); + ipacc_dyn_pdch_complete(ts, rc); + return; + } + + if (ts->flags & TS_F_PDCH_DEACT_PENDING) { + if (ts->lchan[0].type != GSM_LCHAN_TCH_F) + LOGP(DRSL, LOGL_ERROR, "%s PDCH DEACT error:" + " timeslot connected, so expecting" + " lchan type TCH/F, but is %s\n", + gsm_lchan_name(ts->lchan), + gsm_lchant_name(ts->lchan[0].type)); + + LOGP(DRSL, LOGL_DEBUG, "%s PDCH DEACT operation:" + " timeslot connected as TCH/F\n", + gsm_lchan_name(ts->lchan)); + + /* During PDCH DEACT, we're done right after the TCH/F came + * back up. */ + ipacc_dyn_pdch_complete(ts, 0); + + } else if (ts->flags & TS_F_PDCH_ACT_PENDING) { + if (ts->lchan[0].type != GSM_LCHAN_PDTCH) + LOGP(DRSL, LOGL_ERROR, "%s PDCH ACT error:" + " timeslot connected, so expecting" + " lchan type PDTCH, but is %s\n", + gsm_lchan_name(ts->lchan), + gsm_lchant_name(ts->lchan[0].type)); + + LOGP(DRSL, LOGL_DEBUG, "%s PDCH ACT operation:" + " timeslot connected as PDTCH\n", + gsm_lchan_name(ts->lchan)); + + /* The PDTCH is connected, now tell the PCU about it. Except + * when the PCU is not connected (yet), then there's nothing + * left to do now. The PCU will catch up when it connects. */ + if (!pcu_connected()) { + ipacc_dyn_pdch_complete(ts, 0); + return; + } + + /* The PCU will request to activate the PDTCH SAPIs, which, + * when done, will call back to ipacc_dyn_pdch_complete(). */ + /* TODO: timeout on channel connect / disconnect request from PCU? */ + rc = pcu_tx_info_ind(); + + /* Error? then NACK right now. */ + if (rc) + ipacc_dyn_pdch_complete(ts, rc); + } +} + +static void osmo_dyn_ts_connected(struct gsm_bts_trx_ts *ts, int rc) +{ + struct msgb *msg = ts->dyn.pending_chan_activ; + ts->dyn.pending_chan_activ = NULL; + + if (rc) { + LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT OSMO operation failed (%d) in bts model\n", + gsm_lchan_name(ts->lchan), rc); + ipacc_dyn_pdch_complete(ts, rc); + return; + } + + if (!msg) { + LOGP(DRSL, LOGL_ERROR, + "%s TS re-connected, but no chan activ msg pending\n", + gsm_ts_and_pchan_name(ts)); + return; + } + + ts->dyn.pchan_is = ts->dyn.pchan_want; + DEBUGP(DRSL, "%s Connected\n", gsm_ts_and_pchan_name(ts)); + + /* continue where we left off before re-connecting the TS. */ + rc = rsl_rx_chan_activ(msg); + if (rc != 1) + msgb_free(msg); +} + +void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc) +{ + OSMO_ASSERT(ts); + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + return ipacc_dyn_pdch_ts_connected(ts, rc); + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return osmo_dyn_ts_connected(ts, rc); + default: + return; + } +} + +void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc) +{ + bool pdch_act; + OSMO_ASSERT(ts); + + pdch_act = ts->flags & TS_F_PDCH_ACT_PENDING; + + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == TS_F_PDCH_PENDING_MASK) + LOGP(DRSL, LOGL_ERROR, + "%s Internal Error: both PDCH ACT and PDCH DEACT pending\n", + gsm_lchan_name(ts->lchan)); + + ts->flags &= ~TS_F_PDCH_PENDING_MASK; + + if (rc != 0) { + LOGP(DRSL, LOGL_ERROR, + "PDCH %s on dynamic TCH/F_PDCH returned error %d\n", + pdch_act? "ACT" : "DEACT", rc); + rsl_tx_dyn_pdch_nack(ts->lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC); + return; + } + + if (pdch_act) + ts->flags |= TS_F_PDCH_ACTIVE; + else + ts->flags &= ~TS_F_PDCH_ACTIVE; + DEBUGP(DRSL, "%s %s switched to %s mode (ts->flags == %x)\n", + gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan), + pdch_act? "PDCH" : "TCH/F", ts->flags); + + rc = rsl_tx_dyn_pdch_ack(ts->lchan, pdch_act); + if (rc) + LOGP(DRSL, LOGL_ERROR, + "Failed to transmit PDCH %s ACK, rc %d\n", + pdch_act? "ACT" : "DEACT", rc); +} + +/* handle a message with an RSL CHAN_NR that is incompatible/unknown */ +static int rsl_reject_unknown_lchan(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rh = msgb_l2(msg); + struct abis_rsl_dchan_hdr *dch; + int rc; + + /* 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. */ + + /* TS 48.058 Section 7 explains how to do error handling */ + switch (rh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_DED_CHAN: + dch = msgb_l2(msg); + switch (dch->c.msg_type) { + case RSL_MT_CHAN_ACTIV: + rc = _rsl_tx_chan_act_nack(msg->trx, dch->chan_nr, + RSL_ERR_MAND_IE_ERROR, NULL); + break; + case RSL_MT_MODE_MODIFY_REQ: + rc = _rsl_tx_mode_modif_nack(msg->trx, dch->chan_nr, + RSL_ERR_MAND_IE_ERROR, NULL); + break; + default: + rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + break; + } + break; + case ABIS_RSL_MDISC_RLL: + /* fall-through */ + case ABIS_RSL_MDISC_COM_CHAN: + /* fall-through */ + case ABIS_RSL_MDISC_TRX: + /* fall-through */ + case ABIS_RSL_MDISC_IPACCESS: + /* fall-through */ + default: + /* ERROR REPORT */ + rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + } + + msgb_free(msg); + 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"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, &rh->chan_nr, &rh->link_id, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)rh + sizeof(*rh); + + if (!chan_nr_is_dchan(rh->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + lchan = lchan_lookup(trx, rh->chan_nr, "RSL rx RLL: "); + if (!lchan) { + LOGP(DRLL, LOGL_NOTICE, "Rx RLL %s for unknown lchan\n", + rsl_msg_name(rh->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + 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; +} + +static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, const struct lapdm_entity *le) +{ + return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - le->ta); +} + +static inline bool ms_to_valid(const struct gsm_lchan *lchan) +{ + return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0); +} + +struct osmo_bts_supp_meas_info { + int16_t toa256_mean; + int16_t toa256_min; + int16_t toa256_max; + uint16_t toa256_std_dev; +} __attribute__((packed)); + +/* 8.4.8 MEASUREMENT RESult */ +static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, const struct lapdm_entity *le) +{ + 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; + struct gsm_bts *bts = lchan->ts->trx->bts; + + LOGP(DRSL, LOGL_DEBUG, + "%s chan_num:%u Tx MEAS RES valid(%d), flags(%02x)\n", + gsm_lchan_name(lchan), chan_nr, res_valid, lchan->meas.flags); + + if (!res_valid) + return -EINPROGRESS; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + LOGP(DRSL, LOGL_DEBUG, + "%s Send Meas RES: NUM:%u, RXLEV_FULL:%u, RXLEV_SUB:%u, RXQUAL_FULL:%u, RXQUAL_SUB:%u, MS_PWR:%u, UL_TA:%u, L3_LEN:%d, TimingOff:%u\n", + gsm_lchan_name(lchan), + lchan->meas.res_nr, + lchan->meas.ul_res.full.rx_lev, + lchan->meas.ul_res.sub.rx_lev, + lchan->meas.ul_res.full.rx_qual, + lchan->meas.ul_res.sub.rx_qual, + lchan->meas.l1_info[0], + lchan->meas.l1_info[1], l3_len, ms_to2rsl(lchan, le) - MEAS_MAX_TIMING_ADVANCE); + + msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++); + size_t ie_len = gsm0858_rsl_ul_meas_enc(&lchan->meas.ul_res, + lchan->tch.dtx.dl_active, + meas_res); + lchan->tch.dtx.dl_active = false; + if (ie_len >= 3) { + if (bts->supp_meas_toa256 && lchan->meas.flags & LC_UL_M_F_OSMO_EXT_VALID) { + struct osmo_bts_supp_meas_info *smi; + smi = (struct osmo_bts_supp_meas_info *) &meas_res[ie_len]; + ie_len += sizeof(struct osmo_bts_supp_meas_info); + /* append signed 16bit value containing MS timing offset in 1/256th symbols + * in the vendor-specific "Supplementary Measurement Information" part of + * the uplink measurements IE. The lchan->meas.ext members are the current + * offset *relative* to the TA which the MS has already applied. As we want + * to know the total propagation time between MS and BTS, we need to add + * the actual TA value applied by the MS plus the respective toa256 value in + * 1/256 symbol periods. */ + int16_t ta256 = lchan_get_ta(lchan) * 256; + smi->toa256_mean = htons(ta256 + lchan->meas.ms_toa256); + smi->toa256_min = htons(ta256 + lchan->meas.ext.toa256_min); + smi->toa256_max = htons(ta256 + lchan->meas.ext.toa256_max); + smi->toa256_std_dev = htons(lchan->meas.ext.toa256_std_dev); + lchan->meas.flags &= ~LC_UL_M_F_OSMO_EXT_VALID; + } + 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); + if (ms_to_valid(lchan)) { + msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, ms_to2rsl(lchan, le)); + lchan->ms_t_offs = -1; + lchan->p_offs = -1; + } + + 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; + + OSMO_ASSERT(msg); + rh = msgb_l2(msg); + + if (lchan->state != LCHAN_S_ACTIVE) { + LOGP(DRSL, LOGL_ERROR, "%s(%s) is not active. Dropping message (len=%u): %s\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), + msgb_l2len(msg), msgb_hexdump_l2(msg)); + msgb_free(msg); + return 0; + } + + msg->trx = lchan->ts->trx; + msg->lchan = lchan; + + /* 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)); + + /* REL_IND handling */ + if (rh->msg_type == RSL_MT_REL_IND && + (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)) { + LOGP(DRSL, LOGL_INFO, "%s Scheduling %s to L3 in next associated TCH-RTS.ind\n", + gsm_lchan_name(lchan), + rsl_msg_name(rh->msg_type)); + + if(lchan->pending_rel_ind_msg) { + LOGP(DRSL, LOGL_INFO, "Dropping pending release indication message\n"); + msgb_free(lchan->pending_rel_ind_msg); + } + + lchan->pending_rel_ind_msg = msg; + return 0; + } + + rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg), le); + 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"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)cch + sizeof(*cch); + + /* normally we don't permit dedicated channels here ... */ + if (chan_nr_is_dchan(cch->chan_nr)) { + /* ... however, CBCH is on a SDCCH, so we must permit it */ + if (cch->c.msg_type != RSL_MT_SMS_BC_CMD && cch->c.msg_type != RSL_MT_SMS_BC_REQ) + return rsl_reject_unknown_lchan(msg); + } + + msg->lchan = lchan_lookup(trx, cch->chan_nr, "RSL rx CCHAN: "); + if (!msg->lchan) { + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", + rsl_msg_name(cch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + 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); + + if (!chan_nr_is_dchan(dch->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx DCHAN: "); + if (!msg->lchan) { + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", + rsl_or_ipac_msg_name(dch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + LOGP(DRSL, LOGL_INFO, "%s ss=%d Rx RSL %s\n", + gsm_ts_and_pchan_name(msg->lchan->ts), msg->lchan->nr, + rsl_or_ipac_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_IPAC_PDCH_ACT: + case RSL_MT_IPAC_PDCH_DEACT: + rsl_rx_dyn_pdch(msg, dch->c.msg_type == RSL_MT_IPAC_PDCH_ACT); + ret = 0; + 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"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + 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"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)dch + sizeof(*dch); + + if (!chan_nr_is_dchan(dch->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: "); + if (!msg->lchan) { + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", + rsl_msg_name(dch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + 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 lchan_deactivate(struct gsm_lchan *lchan) +{ + OSMO_ASSERT(lchan); + + lchan->ciph_state = 0; + return bts_model_lchan_deactivate(lchan); +} + +int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh; + int ret = 0; + + OSMO_ASSERT(trx); + OSMO_ASSERT(msg); + + rslh = msgb_l2(msg); + + if (msgb_l2len(msg) < sizeof(*rslh)) { + LOGP(DRSL, LOGL_NOTICE, "RSL message too short\n"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + 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); + rsl_tx_error_report(trx, RSL_ERR_MSG_DISCR, NULL, NULL, msg); + 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; +} diff --git a/src/common/scheduler.c b/src/common/scheduler.c new file mode 100644 index 00000000..f705ddf7 --- /dev/null +++ b/src/common/scheduler.c @@ -0,0 +1,1025 @@ +/* Scheduler for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/a5.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +extern void *tall_bts_ctx; + +static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +/*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */ +static const ubit_t dummy_burst[GSM_BURST_LEN] = { + 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, +}; + +/*! \brief FCCH Burst (TS 05.02 Chapter 5.2.4) */ +const ubit_t _sched_fcch_burst[GSM_BURST_LEN] = { + 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, +}; + +/*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ +const ubit_t _sched_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, }, +}; + +const ubit_t _sched_egprs_tsc[8][78] = { + { 1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0, + 1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1, + 1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, }, + { 1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0, + 1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1,1, + 1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, }, + { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0, + 1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1, }, + { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0, + 1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1, }, + { 1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0, + 1,0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1, }, + { 1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0, + 1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1, }, + { 0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0, + 1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1, + 1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1, }, + { 0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1, + 1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0, + 0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1, }, +}; + +/*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */ +const ubit_t _sched_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 + */ + +const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { + /* is_pdch chan_type chan_nr link_id name rts_fn dl_fn ul_fn auto_active */ + { 0, TRXC_IDLE, 0, LID_DEDIC, "IDLE", NULL, tx_idle_fn, NULL, 1 }, + { 0, TRXC_FCCH, 0, LID_DEDIC, "FCCH", NULL, tx_fcch_fn, NULL, 1 }, + { 0, TRXC_SCH, 0, LID_DEDIC, "SCH", NULL, tx_sch_fn, NULL, 1 }, + { 0, TRXC_BCCH, 0x80, LID_DEDIC, "BCCH", rts_data_fn, tx_data_fn, NULL, 1 }, + { 0, TRXC_RACH, 0x88, LID_DEDIC, "RACH", NULL, NULL, rx_rach_fn, 1 }, + { 0, TRXC_CCCH, 0x90, LID_DEDIC, "CCCH", rts_data_fn, tx_data_fn, NULL, 1 }, + { 0, TRXC_TCHF, 0x08, LID_DEDIC, "TCH/F", rts_tchf_fn, tx_tchf_fn, rx_tchf_fn, 0 }, + { 0, TRXC_TCHH_0, 0x10, LID_DEDIC, "TCH/H(0)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 }, + { 0, TRXC_TCHH_1, 0x18, LID_DEDIC, "TCH/H(1)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 }, + { 0, TRXC_SDCCH4_0, 0x20, LID_DEDIC, "SDCCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH4_1, 0x28, LID_DEDIC, "SDCCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH4_2, 0x30, LID_DEDIC, "SDCCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH4_3, 0x38, LID_DEDIC, "SDCCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_0, 0x40, LID_DEDIC, "SDCCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_1, 0x48, LID_DEDIC, "SDCCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_2, 0x50, LID_DEDIC, "SDCCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_3, 0x58, LID_DEDIC, "SDCCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_4, 0x60, LID_DEDIC, "SDCCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_5, 0x68, LID_DEDIC, "SDCCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_6, 0x70, LID_DEDIC, "SDCCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_7, 0x78, LID_DEDIC, "SDCCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCHTF, 0x08, LID_SACCH, "SACCH/TF", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCHTH_0, 0x10, LID_SACCH, "SACCH/TH(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCHTH_1, 0x18, LID_SACCH, "SACCH/TH(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_0, 0x20, LID_SACCH, "SACCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_1, 0x28, LID_SACCH, "SACCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_2, 0x30, LID_SACCH, "SACCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_3, 0x38, LID_SACCH, "SACCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_0, 0x40, LID_SACCH, "SACCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_1, 0x48, LID_SACCH, "SACCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_2, 0x50, LID_SACCH, "SACCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_3, 0x58, LID_SACCH, "SACCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_4, 0x60, LID_SACCH, "SACCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_5, 0x68, LID_SACCH, "SACCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_6, 0x70, LID_SACCH, "SACCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_7, 0x78, LID_SACCH, "SACCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 1, TRXC_PDTCH, 0xc0, LID_DEDIC, "PDTCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 }, + { 1, TRXC_PTCCH, 0xc0, LID_DEDIC, "PTCCH", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_CBCH, 0xc8, LID_DEDIC, "CBCH", rts_data_fn, tx_data_fn, NULL, 1 }, +}; + +const struct value_string trx_chan_type_names[] = { + OSMO_VALUE_STRING(TRXC_IDLE), + OSMO_VALUE_STRING(TRXC_FCCH), + OSMO_VALUE_STRING(TRXC_SCH), + OSMO_VALUE_STRING(TRXC_BCCH), + OSMO_VALUE_STRING(TRXC_RACH), + OSMO_VALUE_STRING(TRXC_CCCH), + OSMO_VALUE_STRING(TRXC_TCHF), + OSMO_VALUE_STRING(TRXC_TCHH_0), + OSMO_VALUE_STRING(TRXC_TCHH_1), + OSMO_VALUE_STRING(TRXC_SDCCH4_0), + OSMO_VALUE_STRING(TRXC_SDCCH4_1), + OSMO_VALUE_STRING(TRXC_SDCCH4_2), + OSMO_VALUE_STRING(TRXC_SDCCH4_3), + OSMO_VALUE_STRING(TRXC_SDCCH8_0), + OSMO_VALUE_STRING(TRXC_SDCCH8_1), + OSMO_VALUE_STRING(TRXC_SDCCH8_2), + OSMO_VALUE_STRING(TRXC_SDCCH8_3), + OSMO_VALUE_STRING(TRXC_SDCCH8_4), + OSMO_VALUE_STRING(TRXC_SDCCH8_5), + OSMO_VALUE_STRING(TRXC_SDCCH8_6), + OSMO_VALUE_STRING(TRXC_SDCCH8_7), + OSMO_VALUE_STRING(TRXC_SACCHTF), + OSMO_VALUE_STRING(TRXC_SACCHTH_0), + OSMO_VALUE_STRING(TRXC_SACCHTH_1), + OSMO_VALUE_STRING(TRXC_SACCH4_0), + OSMO_VALUE_STRING(TRXC_SACCH4_1), + OSMO_VALUE_STRING(TRXC_SACCH4_2), + OSMO_VALUE_STRING(TRXC_SACCH4_3), + OSMO_VALUE_STRING(TRXC_SACCH8_0), + OSMO_VALUE_STRING(TRXC_SACCH8_1), + OSMO_VALUE_STRING(TRXC_SACCH8_2), + OSMO_VALUE_STRING(TRXC_SACCH8_3), + OSMO_VALUE_STRING(TRXC_SACCH8_4), + OSMO_VALUE_STRING(TRXC_SACCH8_5), + OSMO_VALUE_STRING(TRXC_SACCH8_6), + OSMO_VALUE_STRING(TRXC_SACCH8_7), + OSMO_VALUE_STRING(TRXC_PDTCH), + OSMO_VALUE_STRING(TRXC_PTCCH), + OSMO_VALUE_STRING(TRXC_CBCH), + OSMO_VALUE_STRING(_TRX_CHAN_MAX), + { 0, NULL } +}; + +/* + * init / exit + */ + +int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx) +{ + uint8_t tn; + unsigned int i; + + if (!trx) + return -EINVAL; + + l1t->trx = trx; + + LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr); + + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + l1ts->mf_index = 0; + INIT_LLIST_HEAD(&l1ts->dl_prims); + for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + chan_state->active = 0; + } + } + + return 0; +} + +void trx_sched_exit(struct l1sched_trx *l1t) +{ + struct gsm_bts_trx_ts *ts; + uint8_t tn; + int i; + + LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1t->trx->nr); + + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + msgb_queue_flush(&l1ts->dl_prims); + for (i = 0; i < _TRX_CHAN_MAX; i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[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 */ + ts = &l1t->trx->ts[tn]; + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) + lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); + } +} + +/* close all logical channels and reset timeslots */ +void trx_sched_reset(struct l1sched_trx *l1t) +{ + trx_sched_exit(l1t); + trx_sched_init(l1t, l1t->trx); +} + +struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, 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; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + /* get prim of current fn from queue */ + llist_for_each_entry_safe(msg, msg2, &l1ts->dl_prims, list) { + l1sap = msgb_l1sap_prim(msg); + if (l1sap->oph.operation != PRIM_OP_REQUEST) { +wrong_type: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong type.\n"); +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 + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + break; + case PRIM_TCH: + chan_nr = l1sap->u.tch.chan_nr; + link_id = 0; + prim_fn = ((l1sap->u.tch.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + break; + default: + goto wrong_type; + } + if (prim_fn > 100) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Prim %u is out of range (100), or channel %s with " + "type %s is already disabled. If this happens in " + "conjunction with PCU, increase 'rts-advance' by 5.\n", + prim_fn, get_lchan_by_chan_nr(l1t->trx, chan_nr)->name, + get_value_string(trx_chan_type_names, chan)); + /* 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)) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong chan_nr=0x%02x link_id=%02x, " + "expecting chan_nr=0x%02x link_id=%02x.\n", 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; +} + +int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, + uint8_t l2_len, float rssi, + int16_t ta_offs_256bits, int16_t link_qual_cb, + uint16_t ber10k, + enum osmo_ph_pres_info_type presence_info) +{ + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, 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); + l1sap->u.data.ber10k = ber10k; + l1sap->u.data.ta_offs_256bits = ta_offs_256bits; + l1sap->u.data.lqual_cb = link_qual_cb; + l1sap->u.data.pdch_presence_info = presence_info; + 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)) + l1ts->chan_state[chan].lost_frames = 0; + + /* forward primitive */ + l1sap_up(l1t->trx, l1sap); + + return 0; +} + +int _sched_compose_tch_ind(struct l1sched_trx *l1t, 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; + struct gsm_bts_trx *trx = l1t->trx; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + /* 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 = chan_nr; + l1sap->u.tch.fn = fn; + msg->l2h = msgb_put(msg, tch_len); + if (tch_len) + memcpy(msg->l2h, tch, tch_len); + + if (l1ts->chan_state[chan].lost_frames) + l1ts->chan_state[chan].lost_frames--; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, -1, l1sap->u.data.fn, + "%s Rx -> RTP: %s\n", + gsm_lchan_name(lchan), osmo_hexdump(msgb_l2(msg), msgb_l2len(msg))); + /* forward primitive */ + l1sap_up(l1t->trx, l1sap); + + return 0; +} + + + +/* + * data request (from upper layer) + */ + +int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +{ + uint8_t tn = l1sap->u.data.chan_nr & 7; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.data.fn, + "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x\n", + l1sap->u.data.chan_nr, l1sap->u.data.link_id); + + if (!l1sap->oph.msg) + abort(); + + /* ignore empty frame */ + if (!msgb_l2len(l1sap->oph.msg)) { + msgb_free(l1sap->oph.msg); + return 0; + } + + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); + + return 0; +} + +int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +{ + uint8_t tn = l1sap->u.tch.chan_nr & 7; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.tch.fn, "TCH.req: chan_nr=0x%02x\n", + l1sap->u.tch.chan_nr); + + if (!l1sap->oph.msg) + abort(); + + /* ignore empty frame */ + if (!msgb_l2len(l1sap->oph.msg)) { + msgb_free(l1sap->oph.msg); + return 0; + } + + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); + + return 0; +} + + +/* + * ready-to-send indication (to upper layer) + */ + +/* RTS for data frame */ +static int rts_data_fn(struct l1sched_trx *l1t, 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) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "RTS func with non-existing chan_nr %d\n", chan_nr); + return -ENODEV; + } + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, + "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); + + /* 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(l1t->trx, l1sap); +} + +static int rts_tch_common(struct l1sched_trx *l1t, 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; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + 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) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "RTS func with non-existing chan_nr %d\n", chan_nr); + return -ENODEV; + } + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "TCH RTS.ind: chan_nr=0x%02x\n", chan_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(l1t->trx, l1sap); + } + + /* dont send, if TCH is in signalling only mode */ + if (l1ts->chan_state[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(l1t->trx, l1sap); + } + + return rc; +} + +/* RTS for full rate traffic frame */ +static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + /* TCH/F may include FACCH on every 4th burst */ + return rts_tch_common(l1t, tn, fn, chan, 1); +} + + +/* RTS for half rate traffic frame */ +static int rts_tchh_fn(struct l1sched_trx *l1t, 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(l1t, tn, fn, chan, ((fn % 26) >> 2) & 1); +} + +/* set multiframe scheduler to given pchan */ +int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, + enum gsm_phys_chan_config pchan) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + int i; + + i = find_sched_mframe_idx(pchan, tn); + if (i < 0) { + LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe " + "trx=%d ts=%d\n", l1t->trx->nr, tn); + return -ENOTSUP; + } + l1ts->mf_index = i; + l1ts->mf_period = trx_sched_multiframes[i].period; + l1ts->mf_frames = trx_sched_multiframes[i].frames; + LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with %s trx=%d ts=%d\n", + trx_sched_multiframes[i].name, l1t->trx->nr, tn); + return 0; +} + +/* setting all logical channels given attributes to active/inactive */ +int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, + int active) +{ + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t ss = l1sap_chan2ss(chan_nr); + int i; + int rc = -EINVAL; + + /* look for all matching chan_nr/link_id */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + /* skip if pchan type does not match pdch flag */ + if ((trx_sched_multiframes[l1ts->mf_index].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) { + 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, l1t->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; + } + if (!active) + chan_state->ho_rach_detect = 0; + } + } + + /* disable handover detection (on deactivation) */ + if (!active) + _sched_act_rach_det(l1t, tn, ss, 0); + + return rc; +} + +/* setting all logical channels given attributes to active/inactive */ +int trx_sched_set_mode(struct l1sched_trx *l1t, 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); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t ss = l1sap_chan2ss(chan_nr); + int i; + int rc = -EINVAL; + struct l1sched_chan_state *chan_state; + + /* no mode for PDCH */ + if (trx_sched_multiframes[l1ts->mf_index].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 = &l1ts->chan_state[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, l1t->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) */ + _sched_act_rach_det(l1t, tn, ss, handover); + + return rc; +} + +/* setting cipher on logical channels */ +int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, + int algo, uint8_t *key, int key_len) +{ + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + int i; + int rc = -EINVAL; + struct l1sched_chan_state *chan_state; + + /* no cipher for PDCH */ + if (trx_sched_multiframes[l1ts->mf_index].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 = &l1ts->chan_state[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, l1t->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 */ +int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + const struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_rts_func *func; + enum trx_chan_type chan; + + /* no multiframe set */ + if (!l1ts->mf_index) + return 0; + + /* get frame from multiframe */ + period = l1ts->mf_period; + offset = fn % period; + frame = l1ts->mf_frames + 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 + && !l1ts->chan_state[chan].active) + return -EINVAL; + + return func(l1t, tn, fn, frame->dl_chan); +} + +/* process downlink burst */ +const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, uint16_t *nbits) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *l1cs; + const 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 (!l1ts->mf_index) + goto no_data; + + /* get frame from multiframe */ + period = l1ts->mf_period; + offset = fn % period; + frame = l1ts->mf_frames + offset; + + chan = frame->dl_chan; + bid = frame->dl_bid; + func = trx_chan_desc[chan].dl_fn; + + l1cs = &l1ts->chan_state[chan]; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active && !l1cs->active) { + if (nbits) + *nbits = GSM_BURST_LEN; + goto no_data; + } + + /* get burst from function */ + bits = func(l1t, tn, fn, chan, bid, nbits); + + /* encrypt */ + if (bits && l1cs->dl_encr_algo) { + ubit_t ks[114]; + int i; + + osmo_a5(l1cs->dl_encr_algo, l1cs->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 && l1t->trx == l1t->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); +#endif + bits = (ubit_t *) dummy_burst; + } + + return bits; +} + +#define TDMA_FN_SUM(a, b) \ + ((a + GSM_HYPERFRAME + b) % GSM_HYPERFRAME) + +#define TDMA_FN_SUB(a, b) \ + ((a + GSM_HYPERFRAME - b) % GSM_HYPERFRAME) + +static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, + struct l1sched_chan_state *l1cs, uint8_t tn, uint32_t fn) +{ + const struct trx_sched_frame *frame_head; + const struct trx_sched_frame *frame; + struct l1sched_ts *l1ts; + uint32_t elapsed_fs; + uint8_t offset, i; + uint32_t fn_i; + + /** + * When a channel is just activated, the MS needs some time + * to synchronize and start burst transmission, + * so let's wait until the first UL burst... + */ + if (l1cs->proc_tdma_fs == 0) + return 0; + + /* Get current TDMA frame info */ + l1ts = l1sched_trx_get_ts(l1t, tn); + offset = fn % l1ts->mf_period; + frame_head = l1ts->mf_frames + offset; + + /* Not applicable for some logical channels */ + switch (frame_head->ul_chan) { + case TRXC_IDLE: + case TRXC_RACH: + case TRXC_PDTCH: + case TRXC_PTCCH: + return 0; + default: + /* No applicable if we are waiting for handover RACH */ + if (l1cs->ho_rach_detect) + return 0; + } + + /* How many frames elapsed since the last one? */ + elapsed_fs = TDMA_FN_SUB(fn, l1cs->last_tdma_fn); + if (elapsed_fs > l1ts->mf_period) { /* Too many! */ + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, frame_head->ul_chan, fn, + "Too many (>%u) contiguous TDMA frames=%u elapsed " + "since the last processed fn=%u\n", l1ts->mf_period, + elapsed_fs, l1cs->last_tdma_fn); + /* FIXME: how should this affect the measurements? */ + return -EINVAL; + } + + /** + * There are several TDMA frames between the last processed + * frame and currently received one. Let's walk through this + * path and count potentially lost frames, i.e. for which + * we didn't receive the corresponsing UL bursts. + * + * Start counting from the last_fn + 1. + */ + for (i = 1; i < elapsed_fs; i++) { + fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i); + offset = fn_i % l1ts->mf_period; + frame = l1ts->mf_frames + offset; + + if (frame->ul_chan == frame_head->ul_chan) + l1cs->lost_tdma_fs++; + } + + if (l1cs->lost_tdma_fs > 0) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame_head->ul_chan, fn, + "At least %u TDMA frames were lost since the last " + "processed fn=%u\n", l1cs->lost_tdma_fs, l1cs->last_tdma_fn); + + /** + * HACK: substitute lost bursts by zero-filled ones + * + * Instead of doing this, it makes sense to use the + * amount of lost frames in measurement calculations. + */ + static sbit_t zero_burst[GSM_BURST_LEN] = { 0 }; + trx_sched_ul_func *func; + + for (i = 1; i < elapsed_fs; i++) { + fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i); + offset = fn_i % l1ts->mf_period; + frame = l1ts->mf_frames + offset; + func = trx_chan_desc[frame->ul_chan].ul_fn; + + if (frame->ul_chan != frame_head->ul_chan) + continue; + + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame->ul_chan, fn, + "Substituting lost TDMA frame=%u by all-zero " + "dummy burst\n", fn_i); + + func(l1t, tn, fn_i, frame->ul_chan, frame->ul_bid, + zero_burst, GSM_BURST_LEN, -128, 0); + + l1cs->lost_tdma_fs--; + } + } + + return 0; +} + +/* process uplink burst */ +int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *l1cs; + const struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_ul_func *func; + enum trx_chan_type chan; + + if (!l1ts->mf_index) + return -EINVAL; + + /* get frame from multiframe */ + period = l1ts->mf_period; + offset = fn % period; + frame = l1ts->mf_frames + offset; + + chan = frame->ul_chan; + bid = frame->ul_bid; + l1cs = &l1ts->chan_state[chan]; + func = trx_chan_desc[chan].ul_fn; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active && !l1cs->active) + return -EINVAL; + + /* omit bursts which have no handler, like IDLE bursts */ + if (!func) + return -EINVAL; + + /* calculate how many TDMA frames were potentially lost */ + trx_sched_calc_frame_loss(l1t, l1cs, tn, fn); + + /* update TDMA frame counters */ + l1cs->last_tdma_fn = fn; + l1cs->proc_tdma_fs++; + + /* decrypt */ + if (bits && l1cs->ul_encr_algo) { + ubit_t ks[114]; + int i; + + osmo_a5(l1cs->ul_encr_algo, l1cs->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]; + } + } + + /* put burst to function */ + func(l1t, tn, fn, chan, bid, bits, nbits, rssi, toa256); + + return 0; +} + +struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn) +{ + OSMO_ASSERT(tn < ARRAY_SIZE(l1t->ts)); + return &l1t->ts[tn]; +} diff --git a/src/common/scheduler_mframe.c b/src/common/scheduler_mframe.c new file mode 100644 index 00000000..b969407c --- /dev/null +++ b/src/common/scheduler_mframe.c @@ -0,0 +1,1040 @@ +/* Scheduler for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2015-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> + + +/* + * multiframe structure + */ + +static const 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 const 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 const struct trx_sched_frame frame_bcch_sdcch4_cbch[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_IDLE, 0 }, + { TRXC_CCCH, 1, TRXC_IDLE, 0 }, + { TRXC_CCCH, 2, TRXC_IDLE, 0 }, + { TRXC_CCCH, 3, TRXC_IDLE, 0 }, + { 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_CBCH, 0, TRXC_RACH, 0 }, + { TRXC_CBCH, 1, TRXC_RACH, 0 }, + { TRXC_CBCH, 2, TRXC_RACH, 0 }, + { TRXC_CBCH, 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_IDLE, 0 }, + { TRXC_SACCH4_1, 2, TRXC_IDLE, 0 }, + { TRXC_SACCH4_1, 3, TRXC_IDLE, 0 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + + { 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_CBCH, 0, TRXC_RACH, 0 }, + { TRXC_CBCH, 1, TRXC_RACH, 0 }, + { TRXC_CBCH, 2, TRXC_RACH, 0 }, + { TRXC_CBCH, 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_IDLE, 0, TRXC_SDCCH4_1, 1 }, + { TRXC_IDLE, 0, TRXC_SDCCH4_1, 2 }, + { TRXC_IDLE, 0, TRXC_SDCCH4_1, 3 }, + { TRXC_IDLE, 0, TRXC_RACH, 0 }, + { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SACCH4_3, 1, TRXC_IDLE, 0 }, + { TRXC_SACCH4_3, 2, TRXC_IDLE, 0 }, + { TRXC_SACCH4_3, 3, TRXC_IDLE, 0 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +static const 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 const struct trx_sched_frame frame_sdcch8_cbch[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_CBCH, 0, TRXC_SACCH8_7, 0 }, + { TRXC_CBCH, 1, TRXC_SACCH8_7, 1 }, + { TRXC_CBCH, 2, TRXC_SACCH8_7, 2 }, + { TRXC_CBCH, 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_IDLE, 0 }, + { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 }, + { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 }, + { TRXC_SDCCH8_6, 2, TRXC_IDLE, 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_IDLE, 0, TRXC_SDCCH8_6, 1 }, + { TRXC_IDLE, 1, TRXC_SDCCH8_6, 2 }, + { TRXC_IDLE, 2, TRXC_SDCCH8_6, 3 }, + { TRXC_IDLE, 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_IDLE, 0 }, + { TRXC_SDCCH8_1, 1, TRXC_IDLE, 1 }, + { TRXC_SDCCH8_1, 2, TRXC_IDLE, 2 }, + { TRXC_SDCCH8_1, 3, TRXC_IDLE, 3 }, + { TRXC_CBCH, 0, TRXC_SACCH8_3, 0 }, + { TRXC_CBCH, 1, TRXC_SACCH8_3, 1 }, + { TRXC_CBCH, 2, TRXC_SACCH8_3, 2 }, + { TRXC_CBCH, 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_IDLE, 0 }, + { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 }, + { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 }, + { TRXC_SDCCH8_6, 2, TRXC_IDLE, 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 }, +}; + +const 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_CCCH_SDCCH4_CBCH, 0xff, 102, frame_bcch_sdcch4_cbch, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH" }, + { GSM_PCHAN_SDCCH8_SACCH8C, 0xff, 102, frame_sdcch8, "SDCCH/8+SACCH/8" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH,0xff, 102, frame_sdcch8_cbch, "SDCCH/8+SACCH/8+CBCH" }, + { 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 + */ + +int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(trx_sched_multiframes); i++) { + if (trx_sched_multiframes[i].pchan == pchan + && (trx_sched_multiframes[i].slotmask & (1 << tn))) { + return i; + } + } + return -1; +} + +/* Determine if given frame number contains SACCH (true) or other (false) burst */ +bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink) +{ + int i; + const struct trx_sched_multiframe *sched; + const struct trx_sched_frame *frame; + enum trx_chan_type ch_type; + + i = find_sched_mframe_idx(ts->pchan, ts->nr); + if (i < 0) + return -EINVAL; + sched = &trx_sched_multiframes[i]; + frame = &sched->frames[fn % sched->period]; + if (uplink) + ch_type = frame->ul_chan; + else + ch_type = frame->dl_chan; + + switch (ch_type) { + case TRXC_SACCH4_0: + case TRXC_SACCH4_1: + case TRXC_SACCH4_2: + case TRXC_SACCH4_3: + case TRXC_SACCH8_0: + case TRXC_SACCH8_1: + case TRXC_SACCH8_2: + case TRXC_SACCH8_3: + case TRXC_SACCH8_4: + case TRXC_SACCH8_5: + case TRXC_SACCH8_6: + case TRXC_SACCH8_7: + case TRXC_SACCHTF: + case TRXC_SACCHTH_0: + case TRXC_SACCHTH_1: + return true; + default: + return false; + } +} diff --git a/src/common/sysinfo.c b/src/common/sysinfo.c new file mode 100644 index 00000000..5c66e086 --- /dev/null +++ b/src/common/sysinfo.c @@ -0,0 +1,177 @@ +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/sysinfo.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +/* properly increment SI2q index and return SI2q data for scheduling */ +static inline uint8_t *get_si2q_inc_index(struct gsm_bts *bts) +{ + uint8_t i = bts->si2q_index; + /* si2q_count is the max si2q_index value, not the number of messages */ + bts->si2q_index = (bts->si2q_index + 1) % (bts->si2q_count + 1); + + return (uint8_t *)GSM_BTS_SI2Q(bts, i); +} + +/* Apply the rules from 05.02 6.3.1.3 Mapping of BCCH Data */ +uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time) +{ + 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. */ + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_1)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_1); + return GSM_BTS_SI(bts, SYSINFO_TYPE_2); + 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 (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) { + tc4_sub[tc4_cnt] = SYSINFO_TYPE_2ter; + tc4_cnt += 1; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) && + (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) || GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))) { + tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater; + tc4_cnt += 1; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) { + tc4_sub[tc4_cnt] = SYSINFO_TYPE_13; + tc4_cnt += 1; + } + if (GSM_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 */ + bts->si.tc4_ctr = (bts->si.tc4_ctr + 1) % tc4_cnt; + + if (tc4_sub[bts->si.tc4_ctr] == SYSINFO_TYPE_2quater) + return get_si2q_inc_index(bts); + + return GSM_BTS_SI(bts, tc4_sub[bts->si.tc4_ctr]); + } + case 5: + /* 2bis, 2ter, 2quater */ + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis); + + else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2ter); + + else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis); + + else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) && + !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) + return get_si2q_inc_index(bts); + + /* simply send SI2 if we have nothing else to send */ + else + return GSM_BTS_SI(bts, SYSINFO_TYPE_2); + break; + case 6: + return GSM_BTS_SI(bts, SYSINFO_TYPE_3); + case 7: + return GSM_BTS_SI(bts, SYSINFO_TYPE_4); + } + + /* this should never bve reached. We must transmit a BCCH + * message on the normal BCCH in all cases. */ + OSMO_ASSERT(0); + return 0; +} + +uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg) +{ + struct gsm_bts *b = trx->bts; + struct gsm48_system_information_type_3 *si3; + if (GSM_BTS_HAS_SI(b, SYSINFO_TYPE_3)) { + si3 = GSM_BTS_SI(b, SYSINFO_TYPE_3); + return si3->control_channel_desc.bs_ag_blks_res; + } + LOGP(DL1P, LOGL_ERROR, "%s: Unable to determine actual BS_AG_BLKS_RES " + "value as SI3 is not available yet, fallback to 1\n", arg); + return 1; +} + +/* obtain the next to-be transmitted dowlink SACCH frame (L2 hdr + L3); returns pointer to lchan->si buffer */ +uint8_t *lchan_sacch_get(struct gsm_lchan *lchan) +{ + uint32_t tmp, i; + + for (i = 0; i < _MAX_SYSINFO_TYPE; i++) { + tmp = (lchan->si.last + 1 + i) % _MAX_SYSINFO_TYPE; + if (!(lchan->si.valid & (1 << tmp))) + continue; + lchan->si.last = tmp; + return GSM_LCHAN_SI(lchan, tmp); + } + LOGP(DL1P, LOGL_NOTICE, "%s SACCH no SI available\n", gsm_lchan_name(lchan)); + return NULL; +} diff --git a/src/common/tx_power.c b/src/common/tx_power.c new file mode 100644 index 00000000..e418cec5 --- /dev/null +++ b/src/common/tx_power.c @@ -0,0 +1,306 @@ +/* Transmit Power computation */ + +/* (C) 2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <limits.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/tx_power.h> + +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.delta_mdB)) + return INT_MIN; + + /* FIXME: temperature compensation */ + + return desired_p_out_mdBm - pa->nominal_gain_mdB - pa->calib.delta_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 attenuation 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 + * attenuation 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 + * attenuation 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 radius 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 timer */ +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 first 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 first step */ + power_ramp_do_step(trx, 1); + } + + return 0; +} + +/* determine the initial transceiver output power at start-up time */ +int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx) +{ + struct trx_power_params *tpp = &trx->power_params; + int pout_mdBm; + + /* this is the maximum initial output on the antenna connector + * towards the antenna */ + pout_mdBm = tpp->ramp.max_initial_pout_mdBm; + + /* use this as input to compute transceiver board power + * (reflecting gains in internal/external amplifiers */ + return get_p_trxout_eff_mdBm(trx, pout_mdBm); +} diff --git a/src/common/vty.c b/src/common/vty.c new file mode 100644 index 00000000..bb88c578 --- /dev/null +++ b/src/common/vty.c @@ -0,0 +1,1651 @@ +/* OsmoBTS VTY interface */ + +/* (C) 2011-2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "btsconfig.h" + +#include <inttypes.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <ctype.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/ports.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/utils.h> +#include <osmocom/trau/osmo_ortp.h> + + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/l1sap.h> + +#define VTY_STR "Configure the VTY\n" + +#define BTS_NR_STR "BTS Number\n" +#define TRX_NR_STR "TRX Number\n" +#define TS_NR_STR "Timeslot Number\n" +#define LCHAN_NR_STR "Logical Channel Number\n" +#define BTS_TRX_STR BTS_NR_STR TRX_NR_STR +#define BTS_TRX_TS_STR BTS_TRX_STR TS_NR_STR +#define BTS_TRX_TS_LCHAN_STR BTS_TRX_TS_STR LCHAN_NR_STR + +int g_vty_port_num = OSMO_VTY_PORT_BTS; + +struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr) +{ + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + + if (!plink) { + vty_out(vty, "Cannot find PHY link number %d%s", + phy_nr, VTY_NEWLINE); + return NULL; + } + + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + vty_out(vty, "Cannot find PHY instance number %d%s", + inst_nr, VTY_NEWLINE); + return NULL; + } + return pinst; +} + +int bts_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case PHY_INST_NODE: + vty->node = PHY_NODE; + { + struct phy_instance *pinst = vty->index; + vty->index = pinst->phy_link; + } + break; + case TRX_NODE: + vty->node = BTS_NODE; + { + struct gsm_bts_trx *trx = vty->index; + vty->index = trx->bts; + } + break; + case PHY_NODE: + 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: + case PHY_NODE: + case PHY_INST_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 PHY_INST_NODE: + vty->node = PHY_NODE; + { + struct phy_instance *pinst = vty->index; + vty->index = pinst->phy_link; + } + break; + case PHY_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + 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 <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +struct vty_app_info 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, +}; + +gDEFUN(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; + + bts->auto_band = 1; + return CMD_SUCCESS; +} + +gDEFUN(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; + + bts->auto_band = 0; + return CMD_SUCCESS; +} + + +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; + + + if (trx_nr > bts->num_trx) { + vty_out(vty, "%% The next unused TRX number is %u%s", + bts->num_trx, VTY_NEWLINE); + return CMD_WARNING; + } else if (trx_nr == bts->num_trx) { + /* Allocate a new TRX + * Remark: TRX0 was already created during gsm_bts_alloc() and + * initialized in bts_init(), not here. + */ + trx = gsm_bts_trx_alloc(bts); + if (trx) + bts_trx_init(trx); + } else + trx = gsm_bts_trx_num(bts, trx_nr); + + if (!trx) { + vty_out(vty, "%% Unable to allocate TRX %u%s", + trx_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = trx; + vty->index_sub = &trx->description; + vty->node = TRX_NODE; + + return CMD_SUCCESS; +} + +static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + const char *sapi_buf; + 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); + if (bts->auto_band) + vty_out(vty, " auto-band%s", 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", bts->bsc_oml_host, VTY_NEWLINE); + vty_out(vty, " rtp jitter-buffer %u", bts->rtp_jitter_buf_ms); + if (bts->rtp_jitter_adaptive) + vty_out(vty, " adaptive"); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " rtp port-range %u %u%s", bts->rtp_port_range_start, + bts->rtp_port_range_end, VTY_NEWLINE); + vty_out(vty, " paging queue-size %u%s", paging_get_queue_max(bts->paging_state), + VTY_NEWLINE); + vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(bts->paging_state), + VTY_NEWLINE); + vty_out(vty, " uplink-power-target %d%s", bts->ul_power_target, VTY_NEWLINE); + if (bts->agch_queue.thresh_level != GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT + || bts->agch_queue.low_level != GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT + || bts->agch_queue.high_level != GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT) + vty_out(vty, " agch-queue-mgmt threshold %d low %d high %d%s", + bts->agch_queue.thresh_level, bts->agch_queue.low_level, + bts->agch_queue.high_level, VTY_NEWLINE); + + for (i = 0; i < 32; i++) { + if (gsmtap_sapi_mask & (1 << i)) { + sapi_buf = osmo_str_tolower(get_value_string(gsmtap_sapi_names, i)); + vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE); + } + } + if (gsmtap_sapi_acch) { + sapi_buf = osmo_str_tolower(get_value_string(gsmtap_sapi_names, GSMTAP_CHANNEL_ACCH)); + vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE); + } + vty_out(vty, " min-qual-rach %.0f%s", bts->min_qual_rach * 10.0f, + VTY_NEWLINE); + vty_out(vty, " min-qual-norm %.0f%s", bts->min_qual_norm * 10.0f, + VTY_NEWLINE); + vty_out(vty, " max-ber10k-rach %u%s", bts->max_ber10k_rach, + VTY_NEWLINE); + if (strcmp(bts->pcu.sock_path, PCU_SOCK_DEFAULT)) + vty_out(vty, " pcu-socket %s%s", bts->pcu.sock_path, VTY_NEWLINE); + if (bts->supp_meas_toa256) + vty_out(vty, " supp-meas-info toa256%s", 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; + struct phy_instance *pinst = trx_phy_instance(trx); + 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); + vty_out(vty, " phy %u instance %u%s", pinst->phy_link->num, + pinst->num, 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 void config_write_phy_single(struct vty *vty, struct phy_link *plink) +{ + int i; + + vty_out(vty, "phy %u%s", plink->num, VTY_NEWLINE); + bts_model_config_write_phy(vty, plink); + + for (i = 0; i < 255; i++) { + struct phy_instance *pinst = phy_instance_by_num(plink, i); + if (!pinst) + break; + vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE); + bts_model_config_write_phy_inst(vty, pinst); + } +} + +static int config_write_phy(struct vty *vty) +{ + int i; + + for (i = 0; i < 255; i++) { + struct phy_link *plink = phy_link_by_num(i); + if (!plink) + break; + config_write_phy_single(vty, plink); + } + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +DEFUN(cfg_vty_telnet_port, cfg_vty_telnet_port_cmd, + "vty telnet-port <0-65535>", + VTY_STR "Set the VTY telnet port\n" + "TCP Port number\n") +{ + g_vty_port_num = atoi(argv[0]); + 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; +} + +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; + + if (bts->bsc_oml_host) + talloc_free(bts->bsc_oml_host); + + bts->bsc_oml_host = talloc_strdup(bts, argv[0]); + + return CMD_SUCCESS; +} + +#define RTP_STR "RTP parameters\n" + +DEFUN_DEPRECATED(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> [adaptive]", + RTP_STR "RTP jitter buffer\n" "jitter buffer in ms\n") +{ + struct gsm_bts *bts = vty->index; + + bts->rtp_jitter_buf_ms = atoi(argv[0]); + if (argc > 1) + bts->rtp_jitter_adaptive = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rtp_port_range, + cfg_bts_rtp_port_range_cmd, + "rtp port-range <1-65534> <1-65534>", + RTP_STR "Range of local ports to use for RTP/RTCP traffic\n") +{ + struct gsm_bts *bts = vty->index; + unsigned int start; + unsigned int end; + + start = atoi(argv[0]); + end = atoi(argv[1]); + + if (end < start) { + vty_out(vty, "range end port (%u) must be greater than the range start port (%u)!%s", + end, start, VTY_NEWLINE); + return CMD_WARNING; + } + + if (start & 1) { + vty_out(vty, "range must begin at an even port number! (%u not even)%s", + start, VTY_NEWLINE); + return CMD_WARNING; + } + + if ((end & 1) == 0) { + vty_out(vty, "range must end at an odd port number! (%u not odd)%s", + end, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->rtp_port_range_start = start; + bts->rtp_port_range_end = end; + bts->rtp_port_range_next = bts->rtp_port_range_start; + + 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; + + paging_set_queue_max(bts->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; + + paging_set_lifetime(bts->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; + + bts->agch_queue.thresh_level = atoi(argv[0]); + bts->agch_queue.low_level = atoi(argv[1]); + bts->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; + + bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT; + bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT; + bts->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; + + bts->ul_power_target = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_min_qual_rach, cfg_bts_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 *bts = vty->index; + + bts->min_qual_rach = strtof(argv[0], NULL) / 10.0f; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_min_qual_norm, cfg_bts_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 *bts = vty->index; + + bts->min_qual_norm = strtof(argv[0], NULL) / 10.0f; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd, + "max-ber10k-rach <0-10000>", + "Set the maximum BER for valid RACH requests\n" + "BER in 1/10000 units (0=no BER; 100=1% BER)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->max_ber10k_rach = strtoul(argv[0], NULL, 10); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd, + "pcu-socket PATH", + "Configure the PCU socket file/path name\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->pcu.sock_path) { + /* FIXME: close the interface? */ + talloc_free(bts->pcu.sock_path); + } + bts->pcu.sock_path = talloc_strdup(bts, argv[0]); + /* FIXME: re-open the interface? */ + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd, + "supp-meas-info toa256", + "Configure the RSL Supplementary Measurement Info\n" + "Report the TOA in 1/256th symbol periods\n") +{ + struct gsm_bts *bts = vty->index; + + bts->supp_meas_toa256 = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd, + "no supp-meas-info toa256", + NO_STR "Configure the RSL Supplementary Measurement Info\n" + "Report the TOA in 1/256th symbol periods\n") +{ + struct gsm_bts *bts = vty->index; + + bts->supp_meas_toa256 = false; + 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; +} + +DEFUN(cfg_trx_phy, cfg_trx_phy_cmd, + "phy <0-255> instance <0-255>", + "Configure PHY Link+Instance for this TRX\n" + "PHY Link number\n" "PHY instance\n" "PHY Instance number") +{ + struct gsm_bts_trx *trx = vty->index; + struct phy_link *plink = phy_link_by_num(atoi(argv[0])); + struct phy_instance *pinst; + + if (!plink) { + vty_out(vty, "phy%s does not exist%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + pinst = phy_instance_by_num(plink, atoi(argv[1])); + if (!pinst) { + vty_out(vty, "phy%s instance %s does not exit%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + trx->role_bts.l1h = pinst; + pinst->trx = trx; + + return CMD_SUCCESS; +} + +/* ====================================================================== + * SHOW + * ======================================================================*/ + +static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) +{ + vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s", + abis_nm_opstate_name(nms->operational), + get_value_string(abis_nm_adm_state_names, 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_features(struct vty *vty, struct gsm_bts *bts) +{ + unsigned int i; + bool no_features = true; + vty_out(vty, " Features:%s", VTY_NEWLINE); + + for (i = 0; i < _NUM_BTS_FEAT; i++) { + if (gsm_bts_has_feature(bts, i)) { + vty_out(vty, " %03u ", i); + vty_out(vty, "%-40s%s", get_value_string(gsm_bts_features_descs, i), VTY_NEWLINE); + no_features = false; + } + } + + if (no_features) + vty_out(vty, " (not available)%s", VTY_NEWLINE); +} + +static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " + "BSIC %u and %u TRX%s", + bts->nr, "FIXME", gsm_band_name(bts->band), + bts->cell_identity, + bts->location_area_code, bts->bsic, + 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); + if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH)) + vty_out(vty, " PCU version %s connected%s", + bts->pcu_version, VTY_NEWLINE); + vty_out(vty, " Paging: Queue size %u, occupied %u, lifetime %us%s", + paging_get_queue_max(bts->paging_state), paging_queue_length(bts->paging_state), + paging_get_lifetime(bts->paging_state), VTY_NEWLINE); + vty_out(vty, " AGCH: Queue limit %u, occupied %d, " + "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", " + "ag-res %"PRIu64", non-res %"PRIu64"%s", + bts->agch_queue.max_length, bts->agch_queue.length, + bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs, + bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs, + bts->agch_queue.pch_msgs, + VTY_NEWLINE); + vty_out(vty, " CBCH backlog queue length: %u%s", + llist_length(&bts->smscb_state.queue), VTY_NEWLINE); + vty_out(vty, " Paging: queue length %d, buffer space %d%s", + paging_queue_length(bts->paging_state), paging_buffer_space(bts->paging_state), + VTY_NEWLINE); + vty_out(vty, " OML Link state: %s.%s", + bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE); + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + vty_out(vty, " TRX %u%s", trx->nr, VTY_NEWLINE); + if (pinst) { + vty_out(vty, " phy %d %s", pinst->num, pinst->version); + if (pinst->description) + vty_out(vty, " (%s)", pinst->description); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + bts_dump_vty_features(vty, bts); + vty_out_rate_ctr_group(vty, " ", bts->ctrs); +} + + +DEFUN(show_bts, show_bts_cmd, "show bts <0-255>", + SHOW_STR "Display information about a BTS\n" + BTS_NR_STR) +{ + 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 void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s", + trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE); + vty_out(vty, "Description: %s%s", + trx->description ? trx->description : "(null)", VTY_NEWLINE); + vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, " + "resulting BS power: %d dBm%s", + trx->nominal_power, trx->max_power_red, + trx->nominal_power - trx->max_power_red, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &trx->mo.nm_state); + vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE); + vty_out(vty, " Baseband Transceiver NM State: "); + net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state); + vty_out(vty, " IPA stream ID: 0x%02x%s", trx->rsl_tei, VTY_NEWLINE); +} + +static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts) +{ + uint8_t trx_nr; + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); +} + +DEFUN(show_trx, + show_trx_cmd, + "show trx [<0-255>] [<0-255>]", + SHOW_STR "Display information about a TRX\n" + BTS_TRX_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + int bts_nr, trx_nr; + + if (argc >= 1) { + /* 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 = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); + return CMD_SUCCESS; + } + if (bts) { + /* print all TRX in this BTS */ + print_all_trx(vty, bts); + return CMD_SUCCESS; + } + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + print_all_trx(vty, gsm_bts_num(net, bts_nr)); + + return CMD_SUCCESS; +} + + +static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s, TSC %u", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts)); + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) + vty_out(vty, " (%s mode)", + ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F"); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &ts->mo.nm_state); +} + +DEFUN(show_ts, + show_ts_cmd, + "show timeslot [<0-255>] [<0-255>] [<0-7>]", + SHOW_STR "Display information about a TS\n" + BTS_TRX_TS_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + int bts_nr, trx_nr, ts_nr; + + if (argc >= 1) { + /* 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 = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS '%s'%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + /* Fully Specified: print and exit */ + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + return CMD_SUCCESS; + } + + if (bts && trx) { + /* Iterate over all TS in this TRX */ + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } else if (bts) { + /* Iterate over all TRX in this BTS, TS in each TRX */ + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } + } else { + /* Iterate over all BTS, TRX in each BTS, TS in each TRX */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } + } + } + + return CMD_SUCCESS; +} + +/* FIXME: move this to libosmogsm */ +static const struct value_string gsm48_cmode_names[] = { + { GSM48_CMODE_SIGN, "signalling" }, + { GSM48_CMODE_SPEECH_V1, "FR or HR" }, + { GSM48_CMODE_SPEECH_EFR, "EFR" }, + { GSM48_CMODE_SPEECH_AMR, "AMR" }, + { GSM48_CMODE_DATA_14k5, "CSD(14k5)" }, + { GSM48_CMODE_DATA_12k0, "CSD(12k0)" }, + { GSM48_CMODE_DATA_6k0, "CSD(6k0)" }, + { GSM48_CMODE_DATA_3k6, "CSD(3k6)" }, + { 0, NULL } +}; + +/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots. + * Don't do anything if the ts is not dynamic. */ +static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + vty_out(vty, " as %s", + gsm_pchan_name(ts->dyn.pchan_is)); + else + vty_out(vty, " switching %s -> %s", + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + vty_out(vty, " as %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + vty_out(vty, " switching %s -> %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + /* no dyn ts */ + break; + } +} + +static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + struct in_addr ia; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE); + /* show dyn TS details, if applicable */ + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + vty_out(vty, " Osmocom Dyn TS:"); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + case GSM_PCHAN_TCH_F_PDCH: + vty_out(vty, " IPACC Dyn PDCH TS:"); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + default: + /* no dyn ts */ + break; + } + vty_out(vty, " State: %s%s%s%s", + gsm_lchans_name(lchan->state), + lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "", + lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "", + VTY_NEWLINE); + vty_out(vty, " BS Power: %d dBm, MS Power: %u dBm%s", + lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red + - lchan->bs_power*2, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + VTY_NEWLINE); + vty_out(vty, " Channel Mode / Codec: %s%s", + get_value_string(gsm48_cmode_names, lchan->tch_mode), + VTY_NEWLINE); + + if (lchan->abis_ip.bound_ip) { + ia.s_addr = htonl(lchan->abis_ip.bound_ip); + vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s", + inet_ntoa(ia), lchan->abis_ip.bound_port, + lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id, + VTY_NEWLINE); + } + if (lchan->abis_ip.connect_ip) { + ia.s_addr = htonl(lchan->abis_ip.connect_ip); + vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02u%s", + inet_ntoa(ia), lchan->abis_ip.connect_port, + lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode, + VTY_NEWLINE); + } +#define LAPDM_ESTABLISHED(link, sapi_idx) \ + (link).datalink[sapi_idx].dl.state == LAPD_STATE_MF_EST + vty_out(vty, " LAPDm SAPIs: DCCH %c%c, SACCH %c%c%s", + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI0) ? '0' : '-', + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI3) ? '3' : '-', + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI0) ? '0' : '-', + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI3) ? '3' : '-', + VTY_NEWLINE); +#undef LAPDM_ESTABLISHED + vty_out(vty, " Valid System Information: 0x%08x%s", + lchan->si.valid, VTY_NEWLINE); + /* TODO: L1 SAPI (but those are BTS speific :( */ + /* TODO: AMR bits */ + vty_out(vty, " MS Timing Offset: %d, propagation delay: %d symbols %s", + lchan->ms_t_offs, lchan->p_offs, VTY_NEWLINE); + if (lchan->encr.alg_id) { + vty_out(vty, " Ciphering A5/%u State: %s, N(S)=%u%s", + lchan->encr.alg_id-1, lchan_ciph_state_name(lchan->ciph_state), + lchan->ciph_ns, VTY_NEWLINE); + } + if (lchan->loopback) + vty_out(vty, " RTP/PDCH Loopback Enabled%s", VTY_NEWLINE); + vty_out(vty, " Radio Link Failure Counter 'S': %d%s", lchan->s, VTY_NEWLINE); + /* TODO: MS Power Control */ +} + +static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + const struct gsm_meas_rep_unidir *mru = &lchan->meas.ul_res; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + gsm_pchan_name(lchan->ts->pchan)); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, ", Lchan %u, Type %s, State %s - " + "RXL-FULL-ul: %4d dBm%s", + lchan->nr, + gsm_lchant_name(lchan->type), gsm_lchans_name(lchan->state), + rxlev2dbm(mru->full.rx_lev), + VTY_NEWLINE); +} + +static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int lchan_nr; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) { + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + if (lchan->state == LCHAN_S_NONE) + continue; + dump_cb(vty, lchan); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int ts_nr; + + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + dump_lchan_trx_ts(ts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int trx_nr; + + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr); + dump_lchan_trx(trx, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int lchan_summary(struct vty *vty, int argc, const char **argv, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int bts_nr, trx_nr, ts_nr, lchan_nr; + + if (argc >= 1) { + /* 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 = gsm_bts_num(net, bts_nr); + + if (argc == 1) + return dump_lchan_bts(bts, vty, dump_cb); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX %s%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + + if (argc == 2) + return dump_lchan_trx(trx, vty, dump_cb); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS %s%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[ts_nr]; + + if (argc == 3) + return dump_lchan_trx_ts(ts, vty, dump_cb); + } + if (argc >= 4) { + lchan_nr = atoi(argv[3]); + if (lchan_nr >= TS_MAX_LCHAN) { + vty_out(vty, "%% can't find LCHAN %s%s", argv[3], + VTY_NEWLINE); + return CMD_WARNING; + } + lchan = &ts->lchan[lchan_nr]; + dump_cb(vty, lchan); + return CMD_SUCCESS; + } + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + dump_lchan_bts(bts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +DEFUN(show_lchan, + show_lchan_cmd, + "show lchan [<0-255>] [<0-255>] [<0-7>] [<0-7>]", + SHOW_STR "Display information about a logical channel\n" + BTS_TRX_TS_LCHAN_STR) +{ + return lchan_summary(vty, argc, argv, lchan_dump_full_vty); +} + +DEFUN(show_lchan_summary, + show_lchan_summary_cmd, + "show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]", + SHOW_STR "Display information about a logical channel\n" + "Short summary\n" + BTS_TRX_TS_LCHAN_STR) +{ + return lchan_summary(vty, argc, argv, lchan_dump_short_vty); +} + +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]); + OSMO_ASSERT(sapi >= 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]); + OSMO_ASSERT(sapi >= 0); + + if (sapi == GSMTAP_CHANNEL_ACCH) + gsmtap_sapi_acch = 0; + else + gsmtap_sapi_mask &= ~(1 << sapi); + + return CMD_SUCCESS; +} + +static struct cmd_node phy_node = { + PHY_NODE, + "%s(phy)# ", + 1, +}; + +static struct cmd_node phy_inst_node = { + PHY_INST_NODE, + "%s(phy-inst)# ", + 1, +}; + +DEFUN(cfg_phy, cfg_phy_cmd, + "phy <0-255>", + "Select a PHY to configure\n" "PHY number\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink; + + plink = phy_link_by_num(phy_nr); + if (!plink) + plink = phy_link_create(tall_bts_ctx, phy_nr); + if (!plink) + return CMD_WARNING; + + vty->index = plink; + vty->index_sub = &plink->description; + vty->node = PHY_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_inst, cfg_phy_inst_cmd, + "instance <0-255>", + "Select a PHY instance to configure\n" "PHY Instance number\n") +{ + int inst_nr = atoi(argv[0]); + struct phy_link *plink = vty->index; + struct phy_instance *pinst; + + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + pinst = phy_instance_create(plink, inst_nr); + if (!pinst) { + vty_out(vty, "Unable to create phy%u instance %u%s", + plink->num, inst_nr, VTY_NEWLINE); + return CMD_WARNING; + } + } + + vty->index = pinst; + vty->index_sub = &pinst->description; + vty->node = PHY_INST_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd, + "no instance <0-255>" + NO_STR "Select a PHY instance to remove\n", "PHY Instance number\n") +{ + int inst_nr = atoi(argv[0]); + struct phy_link *plink = vty->index; + struct phy_instance *pinst; + + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + phy_instance_destroy(pinst); + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_phy_type, cfg_phy_type_cmd, + "type (sysmobts|osmo-trx|virtual)", + "configure the type of the PHY\n" + "sysmocom sysmoBTS PHY\n" + "OsmoTRX based PHY\n" + "Virtual PHY (GSMTAP based)\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "sysmobts")) + plink->type = PHY_LINK_T_SYSMOBTS; + else if (!strcmp(argv[0], "osmo-trx")) + plink->type = PHY_LINK_T_OSMOTRX; + else if (!strcmp(argv[0], "virtual")) + plink->type = PHY_LINK_T_VIRTUAL; +} +#endif + +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]), rc; + + 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; + } + rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, + lchan->ts->trx->bts->rtp_jitter_adaptive ? + OSMO_RTP_P_JIT_ADAP : OSMO_RTP_P_JITBUF, + jitbuf_ms); + if (rc < 0) + vty_out(vty, "%% error setting jitter parameters: %s%s", + strerror(-rc), VTY_NEWLINE); + else + vty_out(vty, "%% jitter parameters set: %d%s", rc, VTY_NEWLINE); + + 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); + install_element_ve(&show_trx_cmd); + install_element_ve(&show_ts_cmd); + install_element_ve(&show_lchan_cmd); + install_element_ve(&show_lchan_summary_cmd); + + logging_vty_add_cmds(cat); + osmo_talloc_vty_add_cmds(); + + install_node(&bts_node, config_write_bts); + install_element(CONFIG_NODE, &cfg_bts_cmd); + install_element(CONFIG_NODE, &cfg_vty_telnet_port_cmd); + 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_rtp_port_range_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_bts_min_qual_rach_cmd); + install_element(BTS_NODE, &cfg_bts_min_qual_norm_cmd); + install_element(BTS_NODE, &cfg_bts_max_ber_rach_cmd); + install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd); + install_element(BTS_NODE, &cfg_bts_supp_meas_toa256_cmd); + install_element(BTS_NODE, &cfg_bts_no_supp_meas_toa256_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_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(TRX_NODE, &cfg_trx_phy_cmd); + + install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd); + install_element(ENABLE_NODE, &bts_t_t_l_loopback_cmd); + install_element(ENABLE_NODE, &no_bts_t_t_l_loopback_cmd); + + install_element(CONFIG_NODE, &cfg_phy_cmd); + install_node(&phy_node, config_write_phy); + install_element(PHY_NODE, &cfg_phy_inst_cmd); + install_element(PHY_NODE, &cfg_phy_no_inst_cmd); + + install_node(&phy_inst_node, config_write_dummy); + + return 0; +} diff --git a/src/osmo-bts-litecell15/Makefile.am b/src/osmo-bts-litecell15/Makefile.am new file mode 100644 index 00000000..0cc124ab --- /dev/null +++ b/src/osmo-bts-litecell15/Makefile.am @@ -0,0 +1,38 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LITECELL15_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(LIBSYSTEMD_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) + +AM_CFLAGS += -DENABLE_LC15BTS + +EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h misc/lc15bts_led.h \ + misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \ + misc/lc15bts_bid.h misc/lc15bts_nl.h misc/lc15bts_bts.h misc/lc15bts_swd.h \ + hw_misc.h l1_if.h l1_transp.h lc15bts.h oml_router.h utils.h + +bin_PROGRAMS = osmo-bts-lc15 lc15bts-mgr lc15bts-util + +COMMON_SOURCES = main.c lc15bts.c l1_if.c oml.c lc15bts_vty.c tch.c hw_misc.c calib_file.c \ + utils.c misc/lc15bts_par.c misc/lc15bts_bid.c oml_router.c + +osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_lc15_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +lc15bts_mgr_SOURCES = \ + misc/lc15bts_mgr.c misc/lc15bts_misc.c \ + misc/lc15bts_par.c misc/lc15bts_nl.c \ + misc/lc15bts_temp.c misc/lc15bts_power.c \ + misc/lc15bts_clock.c misc/lc15bts_bid.c \ + misc/lc15bts_mgr_vty.c \ + misc/lc15bts_mgr_nl.c \ + misc/lc15bts_mgr_temp.c \ + misc/lc15bts_mgr_calib.c \ + misc/lc15bts_led.c \ + misc/lc15bts_bts.c \ + misc/lc15bts_swd.c + +lc15bts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) + +lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c +lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-litecell15/calib_file.c b/src/osmo-bts-litecell15/calib_file.c new file mode 100644 index 00000000..b7049df1 --- /dev/null +++ b/src/osmo-bts-litecell15/calib_file.c @@ -0,0 +1,456 @@ +/* NuRAN Wireless Litecell 1.5 BTS L1 calibration file routines*/ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1const.h> + +#include "l1_if.h" +#include "lc15bts.h" +#include "utils.h" + +/* Maximum calibration data chunk size */ +#define MAX_CALIB_TBL_SIZE 65536 +/* Calibration header version */ +#define CALIB_HDR_V1 0x01 + +struct calib_file_desc { + const char *fname; + int rx; + int trx; + int rxpath; +}; + +static const struct calib_file_desc calib_files[] = { + { + .fname = "calib_rx0a.conf", + .rx = 1, + .trx = 0, + .rxpath = 0, + }, { + .fname = "calib_rx0b.conf", + .rx = 1, + .trx = 0, + .rxpath = 1, + }, { + .fname = "calib_rx1a.conf", + .rx = 1, + .trx = 1, + .rxpath = 0, + }, { + .fname = "calib_rx1b.conf", + .rx = 1, + .trx = 1, + .rxpath = 1, + }, { + .fname = "calib_tx0.conf", + .rx = 0, + .trx = 0, + }, { + .fname = "calib_tx1.conf", + .rx = 0, + .trx = 1, + }, +}; + +struct calTbl_t +{ + union + { + struct + { + uint8_t u8Version; /* Header version (1) */ + uint8_t u8Parity; /* Parity byte (xor) */ + uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */ + uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */ + uint32_t u32Len; /* Table length in bytes including the header */ + struct + { + uint32_t u32DescOfst; /* Description section offset */ + uint32_t u32DateOfst; /* Date section offset */ + uint32_t u32StationOfst; /* Calibration test station section offset */ + uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */ + uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */ + uint32_t u32DataOfst; /* Calibration data section offset */ + } toc; + } v1; + } hdr; + + uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32]; +}; + + +static int calib_file_send(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc); +static int calib_verify(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc); + +/* determine next calibration file index based on supported bands */ +static int get_next_calib_file_idx(struct lc15l1_hdl *fl1h, int last_idx) +{ + struct phy_link *plink = fl1h->phy_inst->phy_link; + int i; + + for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { + if (calib_files[i].trx == plink->num) + return i; + } + return -1; +} + +static int calib_file_open(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.lc15.calib_path; + char fname[PATH_MAX]; + + if (st->fp) { + LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n"); + fclose(st->fp); + st->fp = NULL; + } + + fname[0] = '\0'; + snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname); + fname[sizeof(fname)-1] = '\0'; + + st->fp = fopen(fname, "rb"); + if (!st->fp) { + LOGP(DL1C, LOGL_ERROR, + "Failed to open '%s' for calibration data.\n", fname); + return -1; + } + return 0; +} + +static int calib_file_close(struct lc15l1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + + if (st->fp) { + fclose(st->fp); + st->fp = NULL; + } + 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 a chunk of calibration tabledata for a single specified file */ +static int calib_file_send_next_chunk(struct lc15l1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + Litecell15_Prim_t *prim; + struct msgb *msg; + size_t n; + + msg = sysp_msgb_alloc(); + prim = msgb_sysprim(msg); + + prim->id = Litecell15_PrimId_SetCalibTblReq; + prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp); + n = fread(prim->u.setCalibTblReq.u8Data, 1, + sizeof(prim->u.setCalibTblReq.u8Data), st->fp); + prim->u.setCalibTblReq.length = n; + + + if (n == 0) { + /* The table data has been completely sent and acknowledged */ + LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n", + calib_files[st->last_file_idx].fname); + + calib_file_close(fl1h); + + msgb_free(msg); + + /* Send the next one if any */ + st->last_file_idx = get_next_calib_file_idx(fl1h, 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"); + return 0; + } + + return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL); +} + +/* send the calibration table for a single specified file */ +static int calib_file_send(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + int rc; + + rc = calib_file_open(fl1h, desc); + if (rc < 0) { + /* still, we'd like to continue trying to load + * calibration for all other bands */ + st->last_file_idx = get_next_calib_file_idx(fl1h, 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"); + return 0; + } + + rc = calib_verify(fl1h, desc); + if ( rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc); + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + return 0; + + } + + LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname); + + return calib_file_send_next_chunk(fl1h); +} + +/* 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 lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct calib_send_state *st = &fl1h->st; + Litecell15_Prim_t *prim = msgb_sysprim(l1_msg); + + if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n"); + + msgb_free(l1_msg); + + calib_file_close(fl1h); + + /* Skip this one and try the next one */ + st->last_file_idx = get_next_calib_file_idx(fl1h, 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"); + return 0; + } + + msgb_free(l1_msg); + + /* Keep sending the calibration file data */ + return calib_file_send_next_chunk(fl1h); +} + +int calib_load(struct lc15l1_hdl *fl1h) +{ + int rc; + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.lc15.calib_path; + + if (!calib_path) { + LOGP(DL1C, LOGL_ERROR, "Calibration file path not specified\n"); + return -1; + } + + rc = get_next_calib_file_idx(fl1h, -1); + if (rc < 0) { + return -1; + } + st->last_file_idx = rc; + + return calib_file_send(fl1h, &calib_files[st->last_file_idx]); +} + + +static int calib_verify(struct lc15l1_hdl *fl1h, const struct calib_file_desc *desc) +{ + int rc, sz; + struct calib_send_state *st = &fl1h->st; + struct phy_link *plink = fl1h->phy_inst->phy_link; + char *rbuf; + struct calTbl_t *calTbl; + char calChkSum ; + + /* calculate file size in bytes */ + fseek(st->fp, 0L, SEEK_END); + sz = ftell(st->fp); + + /* rewind read poiner */ + fseek(st->fp, 0L, SEEK_SET); + + /* read file */ + rbuf = (char *) malloc( sizeof(char) * sz ); + + rc = fread(rbuf, 1, sizeof(char) * sz, st->fp); + if ( rc != sz) { + + LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname); + free(rbuf); + + /* close file */ + rc = calib_file_close(fl1h); + if (rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname); + return rc; + } + + return -2; + } + + calTbl = (struct calTbl_t*) rbuf; + /* calculate file checksum */ + calChkSum = 0; + while ( sz-- ) { + calChkSum ^= rbuf[sz]; + } + + /* validate Tx calibration parity */ + if ( calChkSum ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum); + return -4; + } + + /* validate Tx calibration header */ + if ( calTbl->hdr.v1.u8Version != CALIB_HDR_V1 ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version); + return -5; + } + + /* validate calibration description */ + if ( calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname); + return -6; + } + + /* validate calibration date */ + if ( calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname); + return -7; + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n", + desc->fname, + calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst); + + /* validate calibration station */ + if ( calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname); + return -8; + } + + /* validate FPGA FW version */ + if ( calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname); + return -9; + } + + /* validate DSP FW version */ + if ( calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname); + return -10; + } + + /* validate Tx calibration data offset */ + if ( calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname); + return -11; + } + + if ( !desc->rx ) { + + /* parse min/max Tx power */ + fl1h->phy_inst->u.lc15.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)]; + fl1h->phy_inst->u.lc15.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)]; + + /* override nominal Tx power of given TRX if needed */ + if ( fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.lc15.maxTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.lc15.maxTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.maxTxPower; + } + + if ( fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.lc15.minTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.lc15.minTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower; + } + + if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.lc15.maxTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.lc15.maxTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.maxTxPower); + } + + if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.lc15.minTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.lc15.minTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.minTxPower); + } + + LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n", + desc->fname, + fl1h->phy_inst->u.lc15.minTxPower, + fl1h->phy_inst->u.lc15.maxTxPower ); + } + + /* rewind read pointer for subsequence tasks */ + fseek(st->fp, 0L, SEEK_SET); + free(rbuf); + + return 0; +} + diff --git a/src/osmo-bts-litecell15/hw_misc.c b/src/osmo-bts-litecell15/hw_misc.c new file mode 100644 index 00000000..9f070bba --- /dev/null +++ b/src/osmo-bts-litecell15/hw_misc.c @@ -0,0 +1,88 @@ +/* Misc HW routines for NuRAN Wireless Litecell 1.5 BTS */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/utils.h> + +#include "hw_misc.h" + +int lc15bts_led_set(enum lc15bts_led_color c) +{ + int fd, rc; + uint8_t cmd[2]; + + switch (c) { + case LED_OFF: + cmd[0] = 0; + cmd[1] = 0; + break; + case LED_RED: + cmd[0] = 1; + cmd[1] = 0; + break; + case LED_GREEN: + cmd[0] = 0; + cmd[1] = 1; + break; + case LED_ORANGE: + cmd[0] = 1; + cmd[1] = 1; + break; + default: + return -EINVAL; + } + + fd = open("/var/lc15/leds/led0/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[0] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + + fd = open("/var/lc15/leds/led1/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[1] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + return 0; +} diff --git a/src/osmo-bts-litecell15/hw_misc.h b/src/osmo-bts-litecell15/hw_misc.h new file mode 100644 index 00000000..59ed04b7 --- /dev/null +++ b/src/osmo-bts-litecell15/hw_misc.h @@ -0,0 +1,13 @@ +#ifndef _HW_MISC_H +#define _HW_MISC_H + +enum lc15bts_led_color { + LED_OFF, + LED_RED, + LED_GREEN, + LED_ORANGE, +}; + +int lc15bts_led_set(enum lc15bts_led_color c); + +#endif diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-litecell15/l1_if.c new file mode 100644 index 00000000..99852e39 --- /dev/null +++ b/src/osmo-bts-litecell15/l1_if.c @@ -0,0 +1,1586 @@ +/* Interface handler for NuRAN Wireless Litecell 1.5 L1 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011-2014 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/lapdm.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1types.h> + +#include "lc15bts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "hw_misc.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" +#include "utils.h" + +extern unsigned int dsp_trace; + +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 */ + HANDLE conf_hLayer3; /* layer 3 handle 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(lc15bts_sysprim_names, wlc->conf_prim_id)); + else + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(lc15bts_l1prim_names, wlc->conf_prim_id)); + exit(23); +} + +static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + +static int _l1if_req_compl(struct lc15l1_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(lc15bts_l1prim_names, l1p->id)); + + if (lc15bts_get_l1prim_type(l1p->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", + get_value_string(lc15bts_l1prim_names, l1p->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 0; + wlc->conf_prim_id = lc15bts_get_l1prim_conf(l1p->id); + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + wqueue = &fl1h->write_q[MQ_L1_WRITE]; + timeout_secs = 30; + } else { + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + + if (lc15bts_get_sysprim_type(sysp->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 1; + wlc->conf_prim_id = lc15bts_get_sysprim_conf(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 lc15l1_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 lc15l1_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 Litecell15_Prim_t */ +struct msgb *sysp_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(Litecell15_Prim_t), "sys_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(Litecell15_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; +} + +/* fill PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +data_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_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 = (HANDLE)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 lc15l1_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 = (HANDLE)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, bool use_cache) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + 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_CBCH(chan_nr)) { + sapi = GsmL1_Sapi_Cbch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = l1sap_fn2ccch_block(u32Fn); + if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ")) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + /* empty frame */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + + 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], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + 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)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* 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); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* 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 */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + 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); + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + return 0; +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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); + 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; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + 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; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct lc15l1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + 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) { + msgb_free(msg); + 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; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Cbch: + cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */ + 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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + 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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + 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 lc15l1_hdl *fl1, + GsmL1_PhReadyToSendInd_t *rts_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + 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], 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 = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + 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); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(lc15bts_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; + 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) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + 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, 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_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + uint8_t *data, len; + int rc = 0; + int8_t rssi; + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + gsm_fn2gsmtime(&g_time, fn); + + if (!chan_nr) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(lc15bts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + + process_meas_res(trx, chan_nr, &data_ind->measParam, fn); + + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + get_value_string(lc15bts_l1sapi_names, data_ind->sapi), (uint32_t)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; + if (!pcu_direct) { + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + } + return l1sap_up(trx, l1sap); +} + +static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */ + if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) { + msgb_free(l1p_msg); + return 0; + } + + dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + }; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = 0x88; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to + * osmo-bts-litecell15 */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + 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 = rach_ind_param; + + return l1sap_up(trx, l1sap); +} + +/* handle any random indication from the L1 */ +static int l1if_handle_ind(struct lc15l1_hdl *fl1, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + int rc = 0; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + return 0; + return 1; +} + +int l1if_handle_l1prim(int wq, struct lc15l1_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(lc15bts_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) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(lc15l1_hdl_trx(fl1h), 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 lc15l1_hdl *fl1h, struct msgb *msg) +{ + Litecell15_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(lc15bts_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) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(lc15l1_hdl_trx(fl1h), 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); +} + +static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + int on = 0; + unsigned int i; + + if (sysp->id == Litecell15_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(lc15bts_l1status_names, status)); + + + if (on) { + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", + get_value_string(lc15bts_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; +} + +/* activate or de-activate the entire RF-Frontend */ +int l1if_activate_rf(struct lc15l1_hdl *hdl, int on) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + if (on) { + sysp->id = Litecell15_PrimId_ActivateRfReq; + sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; + sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; + + sysp->u.activateRfReq.u8UnusedTsMode = 0; + sysp->u.activateRfReq.u8McCorrMode = 0; + + /* maximum cell size in quarter-bits, 90 == 12.456 km */ + sysp->u.activateRfReq.u8MaxCellSize = 90; + } else { + sysp->id = Litecell15_PrimId_DeactivateRfReq; + } + + return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); +} + +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 lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + Litecell15_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(lc15bts_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(lc15bts_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; +} + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_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] + ); + + sysp->id = Litecell15_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); +} + +/* 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) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; + struct lc15l1_hdl *fl1h = trx_lc15l1_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; + + LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n", + sic->dspVersion.major, sic->dspVersion.minor, + sic->dspVersion.build, sic->fpgaVersion.major, + sic->fpgaVersion.minor, sic->fpgaVersion.build); + + 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); + + /* load calibration tables */ + rc = calib_load(fl1h); + if (rc < 0) + LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " + "unable to load tables!\n"); + + msgb_free(resp); + return 0; +} + +/* request DSP+FPGA code versions */ +static int l1if_get_info(struct lc15l1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = Litecell15_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 lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + Litecell15_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(lc15bts_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(lc15bts_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 lc15l1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = Litecell15_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 lc15l1_hdl *hdl, uint32_t flags) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", + flags); + + sysp->id = Litecell15_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; +} + +static int get_hwinfo(struct lc15l1_hdl *fl1h) +{ + int rc; + + rc = lc15bts_rev_get(); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS revision: %d\n", rc); + return rc; + } + fl1h->hw_info.ver_major = rc; + + rc = lc15bts_model_get(); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS model: %d\n", rc); + return rc; + } + fl1h->hw_info.ver_minor = rc; + + rc = lc15bts_option_get(LC15BTS_OPTION_BAND); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS_OPTION_BAND: %d\n", rc); + return rc; + } + + switch (rc) { + case LC15BTS_BAND_850: + fl1h->hw_info.band_support = GSM_BAND_850; + break; + case LC15BTS_BAND_900: + fl1h->hw_info.band_support = GSM_BAND_900; + break; + case LC15BTS_BAND_1800: + fl1h->hw_info.band_support = GSM_BAND_1800; + break; + case LC15BTS_BAND_1900: + fl1h->hw_info.band_support = GSM_BAND_1900; + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unexpected LC15BTS_BAND value: %d\n", rc); + return -1; + } + + LOGP(DL1C, LOGL_INFO, "BTS hw support band %s\n", gsm_band_name(fl1h->hw_info.band_support)); + + return 0; +} + +struct lc15l1_hdl *l1if_open(struct phy_instance *pinst) +{ + struct lc15l1_hdl *fl1h; + int rc; + + LOGP(DL1C, LOGL_INFO, "Litecell 1.5 BTS L1IF compiled against API headers " + "v%u.%u.%u\n", LITECELL15_API_VERSION >> 16, + (LITECELL15_API_VERSION >> 8) & 0xff, + LITECELL15_API_VERSION & 0xff); + + fl1h = talloc_zero(pinst, struct lc15l1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.lc15.dsp_trace_f; + + get_hwinfo(fl1h); + + 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 lc15l1_hdl *fl1h) +{ + l1if_transport_close(MQ_L1_WRITE, fl1h); + l1if_transport_close(MQ_SYS_WRITE, fl1h); + return 0; +} + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + + OSMO_ASSERT(pinst); + + if (!pinst->trx) { + LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d " + "because no TRX is associated with it\n", plink->num, pinst->num); + return 0; + } + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.lc15.hdl = l1if_open(pinst); + if (!pinst->u.lc15.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + + struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl; + fl1h->dsp_trace_f = dsp_trace; + + l1if_reset(pinst->u.lc15.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + return 0; +} diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-litecell15/l1_if.h new file mode 100644 index 00000000..aac26075 --- /dev/null +++ b/src/osmo-bts-litecell15/l1_if.h @@ -0,0 +1,134 @@ +#ifndef _L1_IF_H +#define _L1_IF_H + +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/timer.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/phy_link.h> + +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1types.h> + +#include <stdbool.h> + +enum { + MQ_SYS_READ, + MQ_L1_READ, + MQ_TCH_READ, + MQ_PDTCH_READ, + _NUM_MQ_READ +}; + +enum { + MQ_SYS_WRITE, + MQ_L1_WRITE, + MQ_TCH_WRITE, + MQ_PDTCH_WRITE, + _NUM_MQ_WRITE +}; + +struct calib_send_state { + FILE *fp; + const char *path; + int last_file_idx; +}; + +struct lc15l1_hdl { + struct gsm_time gsm_time; + HANDLE hLayer1; /* handle to the L1 instance in the DSP */ + uint32_t dsp_trace_f; /* currently operational DSP trace flags */ + struct llist_head wlc_list; + + struct phy_instance *phy_inst; + + 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; + uint8_t ver_major; + uint8_t ver_minor; + } hw_info; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; +}; + +#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) +#define msgb_sysprim(msg) ((Litecell15_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 lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); +int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); + +struct lc15l1_hdl *l1if_open(struct phy_instance *pinst); +int l1if_close(struct lc15l1_hdl *hdl); +int l1if_reset(struct lc15l1_hdl *hdl); +int l1if_activate_rf(struct lc15l1_hdl *hdl, int on); +int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags); +int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power); +int l1if_mute_rf(struct lc15l1_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 */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +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, uint32_t fn); + +/* ciphering */ +int l1if_set_ciphering(struct lc15l1_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 lc15l1_hdl *fl1h); + +/* public helpers for test */ +int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h, + struct msgb *msg, struct gsm_lchan *lchan); +int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, + const uint8_t ms_power, const float rxLevel); + +static inline struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.lc15.hdl; +} + +static inline struct gsm_bts_trx *lc15l1_hdl_trx(struct lc15l1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} + +#endif /* _L1_IF_H */ diff --git a/src/osmo-bts-litecell15/l1_transp.h b/src/osmo-bts-litecell15/l1_transp.h new file mode 100644 index 00000000..7d6772e8 --- /dev/null +++ b/src/osmo-bts-litecell15/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _L1_TRANSP_H +#define _L1_TRANSP_H + +#include <osmocom/core/msgb.h> + +/* functions a transport calls on arrival of primitive from BTS */ +int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg); +int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg); + +/* functions exported by a transport */ +int l1if_transport_open(int q, struct lc15l1_hdl *fl1h); +int l1if_transport_close(int q, struct lc15l1_hdl *fl1h); + +#endif /* _L1_TRANSP_H */ diff --git a/src/osmo-bts-litecell15/l1_transp_hw.c b/src/osmo-bts-litecell15/l1_transp_hw.c new file mode 100644 index 00000000..c8972be5 --- /dev/null +++ b/src/osmo-bts-litecell15/l1_transp_hw.c @@ -0,0 +1,326 @@ +/* Interface handler for Nuran Wireless Litecell 1.5 L1 (real hardware) */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <assert.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1types.h> + +#include "lc15bts.h" +#include "l1_if.h" +#include "l1_transp.h" + + +#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/litecell15_dsp2arm_trx" +#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/litecell15_arm2dsp_trx" +#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx" +#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx" + +#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx" +#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx" +#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx" +#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx" + +static const char *rd_devnames[] = { + [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME, + [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME, + [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME, + [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME, +}; + +static const char *wr_devnames[] = { + [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME, + [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME, + [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME, + [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME, +}; + +/* + * Make sure that all structs we read fit into the LC15BTS_PRIM_SIZE + */ +osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, l1_prim) +osmo_static_assert(sizeof(Litecell15_Prim_t) + 128 <= LC15BTS_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(Litecell15_Prim_t); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + 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 lc15l1_hdl *fl1h, struct msgb *msg, int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return l1if_handle_sysprim(fl1h, msg); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + 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)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + 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 lc15l1_hdl *hdl) +{ + struct phy_link *plink = hdl->phy_inst->phy_link; + int rc; + char buf[PATH_MAX]; + + /* 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; + + snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_RDONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, 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; + } + + snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_WRONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, 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 lc15l1_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; +} diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-litecell15/lc15bts.c new file mode 100644 index 00000000..172a7e45 --- /dev/null +++ b/src/osmo-bts-litecell15/lc15bts.c @@ -0,0 +1,332 @@ +/* NuRAN Wireless Litecell 1.5 L1 API related definitions */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * based on: + * sysmobts.c + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1dbg.h> + +#include "lc15bts.h" + +enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return L1P_T_REQ; + case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ; + case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ; + case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF; + case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ; + case GsmL1_PrimId_PhDataReq: return L1P_T_REQ; + case GsmL1_PrimId_MphTimeInd: return L1P_T_IND; + case GsmL1_PrimId_MphSyncInd: return L1P_T_IND; + case GsmL1_PrimId_PhConnectInd: return L1P_T_IND; + case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND; + case GsmL1_PrimId_PhDataInd: return L1P_T_IND; + case GsmL1_PrimId_PhRaInd: return L1P_T_IND; + default: return L1P_T_INVALID; + } +} + +const struct value_string lc15bts_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 } +}; + +GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf; + case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf; + case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf; + case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf; + case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf; + case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf; + case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf; + case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf; + default: return -1; // Weak + } +} + +enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id) +{ + switch (id) { + case Litecell15_PrimId_SystemInfoReq: return L1P_T_REQ; + case Litecell15_PrimId_SystemInfoCnf: return L1P_T_CONF; + case Litecell15_PrimId_SystemFailureInd: return L1P_T_IND; + case Litecell15_PrimId_ActivateRfReq: return L1P_T_REQ; + case Litecell15_PrimId_ActivateRfCnf: return L1P_T_CONF; + case Litecell15_PrimId_DeactivateRfReq: return L1P_T_REQ; + case Litecell15_PrimId_DeactivateRfCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetTraceFlagsReq: return L1P_T_REQ; + case Litecell15_PrimId_Layer1ResetReq: return L1P_T_REQ; + case Litecell15_PrimId_Layer1ResetCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetCalibTblReq: return L1P_T_REQ; + case Litecell15_PrimId_SetCalibTblCnf: return L1P_T_CONF; + case Litecell15_PrimId_MuteRfReq: return L1P_T_REQ; + case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ; + case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF; + default: return L1P_T_INVALID; + } +} + +const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = { + { Litecell15_PrimId_SystemInfoReq, "SYSTEM-INFO.req" }, + { Litecell15_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" }, + { Litecell15_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" }, + { Litecell15_PrimId_ActivateRfReq, "ACTIVATE-RF.req" }, + { Litecell15_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" }, + { Litecell15_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" }, + { Litecell15_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" }, + { Litecell15_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" }, + { Litecell15_PrimId_Layer1ResetReq, "LAYER1-RESET.req" }, + { Litecell15_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" }, + { Litecell15_PrimId_SetCalibTblReq, "SET-CALIB.req" }, + { Litecell15_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" }, + { Litecell15_PrimId_MuteRfReq, "MUTE-RF.req" }, + { Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, + { Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" }, + { Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" }, + { 0, NULL } +}; + +Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id) +{ + switch (id) { + case Litecell15_PrimId_SystemInfoReq: return Litecell15_PrimId_SystemInfoCnf; + case Litecell15_PrimId_ActivateRfReq: return Litecell15_PrimId_ActivateRfCnf; + case Litecell15_PrimId_DeactivateRfReq: return Litecell15_PrimId_DeactivateRfCnf; + case Litecell15_PrimId_Layer1ResetReq: return Litecell15_PrimId_Layer1ResetCnf; + case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf; + case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf; + case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf; + default: return -1; // Weak + } +} + +const struct value_string lc15bts_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 lc15bts_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" }, + { GsmL1_Status_ClockError, "System clock error" }, + { 0, NULL } +}; + +const struct value_string lc15bts_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 lc15bts_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 lc15bts_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 lc15bts_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 lc15bts_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 +}; diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h new file mode 100644 index 00000000..4c40db0f --- /dev/null +++ b/src/osmo-bts-litecell15/lc15bts.h @@ -0,0 +1,64 @@ +#ifndef LC15BTS_H +#define LC15BTS_H + +#include <stdlib.h> +#include <osmocom/core/utils.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1const.h> + +/* + * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t + * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the + * bigger struct. + */ +#define LC15BTS_PRIM_SIZE \ + (OSMO_MAX(sizeof(Litecell15_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, +}; + +enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id); +const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1]; +GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id); + +enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id); +const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; +Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id); + +const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; +const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; + +const struct value_string lc15bts_tracef_names[29]; +const struct value_string lc15bts_tracef_docs[29]; + +const struct value_string lc15bts_tch_pl_names[15]; + +const struct value_string lc15bts_clksrc_names[10]; + +const struct value_string lc15bts_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 /* LC15BTS_H */ diff --git a/src/osmo-bts-litecell15/lc15bts_vty.c b/src/osmo-bts-litecell15/lc15bts_vty.c new file mode 100644 index 00000000..d0edc886 --- /dev/null +++ b/src/osmo-bts-litecell15/lc15bts_vty.c @@ -0,0 +1,417 @@ +/* VTY interface for NuRAN Wireless Litecell 1.5 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/rate_ctr.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/rsl.h> + +#include "lc15bts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(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_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.lc15.calib_path) + talloc_free(pinst->u.lc15.calib_path); + + pinst->u.lc15.calib_path = talloc_strdup(pinst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(lc15bts_tracef_names, argv[1]); + pinst->u.lc15.dsp_trace_f |= ~flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(lc15bts_tracef_names, argv[1]); + pinst->u.lc15.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + + +/* runtime */ + +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 lc15l1_hdl *fl1h; + int i; + + if (!trx) + return CMD_WARNING; + + fl1h = trx_lc15l1_hdl(trx); + + vty_out(vty, "Litecell15 L1 DSP trace flags:%s", VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(lc15bts_tracef_names); i++) { + const char *endis; + + if (lc15bts_tracef_names[i].value == 0 && + lc15bts_tracef_names[i].str == NULL) + break; + + if (fl1h->dsp_trace_f & lc15bts_tracef_names[i].value) + endis = "enabled"; + else + endis = "disabled"; + + vty_out(vty, "DSP Trace %-15s %s%s", + lc15bts_tracef_names[i].str, endis, + VTY_NEWLINE); + } + + return CMD_SUCCESS; + +} + +DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.lc15.hdl; + flag = get_string_value(lc15bts_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 phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.lc15.hdl; + flag = get_string_value(lc15bts_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 phy <0-1> instance <0-0> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!plink) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.lc15.hdl; + + 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); + vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.minTxPower, VTY_NEWLINE); + vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.maxTxPower, 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 nr <0-1> tx-power <-110-100>", + TRX_STR + "TRX number \n" + "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(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; +} + +DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + "nominal-tx-power <0-40>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + int nominal_power = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + if (( nominal_power > 40 ) || ( nominal_power < 0 )) { + vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s", + nominal_power, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->nominal_power = nominal_power; + trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); + + return CMD_SUCCESS; +} + +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) +{ + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.lc15.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(lc15bts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + if (pinst->u.lc15.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.lc15.calib_path, 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, lc15bts_tracef_names, + "phy <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs, + TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names, + "no phy <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs, + NO_STR TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_names, + "dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_docs, + DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_names, + "no dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_docs, + NO_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(&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, &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_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-litecell15/main.c b/src/osmo-bts-litecell15/main.c new file mode 100644 index 00000000..e4f9a567 --- /dev/null +++ b/src/osmo-bts-litecell15/main.c @@ -0,0 +1,207 @@ +/* Main program for NuRAN Wireless Litecell 1.5 BTS */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/l1sap.h> + +/*NTQD: Change how rx_nr is handle in multi-trx*/ +#define LC15BTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" + +#include "utils.h" +#include "l1_if.h" +#include "hw_misc.h" +#include "oml_router.h" +#include "misc/lc15bts_bid.h" + +unsigned int dsp_trace = 0x00000000; + +int bts_model_init(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct stat st; + static struct osmo_fd accept_fd, read_fd; + int rc; + + bts->variant = BTS_OSMO_LITECELL15; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + 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 (stat(LC15BTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + trx->nominal_power = 40; + trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power); + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +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); + + lc15bts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF); +} + +void bts_model_print_help() +{ + printf( " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" + ); +} + +static void print_hwversion() +{ + printf(get_hwversion_desc()); + printf("\n"); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "dsp-trace", 1, 0, 'p' }, + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "p:wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'p': + dsp_trace = strtoul(optarg, NULL, 16); + break; + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.c b/src/osmo-bts-litecell15/misc/lc15bts_bid.c new file mode 100644 index 00000000..9284b62e --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> + +#include "lc15bts_bid.h" + +#define BOARD_REV_SYSFS "/var/lc15/platform/revision" +#define BOARD_OPT_SYSFS "/var/lc15/platform/option" + +static const int option_type_mask[_NUM_OPTION_TYPES] = { + [LC15BTS_OPTION_OCXO] = 0x07, + [LC15BTS_OPTION_FPGA] = 0x03, + [LC15BTS_OPTION_PA] = 0x01, + [LC15BTS_OPTION_BAND] = 0x03, + [LC15BTS_OPTION_TX_ISO_BYP] = 0x01, + [LC15BTS_OPTION_RX_DUP_BYP] = 0x01, + [LC15BTS_OPTION_RX_PB_BYP] = 0x01, + [LC15BTS_OPTION_RX_DIV] = 0x01, + [LC15BTS_OPTION_RX1A] = 0x01, + [LC15BTS_OPTION_RX1B] = 0x01, + [LC15BTS_OPTION_RX2A] = 0x01, + [LC15BTS_OPTION_RX2B] = 0x01, + [LC15BTS_OPTION_DDR_32B] = 0x01, + [LC15BTS_OPTION_DDR_ECC] = 0x01, + [LC15BTS_OPTION_LOG_DET] = 0x01, + [LC15BTS_OPTION_DUAL_LOG_DET] = 0x01, +}; + +static const int option_type_shift[_NUM_OPTION_TYPES] = { + [LC15BTS_OPTION_OCXO] = 0, + [LC15BTS_OPTION_FPGA] = 3, + [LC15BTS_OPTION_PA] = 5, + [LC15BTS_OPTION_BAND] = 6, + [LC15BTS_OPTION_TX_ISO_BYP] = 8, + [LC15BTS_OPTION_RX_DUP_BYP] = 9, + [LC15BTS_OPTION_RX_PB_BYP] = 10, + [LC15BTS_OPTION_RX_DIV] = 11, + [LC15BTS_OPTION_RX1A] = 12, + [LC15BTS_OPTION_RX1B] = 13, + [LC15BTS_OPTION_RX2A] = 14, + [LC15BTS_OPTION_RX2B] = 15, + [LC15BTS_OPTION_DDR_32B] = 16, + [LC15BTS_OPTION_DDR_ECC] = 17, + [LC15BTS_OPTION_LOG_DET] = 18, + [LC15BTS_OPTION_DUAL_LOG_DET] = 19, +}; + + +static int board_rev = -1; +static int board_option = -1; + +static inline bool read_board(const char *src, const char *spec, void *dst) +{ + FILE *fp = fopen(src, "r"); + if (!fp) { + fprintf(stderr, "Failed to open %s due to '%s' error\n", src, strerror(errno)); + return false; + } + + if (fscanf(fp, spec, dst) != 1) { + fclose(fp); + fprintf(stderr, "Failed to read %s due to '%s' error\n", src, strerror(errno)); + return false; + } + fclose(fp); + return true; +} + +int lc15bts_rev_get(void) +{ + char rev; + + if (board_rev != -1) { + return board_rev; + } + + if (!read_board(BOARD_REV_SYSFS, "%c", &rev)) + return -1; + + board_rev = rev; + return board_rev; +} + +int lc15bts_model_get(void) +{ + int opt; + + if (board_option != -1) + return board_option; + + if (!read_board(BOARD_OPT_SYSFS, "%X", &opt)) + return -1; + + board_option = opt; + return board_option; +} + +int lc15bts_option_get(enum lc15bts_option_type type) +{ + int rc; + int option; + + if (type >= _NUM_OPTION_TYPES) { + return -EINVAL; + } + + if (board_option == -1) { + rc = lc15bts_model_get(); + if (rc < 0) return rc; + } + + option = (board_option >> option_type_shift[type]) + & option_type_mask[type]; + + return option; +} + +const char* get_hwversion_desc() +{ + int rev; + int model; + size_t len; + static char model_name[64] = {0, }; + len = snprintf(model_name, sizeof(model_name), "NuRAN Litecell 1.5 BTS"); + + rev = lc15bts_rev_get(); + if (rev >= 0) { + len += snprintf(model_name + len, sizeof(model_name) - len, + " Rev %c", (char)rev); + } + + model = lc15bts_model_get(); + if (model >= 0) { + snprintf(model_name + len, sizeof(model_name) - len, + "%s (%05X)", model_name, model); + } + return model_name; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.h b/src/osmo-bts-litecell15/misc/lc15bts_bid.h new file mode 100644 index 00000000..a71fdd7e --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.h @@ -0,0 +1,52 @@ +#ifndef _LC15BTS_BOARD_H +#define _LC15BTS_BOARD_H + +#include <stdint.h> + +enum lc15bts_option_type { + LC15BTS_OPTION_OCXO, + LC15BTS_OPTION_FPGA, + LC15BTS_OPTION_PA, + LC15BTS_OPTION_BAND, + LC15BTS_OPTION_TX_ISO_BYP, + LC15BTS_OPTION_RX_DUP_BYP, + LC15BTS_OPTION_RX_PB_BYP, + LC15BTS_OPTION_RX_DIV, + LC15BTS_OPTION_RX1A, + LC15BTS_OPTION_RX1B, + LC15BTS_OPTION_RX2A, + LC15BTS_OPTION_RX2B, + LC15BTS_OPTION_DDR_32B, + LC15BTS_OPTION_DDR_ECC, + LC15BTS_OPTION_LOG_DET, + LC15BTS_OPTION_DUAL_LOG_DET, + _NUM_OPTION_TYPES +}; + +enum lc15bts_ocxo_type { + LC15BTS_OCXO_BILAY_NVG45AV2072, + LC15BTS_OCXO_TAITIEN_NJ26M003, + _NUM_OCXO_TYPES +}; + +enum lc15bts_fpga_type { + LC15BTS_FPGA_35T, + LC15BTS_FPGA_50T, + LC15BTS_FPGA_75T, + LC15BTS_FPGA_100T, + _NUM_FPGA_TYPES +}; + +enum lc15bts_gsm_band { + LC15BTS_BAND_850, + LC15BTS_BAND_900, + LC15BTS_BAND_1800, + LC15BTS_BAND_1900, +}; + +int lc15bts_rev_get(void); +int lc15bts_model_get(void); +int lc15bts_option_get(enum lc15bts_option_type type); +const char* get_hwversion_desc(); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.c b/src/osmo-bts-litecell15/misc/lc15bts_bts.c new file mode 100644 index 00000000..0343e930 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bts.c @@ -0,0 +1,131 @@ +/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <sys/ioctl.h> +#include <net/if.h> +#include <netdb.h> +#include <ifaddrs.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include "lc15bts_mgr.h" +#include "lc15bts_bts.h" + +static int check_eth_status(char *dev_name) +{ + int fd, rc; + struct ifreq ifr; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + return fd; + + memset(&ifr, 0, sizeof(ifr)); + memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); + rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + + if (rc < 0) + return rc; + + if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING)) + return 0; + + return 1; +} + +void check_bts_led_pattern(uint8_t *led) +{ + FILE *fp; + char str[64] = "\0"; + int rc; + + /* check for existing of BTS state file */ + if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) { + led[BLINK_PATTERN_INIT] = 1; + return; + } + + /* check Ethernet interface status */ + rc = check_eth_status("eth0"); + if (rc > 0) { + LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n"); + led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS is still alive */ + if (system("pidof osmo-bts-lc15 > /dev/null")) { + LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n"); + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS state */ + while (fgets(str, 64, fp) != NULL) { + LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP"); + if (strstr(str, "ABIS DOWN") != NULL) + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + } + fclose(fp); + + return; +} + +int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led) +{ + if(mgr->alarms.temp_high == 1) + led[BLINK_PATTERN_TEMP_HIGH] = 1; + + if(mgr->alarms.temp_max == 1) + led[BLINK_PATTERN_TEMP_MAX] = 1; + + if(mgr->alarms.supply_low == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1; + + if(mgr->alarms.supply_min == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1; + + if(mgr->alarms.vswr_high == 1) + led[BLINK_PATTERN_VSWR_HIGH] = 1; + + if(mgr->alarms.vswr_max == 1) + led[BLINK_PATTERN_VSWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_high == 1) + led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1; + + if(mgr->alarms.supply_pwr_max == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_max2 == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX2] = 1; + + if(mgr->alarms.pa_pwr_high == 1) + led[BLINK_PATTERN_PA_PWR_HIGH] = 1; + + if(mgr->alarms.pa_pwr_max == 1) + led[BLINK_PATTERN_PA_PWR_MAX] = 1; + + if(mgr->alarms.gps_fix_lost == 1) + led[BLINK_PATTERN_GPS_FIX_LOST] = 1; + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.h b/src/osmo-bts-litecell15/misc/lc15bts_bts.h new file mode 100644 index 00000000..3918b870 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bts.h @@ -0,0 +1,21 @@ +#ifndef _LC15BTS_BTS_H_ +#define _LC15BTS_BTS_H_ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <osmo-bts/logging.h> + +/* public function prototypes */ +void check_bts_led_pattern(uint8_t *led); +int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.c b/src/osmo-bts-litecell15/misc/lc15bts_clock.c new file mode 100644 index 00000000..71701496 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.c @@ -0,0 +1,260 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#include "lc15bts_clock.h" + +#define CLKERR_ERR_SYSFS "/var/lc15/clkerr/clkerr1_average" +#define CLKERR_ACC_SYSFS "/var/lc15/clkerr/clkerr1_average_accuracy" +#define CLKERR_INT_SYSFS "/var/lc15/clkerr/clkerr1_average_interval" +#define CLKERR_FLT_SYSFS "/var/lc15/clkerr/clkerr1_fault" +#define CLKERR_RFS_SYSFS "/var/lc15/clkerr/refresh" +#define CLKERR_RST_SYSFS "/var/lc15/clkerr/reset" + +#define OCXODAC_VAL_SYSFS "/var/lc15/ocxo/voltage" +#define OCXODAC_ROM_SYSFS "/var/lc15/ocxo/eeprom" + +/* clock error */ +static int clkerr_fd_err = -1; +static int clkerr_fd_accuracy = -1; +static int clkerr_fd_interval = -1; +static int clkerr_fd_fault = -1; +static int clkerr_fd_refresh = -1; +static int clkerr_fd_reset = -1; + +/* ocxo dac */ +static int ocxodac_fd_value = -1; +static int ocxodac_fd_save = -1; + + +static int sysfs_read_val(int fd, int *val) +{ + int rc; + char szVal[32] = {0}; + + lseek( fd, 0, SEEK_SET ); + + rc = read(fd, szVal, sizeof(szVal) - 1); + if (rc < 0) { + return -errno; + } + + rc = sscanf(szVal, "%d", val); + if (rc != 1) { + return -1; + } + + return 0; +} + +static int sysfs_write_val(int fd, int val) +{ + int n, rc; + char szVal[32] = {0}; + + n = sprintf(szVal, "%d", val); + + lseek(fd, 0, SEEK_SET); + rc = write(fd, szVal, n+1); + if (rc < 0) { + return -errno; + } + return 0; +} + +static int sysfs_write_str(int fd, const char *str) +{ + int rc; + + lseek( fd, 0, SEEK_SET ); + rc = write(fd, str, strlen(str)+1); + if (rc < 0) { + return -errno; + } + return 0; +} + + +int lc15bts_clock_err_open(void) +{ + if (clkerr_fd_err < 0) { + clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY); + if (clkerr_fd_err < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_err; + } + } + + if (clkerr_fd_accuracy < 0) { + clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY); + if (clkerr_fd_accuracy < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_accuracy; + } + } + + if (clkerr_fd_interval < 0) { + clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY); + if (clkerr_fd_interval < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_interval; + } + } + + if (clkerr_fd_fault < 0) { + clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY); + if (clkerr_fd_fault < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_fault; + } + } + + if (clkerr_fd_refresh < 0) { + clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY); + if (clkerr_fd_refresh < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_refresh; + } + } + + if (clkerr_fd_reset < 0) { + clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY); + if (clkerr_fd_reset < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_reset; + } + } + return 0; +} + +void lc15bts_clock_err_close(void) +{ + if (clkerr_fd_err >= 0) { + close(clkerr_fd_err); + clkerr_fd_err = -1; + } + + if (clkerr_fd_accuracy >= 0) { + close(clkerr_fd_accuracy); + clkerr_fd_accuracy = -1; + } + + if (clkerr_fd_interval >= 0) { + close(clkerr_fd_interval); + clkerr_fd_interval = -1; + } + + if (clkerr_fd_fault >= 0) { + close(clkerr_fd_fault); + clkerr_fd_fault = -1; + } + + if (clkerr_fd_refresh >= 0) { + close(clkerr_fd_refresh); + clkerr_fd_refresh = -1; + } + + if (clkerr_fd_reset >= 0) { + close(clkerr_fd_reset); + clkerr_fd_reset = -1; + } +} + +int lc15bts_clock_err_reset(void) +{ + return sysfs_write_val(clkerr_fd_reset, 1); +} + +int lc15bts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec) +{ + int rc; + + rc = sysfs_write_str(clkerr_fd_refresh, "once"); + if (rc < 0) { + return -1; + } + + rc = sysfs_read_val(clkerr_fd_fault, fault); + rc |= sysfs_read_val(clkerr_fd_err, error_ppt); + rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq); + rc |= sysfs_read_val(clkerr_fd_interval, interval_sec); + if (rc) { + return -1; + } + return 0; +} + + +int lc15bts_clock_dac_open(void) +{ + if (ocxodac_fd_value < 0) { + ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR); + if (ocxodac_fd_value < 0) { + lc15bts_clock_dac_close(); + return ocxodac_fd_value; + } + } + + if (ocxodac_fd_save < 0) { + ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY); + if (ocxodac_fd_save < 0) { + lc15bts_clock_dac_close(); + return ocxodac_fd_save; + } + } + return 0; +} + +void lc15bts_clock_dac_close(void) +{ + if (ocxodac_fd_value >= 0) { + close(ocxodac_fd_value); + ocxodac_fd_value = -1; + } + + if (ocxodac_fd_save >= 0) { + close(ocxodac_fd_save); + ocxodac_fd_save = -1; + } +} + +int lc15bts_clock_dac_get(int *dac_value) +{ + return sysfs_read_val(ocxodac_fd_value, dac_value); +} + +int lc15bts_clock_dac_set(int dac_value) +{ + return sysfs_write_val(ocxodac_fd_value, dac_value); +} + +int lc15bts_clock_dac_save(void) +{ + return sysfs_write_val(ocxodac_fd_save, 1); +} + + diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.h b/src/osmo-bts-litecell15/misc/lc15bts_clock.h new file mode 100644 index 00000000..d9673598 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.h @@ -0,0 +1,16 @@ +#ifndef _LC15BTS_CLOCK_H +#define _LC15BTS_CLOCK_H + +int lc15bts_clock_err_open(void); +void lc15bts_clock_err_close(void); +int lc15bts_clock_err_reset(void); +int lc15bts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec); + +int lc15bts_clock_dac_open(void); +void lc15bts_clock_dac_close(void); +int lc15bts_clock_dac_get(int *dac_value); +int lc15bts_clock_dac_set(int dac_value); +int lc15bts_clock_dac_save(void); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.c b/src/osmo-bts-litecell15/misc/lc15bts_led.c new file mode 100644 index 00000000..a93d3fb0 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_led.c @@ -0,0 +1,333 @@ +/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include "lc15bts_led.h" +#include "lc15bts_bts.h" +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +static struct lc15bts_led led_entries[] = { + { + .name = "led0", + .fullname = "led red", + .path = "/var/lc15/leds/led0/brightness" + }, + { + .name = "led1", + .fullname = "led green", + .path = "/var/lc15/leds/led1/brightness" + } +}; + +static const struct value_string lc15bts_led_strs[] = { + { LC15BTS_LED_RED, "LED red" }, + { LC15BTS_LED_GREEN, "LED green" }, + { LC15BTS_LED_ORANGE, "LED orange" }, + { LC15BTS_LED_OFF, "LED off" }, + { 0, NULL } +}; + +static uint8_t led_priority[] = { + BLINK_PATTERN_INIT, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_MAX2, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_NORMAL +}; + + +char *blink_pattern_command[] = BLINK_PATTERN_COMMAND; + +static int lc15bts_led_write(char *path, char *str) +{ + int fd; + + if ((fd = open(path, O_WRONLY)) == -1) + { + return 0; + } + + write(fd, str, strlen(str)+1); + close(fd); + return 1; +} + +static void led_set_red() +{ + lc15bts_led_write(led_entries[0].path, "1"); + lc15bts_led_write(led_entries[1].path, "0"); +} + +static void led_set_green() +{ + lc15bts_led_write(led_entries[0].path, "0"); + lc15bts_led_write(led_entries[1].path, "1"); +} + +static void led_set_orange() +{ + lc15bts_led_write(led_entries[0].path, "1"); + lc15bts_led_write(led_entries[1].path, "1"); +} + +static void led_set_off() +{ + lc15bts_led_write(led_entries[0].path, "0"); + lc15bts_led_write(led_entries[1].path, "0"); +} + +static void led_sleep( struct lc15bts_mgr_instance *mgr, struct lc15bts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) { + /* Cancel any pending timer */ + osmo_timer_del(&led_timer->timer); + /* Start LED timer */ + led_timer->timer.cb = led_timer_cb; + led_timer->timer.data = mgr; + mgr->lc15bts_leds.active_timer = led_timer->idx; + osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec); + LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_timer->idx), + led_timer->param.sleep_sec, + led_timer->param.sleep_usec); + + switch (led_timer->idx) { + case LC15BTS_LED_RED: + led_set_red(); + break; + case LC15BTS_LED_GREEN: + led_set_green(); + break; + case LC15BTS_LED_ORANGE: + led_set_orange(); + break; + case LC15BTS_LED_OFF: + led_set_off(); + break; + default: + led_set_off(); + } +} + +static void led_sleep_cb(void *_data) { + struct lc15bts_mgr_instance *mgr = _data; + struct lc15bts_led_timer_list *led_list; + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + llist_for_each_entry(led_list, &mgr->lc15bts_leds.list, list) { + if (led_list->led_timer.idx == mgr->lc15bts_leds.active_timer) { + LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + /* Rotate the timer list */ + llist_move_tail(&led_list->list, &mgr->lc15bts_leds.list); + break; + } + } + + /* Execute next timer */ + led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->lc15bts_leds.list)); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +static void delete_led_timer_entries(struct lc15bts_mgr_instance *mgr) +{ + struct lc15bts_led_timer_list *led_list, *led_list2; + + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + llist_for_each_entry_safe(led_list, led_list2, &mgr->lc15bts_leds.list, list) { + /* Delete the timer in list */ + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + llist_del(&led_list->list); + talloc_free(led_list); + } + } + return; +} + +static int add_led_timer_entry(struct lc15bts_mgr_instance *mgr, char *cmdstr) +{ + double sec, int_sec, frac_sec; + struct lc15bts_sleep_time led_param; + + led_param.sleep_sec = 0; + led_param.sleep_usec = 0; + + if (strstr(cmdstr, "set red") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_RED; + else if (strstr(cmdstr, "set green") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_GREEN; + else if (strstr(cmdstr, "set orange") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_ORANGE; + else if (strstr(cmdstr, "set off") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_OFF; + else if (strstr(cmdstr, "sleep") != NULL) { + sec = atof(cmdstr + 6); + /* split time into integer and fractional of seconds */ + frac_sec = modf(sec, &int_sec) * 1000000.0; + led_param.sleep_sec = (int)int_sec; + led_param.sleep_usec = (int)frac_sec; + + if ((mgr->lc15bts_leds.led_idx >= LC15BTS_LED_RED) && (mgr->lc15bts_leds.led_idx < _LC15BTS_LED_MAX)) { + struct lc15bts_led_timer_list *led_list; + + /* allocate timer entry */ + led_list = talloc_zero(tall_mgr_ctx, struct lc15bts_led_timer_list); + if (led_list) { + led_list->led_timer.idx = mgr->lc15bts_leds.led_idx; + led_list->led_timer.param.sleep_sec = led_param.sleep_sec; + led_list->led_timer.param.sleep_usec = led_param.sleep_usec; + llist_add_tail(&led_list->list, &mgr->lc15bts_leds.list); + + LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n", + get_value_string(lc15bts_led_strs, mgr->lc15bts_leds.led_idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->lc15bts_leds.list)); + } + } + } else + return -1; + + return 0; +} + +static int parse_led_pattern(char *pattern, struct lc15bts_mgr_instance *mgr) +{ + char str[1024]; + char *pstr; + char *sep; + int rc = 0; + + strcpy(str, pattern); + pstr = str; + while ((sep = strsep(&pstr, ";")) != NULL) { + rc = add_led_timer_entry(mgr, sep); + if (rc < 0) { + break; + } + + } + return rc; +} + +/*** led interface ***/ + +void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id) +{ + int rc; + struct lc15bts_led_timer_list *led_list; + + if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) { + LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n", + pattern_id, + BLINK_PATTERN_POWER_ON, + BLINK_PATTERN_MAX_ITEM - 1); + return; + } + if (pattern_id == mgr->lc15bts_leds.last_pattern_id) + return; + + mgr->lc15bts_leds.last_pattern_id = pattern_id; + + LOGP(DTEMP, LOGL_NOTICE, "blink pattern command : %d\n", pattern_id); + LOGP(DTEMP, LOGL_NOTICE, "%s\n", blink_pattern_command[pattern_id]); + + /* Empty existing LED timer in the list */ + delete_led_timer_entries(mgr); + + /* parse LED pattern */ + rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n"); + return; + } + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + /* Start the first LED timer in the list */ + led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +void select_led_pattern(struct lc15bts_mgr_instance *mgr) +{ + int i; + uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0}; + + /* set normal LED pattern at first */ + led[BLINK_PATTERN_NORMAL] = 1; + + /* check on-board sensors for new LED pattern */ + check_sensor_led_pattern(mgr, led); + + /* check BTS status for new LED pattern */ + check_bts_led_pattern(led); + + /* check by priority */ + for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) { + if(led[led_priority[i]] == 1) { + led_set(mgr, led_priority[i]); + break; + } + } +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.h b/src/osmo-bts-litecell15/misc/lc15bts_led.h new file mode 100644 index 00000000..b6d9d28b --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_led.h @@ -0,0 +1,22 @@ +#ifndef _LC15BTS_LED_H +#define _LC15BTS_LED_H + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <sys/stat.h> + +#include <osmo-bts/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include "lc15bts_mgr.h" + +/* public function prototypes */ +void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id); + +void select_led_pattern(struct lc15bts_mgr_instance *mgr); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c new file mode 100644 index 00000000..dbdcc9f0 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c @@ -0,0 +1,366 @@ +/* Main program for NuRAN Wireless Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr.c + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" +#include "misc/lc15bts_power.h" +#include "misc/lc15bts_swd.h" + +#include "lc15bts_led.h" + +static int no_rom_write = 0; +static int daemonize = 0; +void *tall_mgr_ctx; + +/* every 6 hours means 365*4 = 1460 rom writes per year (max) */ +#define SENSOR_TIMER_SECS (6 * 3600) + +/* every 1 hours means 365*24 = 8760 rom writes per year (max) */ +#define HOURS_TIMER_SECS (1 * 3600) + + +/* the initial state */ +static struct lc15bts_mgr_instance manager = { + .config_file = "lc15bts-mgr.cfg", + .temp = { + .supply_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .soc_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .fpga_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .rmsdet_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .ocxo_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .tx0_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .tx1_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .pa0_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .pa1_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + } + }, + .volt = { + .supply_volt_limit = { + .thresh_warn_max = 30000, + .thresh_crit_max = 30500, + .thresh_warn_min = 19000, + .thresh_crit_min = 17500, + } + }, + .pwr = { + .supply_pwr_limit = { + .thresh_warn_max = 110, + .thresh_crit_max = 120, + }, + .pa0_pwr_limit = { + .thresh_warn_max = 50, + .thresh_crit_max = 60, + }, + .pa1_pwr_limit = { + .thresh_warn_max = 50, + .thresh_crit_max = 60, + } + }, + .vswr = { + .tx0_vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + }, + .tx1_vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + } + }, + .gps = { + .gps_fix_limit = { + .thresh_warn_max = 7, + } + }, + .state = { + .action_norm = SENSOR_ACT_NORM_PA0_ON | SENSOR_ACT_NORM_PA1_ON, + .action_warn = 0, + .action_crit = 0, + .action_comb = 0, + .state = STATE_NORMAL, + } +}; + +static struct osmo_timer_list sensor_timer; +static void check_sensor_timer_cb(void *unused) +{ + lc15bts_check_temp(no_rom_write); + lc15bts_check_power(no_rom_write); + lc15bts_check_vswr(no_rom_write); + osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0); + /* TODO checks if lc15bts_check_temp/lc15bts_check_power/lc15bts_check_vswr went ok */ + lc15bts_swd_event(&manager, SWD_CHECK_SENSOR); +} + +static struct osmo_timer_list hours_timer; +static void hours_timer_cb(void *unused) +{ + lc15bts_update_hours(no_rom_write); + + osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0); + /* TODO: validates if lc15bts_update_hours went correctly */ + lc15bts_swd_event(&manager, SWD_UPDATE_HOURS); +} + +static void print_help(void) +{ + printf("lc15bts-mgr [-nsD] [-d cat]\n"); + printf(" -n Do not write to ROM\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_rom_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: + case SIGTERM: + lc15bts_check_temp(no_rom_write); + lc15bts_check_power(no_rom_write); + lc15bts_check_vswr(no_rom_write); + lc15bts_update_hours(no_rom_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 = "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, + }, + [DSWD] = { + .name = "DSWD", + .description = "Software Watchdog", + .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), +}; + +int main(int argc, char **argv) +{ + int rc; + + tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); + msgb_talloc_ctx_init(tall_mgr_ctx, 0); + + osmo_init_logging2(tall_mgr_ctx, &mgr_log_info); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + lc15bts_mgr_vty_init(); + logging_vty_add_cmds(&mgr_log_info); + rc = lc15bts_mgr_parse_config(&manager); + if (rc < 0) { + LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); + exit(1); + } + + rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + INIT_LLIST_HEAD(&manager.lc15bts_leds.list); + INIT_LLIST_HEAD(&manager.alarms.list); + + /* Initialize the service watchdog notification for SWD_LAST event(s) */ + if (lc15bts_swd_init(&manager, (int)(SWD_LAST)) != 0) + exit(3); + + /* start temperature check timer */ + sensor_timer.cb = check_sensor_timer_cb; + check_sensor_timer_cb(NULL); + + /* start operational hours timer */ + hours_timer.cb = hours_timer_cb; + hours_timer_cb(NULL); + + /* Enable the PAs */ + rc = lc15bts_power_set(LC15BTS_POWER_PA0, 1); + if (rc < 0) { + exit(3); + } + + rc = lc15bts_power_set(LC15BTS_POWER_PA1, 1); + if (rc < 0) { + exit(3); + } + + /* handle broadcast messages for ipaccess-find */ + if (lc15bts_mgr_nl_init() != 0) + exit(3); + + /* Initialize the sensor control */ + lc15bts_mgr_sensor_init(&manager); + + if (lc15bts_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); + lc15bts_swd_event(&manager, SWD_MAINLOOP); + } +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h new file mode 100644 index 00000000..4bfbdbc9 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h @@ -0,0 +1,422 @@ +#ifndef _LC15BTS_MGR_H +#define _LC15BTS_MGR_H + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> + +#include <stdint.h> + +#define LC15BTS_SENSOR_TIMER_DURATION 60 +#define LC15BTS_PREVENT_TIMER_DURATION 15 * 60 +#define LC15BTS_PREVENT_TIMER_SHORT_DURATION 5 * 60 +#define LC15BTS_PREVENT_TIMER_NONE 0 +#define LC15BTS_PREVENT_RETRY INT_MAX - 1 + +enum BLINK_PATTERN { + BLINK_PATTERN_POWER_ON = 0, //hardware set + BLINK_PATTERN_INIT, + BLINK_PATTERN_NORMAL, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_SUPPLY_PWR_MAX2, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_MAX_ITEM +}; + +#define BLINK_PATTERN_COMMAND {\ + "set red; sleep 5.0",\ + "set orange; sleep 5.0",\ + "set green; sleep 2.5; set off; sleep 2.5",\ + "set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 2.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ +} + +enum { + DTEMP, + DFW, + DFIND, + DCALIB, + DSWD, +}; + +// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ... +enum { +#if 0 + SENSOR_ACT_PWR_CONTRL = 0x1, +#endif + SENSOR_ACT_PA0_OFF = 0x2, + SENSOR_ACT_PA1_OFF = 0x4, + SENSOR_ACT_BTS_SRV_OFF = 0x10, +}; + +/* actions only for normal state */ +enum { +#if 0 + SENSOR_ACT_NORM_PW_CONTRL = 0x1, +#endif + SENSOR_ACT_NORM_PA0_ON = 0x2, + SENSOR_ACT_NORM_PA1_ON = 0x4, + SENSOR_ACT_NORM_BTS_SRV_ON= 0x10, +}; + +enum lc15bts_sensor_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 */ +}; + +enum lc15bts_leds_name { + LC15BTS_LED_RED = 0, + LC15BTS_LED_GREEN, + LC15BTS_LED_ORANGE, + LC15BTS_LED_OFF, + _LC15BTS_LED_MAX +}; + +struct lc15bts_led{ + char *name; + char *fullname; + char *path; +}; + +/** + * 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 lc15bts_temp_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; +}; + +struct lc15bts_volt_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; + int thresh_crit_min; +}; + +struct lc15bts_pwr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct lc15bts_vswr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct lc15bts_gps_fix_limit { + int thresh_warn_max; +}; + +struct lc15bts_sleep_time { + int sleep_sec; + int sleep_usec; +}; + +struct lc15bts_led_timer { + uint8_t idx; + struct osmo_timer_list timer; + struct lc15bts_sleep_time param; +}; + +struct lc15bts_led_timer_list { + struct llist_head list; + struct lc15bts_led_timer led_timer; +}; + +struct lc15bts_preventive_list { + struct llist_head list; + struct lc15bts_sleep_time param; + int action_flag; +}; + +enum mgr_vty_node { + MGR_NODE = _LAST_OSMOVTY_NODE + 1, + + ACT_NORM_NODE, + ACT_WARN_NODE, + ACT_CRIT_NODE, + LIMIT_SUPPLY_TEMP_NODE, + LIMIT_SOC_NODE, + LIMIT_FPGA_NODE, + LIMIT_RMSDET_NODE, + LIMIT_OCXO_NODE, + LIMIT_TX0_TEMP_NODE, + LIMIT_TX1_TEMP_NODE, + LIMIT_PA0_TEMP_NODE, + LIMIT_PA1_TEMP_NODE, + LIMIT_SUPPLY_VOLT_NODE, + LIMIT_TX0_VSWR_NODE, + LIMIT_TX1_VSWR_NODE, + LIMIT_SUPPLY_PWR_NODE, + LIMIT_PA0_PWR_NODE, + LIMIT_PA1_PWR_NODE, + LIMIT_GPS_FIX_NODE, +}; + +enum mgr_vty_limit_type { + MGR_LIMIT_TYPE_TEMP = 0, + MGR_LIMIT_TYPE_VOLT, + MGR_LIMIT_TYPE_VSWR, + MGR_LIMIT_TYPE_PWR, + _MGR_LIMIT_TYPE_MAX, +}; + +struct lc15bts_mgr_instance { + const char *config_file; + + struct { + struct lc15bts_temp_limit supply_temp_limit; + struct lc15bts_temp_limit soc_temp_limit; + struct lc15bts_temp_limit fpga_temp_limit; + struct lc15bts_temp_limit rmsdet_temp_limit; + struct lc15bts_temp_limit ocxo_temp_limit; + struct lc15bts_temp_limit tx0_temp_limit; + struct lc15bts_temp_limit tx1_temp_limit; + struct lc15bts_temp_limit pa0_temp_limit; + struct lc15bts_temp_limit pa1_temp_limit; + } temp; + + struct { + struct lc15bts_volt_limit supply_volt_limit; + } volt; + + struct { + struct lc15bts_pwr_limit supply_pwr_limit; + struct lc15bts_pwr_limit pa0_pwr_limit; + struct lc15bts_pwr_limit pa1_pwr_limit; + } pwr; + + struct { + struct lc15bts_vswr_limit tx0_vswr_limit; + struct lc15bts_vswr_limit tx1_vswr_limit; + int tx0_last_vswr; + int tx1_last_vswr; + } vswr; + + struct { + struct lc15bts_gps_fix_limit gps_fix_limit; + time_t last_update; + } gps; + + struct { + int action_norm; + int action_warn; + int action_crit; + int action_comb; + + enum lc15bts_sensor_state state; + } state; + + struct { + int state; + int calib_from_loop; + struct osmo_timer_list calib_timeout; + } calib; + + struct { + int state; + int swd_from_loop; + unsigned long long int swd_events; + unsigned long long int swd_events_cache; + unsigned long long int swd_eventmasks; + int num_events; + struct osmo_timer_list swd_timeout; + } swd; + + struct { + uint8_t led_idx; + uint8_t last_pattern_id; + uint8_t active_timer; + struct llist_head list; + } lc15bts_leds; + + struct { + int is_up; + uint32_t last_seqno; + struct osmo_timer_list recon_timer; + struct ipa_client_conn *bts_conn; + uint32_t crit_flags; + uint32_t warn_flags; + } lc15bts_ctrl; + + struct lc15bts_alarms { + int temp_high; + int temp_max; + int supply_low; + int supply_min; + int vswr_high; + int vswr_max; + int supply_pwr_high; + int supply_pwr_max; + int supply_pwr_max2; + int pa_pwr_high; + int pa_pwr_max; + int gps_fix_lost; + struct llist_head list; + struct osmo_timer_list preventive_timer; + int preventive_duration; + int preventive_retry; + } alarms; + +}; + +enum lc15bts_mgr_fail_evt_rep_crit_sig { + /* Critical alarms */ + S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0), + S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1), + S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2), + S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3), + S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4), + S_MGR_TEMP_TRX0_CRIT_MAX_ALARM = (1 << 5), + S_MGR_TEMP_TRX1_CRIT_MAX_ALARM = (1 << 6), + S_MGR_TEMP_PA0_CRIT_MAX_ALARM = (1 << 7), + S_MGR_TEMP_PA1_CRIT_MAX_ALARM = (1 << 8), + S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 9), + S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 10), + S_MGR_VSWR0_CRIT_MAX_ALARM = (1 << 11), + S_MGR_VSWR1_CRIT_MAX_ALARM = (1 << 12), + S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 13), + S_MGR_PWR_PA0_CRIT_MAX_ALARM = (1 << 14), + S_MGR_PWR_PA1_CRIT_MAX_ALARM = (1 << 15), + _S_MGR_CRIT_ALARM_MAX, +}; + +enum lc15bts_mgr_fail_evt_rep_warn_sig { + /* Warning alarms */ + S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0), + S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 2), + S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 3), + S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 4), + S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 5), + S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 6), + S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 7), + S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 8), + S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 9), + S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 10), + S_MGR_TEMP_TRX0_WARN_MIN_ALARM = (1 << 11), + S_MGR_TEMP_TRX0_WARN_MAX_ALARM = (1 << 12), + S_MGR_TEMP_TRX1_WARN_MIN_ALARM = (1 << 13), + S_MGR_TEMP_TRX1_WARN_MAX_ALARM = (1 << 14), + S_MGR_TEMP_PA0_WARN_MIN_ALARM = (1 << 15), + S_MGR_TEMP_PA0_WARN_MAX_ALARM = (1 << 16), + S_MGR_TEMP_PA1_WARN_MIN_ALARM = (1 << 17), + S_MGR_TEMP_PA1_WARN_MAX_ALARM = (1 << 18), + S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 19), + S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 20), + S_MGR_VSWR0_WARN_MAX_ALARM = (1 << 21), + S_MGR_VSWR1_WARN_MAX_ALARM = (1 << 22), + S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 23), + S_MGR_PWR_PA0_WARN_MAX_ALARM = (1 << 24), + S_MGR_PWR_PA1_WARN_MAX_ALARM = (1 << 25), + S_MGR_GPS_FIX_WARN_ALARM = (1 << 26), + _S_MGR_WARN_ALARM_MAX, +}; + +enum lc15bts_mgr_failure_event_causes { + /* Critical causes */ + NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100, + NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101, + NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102, + NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103, + NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104, + NM_EVT_CAUSE_CRIT_TEMP_TRX0_MAX_FAIL = 0x4105, + NM_EVT_CAUSE_CRIT_TEMP_TRX1_MAX_FAIL = 0x4106, + NM_EVT_CAUSE_CRIT_TEMP_PA0_MAX_FAIL = 0x4107, + NM_EVT_CAUSE_CRIT_TEMP_PA1_MAX_FAIL = 0x4108, + NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4109, + NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x410A, + NM_EVT_CAUSE_CRIT_VSWR0_MAX_FAIL = 0x410B, + NM_EVT_CAUSE_CRIT_VSWR1_MAX_FAIL = 0x410C, + NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410D, + NM_EVT_CAUSE_CRIT_PWR_PA0_MAX_FAIL = 0x410E, + NM_EVT_CAUSE_CRIT_PWR_PA1_MAX_FAIL = 0x410F, + /* Warning causes */ + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400, + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401, + NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402, + NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403, + NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404, + NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407, + NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408, + NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409, + NM_EVT_CAUSE_WARN_TEMP_TRX0_LOW_FAIL = 0x440A, + NM_EVT_CAUSE_WARN_TEMP_TRX0_HIGH_FAIL = 0x440B, + NM_EVT_CAUSE_WARN_TEMP_TRX1_LOW_FAIL = 0x440C, + NM_EVT_CAUSE_WARN_TEMP_TRX1_HIGH_FAIL = 0x440D, + NM_EVT_CAUSE_WARN_TEMP_PA0_LOW_FAIL = 0x440E, + NM_EVT_CAUSE_WARN_TEMP_PA0_HIGH_FAIL = 0x440F, + NM_EVT_CAUSE_WARN_TEMP_PA1_LOW_FAIL = 0x4410, + NM_EVT_CAUSE_WARN_TEMP_PA1_HIGH_FAIL = 0x4411, + NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x4412, + NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x4413, + NM_EVT_CAUSE_WARN_VSWR0_HIGH_FAIL = 0x4414, + NM_EVT_CAUSE_WANR_VSWR1_HIGH_FAIL = 0x4415, + NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4416, + NM_EVT_CAUSE_WARN_PWR_PA0_HIGH_FAIL = 0x4417, + NM_EVT_CAUSE_WARN_PWR_PA1_HIGH_FAIL = 0x4418, + NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4419, +}; + +/* This defines the list of notification events for systemd service watchdog. + all these events must be notified in a certain service defined timeslot + or the service (this app) would be restarted (only if related systemd service + unit file has WatchdogSec!=0). + WARNING: swd events must begin with event 0. Last events must be + SWD_LAST (max 64 events in this list). +*/ +enum mgr_swd_events { + SWD_MAINLOOP = 0, + SWD_CHECK_SENSOR, + SWD_UPDATE_HOURS, + SWD_CHECK_TEMP_SENSOR, + SWD_CHECK_LED_CTRL, + SWD_CHECK_CALIB, + SWD_CHECK_BTS_CONNECTION, + SWD_LAST +}; + +int lc15bts_mgr_vty_init(void); +int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_nl_init(void); +int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr); +const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state); + +int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_control_init(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr); +void lc15bts_mgr_dispatch_alarm(struct lc15bts_mgr_instance *mgr, const int cause, const char *key, const char *text); +extern void *tall_mgr_ctx; + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c new file mode 100644 index 00000000..badb5455 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c @@ -0,0 +1,292 @@ +/* OCXO calibration control for Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_calib.c + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_clock.h" +#include "misc/lc15bts_swd.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_led.h" +#include "osmo-bts/msg_utils.h" + +#include <osmocom/core/logging.h> +#include <osmocom/core/select.h> + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipa.h> + +#include <time.h> + +static void calib_adjust(struct lc15bts_mgr_instance *mgr); +static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int reason); +static void calib_loop_run(void *_data); + +static int ocxodac_saved_value = -1; + +enum calib_state { + CALIB_INITIAL, + CALIB_IN_PROGRESS, +}; + +enum calib_result { + CALIB_FAIL_START, + CALIB_FAIL_GPSFIX, + CALIB_FAIL_CLKERR, + CALIB_FAIL_OCXODAC, + CALIB_SUCCESS, +}; + +static void calib_start(struct lc15bts_mgr_instance *mgr) +{ + int rc; + + rc = lc15bts_clock_err_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + rc = lc15bts_clock_dac_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + calib_adjust(mgr); +} + +static void calib_adjust(struct lc15bts_mgr_instance *mgr) +{ + int rc; + int fault; + int error_ppt; + int accuracy_ppq; + int interval_sec; + int dac_value; + int new_dac_value; + int dac_correction; + time_t now; + time_t last_gps_fix; + + rc = lc15bts_clock_err_get(&fault, &error_ppt, + &accuracy_ppq, &interval_sec); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get clock error measurement %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + /* get current time */ + now = time(NULL); + + /* first time after start of manager program */ + if (mgr->gps.last_update == 0) + mgr->gps.last_update = now; + + /* read last GPS 3D fix from storage */ + rc = lc15bts_par_get_gps_fix(&last_gps_fix); + if (rc < 0) { + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc); + last_gps_fix = 0; + } + + if (fault) { + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + /* We got GPS 3D fix */ + LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, last=%lld, now=%lld\n", + mgr->lc15bts_ctrl.warn_flags, + (long long)last_gps_fix, + (long long)now); + + rc = lc15bts_clock_dac_get(&dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + /* Set OCXO initial dac value */ + if (ocxodac_saved_value < 0) + ocxodac_saved_value = dac_value; + + LOGP(DCALIB, LOGL_NOTICE, + "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n", + error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value); + + /* Need integration time to correct */ + if (interval_sec) { + /* 1 unit of correction equal about 0.5 - 1 PPB correction */ + dac_correction = (int)(-error_ppt * 0.0015); + new_dac_value = dac_value + dac_correction; + + if (new_dac_value > 4095) + new_dac_value = 4095; + else if (new_dac_value < 0) + new_dac_value = 0; + + /* We have a fix, make sure the measured error is + meaningful (10 times the accuracy) */ + if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) { + + LOGP(DCALIB, LOGL_NOTICE, + "Going to apply %d as new clock setting.\n", + new_dac_value); + + rc = lc15bts_clock_dac_set(new_dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to set OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + rc = lc15bts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + } + /* New conditions to store DAC value: + * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ) + * - Error less or equal than 2PPB (or 2000PPT) + * - Solution different than the last one */ + else if (accuracy_ppq <= 10000) { + if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) { + LOGP(DCALIB, LOGL_NOTICE, "Saving OCXO DAC value to memory... val = %d\n", dac_value); + rc = lc15bts_clock_dac_save(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to save OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + } else { + ocxodac_saved_value = dac_value; + } + } + + rc = lc15bts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + } + } + } else { + LOGP(DCALIB, LOGL_NOTICE, "Skipping this iteration, no integration time\n"); + } + + calib_state_reset(mgr, CALIB_SUCCESS); + return; +} + +static void calib_close(struct lc15bts_mgr_instance *mgr) +{ + lc15bts_clock_err_close(); + lc15bts_clock_dac_close(); +} + +static void calib_state_reset(struct lc15bts_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. + * + * TODO NTQ: Select timeout based on last error and accuracy + */ + int timeout = 60; + //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); + /* TODO: do we want to notify if we got a calibration error, like no gps fix? */ + lc15bts_swd_event(mgr, SWD_CHECK_CALIB); + } + + mgr->calib.state = CALIB_INITIAL; + calib_close(mgr); +} + +static int calib_run(struct lc15bts_mgr_instance *mgr, int from_loop) +{ + if (mgr->calib.state != CALIB_INITIAL) { + LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); + return -1; + } + + mgr->calib.calib_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->calib.state = CALIB_IN_PROGRESS; + calib_start(mgr); + return 0; +} + +static void calib_loop_run(void *_data) +{ + int rc; + struct lc15bts_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); + } +} + +int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr) +{ + return calib_run(mgr, 0); +} + +int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr) +{ + mgr->calib.state = CALIB_INITIAL; + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0); + + return 0; +} + diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c new file mode 100644 index 00000000..3a617dd7 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c @@ -0,0 +1,195 @@ +/* NetworkListen for NuRAN Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_nl.c + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_nl.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <arpa/inet.h> + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#define ETH0_ADDR_SYSFS "/var/lc15/net/eth0/address" + +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[64] = {0, }; + 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) { + int fd_eth; + int serno; + + /* fetch the MAC */ + fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY); + if (fd_eth >= 0) { + read(fd_eth, mac_str, sizeof(mac_str)-1); + mac_str[sizeof(mac_str)-1] = '\0'; + close(fd_eth); + } + + /* fetch the serial number */ + lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_SERNR, &serno); + snprintf(ser_str, sizeof(ser_str), "%d", serno); + + strncpy(model_name, get_hwversion_desc(), sizeof(model_name)-1); + 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 lc15bts_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; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c new file mode 100644 index 00000000..9665e1db --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c @@ -0,0 +1,378 @@ +/* Temperature control for NuRAN Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_temp.c + * (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 <http://www.gnu.org/licenses/>. + * + */ +#include <inttypes.h> +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_temp.h" +#include "misc/lc15bts_power.h" +#include "misc/lc15bts_led.h" +#include "misc/lc15bts_swd.h" +#include "limits.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> + +struct lc15bts_mgr_instance *s_mgr; +static struct osmo_timer_list sensor_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 *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state) +{ + return get_value_string(state_names, state); +} + +static int next_state(enum lc15bts_sensor_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 on the PA */ + if (actions & SENSOR_ACT_NORM_PA0_ON) { + if (lc15bts_power_set(LC15BTS_POWER_PA0, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA #0\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the PA #0 as normal action.\n"); + } + } + + if (actions & SENSOR_ACT_NORM_PA1_ON) { + if (lc15bts_power_set(LC15BTS_POWER_PA1, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA #1\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the PA #1 as normal action.\n"); + } + } + + if (actions & SENSOR_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 osmo-bts.service"); + } +} + +static void handle_actions(int actions) +{ + /* switch off the PA */ + if (actions & SENSOR_ACT_PA1_OFF) { + if (lc15bts_power_set(LC15BTS_POWER_PA1, 0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA #1. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA #1 due temperature.\n"); + } + } + + if (actions & SENSOR_ACT_PA0_OFF) { + if (lc15bts_power_set(LC15BTS_POWER_PA0, 0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA #0. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA #0 due temperature.\n"); + } + } + + if (actions & SENSOR_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 osmo-bts.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 lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n"); + handle_normal_actions(manager->state.action_norm); +} + +static void execute_warning_act(struct lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n"); + handle_actions(manager->state.action_warn); +} + +static void execute_critical_act(struct lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n"); + handle_actions(manager->state.action_crit); +} + +static void lc15bts_mgr_sensor_handle(struct lc15bts_mgr_instance *manager, + int critical, int warning) +{ + int new_state = next_state(manager->state.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.state), + get_value_string(state_names, new_state)); + manager->state.state = new_state; + switch (manager->state.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 sensor_ctrl_check(struct lc15bts_mgr_instance *mgr) +{ + int rc; + int temp = 0; + int warn_thresh_passed = 0; + int crit_thresh_passed = 0; + + LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n"); + + /* Read the current supply temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the supply temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "Supply temperature is: %d\n", temp); + } + + /* Read the current SoC temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the SoC temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "SoC temperature is: %d\n", temp); + } + + /* Read the current fpga temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the fpga temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "FPGA temperature is: %d\n", temp); + } + + /* Read the current RMS detector temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the RMS detector temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "RMS detector temperature is: %d\n", temp); + } + + /* Read the current OCXO temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the OCXO temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "OCXO temperature is: %d\n", temp); + } + + /* Read the current TX #0 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the TX #0 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx0_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.tx0_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "TX #0 temperature is: %d\n", temp); + } + + /* Read the current TX #1 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the TX #1 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx1_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.tx1_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "TX #1 temperature is: %d\n", temp); + } + + /* Read the current PA #0 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the PA #0 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa0_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.pa0_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "PA #0 temperature is: %d\n", temp); + } + + /* Read the current PA #1 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the PA #1 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa1_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.pa1_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "PA #1 temperature is: %d\n", temp); + } + + lc15bts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed); +} + +static void sensor_ctrl_check_cb(void *_data) +{ + struct lc15bts_mgr_instance *mgr = _data; + sensor_ctrl_check(mgr); + /* Check every minute? XXX make it configurable! */ + osmo_timer_schedule(&sensor_ctrl_timer, LC15BTS_SENSOR_TIMER_DURATION, 0); + LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n"); + /* TODO: do we want to notify if some sensors could not be read? */ + lc15bts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR); +} + +int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr) +{ + s_mgr = mgr; + sensor_ctrl_timer.cb = sensor_ctrl_check_cb; + sensor_ctrl_timer.data = s_mgr; + sensor_ctrl_check_cb(s_mgr); + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c new file mode 100644 index 00000000..80751fb0 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c @@ -0,0 +1,1074 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_vty.c + * (C) 2014 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso <anayuso@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <inttypes.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/logging.h> + +#include "lc15bts_misc.h" +#include "lc15bts_mgr.h" +#include "lc15bts_temp.h" +#include "lc15bts_power.h" +#include "lc15bts_led.h" +#include "btsconfig.h" + +static struct lc15bts_mgr_instance *s_mgr; + +static const char copyright[] = + "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n" + "(C) 2014 by Holger Hans Peter Freyther\r\n" + "(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n" + "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static int 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_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX0_TEMP_NODE: + case LIMIT_TX1_TEMP_NODE: + case LIMIT_PA0_TEMP_NODE: + case LIMIT_PA1_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_TX0_VSWR_NODE: + case LIMIT_TX1_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA0_PWR_NODE: + case LIMIT_PA1_PWR_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_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX0_TEMP_NODE: + case LIMIT_TX1_TEMP_NODE: + case LIMIT_PA0_TEMP_NODE: + case LIMIT_PA1_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_TX0_VSWR_NODE: + case LIMIT_TX1_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA0_PWR_NODE: + case LIMIT_PA1_PWR_NODE: + return 1; + default: + return 0; + } +} + +static struct vty_app_info vty_info = { + .name = "lc15bts-mgr", + .version = PACKAGE_VERSION, + .go_parent_cb = go_to_parent, + .is_config_node = is_config_node, + .copyright = copyright, +}; + + +#define MGR_STR "Configure lc15bts-mgr\n" + +static struct cmd_node mgr_node = { + MGR_NODE, + "%s(lc15bts-mgr)# ", + 1, +}; + +static struct cmd_node act_norm_node = { + ACT_NORM_NODE, + "%s(actions-normal)# ", + 1, +}; + +static struct cmd_node act_warn_node = { + ACT_WARN_NODE, + "%s(actions-warn)# ", + 1, +}; + +static struct cmd_node act_crit_node = { + ACT_CRIT_NODE, + "%s(actions-critical)# ", + 1, +}; + +static struct cmd_node limit_supply_temp_node = { + LIMIT_SUPPLY_TEMP_NODE, + "%s(limit-supply-temp)# ", + 1, +}; + +static struct cmd_node limit_soc_node = { + LIMIT_SOC_NODE, + "%s(limit-soc)# ", + 1, +}; + +static struct cmd_node limit_fpga_node = { + LIMIT_FPGA_NODE, + "%s(limit-fpga)# ", + 1, +}; + +static struct cmd_node limit_rmsdet_node = { + LIMIT_RMSDET_NODE, + "%s(limit-rmsdet)# ", + 1, +}; + +static struct cmd_node limit_ocxo_node = { + LIMIT_OCXO_NODE, + "%s(limit-ocxo)# ", + 1, +}; + +static struct cmd_node limit_tx0_temp_node = { + LIMIT_TX0_TEMP_NODE, + "%s(limit-tx0-temp)# ", + 1, +}; +static struct cmd_node limit_tx1_temp_node = { + LIMIT_TX1_TEMP_NODE, + "%s(limit-tx1-temp)# ", + 1, +}; +static struct cmd_node limit_pa0_temp_node = { + LIMIT_PA0_TEMP_NODE, + "%s(limit-pa0-temp)# ", + 1, +}; +static struct cmd_node limit_pa1_temp_node = { + LIMIT_PA1_TEMP_NODE, + "%s(limit-pa1-temp)# ", + 1, +}; +static struct cmd_node limit_supply_volt_node = { + LIMIT_SUPPLY_VOLT_NODE, + "%s(limit-supply-volt)# ", + 1, +}; +static struct cmd_node limit_tx0_vswr_node = { + LIMIT_TX0_VSWR_NODE, + "%s(limit-tx0-vswr)# ", + 1, +}; +static struct cmd_node limit_tx1_vswr_node = { + LIMIT_TX1_VSWR_NODE, + "%s(limit-tx1-vswr)# ", + 1, +}; +static struct cmd_node limit_supply_pwr_node = { + LIMIT_SUPPLY_PWR_NODE, + "%s(limit-supply-pwr)# ", + 1, +}; +static struct cmd_node limit_pa0_pwr_node = { + LIMIT_PA0_PWR_NODE, + "%s(limit-pa0-pwr)# ", + 1, +}; +static struct cmd_node limit_pa1_pwr_node = { + LIMIT_PA1_PWR_NODE, + "%s(limit-pa1-pwr)# ", + 1, +}; + +static struct cmd_node limit_gps_fix_node = { + LIMIT_GPS_FIX_NODE, + "%s(limit-gps-fix)# ", + 1, +}; + +DEFUN(cfg_mgr, cfg_mgr_cmd, + "lc15bts-mgr", + MGR_STR) +{ + vty->node = MGR_NODE; + return CMD_SUCCESS; +} + +static void write_volt_limit(struct vty *vty, const char *name, + struct lc15bts_volt_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning min %d%s", + limit->thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " threshold critical min %d%s", + limit->thresh_crit_min, VTY_NEWLINE); +} + +static void write_vswr_limit(struct vty *vty, const char *name, + struct lc15bts_vswr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); +} + +static void write_pwr_limit(struct vty *vty, const char *name, + struct lc15bts_pwr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " threshold critical max %d%s", + limit->thresh_crit_max, 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, " %spa0-on%s", + (actions & SENSOR_ACT_NORM_PA0_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %spa1-on%s", + (actions & SENSOR_ACT_NORM_PA1_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-on%s", + (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); +} + +static void write_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa0-off%s", + (actions & SENSOR_ACT_PA0_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %spa1-off%s", + (actions & SENSOR_ACT_PA1_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-off%s", + (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); +} + +static int config_write_mgr(struct vty *vty) +{ + vty_out(vty, "lc15bts-mgr%s", VTY_NEWLINE); + + write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit); + write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit); + write_vswr_limit(vty, "limits tx0_vswr", &s_mgr->vswr.tx0_vswr_limit); + write_vswr_limit(vty, "limits tx1_vswr", &s_mgr->vswr.tx1_vswr_limit); + + write_norm_action(vty, "actions normal", s_mgr->state.action_norm); + write_action(vty, "actions warn", s_mgr->state.action_warn); + write_action(vty, "actions critical", s_mgr->state.action_crit); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +#define CFG_LIMIT_TEMP(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->temp.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit) +CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit) +CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit) +CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit) +CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit) +CFG_LIMIT_TEMP(tx0_temp, "TX0 TEMP\n", LIMIT_TX0_TEMP_NODE, tx0_temp_limit) +CFG_LIMIT_TEMP(tx1_temp, "TX1 TEMP\n", LIMIT_TX1_TEMP_NODE, tx1_temp_limit) +CFG_LIMIT_TEMP(pa0_temp, "PA0 TEMP\n", LIMIT_PA0_TEMP_NODE, pa0_temp_limit) +CFG_LIMIT_TEMP(pa1_temp, "PA1 TEMP\n", LIMIT_PA1_TEMP_NODE, pa1_temp_limit) +#undef CFG_LIMIT_TEMP + +#define CFG_LIMIT_VOLT(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->volt.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit) +#undef CFG_LIMIT_VOLT + +#define CFG_LIMIT_VSWR(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->vswr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VSWR(tx0_vswr, "TX0 VSWR\n", LIMIT_TX0_VSWR_NODE, tx0_vswr_limit) +CFG_LIMIT_VSWR(tx1_vswr, "TX1 VSWR\n", LIMIT_TX1_VSWR_NODE, tx1_vswr_limit) +#undef CFG_LIMIT_VSWR + +#define CFG_LIMIT_PWR(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->pwr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit) +CFG_LIMIT_PWR(pa0_pwr, "PA0 PWR\n", LIMIT_PA0_PWR_NODE, pa0_pwr_limit) +CFG_LIMIT_PWR(pa1_pwr, "PA1 PWR\n", LIMIT_PA1_PWR_NODE, pa1_pwr_limit) +#undef CFG_LIMIT_PWR + +#define CFG_LIMIT_GPS_FIX(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->gps.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit) +#undef CFG_LIMIT_GPS_FIX + +DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd, + "threshold warning min <0-48000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_volt_limit *limit = vty->index; + limit->thresh_warn_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd, + "threshold critical min <0-48000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_volt_limit *limit = vty->index; + limit->thresh_crit_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd, + "threshold warning max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_vswr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd, + "threshold critical max <1000-200000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_vswr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd, + "threshold warning max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_pwr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd, + "threshold critical max <0-200>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_pwr_limit *limit = vty->index; + limit->thresh_crit_max = 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->state.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_pa0_on, cfg_action_pa0_on_cmd, + "pa0-on", + "Switch the Power Amplifier #0 on\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_PA0_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa0_on, cfg_no_action_pa0_on_cmd, + "no pa0-on", + NO_STR "Switch the Power Amplifieri #0 on\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_PA0_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa1_on, cfg_action_pa1_on_cmd, + "pa1-on", + "Switch the Power Amplifier #1 on\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_PA1_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa1_on, cfg_no_action_pa1_on_cmd, + "no pa1-on", + NO_STR "Switch the Power Amplifieri #1 on\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_PA1_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd, + "bts-service-on", + "Start the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_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 lc15bts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa0_off, cfg_action_pa0_off_cmd, + "pa0-off", + "Switch the Power Amplifier #0 off\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_PA0_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa0_off, cfg_no_action_pa0_off_cmd, + "no pa0-off", + NO_STR "Do not switch off the Power Amplifier #0\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_PA0_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa1_off, cfg_action_pa1_off_cmd, + "pa1-off", + "Switch the Power Amplifier #1 off\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_PA1_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa1_off, cfg_no_action_pa1_off_cmd, + "no pa1-off", + NO_STR "Do not switch off the Power Amplifier #1\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_PA1_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd, + "bts-service-off", + "Stop the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_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 lc15bts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(show_mgr, show_mgr_cmd, "show manager", + SHOW_STR "Display information about the manager") +{ + int temp, volt, current, power, vswr; + vty_out(vty, "Warning alarm flags: 0x%08x%s", + s_mgr->lc15bts_ctrl.warn_flags, VTY_NEWLINE); + vty_out(vty, "Critical alarm flags: 0x%08x%s", + s_mgr->lc15bts_ctrl.crit_flags, VTY_NEWLINE); + vty_out(vty, "Preventive action retried: %d%s", + s_mgr->alarms.preventive_retry, VTY_NEWLINE); + vty_out(vty, "Temperature control state: %s%s", + lc15bts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); + vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp); + vty_out(vty, " Main Supply : %4.2f Celcius%s", + temp/ 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp); + vty_out(vty, " SoC : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp); + vty_out(vty, " FPGA : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); + vty_out(vty, " RMSDet : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); + vty_out(vty, " OCXO : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); + vty_out(vty, " TX 0 : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); + vty_out(vty, " TX 1 : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); + vty_out(vty, " Power Amp #0: %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); + vty_out(vty, " Power Amp #1: %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + + vty_out(vty, "Power Status%s", VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Power Amp #0: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + lc15bts_power_get(LC15BTS_POWER_PA0) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Power Amp #1: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + lc15bts_power_get(LC15BTS_POWER_PA1) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + vty_out(vty, "VSWR Status%s", VTY_NEWLINE); + lc15bts_vswr_get(LC15BTS_VSWR_TX0, &vswr); + vty_out(vty, " VSWR TX 0: %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + lc15bts_vswr_get(LC15BTS_VSWR_TX1, &vswr); + vty_out(vty, " VSWR TX 1: %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(show_thresh, show_thresh_cmd, "show thresholds", + SHOW_STR "Display information about the thresholds") +{ + vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE); + vty_out(vty, " Main supply%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " SoC%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " FPGA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " RMSDet%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " OCXO%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " PA0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " PA1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, "Power limits%s", VTY_NEWLINE); + vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE); + vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " PA0 power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " PA1 power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, "VSWR limits%s", VTY_NEWLINE); + vty_out(vty, " TX0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " TX1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(calibrate_clock, calibrate_clock_cmd, + "calibrate clock", + "Calibration commands\n" + "Calibrate clock against GPS PPS\n") +{ + if (lc15bts_mgr_calib_run(s_mgr) < 0) { + vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(set_led_pattern, set_led_pattern_cmd, + "set led pattern <0-255>", + "Set LED pattern\n" + "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n") +{ + int pattern_id = atoi(argv[0]); + + if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) { + vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE); + return CMD_WARNING; + } + + led_set(s_mgr, pattern_id); + return CMD_SUCCESS; +} + +DEFUN(force_mgr_state, force_mgr_state_cmd, + "force manager state <0-255>", + "Force BTS manager state\n" + "Force BTS manager state for debugging purpose only\n") +{ + int state = atoi(argv[0]); + + if ((state < 0) || (state > STATE_CRITICAL)) { + vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE); + return CMD_WARNING; + } + + s_mgr->state.state = state; + return CMD_SUCCESS; +} + +#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \ + "limit temp " #name " " #criticity " " #min_max " <-200-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->temp.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_min, warning, min) +#undef LIMIT_TEMP + +#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \ + "limit " #name " " #criticity " " #min_max " <0-48000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->volt.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min) +#undef LIMIT_VOLT + +#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \ + DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \ + "limit power " #name " " #criticity " " #min_max " <0-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->pwr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_crit_max, critical, max) +#undef LIMIT_PWR + +#define LIMIT_VSWR(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_vswr_##name##_##variable, limit_vswr_##name##_##variable##_cmd, \ + "limit vswr " #name " " #criticity " " #min_max " <1000-200000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->vswr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_crit_max, critical, max) +LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_crit_max, critical, max) +#undef LIMIT_VSWR + +#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \ + "limit gpsfix " #criticity " " #min_max " <0-365>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->gps.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max) +#undef LIMIT_GPSFIX + +static void register_limit(int limit, uint32_t unit) +{ + switch (unit) { + case MGR_LIMIT_TYPE_VOLT: + install_element(limit, &cfg_thresh_volt_warn_min_cmd); + install_element(limit, &cfg_thresh_volt_crit_min_cmd); + break; + case MGR_LIMIT_TYPE_VSWR: + install_element(limit, &cfg_thresh_vswr_warn_max_cmd); + install_element(limit, &cfg_thresh_vswr_crit_max_cmd); + break; + case MGR_LIMIT_TYPE_PWR: + install_element(limit, &cfg_thresh_pwr_warn_max_cmd); + install_element(limit, &cfg_thresh_pwr_crit_max_cmd); + break; + default: + break; + } +} + +static void register_normal_action(int act) +{ + install_element(act, &cfg_action_pa0_on_cmd); + install_element(act, &cfg_no_action_pa0_on_cmd); + install_element(act, &cfg_action_pa1_on_cmd); + install_element(act, &cfg_no_action_pa1_on_cmd); + install_element(act, &cfg_action_bts_srv_on_cmd); + install_element(act, &cfg_no_action_bts_srv_on_cmd); +} + +static void register_action(int act) +{ + install_element(act, &cfg_action_pa0_off_cmd); + install_element(act, &cfg_no_action_pa0_off_cmd); + install_element(act, &cfg_action_pa1_off_cmd); + install_element(act, &cfg_no_action_pa1_off_cmd); + install_element(act, &cfg_action_bts_srv_off_cmd); + install_element(act, &cfg_no_action_bts_srv_off_cmd); +} + +static void register_hidden_commands() +{ + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_min_cmd); + + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd); + + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_crit_max_cmd); + + install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_crit_max_cmd); + + install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd); +} + +int lc15bts_mgr_vty_init(void) +{ + vty_init(&vty_info); + + install_element_ve(&show_mgr_cmd); + install_element_ve(&show_thresh_cmd); + + install_element(ENABLE_NODE, &calibrate_clock_cmd); + + install_node(&mgr_node, config_write_mgr); + install_element(CONFIG_NODE, &cfg_mgr_cmd); + + /* install the limit nodes */ + install_node(&limit_supply_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_temp_cmd); + + install_node(&limit_soc_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_soc_temp_cmd); + + install_node(&limit_fpga_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd); + + install_node(&limit_rmsdet_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd); + + install_node(&limit_ocxo_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd); + + install_node(&limit_tx0_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx0_temp_cmd); + + install_node(&limit_tx1_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx1_temp_cmd); + + install_node(&limit_pa0_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa0_temp_cmd); + + install_node(&limit_pa1_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa1_temp_cmd); + + install_node(&limit_supply_volt_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_volt_cmd); + register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT); + + install_node(&limit_tx0_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx0_vswr_cmd); + register_limit(LIMIT_TX0_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + + install_node(&limit_tx1_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx1_vswr_cmd); + register_limit(LIMIT_TX1_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + + install_node(&limit_supply_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd); + register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_pa0_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa0_pwr_cmd); + register_limit(LIMIT_PA0_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_pa1_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa1_pwr_cmd); + register_limit(LIMIT_PA1_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_gps_fix_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_gps_fix_cmd); + + /* 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); + + install_node(&act_crit_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_critical_cmd); + register_action(ACT_CRIT_NODE); + + /* install LED pattern command for debugging purpose */ + install_element_ve(&set_led_pattern_cmd); + install_element_ve(&force_mgr_state_cmd); + + register_hidden_commands(); + + return 0; +} + +int lc15bts_mgr_parse_config(struct lc15bts_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; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.c b/src/osmo-bts-litecell15/misc/lc15bts_misc.c new file mode 100644 index 00000000..2cedc5d8 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.c @@ -0,0 +1,383 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <limits.h> +#include <time.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> + +#include "lc15bts_mgr.h" +#include "btsconfig.h" +#include "lc15bts_misc.h" +#include "lc15bts_par.h" +#include "lc15bts_mgr.h" +#include "lc15bts_temp.h" +#include "lc15bts_power.h" + +/********************************************************************* + * Temperature handling + *********************************************************************/ + +static const struct { + const char *name; + int has_max; + enum lc15bts_temp_sensor sensor; + enum lc15bts_par ee_par; +} temp_data[] = { + { + .name = "supply_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_SUPPLY, + .ee_par = LC15BTS_PAR_TEMP_SUPPLY_MAX, + }, { + .name = "soc_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_SOC, + .ee_par = LC15BTS_PAR_TEMP_SOC_MAX, + }, { + .name = "fpga_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_FPGA, + .ee_par = LC15BTS_PAR_TEMP_FPGA_MAX, + + }, { + .name = "rmsdet_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_RMSDET, + .ee_par = LC15BTS_PAR_TEMP_RMSDET_MAX, + }, { + .name = "ocxo_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_OCXO, + .ee_par = LC15BTS_PAR_TEMP_OCXO_MAX, + }, { + .name = "tx0_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_TX0, + .ee_par = LC15BTS_PAR_TEMP_TX0_MAX, + }, { + .name = "tx1_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_TX1, + .ee_par = LC15BTS_PAR_TEMP_TX1_MAX, + }, { + .name = "pa0_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_PA0, + .ee_par = LC15BTS_PAR_TEMP_PA0_MAX, + }, { + .name = "pa1_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_PA1, + .ee_par = LC15BTS_PAR_TEMP_PA1_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum lc15bts_power_source sensor_source; + enum lc15bts_power_type sensor_type; + enum lc15bts_par ee_par; +} power_data[] = { + { + .name = "supply_volt", + .has_max = 1, + .sensor_source = LC15BTS_POWER_SUPPLY, + .sensor_type = LC15BTS_POWER_VOLTAGE, + .ee_par = LC15BTS_PAR_VOLT_SUPPLY_MAX, + }, { + .name = "supply_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_SUPPLY, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_SUPPLY_MAX, + }, { + .name = "pa0_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_PA0, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_PA0_MAX, + }, { + .name = "pa1_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_PA1, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_PA1_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum lc15bts_vswr_sensor sensor; + enum lc15bts_par ee_par; +} vswr_data[] = { + { + .name = "tx0_vswr", + .has_max = 0, + .sensor = LC15BTS_VSWR_TX0, + .ee_par = LC15BTS_PAR_VSWR_TX0_MAX, + }, { + .name = "tx1_vswr", + .has_max = 0, + .sensor = LC15BTS_VSWR_TX1, + .ee_par = LC15BTS_PAR_VSWR_TX1_MAX, + } +}; + +static const struct value_string power_unit_strs[] = { + { LC15BTS_POWER_POWER, "W" }, + { LC15BTS_POWER_VOLTAGE, "V" }, + { 0, NULL } +}; + +void lc15bts_check_temp(int no_rom_write) +{ + int temp_old[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 = lc15bts_par_get_int(tall_mgr_ctx, temp_data[i].ee_par, &ret); + temp_old[i] = ret * 1000; + + lc15bts_temp_get(temp_data[i].sensor, &temp_cur[i]); + if (temp_cur[i] < 0 && temp_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d): unexpected value %d\n", + temp_data[i].sensor, temp_cur[i]); + continue; + } + + 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_cur[i] > temp_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "temperature: %d.%d C\n", temp_data[i].name, + temp_cur[i]/1000, temp_old[i]%1000); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, temp_data[i].ee_par, temp_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max temp %d (%s)\n", temp_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void lc15bts_check_power(int no_rom_write) +{ + int power_old[ARRAY_SIZE(power_data)]; + int power_cur[ARRAY_SIZE(power_data)]; + int i, rc; + int div_ratio; + + for (i = 0; i < ARRAY_SIZE(power_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, power_data[i].ee_par, &ret); + switch(power_data[i].sensor_type) { + case LC15BTS_POWER_VOLTAGE: + div_ratio = 1000; + break; + case LC15BTS_POWER_POWER: + div_ratio = 1000000; + break; + default: + div_ratio = 1000; + } + power_old[i] = ret * div_ratio; + + lc15bts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]); + if (power_cur[i] < 0 && power_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, + power_data[i].sensor_type); + continue; + } + LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n", + power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (power_cur[i] > power_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "power: %d.%d %s\n", power_data[i].name, + power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, power_data[i].ee_par, power_cur[i]/div_ratio); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max power %d (%s)\n", power_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void lc15bts_check_vswr(int no_rom_write) +{ + int vswr_old[ARRAY_SIZE(vswr_data)]; + int vswr_cur[ARRAY_SIZE(vswr_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(vswr_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, vswr_data[i].ee_par, &ret); + vswr_old[i] = ret * 1000; + + lc15bts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]); + if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n", + vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000); + + if (vswr_cur[i] > vswr_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "vswr: %d.%d C\n", vswr_data[i].name, + vswr_cur[i]/1000, vswr_old[i]%1000); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, vswr_data[i].ee_par, vswr_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max vswr %d (%s)\n", vswr_data[i].name, + rc, strerror(errno)); + } + } + } +} + +/********************************************************************* + * Hours handling + *********************************************************************/ +static time_t last_update; + +int lc15bts_update_hours(int no_rom_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 = lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_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 = lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_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_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, op_hrs); + if (rc < 0) + return rc; + } + + last_update = now; + } + + return 0; +} + +/********************************************************************* + * Firmware reloading + *********************************************************************/ + +static const char *fw_sysfs[_NUM_FW] = { + [LC15BTS_FW_DSP0] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery", + [LC15BTS_FW_DSP1] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery", +}; + +int lc15bts_firmware_reload(enum lc15bts_firmware_type type) +{ + int fd; + int rc; + + switch (type) { + case LC15BTS_FW_DSP0: + case LC15BTS_FW_DSP1: + fd = open(fw_sysfs[type], O_WRONLY); + if (fd < 0) { + LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + fw_sysfs[type], strerror(errno)); + close(fd); + return fd; + } + rc = write(fd, "restart", 8); + if (rc < 8) { + LOGP(DFW, LOGL_ERROR, "short write during " + "fw write to %s\n", fw_sysfs[type]); + close(fd); + return -EIO; + } + close(fd); + default: + return -EINVAL; + } + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.h b/src/osmo-bts-litecell15/misc/lc15bts_misc.h new file mode 100644 index 00000000..79e9e686 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.h @@ -0,0 +1,18 @@ +#ifndef _LC15BTS_MISC_H +#define _LC15BTS_MISC_H + +#include <stdint.h> + +void lc15bts_check_temp(int no_rom_write); +void lc15bts_check_power(int no_rom_write); +void lc15bts_check_vswr(int no_rom_write); + +int lc15bts_update_hours(int no_rom_write); + +enum lc15bts_firmware_type { + LC15BTS_FW_DSP0, + LC15BTS_FW_DSP1, + _NUM_FW +}; + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_nl.c new file mode 100644 index 00000000..39f64aae --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_nl.c @@ -0,0 +1,123 @@ +/* Helper for netlink */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_nl.c + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <arpa/inet.h> +#include <netinet/ip.h> + +#include <sys/socket.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#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; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.h b/src/osmo-bts-litecell15/misc/lc15bts_nl.h new file mode 100644 index 00000000..340cf117 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_nl.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_nl.h + * (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 <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.c b/src/osmo-bts-litecell15/misc/lc15bts_par.c new file mode 100644 index 00000000..af9d030f --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_par.c @@ -0,0 +1,232 @@ +/* lc15bts - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_par.c + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> + +#include "lc15bts_par.h" + +const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1] = { + { LC15BTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" }, + { LC15BTS_PAR_TEMP_SOC_MAX, "temp-soc-max" }, + { LC15BTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" }, + { LC15BTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" }, + { LC15BTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" }, + { LC15BTS_PAR_TEMP_TX0_MAX, "temp-tx0-max" }, + { LC15BTS_PAR_TEMP_TX1_MAX, "temp-tx1-max" }, + { LC15BTS_PAR_TEMP_PA0_MAX, "temp-pa0-max" }, + { LC15BTS_PAR_TEMP_PA1_MAX, "temp-pa1-max" }, + { LC15BTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" }, + { LC15BTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" }, + { LC15BTS_PAR_PWR_PA0_MAX, "pwr-pa0-max" }, + { LC15BTS_PAR_PWR_PA1_MAX, "pwr-pa1-max" }, + { LC15BTS_PAR_VSWR_TX0_MAX, "vswr-tx0-max" }, + { LC15BTS_PAR_VSWR_TX1_MAX, "vswr-tx1-max" }, + { LC15BTS_PAR_GPS_FIX, "gps-fix" }, + { LC15BTS_PAR_SERNR, "serial-nr" }, + { LC15BTS_PAR_HOURS, "hours-running" }, + { LC15BTS_PAR_BOOTS, "boot-count" }, + { LC15BTS_PAR_KEY, "key" }, + { 0, NULL } +}; + +int lc15bts_par_is_int(enum lc15bts_par par) +{ + switch (par) { + case LC15BTS_PAR_TEMP_SUPPLY_MAX: + case LC15BTS_PAR_TEMP_SOC_MAX: + case LC15BTS_PAR_TEMP_FPGA_MAX: + case LC15BTS_PAR_TEMP_RMSDET_MAX: + case LC15BTS_PAR_TEMP_OCXO_MAX: + case LC15BTS_PAR_TEMP_TX0_MAX: + case LC15BTS_PAR_TEMP_TX1_MAX: + case LC15BTS_PAR_TEMP_PA0_MAX: + case LC15BTS_PAR_TEMP_PA1_MAX: + case LC15BTS_PAR_VOLT_SUPPLY_MAX: + case LC15BTS_PAR_VSWR_TX0_MAX: + case LC15BTS_PAR_VSWR_TX1_MAX: + case LC15BTS_PAR_SERNR: + case LC15BTS_PAR_HOURS: + case LC15BTS_PAR_BOOTS: + case LC15BTS_PAR_PWR_SUPPLY_MAX: + case LC15BTS_PAR_PWR_PA0_MAX: + case LC15BTS_PAR_PWR_PA1_MAX: + return 1; + default: + return 0; + } +} + +FILE *lc15bts_par_get_path(void *ctx, enum lc15bts_par par, const char* mode) +{ + char *fpath; + FILE *fp; + + if (par >= _NUM_LC15BTS_PAR) + return NULL; + + fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par)); + if (!fpath) + return NULL; + + fp = fopen(fpath, mode); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + return fp; +} + +int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "r"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%d", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + return 0; +} + +int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "w"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + + fsync(fileno(fp)); + fclose(fp); + return 0; +} + +int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "rb"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fread(buf, 1, size, fp); + + fclose(fp); + + return rc; +} + +int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "wb"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fwrite(buf, 1, size, fp); + + fsync(fileno(fp)); + fclose(fp); + + return rc; +} + +int lc15bts_par_get_gps_fix(time_t *ret) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%lld", (long long *)ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +int lc15bts_par_set_gps_fix(time_t val) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%lld", (long long)val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fileno(fp)); + fclose(fp); + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.h b/src/osmo-bts-litecell15/misc/lc15bts_par.h new file mode 100644 index 00000000..217ae5f2 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_par.h @@ -0,0 +1,44 @@ +#ifndef _LC15BTS_PAR_H +#define _LC15BTS_PAR_H + +#include <osmocom/core/utils.h> + +#define FACTORY_ROM_PATH "/mnt/rom/factory" +#define USER_ROM_PATH "/mnt/storage/var/run/lc15bts-mgr" + +enum lc15bts_par { + LC15BTS_PAR_TEMP_SUPPLY_MAX, + LC15BTS_PAR_TEMP_SOC_MAX, + LC15BTS_PAR_TEMP_FPGA_MAX, + LC15BTS_PAR_TEMP_RMSDET_MAX, + LC15BTS_PAR_TEMP_OCXO_MAX, + LC15BTS_PAR_TEMP_TX0_MAX, + LC15BTS_PAR_TEMP_TX1_MAX, + LC15BTS_PAR_TEMP_PA0_MAX, + LC15BTS_PAR_TEMP_PA1_MAX, + LC15BTS_PAR_VOLT_SUPPLY_MAX, + LC15BTS_PAR_PWR_SUPPLY_MAX, + LC15BTS_PAR_PWR_PA0_MAX, + LC15BTS_PAR_PWR_PA1_MAX, + LC15BTS_PAR_VSWR_TX0_MAX, + LC15BTS_PAR_VSWR_TX1_MAX, + LC15BTS_PAR_GPS_FIX, + LC15BTS_PAR_SERNR, + LC15BTS_PAR_HOURS, + LC15BTS_PAR_BOOTS, + LC15BTS_PAR_KEY, + _NUM_LC15BTS_PAR +}; + +extern const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1]; + +int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret); +int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val); +int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size); +int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size); + +int lc15bts_par_is_int(enum lc15bts_par par); +int lc15bts_par_get_gps_fix(time_t *ret); +int lc15bts_par_set_gps_fix(time_t val); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.c b/src/osmo-bts-litecell15/misc/lc15bts_power.c new file mode 100644 index 00000000..1a37d8e6 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_power.c @@ -0,0 +1,210 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#include "lc15bts_power.h" + +#define LC15BTS_PA_VOLTAGE 24000000 + +#define PA_SUPPLY_MIN_SYSFS "/var/lc15/pa-supply/min_microvolts" +#define PA_SUPPLY_MAX_SYSFS "/var/lc15/pa-supply/max_microvolts" + +static const char *power_enable_devs[_NUM_POWER_SOURCES] = { + [LC15BTS_POWER_PA0] = "/var/lc15/pa-state/pa0/state", + [LC15BTS_POWER_PA1] = "/var/lc15/pa-state/pa1/state", +}; + +static const char *power_sensor_devs[_NUM_POWER_SOURCES] = { + [LC15BTS_POWER_SUPPLY] = "/var/lc15/pwr-sense/main-supply/", + [LC15BTS_POWER_PA0] = "/var/lc15/pwr-sense/pa0/", + [LC15BTS_POWER_PA1] = "/var/lc15/pwr-sense/pa1/", +}; + +static const char *power_sensor_type_str[_NUM_POWER_TYPES] = { + [LC15BTS_POWER_POWER] = "power", + [LC15BTS_POWER_VOLTAGE] = "voltage", + [LC15BTS_POWER_CURRENT] = "current", +}; + +int lc15bts_power_sensor_get( + enum lc15bts_power_source source, + enum lc15bts_power_type type, + int *power) +{ + char buf[PATH_MAX]; + char pwrstr[10]; + int fd, rc; + + if (source >= _NUM_POWER_SOURCES) + return -EINVAL; + + if (type >= _NUM_POWER_TYPES) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, pwrstr, sizeof(pwrstr)); + pwrstr[sizeof(pwrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *power = atoi(pwrstr); + return 0; +} + + +int lc15bts_power_set( + enum lc15bts_power_source source, + int en) +{ + int fd; + int rc; + + if ((source != LC15BTS_POWER_PA0) + && (source != LC15BTS_POWER_PA1) ) { + return -EINVAL; + } + + fd = open(PA_SUPPLY_MAX_SYSFS, O_WRONLY); + if (fd < 0) { + return fd; + } + rc = write(fd, "32000000", 9); + close( fd ); + + if (rc != 9) { + return -1; + } + + fd = open(PA_SUPPLY_MIN_SYSFS, O_WRONLY); + if (fd < 0) { + return fd; + } + + /* TODO NTQ: Make the voltage configurable */ + rc = write(fd, "24000000", 9); + close( fd ); + + if (rc != 9) { + return -1; + } + + fd = open(power_enable_devs[source], O_WRONLY); + if (fd < 0) { + return fd; + } + rc = write(fd, en?"1":"0", 2); + close( fd ); + + if (rc != 2) { + return -1; + } + + if (en) usleep(50*1000); + + return 0; +} + +int lc15bts_power_get( + enum lc15bts_power_source source) +{ + int fd; + int rc; + int retVal = 0; + char enstr[10]; + + fd = open(power_enable_devs[source], O_RDONLY); + if (fd < 0) { + return fd; + } + + rc = read(fd, enstr, sizeof(enstr)); + enstr[rc-1] = '\0'; + + close(fd); + + if (rc < 0) { + return rc; + } + if (rc == 0) { + return -EIO; + } + + rc = strcmp(enstr, "enabled"); + if(rc == 0) { + retVal = 1; + } + + return retVal; +} + +static const char *vswr_devs[_NUM_VSWR_SENSORS] = { + [LC15BTS_VSWR_TX0] = "/var/lc15/vswr/tx0/vswr", + [LC15BTS_VSWR_TX1] = "/var/lc15/vswr/tx1/vswr", +}; + +int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr) +{ + char buf[PATH_MAX]; + char vswrstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, vswrstr, sizeof(vswrstr)); + vswrstr[sizeof(vswrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *vswr = atoi(vswrstr); + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.h b/src/osmo-bts-litecell15/misc/lc15bts_power.h new file mode 100644 index 00000000..b48cfdcd --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_power.h @@ -0,0 +1,38 @@ +#ifndef _LC15BTS_POWER_H +#define _LC15BTS_POWER_H + +enum lc15bts_power_source { + LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_PA0, + LC15BTS_POWER_PA1, + _NUM_POWER_SOURCES +}; + +enum lc15bts_power_type { + LC15BTS_POWER_POWER, + LC15BTS_POWER_VOLTAGE, + LC15BTS_POWER_CURRENT, + _NUM_POWER_TYPES +}; + +int lc15bts_power_sensor_get( + enum lc15bts_power_source source, + enum lc15bts_power_type type, + int *volt); + +int lc15bts_power_set( + enum lc15bts_power_source source, + int en); + +int lc15bts_power_get( + enum lc15bts_power_source source); + +enum lc15bts_vswr_sensor { + LC15BTS_VSWR_TX0, + LC15BTS_VSWR_TX1, + _NUM_VSWR_SENSORS +}; + +int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.c b/src/osmo-bts-litecell15/misc/lc15bts_swd.c new file mode 100644 index 00000000..59c7b616 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_swd.c @@ -0,0 +1,178 @@ +/* Systemd service wd notification for Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_swd.h" +#include <osmocom/core/logging.h> + +/* Needed for service watchdog notification */ +#include <systemd/sd-daemon.h> + +/* This is the period used to verify if all events have been registered to be allowed + to notify the systemd service watchdog +*/ +#define SWD_PERIOD 30 + +static void swd_start(struct lc15bts_mgr_instance *mgr); +static void swd_process(struct lc15bts_mgr_instance *mgr); +static void swd_close(struct lc15bts_mgr_instance *mgr); +static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int reason); +static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop); +static void swd_loop_run(void *_data); + +enum swd_state { + SWD_INITIAL, + SWD_IN_PROGRESS, +}; + +enum swd_result { + SWD_FAIL_START, + SWD_FAIL_NOTIFY, + SWD_SUCCESS, +}; + +static void swd_start(struct lc15bts_mgr_instance *mgr) +{ + swd_process(mgr); +} + +static void swd_process(struct lc15bts_mgr_instance *mgr) +{ + int rc = 0, notify = 0; + + /* Did we get all needed conditions ? */ + if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) { + /* Ping systemd service wd if enabled */ + rc = sd_notify(0, "WATCHDOG=1"); + LOGP(DSWD, LOGL_NOTICE, "Watchdog notification attempt\n"); + notify = 1; + } + else { + LOGP(DSWD, LOGL_NOTICE, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks); + } + + if (rc < 0) { + LOGP(DSWD, LOGL_ERROR, + "Failed to notify system service watchdog: %d\n", rc); + swd_state_reset(mgr, SWD_FAIL_NOTIFY); + return; + } + else { + /* Did we notified the watchdog? */ + if (notify) { + mgr->swd.swd_events = 0; + /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */ + if (mgr->swd.swd_events != 0) + mgr->swd.swd_events = 0; + } + } + + swd_state_reset(mgr, SWD_SUCCESS); + return; +} + +static void swd_close(struct lc15bts_mgr_instance *mgr) +{ +} + +static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int outcome) +{ + if (mgr->swd.swd_from_loop) { + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0); + } + + mgr->swd.state = SWD_INITIAL; + swd_close(mgr); +} + +static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop) +{ + if (mgr->swd.state != SWD_INITIAL) { + LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n"); + return -1; + } + + mgr->swd.swd_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->swd.state = SWD_IN_PROGRESS; + swd_start(mgr); + return 0; +} + +static void swd_loop_run(void *_data) +{ + int rc; + struct lc15bts_mgr_instance *mgr = _data; + + LOGP(DSWD, LOGL_NOTICE, "Going to check for watchdog notification.\n"); + rc = swd_run(mgr, 1); + if (rc != 0) { + swd_state_reset(mgr, SWD_FAIL_START); + } +} + +/* 'swd_num_events' configures the number of events to be monitored before notifying the + systemd service watchdog. It must be in the range of [1,64]. Events are notified + through the function 'lc15bts_swd_event' +*/ +int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events) +{ + /* Checks for a valid number of events to validate */ + if (swd_num_events < 1 || swd_num_events > 64) + return(-1); + + mgr->swd.state = SWD_INITIAL; + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0); + + if (swd_num_events == 64){ + mgr->swd.swd_eventmasks = 0xffffffffffffffffULL; + } + else { + mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1); + } + mgr->swd.swd_events = 0; + mgr->swd.num_events = swd_num_events; + + return 0; +} + +/* Notifies that the specified event 'swd_event' happened correctly; + the value must be in the range of [0,'swd_num_events'[ (see lc15bts_swd_init). + For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. + WARNING: if this function can be used from multiple threads at the same time, + it must be protected with a kind of mutex to avoid loosing event notification. +*/ +int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event) +{ + /* Checks for a valid specified event (smaller than max possible) */ + if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events) + return(-1); + + mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event)); + + /* !!! Uncomment following line to debug events notification */ + LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event)); + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.h b/src/osmo-bts-litecell15/misc/lc15bts_swd.h new file mode 100644 index 00000000..b78a2c2a --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_swd.h @@ -0,0 +1,7 @@ +#ifndef _LC15BTS_SWD_H +#define _LC15BTS_SWD_H + +int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events); +int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_temp.c new file mode 100644 index 00000000..45602dcc --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.c @@ -0,0 +1,74 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#include <osmocom/core/utils.h> + +#include "lc15bts_temp.h" + +static const char *temp_devs[_NUM_TEMP_SENSORS] = { + [LC15BTS_TEMP_SUPPLY] = "/var/lc15/temp/main-supply/temp", + [LC15BTS_TEMP_SOC] = "/var/lc15/temp/cpu/temp", + [LC15BTS_TEMP_FPGA] = "/var/lc15/temp/fpga/temp", + [LC15BTS_TEMP_RMSDET] = "/var/lc15/temp/rmsdet/temp", + [LC15BTS_TEMP_OCXO] = "/var/lc15/temp/ocxo/temp", + [LC15BTS_TEMP_TX0] = "/var/lc15/temp/tx0/temp", + [LC15BTS_TEMP_TX1] = "/var/lc15/temp/tx1/temp", + [LC15BTS_TEMP_PA0] = "/var/lc15/temp/pa0/temp", + [LC15BTS_TEMP_PA1] = "/var/lc15/temp/pa1/temp", +}; + +int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp) +{ + char buf[PATH_MAX]; + char tempstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]); + 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); + *temp = atoi(tempstr); + return 0; +} + diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.h b/src/osmo-bts-litecell15/misc/lc15bts_temp.h new file mode 100644 index 00000000..35d81f1b --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.h @@ -0,0 +1,28 @@ +#ifndef _LC15BTS_TEMP_H +#define _LC15BTS_TEMP_H + +enum lc15bts_temp_sensor { + LC15BTS_TEMP_SUPPLY, + LC15BTS_TEMP_SOC, + LC15BTS_TEMP_FPGA, + LC15BTS_TEMP_RMSDET, + LC15BTS_TEMP_OCXO, + LC15BTS_TEMP_TX0, + LC15BTS_TEMP_TX1, + LC15BTS_TEMP_PA0, + LC15BTS_TEMP_PA1, + _NUM_TEMP_SENSORS +}; + +enum lc15bts_temp_type { + LC15BTS_TEMP_INPUT, + LC15BTS_TEMP_LOWEST, + LC15BTS_TEMP_HIGHEST, + LC15BTS_TEMP_FAULT, + _NUM_TEMP_TYPES +}; + +int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp); + + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_util.c b/src/osmo-bts-litecell15/misc/lc15bts_util.c new file mode 100644 index 00000000..430ce0f7 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_util.c @@ -0,0 +1,164 @@ +/* lc15bts-util - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/msgb.h> + +#include "lc15bts_par.h" + +void *tall_util_ctx; + +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 = lc15bts_par_names; + + printf("lc15bts-util [--void-warranty -r | -w value] param_name\n"); + printf("Possible param names:\n"); + + for (; par->str != NULL; par += 1) { + if (!lc15bts_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 lc15bts_par par; + int rc, val; + + tall_util_ctx = talloc_named_const(NULL, 1, "lc15 utils"); + msgb_talloc_ctx_init(tall_util_ctx, 0); + + 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(lc15bts_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 = lc15bts_par_get_int(tall_util_ctx, par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("%d\n", val); + break; + case ACT_SET: + rc = lc15bts_par_get_int(tall_util_ctx, 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 = lc15bts_par_set_int(tall_util_ctx, 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); +} + diff --git a/src/osmo-bts-litecell15/oml.c b/src/osmo-bts-litecell15/oml.c new file mode 100644 index 00000000..f084f1bf --- /dev/null +++ b/src/osmo-bts-litecell15/oml.c @@ -0,0 +1,1941 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1types.h> +#include <nrw/litecell15/litecell15.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> + +#include "l1_if.h" +#include "lc15bts.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_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +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 lc15l1_hdl *gl1, + uint32_t hLayer3_uint32) +{ + HANDLE hLayer3; + prim->id = id; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + hLayer3 = (void*)hLayer3_uint32; + + switch (id) { + case GsmL1_PrimId_MphInitReq: + //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + 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; +} + +static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) +{ + 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(lc15bts_l1prim_names, l1p->id), + get_value_string(lc15bts_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(lc15bts_l1prim_names, l1p->id), + get_value_string(lc15bts_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[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + 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); +} + +static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_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(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-MUTE failure"); + } + + msgb_free(resp); + + return 0; +} + +static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_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(lc15bts_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(lc15bts_l1status_names, ic->status)); + bts_shutdown(trx->bts, "MPH-INIT failure"); + } + + fl1h->hLayer1 = ic->hLayer1; + + /* 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); + + /* 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 lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int lc15_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); + } + + lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn); + if (lc15_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, + l1p_handle_for_trx(trx)); + dev_par = &mi_req->deviceParam; + dev_par->devType = GsmL1_DevType_TxdRxu; + dev_par->freqBand = lc15_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 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = 0.0; + LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " + "TxPower % 2.2f dBm\n", dev_par->freqBand, 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 lc15l1_hdl *fl1h = trx_lc15l1_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 lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg; + + msg = l1p_msgb_alloc(); + prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, + l1p_handle_for_trx(trx)); + 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 lc15l1_hdl *fl1h = trx_lc15l1_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_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, 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) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (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: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + 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_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + 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, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_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(lc15bts_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(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(lc15bts_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 lc15l1_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) +{ + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; +} + +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 lc15l1_hdl *fl1h = trx_lc15l1_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, l1p_handle_for_lchan(lchan)); + 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 = (HANDLE *)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: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * 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; + 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), (uint32_t)act_req->hLayer2, + get_value_string(lc15bts_l1sapi_names, act_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_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 lc15l1_hdl *fl1h = trx_lc15l1_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 lc15l1_hdl *fl1h = trx_lc15l1_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 lc15bts_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(lc15bts_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, (uint32_t)cc->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_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 lc15l1_hdl *fl1h = trx_lc15l1_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, + l1p_handle_for_lchan(lchan)); + 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 = (HANDLE)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(lc15bts_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 lc15l1_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, 0); + 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 lc15l1_hdl *fl1h = trx_lc15l1_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, + l1p_handle_for_lchan(lchan)); + + 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 = (HANDLE)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(lc15bts_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 lc15l1_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, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_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(lc15bts_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(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(lc15bts_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 lc15l1_hdl *fl1h = trx_lc15l1_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, l1p_handle_for_lchan(lchan)); + 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 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, deact_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_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); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + 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 lc15l1_hdl *fl1h = trx_lc15l1_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 bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + 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); +} + +int bts_model_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_PRES_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 lc15l1_hdl *fl1h = trx_lc15l1_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_opstart(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 bts_model_lchan_deactivate_sacch(lchan); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_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_lc15l1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts, 0); + + return 0; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + + rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); + if (rc) + cb_ts_connected(ts, rc); +} diff --git a/src/osmo-bts-litecell15/oml_router.c b/src/osmo-bts-litecell15/oml_router.c new file mode 100644 index 00000000..198d5e30 --- /dev/null +++ b/src/osmo-bts-litecell15/oml_router.c @@ -0,0 +1,132 @@ +/* Beginnings of an OML router */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "oml_router.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/msg_utils.h> + +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +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; +} diff --git a/src/osmo-bts-litecell15/oml_router.h b/src/osmo-bts-litecell15/oml_router.h new file mode 100644 index 00000000..8c08baaa --- /dev/null +++ b/src/osmo-bts-litecell15/oml_router.h @@ -0,0 +1,13 @@ +#pragma once + +struct gsm_bts; +struct osmo_fd; + +/** + * The default path lc15bts will listen for incoming + * registrations for OML routing and sending. + */ +#define OML_ROUTER_PATH "/var/run/lc15bts_oml_router" + + +int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-litecell15/tch.c new file mode 100644 index 00000000..5eae7538 --- /dev/null +++ b/src/osmo-bts-litecell15/tch.c @@ -0,0 +1,533 @@ +/* Traffic channel support for NuRAN Wireless Litecell 1.5 BTS L1 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1types.h> + +#include "lc15bts.h" +#include "l1_if.h" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_FR_BYTES); + memcpy(cur, l1_payload, GSM_FR_BYTES); + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + 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) +{ + /* new L1 can deliver bits like we need them */ + memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); + return GSM_FR_BYTES; +} + +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, + uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_EFR_BYTES); + memcpy(cur, l1_payload, GSM_EFR_BYTES); + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + return msg; +} + +static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + memcpy(l1_payload, rtp_payload, payload_len); + + return payload_len; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, 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); + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + 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(DL1P, 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); + + return GSM_HR_BYTES; +} + +static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t amr_if2_len = payload_len - 2; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + 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; + + return msg; +} + +/*! \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, uint8_t ft) +{ + memcpy(l1_payload, rtp_payload, payload_len); + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * 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. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + 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); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, + rtp_pl_len); + /* FIXME: detect and save EFR SID */ + break; + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + 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 -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +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, payload_type, payload_len, sid_first[9] = { 0 }; + 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) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + payload_len = data_ind->msgUnitParam.u8Size - 1; + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + case GsmL1_TchPlType_Efr: + 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; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), + get_value_string(lc15bts_tch_pl_names, payload_type)); + break; + } + + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type)); + return -EINVAL; +} + +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) +{ + 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; + int rc; + + 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: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-litecell15/utils.c b/src/osmo-bts-litecell15/utils.c new file mode 100644 index 00000000..8d980ba8 --- /dev/null +++ b/src/osmo-bts-litecell15/utils.c @@ -0,0 +1,118 @@ +/* Helper utilities that are used in OMLs */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "utils.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#include "lc15bts.h" +#include "l1_if.h" + +int band_lc152osmo(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_osmo2lc15(struct gsm_bts_trx *trx, enum gsm_band osmo_band) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_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 lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn) +{ + enum gsm_band band; + struct gsm_bts *bts = trx->bts; + int rc; + + if (!bts->auto_band) + return band_osmo2lc15(trx, bts->band); + + /* + * We need to check what will happen now. + */ + rc = gsm_arfcn2band_rc(arfcn, &band); + if (rc) /* wrong ARFCN, give up */ + return -1; + + /* if we are already on the right band return */ + if (band == bts->band) + return band_osmo2lc15(trx, bts->band); + + /* Check if it is GSM1800/GSM1900 */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return band_osmo2lc15(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_osmo2lc15(trx, band); + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850) + return band_osmo2lc15(trx, GSM_BAND_1900); + + /* give up */ + return -1; +} diff --git a/src/osmo-bts-litecell15/utils.h b/src/osmo-bts-litecell15/utils.h new file mode 100644 index 00000000..a2a22348 --- /dev/null +++ b/src/osmo-bts-litecell15/utils.h @@ -0,0 +1,13 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include <stdint.h> +#include "lc15bts.h" + +struct gsm_bts_trx; + +int band_lc152osmo(GsmL1_FreqBand_t band); + +int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn); + +#endif diff --git a/src/osmo-bts-oc2g/Makefile.am b/src/osmo-bts-oc2g/Makefile.am new file mode 100644 index 00000000..7188626f --- /dev/null +++ b/src/osmo-bts-oc2g/Makefile.am @@ -0,0 +1,38 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OC2G_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) $(LIBSYSTEMD_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) + +AM_CFLAGS += -DENABLE_OC2GBTS + +EXTRA_DIST = misc/oc2gbts_mgr.h misc/oc2gbts_misc.h misc/oc2gbts_par.h misc/oc2gbts_led.h \ + misc/oc2gbts_temp.h misc/oc2gbts_power.h misc/oc2gbts_clock.h \ + misc/oc2gbts_bid.h misc/oc2gbts_nl.h \ + hw_misc.h l1_if.h l1_transp.h oc2gbts.h oml_router.h utils.h + +bin_PROGRAMS = osmo-bts-oc2g oc2gbts-mgr oc2gbts-util + +COMMON_SOURCES = main.c oc2gbts.c l1_if.c oml.c oc2gbts_vty.c tch.c hw_misc.c calib_file.c \ + utils.c misc/oc2gbts_par.c misc/oc2gbts_bid.c oml_router.c + +osmo_bts_oc2g_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_oc2g_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +oc2gbts_mgr_SOURCES = \ + misc/oc2gbts_mgr.c misc/oc2gbts_misc.c \ + misc/oc2gbts_par.c misc/oc2gbts_nl.c \ + misc/oc2gbts_temp.c misc/oc2gbts_power.c \ + misc/oc2gbts_clock.c misc/oc2gbts_bid.c \ + misc/oc2gbts_mgr_vty.c \ + misc/oc2gbts_mgr_nl.c \ + misc/oc2gbts_mgr_temp.c \ + misc/oc2gbts_mgr_calib.c \ + misc/oc2gbts_led.c \ + misc/oc2gbts_bts.c \ + misc/oc2gbts_swd.c + +oc2gbts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) + +oc2gbts_util_SOURCES = misc/oc2gbts_util.c misc/oc2gbts_par.c +oc2gbts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-oc2g/calib_file.c b/src/osmo-bts-oc2g/calib_file.c new file mode 100644 index 00000000..49768480 --- /dev/null +++ b/src/osmo-bts-oc2g/calib_file.c @@ -0,0 +1,469 @@ +/* NuRAN Wireless OC-2G BTS L1 calibration file routines*/ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#include <nrw/oc2g/oc2g.h> +#include <nrw/oc2g/gsml1const.h> + +#include "l1_if.h" +#include "oc2gbts.h" +#include "utils.h" +#include "osmo-bts/oml.h" + +/* Maximum calibration data chunk size */ +#define MAX_CALIB_TBL_SIZE 65536 +/* Calibration header version */ +#define CALIB_HDR_V1 0x01 + +struct calib_file_desc { + const char *fname; + int rx; + int trx; + int rxpath; +}; + +static const struct calib_file_desc calib_files[] = { + { + .fname = "calib_rx0.conf", + .rx = 1, + .trx = 0, + .rxpath = 0, + }, { + .fname = "calib_tx0.conf", + .rx = 0, + .trx = 0, + }, +}; + +struct calTbl_t +{ + union + { + struct + { + uint8_t u8Version; /* Header version (1) */ + uint8_t u8Parity; /* Parity byte (xor) */ + uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */ + uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */ + uint32_t u32Len; /* Table length in bytes including the header */ + struct + { + uint32_t u32DescOfst; /* Description section offset */ + uint32_t u32DateOfst; /* Date section offset */ + uint32_t u32StationOfst; /* Calibration test station section offset */ + uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */ + uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */ + uint32_t u32DataOfst; /* Calibration data section offset */ + } toc; + } v1; + } hdr; + + uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32]; +}; + + +static int calib_file_send(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc); +static int calib_verify(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc); + +/* determine next calibration file index based on supported bands */ +static int get_next_calib_file_idx(struct oc2gl1_hdl *fl1h, int last_idx) +{ + struct phy_link *plink = fl1h->phy_inst->phy_link; + int i; + + for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { + if (calib_files[i].trx == plink->num) + return i; + } + return -1; +} + +static int calib_file_open(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.oc2g.calib_path; + char fname[PATH_MAX]; + + if (st->fp) { + LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n"); + fclose(st->fp); + st->fp = NULL; + } + + fname[0] = '\0'; + snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname); + fname[sizeof(fname)-1] = '\0'; + + st->fp = fopen(fname, "rb"); + if (!st->fp) { + LOGP(DL1C, LOGL_NOTICE, "Failed to open '%s' for calibration data.\n", fname); + + /* TODO (oramadan): Fix OML alarms + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + alarm_sig_data.add_text = (char*)&fname[0]; + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_OPEN_CALIB_ALARM, &alarm_sig_data); + } + */ + return -1; + } + return 0; +} + +static int calib_file_close(struct oc2gl1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + + if (st->fp) { + fclose(st->fp); + st->fp = NULL; + } + 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 a chunk of calibration tabledata for a single specified file */ +static int calib_file_send_next_chunk(struct oc2gl1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + Oc2g_Prim_t *prim; + struct msgb *msg; + size_t n; + + msg = sysp_msgb_alloc(); + prim = msgb_sysprim(msg); + + prim->id = Oc2g_PrimId_SetCalibTblReq; + prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp); + n = fread(prim->u.setCalibTblReq.u8Data, 1, + sizeof(prim->u.setCalibTblReq.u8Data), st->fp); + prim->u.setCalibTblReq.length = n; + + + if (n == 0) { + /* The table data has been completely sent and acknowledged */ + LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n", + calib_files[st->last_file_idx].fname); + + calib_file_close(fl1h); + + msgb_free(msg); + + /* Send the next one if any */ + st->last_file_idx = get_next_calib_file_idx(fl1h, 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"); + return 0; + } + + return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL); +} + +/* send the calibration table for a single specified file */ +static int calib_file_send(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + int rc; + + rc = calib_file_open(fl1h, desc); + if (rc < 0) { + /* still, we'd like to continue trying to load + * calibration for all other bands */ + st->last_file_idx = get_next_calib_file_idx(fl1h, 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"); + return 0; + } + + rc = calib_verify(fl1h, desc); + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE,"Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc); + + /* TODO (oramadan): Fix OML alarms + if (fl1h->phy_inst->trx) { + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + alarm_sig_data.add_text = (char*)&desc->fname[0]; + memcpy(alarm_sig_data.spare, &rc, sizeof(int)); + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_VERIFY_CALIB_ALARM, &alarm_sig_data); + } + */ + + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + return 0; + + } + + LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname); + + return calib_file_send_next_chunk(fl1h); +} + +/* 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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct calib_send_state *st = &fl1h->st; + Oc2g_Prim_t *prim = msgb_sysprim(l1_msg); + + if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n"); + + msgb_free(l1_msg); + + calib_file_close(fl1h); + + /* Skip this one and try the next one */ + st->last_file_idx = get_next_calib_file_idx(fl1h, 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"); + return 0; + } + + msgb_free(l1_msg); + + /* Keep sending the calibration file data */ + return calib_file_send_next_chunk(fl1h); +} + +int calib_load(struct oc2gl1_hdl *fl1h) +{ + int rc; + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.oc2g.calib_path; + + if (!calib_path) { + LOGP(DL1C, LOGL_NOTICE, "Calibration file path not specified\n"); + + /* TODO (oramadan): Fix OML alarms + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_NO_CALIB_PATH_ALARM, &alarm_sig_data); + } + */ + return -1; + } + + rc = get_next_calib_file_idx(fl1h, -1); + if (rc < 0) { + return -1; + } + st->last_file_idx = rc; + + return calib_file_send(fl1h, &calib_files[st->last_file_idx]); +} + + +static int calib_verify(struct oc2gl1_hdl *fl1h, const struct calib_file_desc *desc) +{ + int rc, sz; + struct calib_send_state *st = &fl1h->st; + struct phy_link *plink = fl1h->phy_inst->phy_link; + char *rbuf; + struct calTbl_t *calTbl; + char calChkSum ; + + + /* calculate file size in bytes */ + fseek(st->fp, 0L, SEEK_END); + sz = ftell(st->fp); + + /* rewind read poiner */ + fseek(st->fp, 0L, SEEK_SET); + + /* read file */ + rbuf = (char *) malloc( sizeof(char) * sz ); + + rc = fread(rbuf, 1, sizeof(char) * sz, st->fp); + if (rc != sz) { + LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname); + free(rbuf); + + /* close file */ + rc = calib_file_close(fl1h); + if (rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname); + return rc; + } + + return -2; + } + + + calTbl = (struct calTbl_t*) rbuf; + /* calculate file checksum */ + calChkSum = 0; + while (sz--) { + calChkSum ^= rbuf[sz]; + } + + /* validate Tx calibration parity */ + if (calChkSum) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum); + return -4; + } + + /* validate Tx calibration header */ + if (calTbl->hdr.v1.u8Version != CALIB_HDR_V1) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version); + return -5; + } + + /* validate calibration description */ + if (calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname); + return -6; + } + + /* validate calibration date */ + if (calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname); + return -7; + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n", + desc->fname, + calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst); + + /* validate calibration station */ + if (calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname); + return -8; + } + + /* validate FPGA FW version */ + if (calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname); + return -9; + } + + /* validate DSP FW version */ + if (calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname); + return -10; + } + + /* validate Tx calibration data offset */ + if (calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname); + return -11; + } + + if (!desc->rx) { + + /* parse min/max Tx power */ + fl1h->phy_inst->u.oc2g.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)]; + fl1h->phy_inst->u.oc2g.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)]; + + /* override nominal Tx power of given TRX if needed */ + if (fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.oc2g.maxTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.oc2g.maxTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.oc2g.maxTxPower; + } + + if (fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.oc2g.minTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.oc2g.minTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower; + } + + if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower); + } + + if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.oc2g.minTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.oc2g.minTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.minTxPower); + } + + LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n", + desc->fname, + fl1h->phy_inst->u.oc2g.minTxPower, + fl1h->phy_inst->u.oc2g.maxTxPower ); + } + + /* rewind read pointer for subsequence tasks */ + fseek(st->fp, 0L, SEEK_SET); + free(rbuf); + + return 0; +} + diff --git a/src/osmo-bts-oc2g/hw_info.ver_major b/src/osmo-bts-oc2g/hw_info.ver_major new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/osmo-bts-oc2g/hw_info.ver_major diff --git a/src/osmo-bts-oc2g/hw_misc.c b/src/osmo-bts-oc2g/hw_misc.c new file mode 100644 index 00000000..31daf078 --- /dev/null +++ b/src/osmo-bts-oc2g/hw_misc.c @@ -0,0 +1,88 @@ +/* Misc HW routines for NuRAN Wireless OC-2G BTS */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/utils.h> + +#include "hw_misc.h" + +int oc2gbts_led_set(enum oc2gbts_led_color c) +{ + int fd, rc; + uint8_t cmd[2]; + + switch (c) { + case LED_OFF: + cmd[0] = 0; + cmd[1] = 0; + break; + case LED_RED: + cmd[0] = 1; + cmd[1] = 0; + break; + case LED_GREEN: + cmd[0] = 0; + cmd[1] = 1; + break; + case LED_ORANGE: + cmd[0] = 1; + cmd[1] = 1; + break; + default: + return -EINVAL; + } + + fd = open("/var/oc2g/leds/led0/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[0] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + + fd = open("/var/oc2g/leds/led1/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[1] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + return 0; +} diff --git a/src/osmo-bts-oc2g/hw_misc.h b/src/osmo-bts-oc2g/hw_misc.h new file mode 100644 index 00000000..b8f3332a --- /dev/null +++ b/src/osmo-bts-oc2g/hw_misc.h @@ -0,0 +1,13 @@ +#ifndef _HW_MISC_H +#define _HW_MISC_H + +enum oc2gbts_led_color { + LED_OFF, + LED_RED, + LED_GREEN, + LED_ORANGE, +}; + +int oc2gbts_led_set(enum oc2gbts_led_color c); + +#endif diff --git a/src/osmo-bts-oc2g/l1_if.c b/src/osmo-bts-oc2g/l1_if.c new file mode 100644 index 00000000..d8fdd968 --- /dev/null +++ b/src/osmo-bts-oc2g/l1_if.c @@ -0,0 +1,1755 @@ +/* Interface handler for NuRAN Wireless OC-2G L1 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011-2014 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/lapdm.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <nrw/oc2g/oc2g.h> +#include <nrw/oc2g/gsml1prim.h> +#include <nrw/oc2g/gsml1const.h> +#include <nrw/oc2g/gsml1types.h> + +#include "oc2gbts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "hw_misc.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_bid.h" +#include "utils.h" + +extern unsigned int dsp_trace; + +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 */ + HANDLE conf_hLayer3; /* layer 3 handle 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(oc2gbts_sysprim_names, wlc->conf_prim_id)); + else + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(oc2gbts_l1prim_names, wlc->conf_prim_id)); + exit(23); +} + +static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + +static int _l1if_req_compl(struct oc2gl1_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_DEBUG, "Tx L1 prim %s\n", + get_value_string(oc2gbts_l1prim_names, l1p->id)); + + if (oc2gbts_get_l1prim_type(l1p->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", + get_value_string(oc2gbts_l1prim_names, l1p->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 0; + wlc->conf_prim_id = oc2gbts_get_l1prim_conf(l1p->id); + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + wqueue = &fl1h->write_q[MQ_L1_WRITE]; + timeout_secs = 30; + } else { + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s\n", + get_value_string(oc2gbts_sysprim_names, sysp->id)); + + if (oc2gbts_get_sysprim_type(sysp->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", + get_value_string(oc2gbts_sysprim_names, sysp->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 1; + wlc->conf_prim_id = oc2gbts_get_sysprim_conf(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 oc2gl1_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 oc2gl1_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 Oc2g_Prim_t */ +struct msgb *sysp_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(Oc2g_Prim_t), "sys_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(Oc2g_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; +} + +/* fill PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +data_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_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 = (HANDLE)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 oc2gl1_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 = (HANDLE)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; +} + +/* fill frame PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +fill_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, + uint8_t block_nr) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + GsmL1_MsgUnitParam_t *msu_param; + uint8_t *l1_payload; + + msu_param = &data_req->msgUnitParam; + l1_payload = &msu_param->u8Buffer[0]; + l1p->id = GsmL1_PrimId_PhDataReq; + + memset(l1_payload, 0x2B, GSM_MACBLOCK_LEN); + /* address field */ + l1_payload[0] = 0x03; + /* control field */ + l1_payload[1] = 0x03; + /* length field */ + l1_payload[2] = 0x01; + + /* copy fields from PH-RTS.ind */ + data_req->hLayer1 = (HANDLE)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 = GSM_MACBLOCK_LEN; + + + LOGP(DL1C, LOGL_DEBUG, "Send fill frame on in none DTX mode Tn=%d, Fn=%d, SAPI=%d, SubCh=%d, BlockNr=%d dump=%s\n", + tn, + fn, + sapi, + sub_ch, + block_nr, + osmo_hexdump(data_req->msgUnitParam.u8Buffer, data_req->msgUnitParam.u8Size)); + + return data_req; +} + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + 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_CBCH(chan_nr)) { + sapi = GsmL1_Sapi_Cbch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = l1sap_fn2ccch_block(u32Fn); + if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ")) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN) + /* fill frame */ + fill_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + else { + if (lchan->ts->trx->bts->dtxd) + /* empty frame */ + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + else + /* fill frame */ + fill_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], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + 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)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* 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); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* 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 */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + 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); + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + return 0; +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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); + 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; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + 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; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct oc2gl1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + 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) { + msgb_free(msg); + 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; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Cbch: + cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */ + 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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + 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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + 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 oc2gl1_hdl *fl1, + GsmL1_PhReadyToSendInd_t *rts_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + 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], 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 = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + 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); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(oc2gbts_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) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + 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, 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_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + uint8_t *data, len; + int rc = 0; + int8_t rssi; + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + gsm_fn2gsmtime(&g_time, fn); + + if (!chan_nr) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + + process_meas_res(trx, chan_nr, &data_ind->measParam, fn); + + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), (uint32_t)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; + if (!pcu_direct) { + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + } + return l1sap_up(trx, l1sap); +} + +static int handle_ph_ra_ind(struct oc2gl1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */ + if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) { + msgb_free(l1p_msg); + return 0; + } + + dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + }; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = 0x88; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to + * osmo-bts-oc2g */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + 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 = rach_ind_param; + + return l1sap_up(trx, l1sap); +} + +/* handle any random indication from the L1 */ +static int l1if_handle_ind(struct oc2gl1_hdl *fl1, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + int rc = 0; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + return 0; + return 1; +} + +int l1if_handle_l1prim(int wq, struct oc2gl1_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(oc2gbts_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) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(oc2gl1_hdl_trx(fl1h), 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 oc2gl1_hdl *fl1h, struct msgb *msg) +{ + Oc2g_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(oc2gbts_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) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(oc2gl1_hdl_trx(fl1h), 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); +} + +static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + int on = 0; + unsigned int i; + struct gsm_bts *bts = trx->bts; + + if (sysp->id == Oc2g_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(oc2gbts_l1status_names, status)); + + + if (on) { + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", + get_value_string(oc2gbts_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; +} + +/* activate or de-activate the entire RF-Frontend */ +int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + struct phy_instance *pinst = hdl->phy_inst; + + if (on) { + sysp->id = Oc2g_PrimId_ActivateRfReq; + sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; + sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; + + sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.oc2g.pedestal_mode; + + /* maximum cell size in quarter-bits, 90 == 12.456 km */ + sysp->u.activateRfReq.u8MaxCellSize = pinst->u.oc2g.max_cell_size; + + /* auto tx power adjustment mode 0:none, 1: automatic*/ + sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.oc2g.tx_pwr_adj_mode; + + } else { + sysp->id = Oc2g_PrimId_DeactivateRfReq; + } + + return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); +} + +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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + Oc2g_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(oc2gbts_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(oc2gbts_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; +} + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_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] + ); + + sysp->id = Oc2g_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); +} + +/* 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) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; + struct oc2gl1_hdl *fl1h = trx_oc2gl1_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; + + LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n", + sic->dspVersion.major, sic->dspVersion.minor, + sic->dspVersion.build, sic->fpgaVersion.major, + sic->fpgaVersion.minor, sic->fpgaVersion.build); + + LOGP(DL1C, LOGL_INFO, "Band support %s", gsm_band_name(fl1h->hw_info.band_support)); + + 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); + + /* load calibration tables */ + rc = calib_load(fl1h); + if (rc < 0) + LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " + "unable to load tables!\n"); + + msgb_free(resp); + return 0; +} + +/* request DSP+FPGA code versions */ +static int l1if_get_info(struct oc2gl1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = Oc2g_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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + Oc2g_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(oc2gbts_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(oc2gbts_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 oc2gl1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = Oc2g_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 oc2gl1_hdl *hdl, uint32_t flags) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", + flags); + + sysp->id = Oc2g_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; +} + +static int get_hwinfo(struct oc2gl1_hdl *fl1h) +{ + int rc = 0; + char rev_maj = 0, rev_min = 0; + + oc2gbts_rev_get(&rev_maj, &rev_min); + if (rc < 0) + return rc; + fl1h->hw_info.ver_major = (uint8_t)rev_maj; + fl1h->hw_info.ver_minor = (uint8_t)rev_min; + + rc = oc2gbts_model_get(); + if (rc < 0) + return rc; + fl1h->hw_info.options = (uint32_t)rc; + + rc = oc2gbts_option_get(OC2GBTS_OPTION_BAND); + if (rc < 0) + return rc; + + switch (rc) { + case OC2GBTS_BAND_850: + fl1h->hw_info.band_support = GSM_BAND_850; + break; + case OC2GBTS_BAND_900: + fl1h->hw_info.band_support = GSM_BAND_900; + break; + case OC2GBTS_BAND_1800: + fl1h->hw_info.band_support = GSM_BAND_1800; + break; + case OC2GBTS_BAND_1900: + fl1h->hw_info.band_support = GSM_BAND_1900; + break; + default: + return -1; + } + return 0; +} + +struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst) +{ + struct oc2gl1_hdl *fl1h; + int rc; + + LOGP(DL1C, LOGL_INFO, "OC-2G BTS L1IF compiled against API headers " + "v%u.%u.%u\n", OC2G_API_VERSION >> 16, + (OC2G_API_VERSION >> 8) & 0xff, + OC2G_API_VERSION & 0xff); + + fl1h = talloc_zero(pinst, struct oc2gl1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.oc2g.dsp_trace_f; + + get_hwinfo(fl1h); + + 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 oc2gl1_hdl *fl1h) +{ + l1if_transport_close(MQ_L1_WRITE, fl1h); + l1if_transport_close(MQ_SYS_WRITE, fl1h); + return 0; +} + +/* TODO(oramadan) MERGE */ +#ifdef MERGE_ME +static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_IsAliveCnf_t *sac = &sysp->u.isAliveCnf; + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + + fl1h->hw_alive.dsp_alive_cnt++; + LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n", + get_value_string(oc2gbts_sysprim_names, sysp->id), sac->status, trx->nr); + + msgb_free(resp); +} + +static int dsp_alive_timer_cb(void *data) +{ + struct oc2gl1_hdl *fl1h = data; + struct gsm_bts_trx *trx = fl1h->phy_inst->trx; + struct msgb *msg = sysp_msgb_alloc(); + int rc; + struct oml_alarm_list *alarm_sent; + + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_IsAliveReq; + + if (fl1h->hw_alive.dsp_alive_cnt == 0) { + /* check for the alarm has already sent or not */ + llist_for_each_entry(alarm_sent, &fl1h->alarm_list, list) { + llist_del(&alarm_sent->list); + if (alarm_sent->alarm_signal != S_NM_OML_BTS_DSP_ALIVE_ALARM) + continue; + + LOGP(DL1C, LOGL_ERROR, "Alarm %d has removed from sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr); + exit(23); + } + + LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n", + get_value_string(oc2gbts_sysprim_names, sys_prim->id + 1), trx->nr); + + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + memcpy(alarm_sig_data.spare, &sys_prim->id, sizeof(unsigned int)); + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_DSP_ALIVE_ALARM, &alarm_sig_data); + if (!alarm_sig_data.rc) { + /* allocate new list of sent alarms */ + alarm_sent = talloc_zero(fl1h, struct oml_alarm_list); + if (!alarm_sent) + return -EIO; + + alarm_sent->alarm_signal = S_NM_OML_BTS_DSP_ALIVE_ALARM; + /* add alarm to sent list */ + llist_add(&alarm_sent->list, &fl1h->alarm_list); + LOGP(DL1C, LOGL_ERROR, "Alarm %d has added to sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr); + } + } + } + + LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n", + get_value_string(oc2gbts_sysprim_names, sys_prim->id), trx->nr); + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id)); + return -EIO; + } + + /* restart timer */ + fl1h->hw_alive.dsp_alive_cnt = 0; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); + + return 0; +} +#endif + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + + OSMO_ASSERT(pinst); + + if (!pinst->trx) { + LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d " + "because no TRX is associated with it\n", plink->num, pinst->num); + return 0; + } + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.oc2g.hdl = l1if_open(pinst); + if (!pinst->u.oc2g.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + /* Set default PHY parameters */ + if (!pinst->u.oc2g.max_cell_size) + pinst->u.oc2g.max_cell_size = OC2G_BTS_MAX_CELL_SIZE_DEFAULT; + + if (!pinst->u.oc2g.pedestal_mode) + pinst->u.oc2g.pedestal_mode = OC2G_BTS_PEDESTAL_MODE_DEFAULT; + + if (!pinst->u.oc2g.dsp_alive_period) + pinst->u.oc2g.dsp_alive_period = OC2G_BTS_DSP_ALIVE_TMR_DEFAULT; + + if (!pinst->u.oc2g.tx_pwr_adj_mode) + pinst->u.oc2g.tx_pwr_adj_mode = OC2G_BTS_TX_PWR_ADJ_DEFAULT; + + if (!pinst->u.oc2g.tx_pwr_red_8psk) + pinst->u.oc2g.tx_pwr_red_8psk = OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT; + + if (!pinst->u.oc2g.tx_c0_idle_pwr_red) + pinst->u.oc2g.tx_c0_idle_pwr_red = OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT; + + struct oc2gl1_hdl *fl1h = pinst->u.oc2g.hdl; + fl1h->dsp_trace_f = dsp_trace; + + l1if_reset(pinst->u.oc2g.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + /* TODO (oramadan) MERGE) + / * Send first IS_ALIVE primitive * / + struct msgb *msg = sysp_msgb_alloc(); + int rc; + + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_IsAliveReq; + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id)); + return -EIO; + } + + /* initialize DSP heart beat alive timer * / + fl1h->hw_alive.dsp_alive_timer.cb = dsp_alive_timer_cb; + fl1h->hw_alive.dsp_alive_timer.data = fl1h; + fl1h->hw_alive.dsp_alive_cnt = 0; + fl1h->hw_alive.dsp_alive_period = pinst->u.oc2g.dsp_alive_period; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); */ + return 0; +} diff --git a/src/osmo-bts-oc2g/l1_if.h b/src/osmo-bts-oc2g/l1_if.h new file mode 100644 index 00000000..38699e01 --- /dev/null +++ b/src/osmo-bts-oc2g/l1_if.h @@ -0,0 +1,145 @@ +#ifndef _L1_IF_H +#define _L1_IF_H + +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/timer.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/phy_link.h> + +#include <nrw/oc2g/gsml1prim.h> +#include <nrw/oc2g/gsml1types.h> + +#include <stdbool.h> + +enum { + MQ_SYS_READ, + MQ_L1_READ, + MQ_TCH_READ, + MQ_PDTCH_READ, + _NUM_MQ_READ +}; + +enum { + MQ_SYS_WRITE, + MQ_L1_WRITE, + MQ_TCH_WRITE, + MQ_PDTCH_WRITE, + _NUM_MQ_WRITE +}; + +struct calib_send_state { + FILE *fp; + const char *path; + int last_file_idx; +}; + +struct oc2gl1_hdl { + struct gsm_time gsm_time; + HANDLE hLayer1; /* handle to the L1 instance in the DSP */ + uint32_t dsp_trace_f; /* currently operational DSP trace flags */ + struct llist_head wlc_list; + struct llist_head alarm_list; /* list of sent alarms */ + + struct phy_instance *phy_inst; + + 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; + uint8_t ver_major; + uint8_t ver_minor; + uint32_t options; + } hw_info; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; + + struct { + struct osmo_timer_list dsp_alive_timer; + unsigned int dsp_alive_cnt; + uint8_t dsp_alive_period; + } hw_alive; +}; + +#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) +#define msgb_sysprim(msg) ((Oc2g_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 oc2gl1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); +int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); + +struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst); +int l1if_close(struct oc2gl1_hdl *hdl); +int l1if_reset(struct oc2gl1_hdl *hdl); +int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on); +int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags); +int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power); +int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff); +int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red); +int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size); +int l1if_mute_rf(struct oc2gl1_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 */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +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, uint32_t fn); + +/* ciphering */ +int l1if_set_ciphering(struct oc2gl1_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 oc2gl1_hdl *fl1h); + +/* public helpers for test */ +int bts_check_for_ciph_cmd(struct oc2gl1_hdl *fl1h, + struct msgb *msg, struct gsm_lchan *lchan); +int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, + const uint8_t ms_power, const float rxLevel); + +static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.oc2g.hdl; +} + +static inline struct gsm_bts_trx *oc2gl1_hdl_trx(struct oc2gl1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} + +#endif /* _L1_IF_H */ diff --git a/src/osmo-bts-oc2g/l1_transp.h b/src/osmo-bts-oc2g/l1_transp.h new file mode 100644 index 00000000..5af79dcb --- /dev/null +++ b/src/osmo-bts-oc2g/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _L1_TRANSP_H +#define _L1_TRANSP_H + +#include <osmocom/core/msgb.h> + +/* functions a transport calls on arrival of primitive from BTS */ +int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg); +int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg); + +/* functions exported by a transport */ +int l1if_transport_open(int q, struct oc2gl1_hdl *fl1h); +int l1if_transport_close(int q, struct oc2gl1_hdl *fl1h); + +#endif /* _L1_TRANSP_H */ diff --git a/src/osmo-bts-oc2g/l1_transp_hw.c b/src/osmo-bts-oc2g/l1_transp_hw.c new file mode 100644 index 00000000..e1d46581 --- /dev/null +++ b/src/osmo-bts-oc2g/l1_transp_hw.c @@ -0,0 +1,326 @@ +/* Interface handler for Nuran Wireless OC-2G L1 (real hardware) */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <assert.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <nrw/oc2g/oc2g.h> +#include <nrw/oc2g/gsml1prim.h> +#include <nrw/oc2g/gsml1const.h> +#include <nrw/oc2g/gsml1types.h> + +#include "oc2gbts.h" +#include "l1_if.h" +#include "l1_transp.h" + + +#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/oc2g_dsp2arm_trx" +#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/oc2g_arm2dsp_trx" +#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx" +#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx" + +#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx" +#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx" +#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx" +#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx" + +static const char *rd_devnames[] = { + [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME, + [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME, + [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME, + [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME, +}; + +static const char *wr_devnames[] = { + [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME, + [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME, + [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME, + [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME, +}; + +/* + * Make sure that all structs we read fit into the OC2GBTS_PRIM_SIZE + */ +osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, l1_prim) +osmo_static_assert(sizeof(Oc2g_Prim_t) + 128 <= OC2GBTS_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(Oc2g_Prim_t); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + 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 oc2gl1_hdl *fl1h, struct msgb *msg, int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return l1if_handle_sysprim(fl1h, msg); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + 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)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + 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 oc2gl1_hdl *hdl) +{ + struct phy_link *plink = hdl->phy_inst->phy_link; + int rc; + char buf[PATH_MAX]; + + /* 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; + + snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_RDONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, 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; + } + + snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_WRONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, 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 oc2gl1_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; +} diff --git a/src/osmo-bts-oc2g/main.c b/src/osmo-bts-oc2g/main.c new file mode 100644 index 00000000..574bc723 --- /dev/null +++ b/src/osmo-bts-oc2g/main.c @@ -0,0 +1,242 @@ +/* Main program for NuRAN Wireless OC-2G BTS */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/l1sap.h> + +static int write_status_file(char *status_file, char *status_str) +{ + FILE *outf; + char tmp[PATH_MAX+1]; + + snprintf(tmp, sizeof(tmp)-1, "/var/run/osmo-bts/%s", status_file); + tmp[PATH_MAX-1] = '\0'; + + outf = fopen(tmp, "w"); + if (!outf) + return -1; + + fprintf(outf, "%s\n", status_str); + + fclose(outf); + + return 0; +} + +/*NTQD: Change how rx_nr is handle in multi-trx*/ +#define OC2GBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" + +#include "utils.h" +#include "l1_if.h" +#include "hw_misc.h" +#include "oml_router.h" +#include "misc/oc2gbts_bid.h" + +unsigned int dsp_trace = 0x00000000; + +int bts_model_init(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct stat st; + static struct osmo_fd accept_fd, read_fd; + int rc; + + bts->variant = BTS_OSMO_OC2G; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + /* specific default values for OC2G platform */ + + /* TODO(oramadan) MERGE + bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT; + /* RTP drift threshold default * / + bts->oc2g.rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT; + */ + + 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 (stat(OC2GBTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + trx->nominal_power = 40; + trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power); + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + /* update status file */ + write_status_file("state", ""); + + return 0; +} + +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); + + oc2gbts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF); +} + +void bts_model_print_help() +{ + printf( " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" + ); +} + +static void print_hwversion() +{ + printf(get_hwversion_desc()); + printf("\n"); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "dsp-trace", 1, 0, 'p' }, + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "p:wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'p': + dsp_trace = strtoul(optarg, NULL, 16); + break; + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* write to status file */ + write_status_file("state", "ABIS DOWN"); + + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + /* create status file with initial state */ + write_status_file("state", "ABIS DOWN"); + + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c new file mode 100644 index 00000000..6eaa9c69 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c @@ -0,0 +1,175 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#include "oc2gbts_bid.h" + +#define BOARD_REV_MAJ_SYSFS "/var/oc2g/platform/rev/major" +#define BOARD_REV_MIN_SYSFS "/var/oc2g/platform/rev/minor" +#define BOARD_OPT_SYSFS "/var/oc2g/platform/option" + +static const int option_type_mask[_NUM_OPTION_TYPES] = { + [OC2GBTS_OPTION_OCXO] = 0x07, + [OC2GBTS_OPTION_FPGA] = 0x03, + [OC2GBTS_OPTION_PA] = 0x01, + [OC2GBTS_OPTION_BAND] = 0x03, + [OC2GBTS_OPTION_TX_ATT] = 0x01, + [OC2GBTS_OPTION_TX_FIL] = 0x01, + [OC2GBTS_OPTION_RX_ATT] = 0x01, + [OC2GBTS_OPTION_RMS_FWD] = 0x01, + [OC2GBTS_OPTION_RMS_REFL] = 0x01, + [OC2GBTS_OPTION_DDR_32B] = 0x01, + [OC2GBTS_OPTION_DDR_ECC] = 0x01, + [OC2GBTS_OPTION_PA_TEMP] = 0x01, +}; + +static const int option_type_shift[_NUM_OPTION_TYPES] = { + [OC2GBTS_OPTION_OCXO] = 0, + [OC2GBTS_OPTION_FPGA] = 3, + [OC2GBTS_OPTION_PA] = 5, + [OC2GBTS_OPTION_BAND] = 6, + [OC2GBTS_OPTION_TX_ATT] = 8, + [OC2GBTS_OPTION_TX_FIL] = 9, + [OC2GBTS_OPTION_RX_ATT] = 10, + [OC2GBTS_OPTION_RMS_FWD] = 11, + [OC2GBTS_OPTION_RMS_REFL] = 12, + [OC2GBTS_OPTION_DDR_32B] = 13, + [OC2GBTS_OPTION_DDR_ECC] = 14, + [OC2GBTS_OPTION_PA_TEMP] = 15, +}; + + +static char board_rev_maj = -1; +static char board_rev_min = -1; +static int board_option = -1; + +void oc2gbts_rev_get(char *rev_maj, char *rev_min) +{ + FILE *fp; + char rev; + + *rev_maj = 0; + *rev_min = 0; + + if (board_rev_maj != -1 && board_rev_min != -1) { + *rev_maj = board_rev_maj; + *rev_min = board_rev_min; + } + + fp = fopen(BOARD_REV_MAJ_SYSFS, "r"); + if (fp == NULL) return; + + if (fscanf(fp, "%c", &rev) != 1) { + fclose(fp); + return; + } + + fclose(fp); + + *rev_maj = board_rev_maj = rev; + + fp = fopen(BOARD_REV_MIN_SYSFS, "r"); + if (fp == NULL) return; + + if (fscanf(fp, "%c", &rev) != 1) { + fclose(fp); + return; + } + + fclose(fp); + + *rev_min = board_rev_min = rev; +} + +const char* get_hwversion_desc() +{ + int rev; + int model; + size_t len; + static char model_name[64] = {0, }; + len = snprintf(model_name, sizeof(model_name), "NuRAN OC-2G BTS"); + + char rev_maj = 0, rev_min = 0; + + int rc = 0; + oc2gbts_rev_get(&rev_maj, &rev_min); + if (rc < 0) + return rc; + if (rev >= 0) { + len += snprintf(model_name + len, sizeof(model_name) - len, + " Rev %d.%d", (uint8_t)rev_maj, (uint8_t)rev_min); + } + + model = oc2gbts_model_get(); + if (model >= 0) { + snprintf(model_name + len, sizeof(model_name) - len, + "%s (%05X)", model_name, model); + } + return model_name; +} + +int oc2gbts_model_get(void) +{ + FILE *fp; + int opt; + + + if (board_option == -1) { + fp = fopen(BOARD_OPT_SYSFS, "r"); + if (fp == NULL) { + return -1; + } + + if (fscanf(fp, "%X", &opt) != 1) { + fclose( fp ); + return -1; + } + fclose(fp); + + board_option = opt; + } + return board_option; +} + +int oc2gbts_option_get(enum oc2gbts_option_type type) +{ + int rc; + int option; + + if (type >= _NUM_OPTION_TYPES) { + return -EINVAL; + } + + if (board_option == -1) { + rc = oc2gbts_model_get(); + if (rc < 0) return rc; + } + + option = (board_option >> option_type_shift[type]) + & option_type_mask[type]; + + return option; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.h b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h new file mode 100644 index 00000000..00cf3899 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h @@ -0,0 +1,47 @@ +#ifndef _OC2GBTS_BOARD_H +#define _OC2GBTS_BOARD_H + +#include <stdint.h> + +enum oc2gbts_option_type { + OC2GBTS_OPTION_OCXO, + OC2GBTS_OPTION_FPGA, + OC2GBTS_OPTION_PA, + OC2GBTS_OPTION_BAND, + OC2GBTS_OPTION_TX_ATT, + OC2GBTS_OPTION_TX_FIL, + OC2GBTS_OPTION_RX_ATT, + OC2GBTS_OPTION_RMS_FWD, + OC2GBTS_OPTION_RMS_REFL, + OC2GBTS_OPTION_DDR_32B, + OC2GBTS_OPTION_DDR_ECC, + OC2GBTS_OPTION_PA_TEMP, + _NUM_OPTION_TYPES +}; + +enum oc2gbts_ocxo_type { + OC2GBTS_OCXO_BILAY_NVG45AV2072, + OC2GBTS_OCXO_TAITIEN_NJ26M003, + _NUM_OCXO_TYPES +}; + +enum oc2gbts_fpga_type { + OC2GBTS_FPGA_35T, + OC2GBTS_FPGA_50T, + OC2GBTS_FPGA_75T, + OC2GBTS_FPGA_100T, + _NUM_FPGA_TYPES +}; + +enum oc2gbts_gsm_band { + OC2GBTS_BAND_850, + OC2GBTS_BAND_900, + OC2GBTS_BAND_1800, + OC2GBTS_BAND_1900, +}; + +void oc2gbts_rev_get(char *rev_maj, char *rev_min); +int oc2gbts_model_get(void); +int oc2gbts_option_get(enum oc2gbts_option_type type); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c new file mode 100644 index 00000000..b3dae76e --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c @@ -0,0 +1,129 @@ +/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <sys/ioctl.h> +#include <net/if.h> +#include <netdb.h> +#include <ifaddrs.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include "oc2gbts_mgr.h" +#include "oc2gbts_bts.h" + +static int check_eth_status(char *dev_name) +{ + int fd, rc; + struct ifreq ifr; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + return fd; + + memset(&ifr, 0, sizeof(ifr)); + memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); + rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + + if (rc < 0) + return rc; + + if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING)) + return 0; + + return 1; +} + +void check_bts_led_pattern(uint8_t *led) +{ + FILE *fp; + char str[64] = "\0"; + int rc; + + /* check for existing of BTS state file */ + if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) { + led[BLINK_PATTERN_INIT] = 1; + return; + } + + /* check Ethernet interface status */ + rc = check_eth_status("eth0"); + if (rc > 0) { + LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n"); + led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS is still alive */ + if (system("pidof osmo-bts-oc2g > /dev/null")) { + LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n"); + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS state */ + while (fgets(str, 64, fp) != NULL) { + LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP"); + if (strstr(str, "ABIS DOWN") != NULL) + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + } + fclose(fp); + + return; +} + +int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led) +{ + if(mgr->alarms.temp_high == 1) + led[BLINK_PATTERN_TEMP_HIGH] = 1; + + if(mgr->alarms.temp_max == 1) + led[BLINK_PATTERN_TEMP_MAX] = 1; + + if(mgr->alarms.supply_low == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1; + + if(mgr->alarms.supply_min == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1; + + if(mgr->alarms.vswr_high == 1) + led[BLINK_PATTERN_VSWR_HIGH] = 1; + + if(mgr->alarms.vswr_max == 1) + led[BLINK_PATTERN_VSWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_high == 1) + led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1; + + if(mgr->alarms.supply_pwr_max == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1; + + if(mgr->alarms.pa_pwr_high == 1) + led[BLINK_PATTERN_PA_PWR_HIGH] = 1; + + if(mgr->alarms.pa_pwr_max == 1) + led[BLINK_PATTERN_PA_PWR_MAX] = 1; + + if(mgr->alarms.gps_fix_lost == 1) + led[BLINK_PATTERN_GPS_FIX_LOST] = 1; + + return 0; +} + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.h b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h new file mode 100644 index 00000000..b003bdcd --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h @@ -0,0 +1,21 @@ +#ifndef _OC2GBTS_BTS_H_ +#define _OC2GBTS_BTS_H_ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <osmo-bts/logging.h> + +/* public function prototypes */ +void check_bts_led_pattern(uint8_t *led); +int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c new file mode 100644 index 00000000..acbbbc5c --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c @@ -0,0 +1,263 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#include "oc2gbts_clock.h" + +#define CLKERR_ERR_SYSFS "/var/oc2g/clkerr/clkerr1_average" +#define CLKERR_ACC_SYSFS "/var/oc2g/clkerr/clkerr1_average_accuracy" +#define CLKERR_INT_SYSFS "/var/oc2g/clkerr/clkerr1_average_interval" +#define CLKERR_FLT_SYSFS "/var/oc2g/clkerr/clkerr1_fault" +#define CLKERR_RFS_SYSFS "/var/oc2g/clkerr/refresh" +#define CLKERR_RST_SYSFS "/var/oc2g/clkerr/reset" + +#define OCXODAC_VAL_SYSFS "/var/oc2g/ocxo/voltage" +#define OCXODAC_ROM_SYSFS "/var/oc2g/ocxo/eeprom" + +/* clock error */ +static int clkerr_fd_err = -1; +static int clkerr_fd_accuracy = -1; +static int clkerr_fd_interval = -1; +static int clkerr_fd_fault = -1; +static int clkerr_fd_refresh = -1; +static int clkerr_fd_reset = -1; + +/* ocxo dac */ +static int ocxodac_fd_value = -1; +static int ocxodac_fd_save = -1; + + +static int sysfs_read_val(int fd, int *val) +{ + int rc; + char szVal[32] = {0}; + + lseek( fd, 0, SEEK_SET ); + + rc = read(fd, szVal, sizeof(szVal) - 1); + if (rc < 0) { + return -errno; + } + + rc = sscanf(szVal, "%d", val); + if (rc != 1) { + return -1; + } + + return 0; +} + +static int sysfs_write_val(int fd, int val) +{ + int n, rc; + char szVal[32] = {0}; + + n = sprintf(szVal, "%d", val); + + lseek(fd, 0, SEEK_SET); + rc = write(fd, szVal, n+1); + if (rc < 0) { + return -errno; + } + return 0; +} + +static int sysfs_write_str(int fd, const char *str) +{ + int rc; + + lseek( fd, 0, SEEK_SET ); + rc = write(fd, str, strlen(str)+1); + if (rc < 0) { + return -errno; + } + return 0; +} + + +int oc2gbts_clock_err_open(void) +{ + int rc; + int fault; + + if (clkerr_fd_err < 0) { + clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY); + if (clkerr_fd_err < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_err; + } + } + + if (clkerr_fd_accuracy < 0) { + clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY); + if (clkerr_fd_accuracy < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_accuracy; + } + } + + if (clkerr_fd_interval < 0) { + clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY); + if (clkerr_fd_interval < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_interval; + } + } + + if (clkerr_fd_fault < 0) { + clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY); + if (clkerr_fd_fault < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_fault; + } + } + + if (clkerr_fd_refresh < 0) { + clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY); + if (clkerr_fd_refresh < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_refresh; + } + } + + if (clkerr_fd_reset < 0) { + clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY); + if (clkerr_fd_reset < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_reset; + } + } + return 0; +} + +void oc2gbts_clock_err_close(void) +{ + if (clkerr_fd_err >= 0) { + close(clkerr_fd_err); + clkerr_fd_err = -1; + } + + if (clkerr_fd_accuracy >= 0) { + close(clkerr_fd_accuracy); + clkerr_fd_accuracy = -1; + } + + if (clkerr_fd_interval >= 0) { + close(clkerr_fd_interval); + clkerr_fd_interval = -1; + } + + if (clkerr_fd_fault >= 0) { + close(clkerr_fd_fault); + clkerr_fd_fault = -1; + } + + if (clkerr_fd_refresh >= 0) { + close(clkerr_fd_refresh); + clkerr_fd_refresh = -1; + } + + if (clkerr_fd_reset >= 0) { + close(clkerr_fd_reset); + clkerr_fd_reset = -1; + } +} + +int oc2gbts_clock_err_reset(void) +{ + return sysfs_write_val(clkerr_fd_reset, 1); +} + +int oc2gbts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec) +{ + int rc; + + rc = sysfs_write_str(clkerr_fd_refresh, "once"); + if (rc < 0) { + return -1; + } + + rc = sysfs_read_val(clkerr_fd_fault, fault); + rc |= sysfs_read_val(clkerr_fd_err, error_ppt); + rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq); + rc |= sysfs_read_val(clkerr_fd_interval, interval_sec); + if (rc) { + return -1; + } + return 0; +} + + +int oc2gbts_clock_dac_open(void) +{ + if (ocxodac_fd_value < 0) { + ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR); + if (ocxodac_fd_value < 0) { + oc2gbts_clock_dac_close(); + return ocxodac_fd_value; + } + } + + if (ocxodac_fd_save < 0) { + ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY); + if (ocxodac_fd_save < 0) { + oc2gbts_clock_dac_close(); + return ocxodac_fd_save; + } + } + return 0; +} + +void oc2gbts_clock_dac_close(void) +{ + if (ocxodac_fd_value >= 0) { + close(ocxodac_fd_value); + ocxodac_fd_value = -1; + } + + if (ocxodac_fd_save >= 0) { + close(ocxodac_fd_save); + ocxodac_fd_save = -1; + } +} + +int oc2gbts_clock_dac_get(int *dac_value) +{ + return sysfs_read_val(ocxodac_fd_value, dac_value); +} + +int oc2gbts_clock_dac_set(int dac_value) +{ + return sysfs_write_val(ocxodac_fd_value, dac_value); +} + +int oc2gbts_clock_dac_save(void) +{ + return sysfs_write_val(ocxodac_fd_save, 1); +} + + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.h b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h new file mode 100644 index 00000000..1444b5cf --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h @@ -0,0 +1,16 @@ +#ifndef _OC2GBTS_CLOCK_H +#define _OC2GBTS_CLOCK_H + +int oc2gbts_clock_err_open(void); +void oc2gbts_clock_err_close(void); +int oc2gbts_clock_err_reset(void); +int oc2gbts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec); + +int oc2gbts_clock_dac_open(void); +void oc2gbts_clock_dac_close(void); +int oc2gbts_clock_dac_get(int *dac_value); +int oc2gbts_clock_dac_set(int dac_value); +int oc2gbts_clock_dac_save(void); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c new file mode 100644 index 00000000..b8758b8e --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c @@ -0,0 +1,332 @@ +/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include "oc2gbts_led.h" +#include "oc2gbts_bts.h" +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +static struct oc2gbts_led led_entries[] = { + { + .name = "led0", + .fullname = "led red", + .path = "/var/oc2g/leds/led0/brightness" + }, + { + .name = "led1", + .fullname = "led green", + .path = "/var/oc2g/leds/led1/brightness" + } +}; + +static const struct value_string oc2gbts_led_strs[] = { + { OC2GBTS_LED_RED, "LED red" }, + { OC2GBTS_LED_GREEN, "LED green" }, + { OC2GBTS_LED_ORANGE, "LED orange" }, + { OC2GBTS_LED_OFF, "LED off" }, + { 0, NULL } +}; + +static uint8_t led_priority[] = { + BLINK_PATTERN_INIT, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_NORMAL +}; + + +char *blink_pattern_command[] = BLINK_PATTERN_COMMAND; + +static int oc2gbts_led_write(char *path, char *str) +{ + int fd; + + if ((fd = open(path, O_WRONLY)) == -1) + { + return 0; + } + + write(fd, str, strlen(str)+1); + close(fd); + return 1; +} + +static void led_set_red() +{ + oc2gbts_led_write(led_entries[0].path, "1"); + oc2gbts_led_write(led_entries[1].path, "0"); +} + +static void led_set_green() +{ + oc2gbts_led_write(led_entries[0].path, "0"); + oc2gbts_led_write(led_entries[1].path, "1"); +} + +static void led_set_orange() +{ + oc2gbts_led_write(led_entries[0].path, "1"); + oc2gbts_led_write(led_entries[1].path, "1"); +} + +static void led_set_off() +{ + oc2gbts_led_write(led_entries[0].path, "0"); + oc2gbts_led_write(led_entries[1].path, "0"); +} + +static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) { + /* Cancel any pending timer */ + osmo_timer_del(&led_timer->timer); + /* Start LED timer */ + led_timer->timer.cb = led_timer_cb; + led_timer->timer.data = mgr; + mgr->oc2gbts_leds.active_timer = led_timer->idx; + osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec); + LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_timer->idx), + led_timer->param.sleep_sec, + led_timer->param.sleep_usec); + + switch (led_timer->idx) { + case OC2GBTS_LED_RED: + led_set_red(); + break; + case OC2GBTS_LED_GREEN: + led_set_green(); + break; + case OC2GBTS_LED_ORANGE: + led_set_orange(); + break; + case OC2GBTS_LED_OFF: + led_set_off(); + break; + default: + led_set_off(); + } +} + +static void led_sleep_cb(void *_data) { + struct oc2gbts_mgr_instance *mgr = _data; + struct oc2gbts_led_timer_list *led_list; + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->oc2gbts_leds.list)) + return; + + llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) { + if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) { + LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + /* Rotate the timer list */ + llist_move_tail(led_list, &mgr->oc2gbts_leds.list); + break; + } + } + + /* Execute next timer */ + led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->oc2gbts_leds.list)); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr) +{ + struct oc2gbts_led_timer_list *led_list, *led_list2; + + if (llist_empty(&mgr->oc2gbts_leds.list)) + return; + + llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_leds.list, list) { + /* Delete the timer in list */ + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + llist_del(&led_list->list); + talloc_free(led_list); + } + } + return; +} + +static int add_led_timer_entry(struct oc2gbts_mgr_instance *mgr, char *cmdstr) +{ + double sec, int_sec, frac_sec; + struct oc2gbts_sleep_time led_param; + + led_param.sleep_sec = 0; + led_param.sleep_usec = 0; + + if (strstr(cmdstr, "set red") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED; + else if (strstr(cmdstr, "set green") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN; + else if (strstr(cmdstr, "set orange") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE; + else if (strstr(cmdstr, "set off") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_OFF; + else if (strstr(cmdstr, "sleep") != NULL) { + sec = atof(cmdstr + 6); + /* split time into integer and fractional of seconds */ + frac_sec = modf(sec, &int_sec) * 1000000.0; + led_param.sleep_sec = (int)int_sec; + led_param.sleep_usec = (int)frac_sec; + + if ((mgr->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) { + struct oc2gbts_led_timer_list *led_list; + + /* allocate timer entry */ + led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list); + if (led_list) { + led_list->led_timer.idx = mgr->oc2gbts_leds.led_idx; + led_list->led_timer.param.sleep_sec = led_param.sleep_sec; + led_list->led_timer.param.sleep_usec = led_param.sleep_usec; + llist_add_tail(&led_list->list, &mgr->oc2gbts_leds.list); + + LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n", + get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->oc2gbts_leds.list)); + } + } + } else + return -1; + + return 0; +} + +static int parse_led_pattern(char *pattern, struct oc2gbts_mgr_instance *mgr) +{ + char str[1024]; + char *pstr; + char *sep; + int rc = 0; + + strcpy(str, pattern); + pstr = str; + while ((sep = strsep(&pstr, ";")) != NULL) { + rc = add_led_timer_entry(mgr, sep); + if (rc < 0) { + break; + } + + } + return rc; +} + +/*** led interface ***/ + +void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id) +{ + int rc; + struct oc2gbts_led_timer_list *led_list; + + if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) { + LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n", + pattern_id, + BLINK_PATTERN_POWER_ON, + BLINK_PATTERN_MAX_ITEM - 1); + return; + } + if (pattern_id == mgr->oc2gbts_leds.last_pattern_id) + return; + + mgr->oc2gbts_leds.last_pattern_id = pattern_id; + + LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id); + LOGP(DTEMP, LOGL_INFO, "%s\n", blink_pattern_command[pattern_id]); + + /* Empty existing LED timer in the list */ + delete_led_timer_entries(mgr); + + /* parse LED pattern */ + rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n"); + return; + } + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->oc2gbts_leds.list)) + return; + + /* Start the first LED timer in the list */ + led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +void select_led_pattern(struct oc2gbts_mgr_instance *mgr) +{ + int i; + uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0}; + + /* set normal LED pattern at first */ + led[BLINK_PATTERN_NORMAL] = 1; + + /* check on-board sensors for new LED pattern */ + check_sensor_led_pattern(mgr, led); + + /* check BTS status for new LED pattern */ + check_bts_led_pattern(led); + + /* check by priority */ + for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) { + if(led[led_priority[i]] == 1) { + led_set(mgr, led_priority[i]); + break; + } + } +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.h b/src/osmo-bts-oc2g/misc/oc2gbts_led.h new file mode 100644 index 00000000..cb627e3c --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.h @@ -0,0 +1,22 @@ +#ifndef _OC2GBTS_LED_H +#define _OC2GBTS_LED_H + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <sys/stat.h> + +#include <osmo-bts/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include "oc2gbts_mgr.h" + +/* public function prototypes */ +void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id); + +void select_led_pattern(struct oc2gbts_mgr_instance *mgr); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c new file mode 100644 index 00000000..45ecc65d --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c @@ -0,0 +1,353 @@ +/* Main program for NuRAN Wireless OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr.c + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_bid.h" +#include "misc/oc2gbts_power.h" +#include "misc/oc2gbts_swd.h" + +#include "oc2gbts_led.h" + +static int no_rom_write = 0; +static int daemonize = 0; +void *tall_mgr_ctx; + +/* every 6 hours means 365*4 = 1460 rom writes per year (max) */ +#define SENSOR_TIMER_SECS (6 * 3600) + +/* every 1 hours means 365*24 = 8760 rom writes per year (max) */ +#define HOURS_TIMER_SECS (1 * 3600) + +/* the initial state */ +static struct oc2gbts_mgr_instance manager = { + .config_file = "oc2gbts-mgr.cfg", + .temp = { + .supply_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .soc_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .fpga_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .rmsdet_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .ocxo_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .tx_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .pa_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + } + }, + .volt = { + .supply_volt_limit = { + .thresh_warn_max = 30000, + .thresh_crit_max = 30500, + .thresh_warn_min = 19000, + .thresh_crit_min = 17500, + } + }, + .pwr = { + .supply_pwr_limit = { + .thresh_warn_max = 30, + .thresh_crit_max = 40, + }, + .pa_pwr_limit = { + .thresh_warn_max = 20, + .thresh_crit_max = 30, + } + }, + .vswr = { + .vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + } + }, + .gps = { + .gps_fix_limit = { + .thresh_warn_max = 7, + } + }, + .state = { + .action_norm = SENSOR_ACT_NORM_PA_ON, + .action_warn = 0, + .action_crit = 0, + .action_comb = 0, + .state = STATE_NORMAL, + } +}; + +static struct osmo_timer_list sensor_timer; +static void check_sensor_timer_cb(void *unused) +{ + oc2gbts_check_temp(no_rom_write); + oc2gbts_check_power(no_rom_write); + oc2gbts_check_vswr(no_rom_write); + osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0); + /* TODO checks if oc2gbts_check_temp/oc2gbts_check_power/oc2gbts_check_vswr went ok */ + oc2gbts_swd_event(&manager, SWD_CHECK_SENSOR); +} + +static struct osmo_timer_list hours_timer; +static void hours_timer_cb(void *unused) +{ + oc2gbts_update_hours(no_rom_write); + + osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0); + /* TODO: validates if oc2gbts_update_hours went correctly */ + oc2gbts_swd_event(&manager, SWD_UPDATE_HOURS); +} + +static void print_help(void) +{ + printf("oc2gbts-mgr [-nsD] [-d cat]\n"); + printf(" -n Do not write to ROM\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_rom_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: + oc2gbts_check_temp(no_rom_write); + oc2gbts_check_power(no_rom_write); + oc2gbts_check_vswr(no_rom_write); + oc2gbts_update_hours(no_rom_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 = "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, + }, + [DSWD] = { + .name = "DSWD", + .description = "Software Watchdog", + .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; + pthread_t tid; + + 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(); + + 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); + + oc2gbts_mgr_vty_init(); + logging_vty_add_cmds(&mgr_log_info); + rc = oc2gbts_mgr_parse_config(&manager); + if (rc < 0) { + LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); + exit(1); + } + + rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + INIT_LLIST_HEAD(&manager.oc2gbts_leds.list); + INIT_LLIST_HEAD(&manager.alarms.list); + + /* Initialize the service watchdog notification for SWD_LAST event(s) */ + if (oc2gbts_swd_init(&manager, (int)(SWD_LAST)) != 0) + exit(3); + + /* start temperature check timer */ + sensor_timer.cb = check_sensor_timer_cb; + check_sensor_timer_cb(NULL); + + /* start operational hours timer */ + hours_timer.cb = hours_timer_cb; + hours_timer_cb(NULL); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + /* Enable the PAs */ + rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1); + if (rc < 0) { + exit(3); + } + } + /* handle broadcast messages for ipaccess-find */ + if (oc2gbts_mgr_nl_init() != 0) + exit(3); + + /* Initialize the sensor control */ + oc2gbts_mgr_sensor_init(&manager); + + if (oc2gbts_mgr_calib_init(&manager) != 0) + exit(3); + + if (oc2gbts_mgr_control_init(&manager)) + 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); + oc2gbts_swd_event(&manager, SWD_MAINLOOP); + } +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h new file mode 100644 index 00000000..1f50fb6b --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h @@ -0,0 +1,398 @@ +#ifndef _OC2GBTS_MGR_H +#define _OC2GBTS_MGR_H + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> + +#include <stdint.h> +#include <gps.h> + +#define OC2GBTS_SENSOR_TIMER_DURATION 60 +#define OC2GBTS_PREVENT_TIMER_DURATION 15 * 60 +#define OC2GBTS_PREVENT_TIMER_SHORT_DURATION 5 * 60 +#define OC2GBTS_PREVENT_TIMER_NONE 0 +#define OC2GBTS_PREVENT_RETRY INT_MAX - 1 + +enum BLINK_PATTERN { + BLINK_PATTERN_POWER_ON = 0, //hardware set + BLINK_PATTERN_INIT, + BLINK_PATTERN_NORMAL, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_MAX_ITEM +}; + +#define BLINK_PATTERN_COMMAND {\ + "set red; sleep 5.0",\ + "set orange; sleep 5.0",\ + "set green; sleep 2.5; set off; sleep 2.5",\ + "set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 2.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ +} + +enum { + DTEMP, + DFW, + DFIND, + DCALIB, + DSWD, +}; + +// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ... +enum { +#if 0 + SENSOR_ACT_PWR_CONTRL = 0x1, +#endif + SENSOR_ACT_PA_OFF = 0x2, + SENSOR_ACT_BTS_SRV_OFF = 0x10, +}; + +/* actions only for normal state */ +enum { +#if 0 + SENSOR_ACT_NORM_PW_CONTRL = 0x1, +#endif + SENSOR_ACT_NORM_PA_ON = 0x2, + SENSOR_ACT_NORM_BTS_SRV_ON= 0x10, +}; + +enum oc2gbts_sensor_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 */ +}; + +enum oc2gbts_leds_name { + OC2GBTS_LED_RED = 0, + OC2GBTS_LED_GREEN, + OC2GBTS_LED_ORANGE, + OC2GBTS_LED_OFF, + _OC2GBTS_LED_MAX +}; + +struct oc2gbts_led{ + char *name; + char *fullname; + char *path; +}; + +/** + * 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 oc2gbts_temp_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; +}; + +struct oc2gbts_volt_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; + int thresh_crit_min; +}; + +struct oc2gbts_pwr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct oc2gbts_vswr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct oc2gbts_gps_fix_limit { + int thresh_warn_max; +}; + +struct oc2gbts_sleep_time { + int sleep_sec; + int sleep_usec; +}; + +struct oc2gbts_led_timer { + uint8_t idx; + struct osmo_timer_list timer; + struct oc2gbts_sleep_time param; +}; + +struct oc2gbts_led_timer_list { + struct llist_head list; + struct oc2gbts_led_timer led_timer; +}; + +struct oc2gbts_preventive_list { + struct llist_head list; + struct oc2gbts_sleep_time param; + int action_flag; +}; + +enum mgr_vty_node { + MGR_NODE = _LAST_OSMOVTY_NODE + 1, + + ACT_NORM_NODE, + ACT_WARN_NODE, + ACT_CRIT_NODE, + LIMIT_SUPPLY_TEMP_NODE, + LIMIT_SOC_NODE, + LIMIT_FPGA_NODE, + LIMIT_RMSDET_NODE, + LIMIT_OCXO_NODE, + LIMIT_TX_TEMP_NODE, + LIMIT_PA_TEMP_NODE, + LIMIT_SUPPLY_VOLT_NODE, + LIMIT_VSWR_NODE, + LIMIT_SUPPLY_PWR_NODE, + LIMIT_PA_PWR_NODE, + LIMIT_GPS_FIX_NODE, +}; + +enum mgr_vty_limit_type { + MGR_LIMIT_TYPE_TEMP = 0, + MGR_LIMIT_TYPE_VOLT, + MGR_LIMIT_TYPE_VSWR, + MGR_LIMIT_TYPE_PWR, + _MGR_LIMIT_TYPE_MAX, +}; + +struct oc2gbts_mgr_instance { + const char *config_file; + + struct { + struct oc2gbts_temp_limit supply_temp_limit; + struct oc2gbts_temp_limit soc_temp_limit; + struct oc2gbts_temp_limit fpga_temp_limit; + struct oc2gbts_temp_limit rmsdet_temp_limit; + struct oc2gbts_temp_limit ocxo_temp_limit; + struct oc2gbts_temp_limit tx_temp_limit; + struct oc2gbts_temp_limit pa_temp_limit; + } temp; + + struct { + struct oc2gbts_volt_limit supply_volt_limit; + } volt; + + struct { + struct oc2gbts_pwr_limit supply_pwr_limit; + struct oc2gbts_pwr_limit pa_pwr_limit; + } pwr; + + struct { + struct oc2gbts_vswr_limit vswr_limit; + int last_vswr; + } vswr; + + struct { + struct oc2gbts_gps_fix_limit gps_fix_limit; + int last_update; + time_t last_gps_fix; + time_t gps_fix_now; + int gps_open; + struct osmo_fd gpsfd; + struct gps_data_t gpsdata; + struct osmo_timer_list fix_timeout; + } gps; + + struct { + int action_norm; + int action_warn; + int action_crit; + int action_comb; + + enum oc2gbts_sensor_state state; + } state; + + struct { + int state; + int calib_from_loop; + struct osmo_timer_list calib_timeout; + } calib; + + struct { + int state; + int swd_from_loop; + unsigned long long int swd_events; + unsigned long long int swd_events_cache; + unsigned long long int swd_eventmasks; + int num_events; + struct osmo_timer_list swd_timeout; + } swd; + + struct { + uint8_t led_idx; + uint8_t last_pattern_id; + uint8_t active_timer; + struct llist_head list; + } oc2gbts_leds; + + struct { + int is_up; + uint32_t last_seqno; + struct osmo_timer_list recon_timer; + struct ipa_client_conn *bts_conn; + uint32_t crit_flags; + uint32_t warn_flags; + } oc2gbts_ctrl; + + struct oc2gbts_alarms { + int temp_high; + int temp_max; + int supply_low; + int supply_min; + int vswr_high; + int vswr_max; + int supply_pwr_high; + int supply_pwr_max; + int pa_pwr_high; + int pa_pwr_max; + int gps_fix_lost; + struct llist_head list; + struct osmo_timer_list preventive_timer; + int preventive_duration; + int preventive_retry; + } alarms; + +}; + +enum oc2gbts_mgr_fail_evt_rep_crit_sig { + /* Critical alarms */ + S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0), + S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1), + S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2), + S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3), + S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4), + S_MGR_TEMP_TRX_CRIT_MAX_ALARM = (1 << 5), + S_MGR_TEMP_PA_CRIT_MAX_ALARM = (1 << 6), + S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 7), + S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 8), + S_MGR_VSWR_CRIT_MAX_ALARM = (1 << 9), + S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 10), + S_MGR_PWR_PA_CRIT_MAX_ALARM = (1 << 11), + _S_MGR_CRIT_ALARM_MAX, +}; + +enum oc2gbts_mgr_fail_evt_rep_warn_sig { + /* Warning alarms */ + S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0), + S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 1), + S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 2), + S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 3), + S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 4), + S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 5), + S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 6), + S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 7), + S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 8), + S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 9), + S_MGR_TEMP_TRX_WARN_MIN_ALARM = (1 << 10), + S_MGR_TEMP_TRX_WARN_MAX_ALARM = (1 << 11), + S_MGR_TEMP_PA_WARN_MIN_ALARM = (1 << 12), + S_MGR_TEMP_PA_WARN_MAX_ALARM = (1 << 13), + S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 14), + S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 15), + S_MGR_VSWR_WARN_MAX_ALARM = (1 << 16), + S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 17), + S_MGR_PWR_PA_WARN_MAX_ALARM = (1 << 18), + S_MGR_GPS_FIX_WARN_ALARM = (1 << 19), + _S_MGR_WARN_ALARM_MAX, +}; + +enum oc2gbts_mgr_failure_event_causes { + /* Critical causes */ + NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100, + NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101, + NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102, + NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103, + NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104, + NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL = 0x4105, + NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL = 0x4106, + NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4107, + NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x4108, + NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL = 0x4109, + NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410A, + NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL = 0x410B, + /* Warning causes */ + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400, + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401, + NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402, + NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403, + NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404, + NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407, + NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408, + NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409, + NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL = 0x440A, + NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL = 0x440B, + NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL = 0x440C, + NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL = 0x440D, + NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x440E, + NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x440F, + NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL = 0x4410, + NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4411, + NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL = 0x4412, + NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4413, +}; + +/* This defines the list of notification events for systemd service watchdog. + all these events must be notified in a certain service defined timeslot + or the service (this app) would be restarted (only if related systemd service + unit file has WatchdogSec!=0). + WARNING: swd events must begin with event 0. Last events must be + SWD_LAST (max 64 events in this list). +*/ +enum mgr_swd_events { + SWD_MAINLOOP = 0, + SWD_CHECK_SENSOR, + SWD_UPDATE_HOURS, + SWD_CHECK_TEMP_SENSOR, + SWD_CHECK_LED_CTRL, + SWD_CHECK_CALIB, + SWD_CHECK_BTS_CONNECTION, + SWD_LAST +}; + +int oc2gbts_mgr_vty_init(void); +int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *mgr); +int oc2gbts_mgr_nl_init(void); +int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr); +const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state); + +int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr); +int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr); +int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr); +void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text); +void handle_alert_actions(struct oc2gbts_mgr_instance *mgr); +void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr); +void handle_warn_actions(struct oc2gbts_mgr_instance *mgr); +extern void *tall_mgr_ctx; + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c new file mode 100644 index 00000000..104d2792 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c @@ -0,0 +1,760 @@ +/* OCXO calibration control for OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_calib.c + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_clock.h" +#include "misc/oc2gbts_swd.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_led.h" +#include "osmo-bts/msg_utils.h" + +#include <osmocom/core/logging.h> +#include <osmocom/core/select.h> + +#include <osmocom/ctrl/control_cmd.h> +#include <osmocom/ctrl/ports.h> + +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipa.h> + +#include <time.h> +#include <sys/sysinfo.h> +#include <errno.h> + +static void calib_adjust(struct oc2gbts_mgr_instance *mgr); +static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int reason); +static void calib_loop_run(void *_data); + +static int ocxodac_saved_value = -1; + +enum calib_state { + CALIB_INITIAL, + CALIB_IN_PROGRESS, + CALIB_GPS_WAIT_FOR_FIX, +}; + +enum calib_result { + CALIB_FAIL_START, + CALIB_FAIL_GPSFIX, + CALIB_FAIL_CLKERR, + CALIB_FAIL_OCXODAC, + CALIB_SUCCESS, +}; + +static inline int compat_gps_read(struct gps_data_t *data) +{ +/* API break in gpsd 6bba8b329fc7687b15863d30471d5af402467802 */ +#if GPSD_API_MAJOR_VERSION >= 7 && GPSD_API_MINOR_VERSION >= 0 + return gps_read(data, NULL, 0); +#else + return gps_read(data); +#endif +} + +static int oc2gbts_par_get_uptime(void *ctx, int *ret) +{ + char *fpath; + FILE *fp; + int rc; + + fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH); + if (!fpath) + return NULL; + + fp = fopen(fpath, "r"); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%d", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +static int oc2gbts_par_set_uptime(void *ctx, int val) +{ + char *fpath; + FILE *fp; + int rc; + + fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH); + if (!fpath) + return NULL; + + fp = fopen(fpath, "w"); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + + return 0; +} + +static void mgr_gps_close(struct oc2gbts_mgr_instance *mgr) +{ + if (!mgr->gps.gps_open) + return; + + osmo_timer_del(&mgr->gps.fix_timeout); + + osmo_fd_unregister(&mgr->gps.gpsfd); + gps_close(&mgr->gps.gpsdata); + memset(&mgr->gps.gpsdata, 0, sizeof(mgr->gps.gpsdata)); + mgr->gps.gps_open = 0; +} + +static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr) +{ + struct gps_data_t *data = &mgr->gps.gpsdata; + + /* No 3D fix yet */ + if (data->fix.mode < MODE_3D) { + LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n", + data->fix.mode); + return; + } + + /* Satellite used checking */ + if (data->satellites_used < 1) { + LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n", + data->satellites_used); + return; + } + + mgr->gps.gps_fix_now = (time_t) data->fix.time; + LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n", + data->satellites_used, mgr->gps.gps_fix_now); + osmo_timer_del(&mgr->gps.fix_timeout); + mgr_gps_close(mgr); +} + +static int mgr_gps_read(struct osmo_fd *fd, unsigned int what) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = fd->data; + + rc = compat_gps_read(&mgr->gps.gpsdata); + if (rc == -1) { + LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return -1; + } + + if (rc > 0) + mgr_gps_checkfix(mgr); + return 0; +} + +static void mgr_gps_fix_timeout(void *_data) +{ + struct oc2gbts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPS fix.\n"); + mgr_gps_close(mgr); +} + +static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + + if (mgr->gps.gps_open) + return; + + rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->gps.gpsdata); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + mgr->gps.gps_open = 1; + gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL); + + mgr->gps.gpsfd.data = mgr; + mgr->gps.gpsfd.cb = mgr_gps_read; + mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT; + mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd; + if (osmo_fd_register(&mgr->gps.gpsfd) < 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + } + + mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX; + mgr->gps.fix_timeout.data = mgr; + mgr->gps.fix_timeout.cb = mgr_gps_fix_timeout; + osmo_timer_schedule(&mgr->gps.fix_timeout, 60, 0); + LOGP(DCALIB, LOGL_INFO, "Opened the GPSD connection waiting for fix: %d\n", + mgr->gps.gpsfd.fd); +} + +/* OC2G CTRL interface related functions */ +static void send_ctrl_cmd(struct oc2gbts_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->oc2gbts_ctrl.bts_conn, msg); +} + +static void send_set_ctrl_cmd_int(struct oc2gbts_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->oc2gbts_ctrl.last_seqno++, key, val); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static void send_set_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, const char *key, const int val, const char *text) +{ + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); + ret = snprintf((char *) msg->data, 4096, "SET %u %s %d, %s", + mgr->oc2gbts_ctrl.last_seqno++, key, val, text); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static void calib_start(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + + rc = oc2gbts_clock_err_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + rc = oc2gbts_clock_dac_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + calib_adjust(mgr); +} +static int get_uptime(int *uptime) +{ + struct sysinfo s_info; + int rc; + rc = sysinfo(&s_info); + if(!rc) + *uptime = s_info.uptime /(60 * 60); + + return rc; +} + +static void calib_adjust(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + int fault; + int error_ppt; + int accuracy_ppq; + int interval_sec; + int dac_value; + int new_dac_value; + int dac_correction; + int now = 0; + + /* Get GPS time via GPSD */ + mgr_gps_open(mgr); + + rc = oc2gbts_clock_err_get(&fault, &error_ppt, + &accuracy_ppq, &interval_sec); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get clock error measurement %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + /* get current up time */ + rc = get_uptime(&now); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "Unable to read up time hours: %d (%s)\n", rc, strerror(errno)); + + /* read last up time */ + rc = oc2gbts_par_get_uptime(tall_mgr_ctx, &mgr->gps.last_update); + if (rc < 0) + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc); + + if (fault) { + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix, warn_flags=0x%08x, last=%d, now=%d\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now); + if (now >= mgr->gps.last_update + mgr->gps.gps_fix_limit.thresh_warn_max * 24) { + if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) { + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM; + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost"); + + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last verification, warn_flags=0x%08x, last=%d, now=%d\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now); + + /* schedule LED pattern for GPS fix lost */ + mgr->alarms.gps_fix_lost = 1; + /* update LED pattern */ + select_led_pattern(mgr); + } + } else { + /* read from last GPS 3D fix timestamp */ + rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix); + if (rc < 0) + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix timestamp can not read (%d)\n", rc); + + if (difftime(mgr->gps.gps_fix_now, mgr->gps.last_gps_fix) > mgr->gps.gps_fix_limit.thresh_warn_max * 24 * 60 * 60) { + if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) { + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM; + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost"); + + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last known GPS fix, warn_flags=0x%08x, gps_last=%ld, gps_now=%ld\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now); + + /* schedule LED pattern for GPS fix lost */ + mgr->alarms.gps_fix_lost = 1; + /* update LED pattern */ + select_led_pattern(mgr); + } + } + } + + rc = oc2gbts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + if (!interval_sec) { + LOGP(DCALIB, LOGL_INFO, "Skipping this iteration, no integration time\n"); + calib_state_reset(mgr, CALIB_SUCCESS); + return; + } + + /* We got GPS 3D fix */ + LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, uptime_last=%d, uptime_now=%d, gps_last=%ld, gps_now=%ld\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now); + + if (mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM) { + /* Store GPS fix as soon as we send ceased alarm */ + LOGP(DCALIB, LOGL_NOTICE, "Store GPS fix as soon as we send ceased alarm last=%ld, now=%ld\n", + mgr->gps.last_gps_fix , mgr->gps.gps_fix_now); + rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc); + + /* Store last up time */ + rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc); + + mgr->gps.last_update = now; + + /* schedule LED pattern for GPS fix resume */ + mgr->alarms.gps_fix_lost = 0; + /* update LED pattern */ + select_led_pattern(mgr); + /* send ceased alarm if possible */ + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-ceased", "GPS 3D fix has been lost"); + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_GPS_FIX_WARN_ALARM; + + } + /* Store GPS fix at every hour */ + if (now > mgr->gps.last_update) { + /* Store GPS fix every 60 minutes */ + LOGP(DCALIB, LOGL_INFO, "Store GPS fix every hour last=%ld, now=%ld\n", + mgr->gps.last_gps_fix , mgr->gps.gps_fix_now); + rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc); + + /* Store last up time every 60 minutes */ + rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc); + + /* update last uptime */ + mgr->gps.last_update = now; + } + + rc = oc2gbts_clock_dac_get(&dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + /* Set OCXO initial dac value */ + if (ocxodac_saved_value < 0) + ocxodac_saved_value = dac_value; + + LOGP(DCALIB, LOGL_INFO, + "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n", + error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value); + + /* 1 unit of correction equal about 0.5 - 1 PPB correction */ + dac_correction = (int)(-error_ppt * 0.0015); + new_dac_value = dac_value + dac_correction; + + if (new_dac_value > 4095) + new_dac_value = 4095; + else if (new_dac_value < 0) + new_dac_value = 0; + + /* We have a fix, make sure the measured error is + meaningful (10 times the accuracy) */ + if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) { + + LOGP(DCALIB, LOGL_INFO, + "Going to apply %d as new clock setting.\n", + new_dac_value); + + rc = oc2gbts_clock_dac_set(new_dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to set OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + rc = oc2gbts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + } + /* New conditions to store DAC value: + * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ) + * - Error less or equal than 2PPB (or 2000PPT) + * - Solution different than the last one */ + else if (accuracy_ppq <= 10000) { + if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) { + LOGP(DCALIB, LOGL_INFO, "Saving OCXO DAC value to memory... val = %d\n", dac_value); + rc = oc2gbts_clock_dac_save(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to save OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + } else { + ocxodac_saved_value = dac_value; + } + } + + rc = oc2gbts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + } + } + + calib_state_reset(mgr, CALIB_SUCCESS); + return; +} + +static void calib_close(struct oc2gbts_mgr_instance *mgr) +{ + oc2gbts_clock_err_close(); + oc2gbts_clock_dac_close(); +} + +static void calib_state_reset(struct oc2gbts_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. + * + * TODO NTQ: Select timeout based on last error and accuracy + */ + int timeout = 60; + //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); + /* TODO: do we want to notify if we got a calibration error, like no gps fix? */ + oc2gbts_swd_event(mgr, SWD_CHECK_CALIB); + } + + mgr->calib.state = CALIB_INITIAL; + calib_close(mgr); +} + +static int calib_run(struct oc2gbts_mgr_instance *mgr, int from_loop) +{ + if (mgr->calib.state != CALIB_INITIAL) { + LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); + return -1; + } + + /* Validates if we have a bts connection */ + if (mgr->oc2gbts_ctrl.is_up) { + LOGP(DCALIB, LOGL_DEBUG, "Bts connection is up.\n"); + oc2gbts_swd_event(mgr, SWD_CHECK_BTS_CONNECTION); + } + + mgr->calib.calib_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->calib.state = CALIB_IN_PROGRESS; + calib_start(mgr); + return 0; +} + +static void calib_loop_run(void *_data) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_INFO, "Going to calibrate the system.\n"); + rc = calib_run(mgr, 1); + if (rc != 0) { + calib_state_reset(mgr, CALIB_FAIL_START); + } +} + +int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr) +{ + return calib_run(mgr, 0); +} + +static void schedule_bts_connect(struct oc2gbts_mgr_instance *mgr) +{ + DEBUGP(DLCTRL, "Scheduling BTS connect\n"); + osmo_timer_schedule(&mgr->oc2gbts_ctrl.recon_timer, 1, 0); +} + +/* link to BSC has gone up or down */ +static void bts_updown_cb(struct ipa_client_conn *link, int up) +{ + struct oc2gbts_mgr_instance *mgr = link->data; + + LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down"); + + if (up) { + mgr->oc2gbts_ctrl.is_up = 1; + mgr->oc2gbts_ctrl.last_seqno = 0; + /* handle any pending alarm */ + handle_alert_actions(mgr); + handle_warn_actions(mgr); + } else { + mgr->oc2gbts_ctrl.is_up = 0; + schedule_bts_connect(mgr); + } + +} + +/* BTS re-connect timer call-back */ +static void bts_recon_timer_cb(void *data) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = data; + + /* update LED pattern */ + select_led_pattern(mgr); + + /* The connection failures are to be expected during boot */ + mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE; + rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn); + if (rc < 0) { + LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n"); + schedule_bts_connect(mgr); + } +} + +static void oc2gbts_handle_ctrl(struct oc2gbts_mgr_instance *mgr, struct msgb *msg) +{ + struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg); + int cause = atoi(cmd->reply); + + if (!cmd) { + LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n"); + return; + } + + switch (cmd->type) { + case CTRL_TYPE_GET_REPLY: + LOGP(DCALIB, LOGL_INFO, "Got GET_REPLY from BTS cause=0x%x\n", cause); + break; + case CTRL_TYPE_SET_REPLY: + LOGP(DCALIB, LOGL_INFO, "Got SET_REPLY from BTS cause=0x%x\n", cause); + break; + default: + LOGP(DCALIB, LOGL_ERROR, + "Unhandled CTRL response: %d. Resetting state\n", + cmd->type); + break; + } + + talloc_free(cmd); + return; +} + +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; + + LOGP(DLCTRL, LOGL_DEBUG, "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_OSMO: + hh_ext = (struct ipaccess_head_ext *) hh->data; + switch (hh_ext->proto) { + case IPAC_PROTO_EXT_CTRL: + oc2gbts_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; +} + +int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + + /* initialize last uptime */ + mgr->gps.last_update = 0; + rc = oc2gbts_par_set_uptime(tall_mgr_ctx, mgr->gps.last_update); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc); + + /* get last GPS 3D fix timestamp */ + mgr->gps.last_gps_fix = 0; + rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to get last GPS 3D fix timestamp from storage. Create it anyway %d\n", rc); + rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.last_gps_fix); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store initial GPS fix to storage %d\n", rc); + } + + mgr->calib.state = CALIB_INITIAL; + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0); + + return rc; +} + +int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr) +{ + mgr->oc2gbts_ctrl.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0, + "127.0.0.1", OSMO_CTRL_PORT_BTS, + bts_updown_cb, bts_read_cb, + NULL, mgr); + if (!mgr->oc2gbts_ctrl.bts_conn) { + LOGP(DLCTRL, LOGL_ERROR, "Failed to create IPA connection to BTS\n"); + return -1; + } + + mgr->oc2gbts_ctrl.recon_timer.cb = bts_recon_timer_cb; + mgr->oc2gbts_ctrl.recon_timer.data = mgr; + schedule_bts_connect(mgr); + + return 0; +} + +void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text) +{ + /* Make sure the control link is ready before sending alarm */ + if (mgr->oc2gbts_ctrl.bts_conn->state != IPA_CLIENT_LINK_STATE_CONNECTED) { + LOGP(DLCTRL, LOGL_NOTICE, "MGR losts connection to BTS.\n"); + LOGP(DLCTRL, LOGL_NOTICE, "MGR drops an alert cause=0x%x, text=%s to BTS\n", cause, text); + return; + } + + LOGP(DLCTRL, LOGL_DEBUG, "MGR sends an alert cause=0x%x, text=%s to BTS\n", cause, text); + send_set_ctrl_cmd(mgr, key, cause, text); + return; +} + + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c new file mode 100644 index 00000000..db67caf2 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c @@ -0,0 +1,208 @@ +/* NetworkListen for NuRAN OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_nl.c + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_nl.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_bid.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <arpa/inet.h> + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#define ETH0_ADDR_SYSFS "/var/oc2g/net/eth0/address" + +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[64] = {0, }; + 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) { + int fd_eth; + int serno; + int model; + char rev_maj, rev_min; + + /* fetch the MAC */ + fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY); + if (fd_eth >= 0) { + read(fd_eth, mac_str, sizeof(mac_str)-1); + mac_str[sizeof(mac_str)-1] = '\0'; + close(fd_eth); + } + + /* fetch the serial number */ + oc2gbts_par_get_int(OC2GBTS_PAR_SERNR, &serno); + snprintf(ser_str, sizeof(ser_str), "%d", serno); + + /* fetch the model and trx number */ + snprintf(model_name, sizeof(model_name), "OC-2G BTS"); + + oc2gbts_rev_get(&rev_maj, &rev_min); + snprintf(model_name, sizeof(model_name), "%s Rev %c.%c", + model_name, rev_maj, rev_min); + + model = oc2gbts_model_get(); + if (model >= 0) { + snprintf(model_name, sizeof(model_name), "%s (%05X)", + model_name, model); + } + 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 oc2gbts_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; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c new file mode 100644 index 00000000..f9efd9cd --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c @@ -0,0 +1,980 @@ +/* Temperature control for NuRAN OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_temp.c + * (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 <http://www.gnu.org/licenses/>. + * + */ +#include <inttypes.h> +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_temp.h" +#include "misc/oc2gbts_power.h" +#include "misc/oc2gbts_led.h" +#include "misc/oc2gbts_swd.h" +#include "misc/oc2gbts_bid.h" +#include "limits.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> + +struct oc2gbts_mgr_instance *s_mgr; +static struct osmo_timer_list sensor_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 } +}; + +/* private function prototype */ +static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr); + +const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state) +{ + return get_value_string(state_names, state); +} + +static int next_state(enum oc2gbts_sensor_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 on the PA */ + if (actions & SENSOR_ACT_NORM_PA_ON) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA\n"); + } else { + LOGP(DTEMP, LOGL_INFO, + "Switched on the PA as normal action.\n"); + } + } + + if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) { + LOGP(DTEMP, LOGL_INFO, + "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 osmo-bts.service"); + } +} + +static void handle_actions(int actions) +{ + /* switch off the PA */ + if (actions & SENSOR_ACT_PA_OFF) { + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 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 & SENSOR_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 osmo-bts.service"); + } +} + +void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr) +{ int i; + uint32_t cause; + + if (!mgr->oc2gbts_ctrl.is_up) + return; + + LOGP(DTEMP, LOGL_DEBUG, "handle_ceased_actions in state %s, warn_flags=0x%x, crit_flags=0x%x\n", + oc2gbts_mgr_sensor_get_state(mgr->state.state), + mgr->oc2gbts_ctrl.warn_flags, + mgr->oc2gbts_ctrl.crit_flags); + + for (i = 0; i < 32; i++) { + cause = 1 << i; + /* clear warning flag without sending ceased alarm */ + if (mgr->oc2gbts_ctrl.warn_flags & cause) + mgr->oc2gbts_ctrl.warn_flags &= ~cause; + + /* clear warning flag with sending ceased alarm */ + if (mgr->oc2gbts_ctrl.crit_flags & cause) { + /* clear associated flag */ + mgr->oc2gbts_ctrl.crit_flags &= ~cause; + /* dispatch ceased alarm */ + switch (cause) { + case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Main power supply temperature is too high"); + break; + case S_MGR_TEMP_SOC_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-ceased", "SoC temperature is too high"); + break; + case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-ceased", "FPGA temperature is too high"); + break; + case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-ceased", "RMS detector temperature is too high"); + break; + case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-ceased", "OCXO temperature is too high"); + break; + case S_MGR_TEMP_TRX_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-ceased", "TRX temperature is too high"); + break; + case S_MGR_TEMP_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-ceased", "PA temperature is too high"); + break; + case S_MGR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply voltage is too high"); + break; + case S_MGR_SUPPLY_CRIT_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-ceased", "Power supply voltage is too low"); + break; + case S_MGR_VSWR_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-ceased", "VSWR is too high"); + break; + case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply consumption is too high"); + break; + case S_MGR_PWR_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-ceased", "PA power consumption is too high"); + break; + default: + break; + } + } + } + return; +} + +void handle_alert_actions(struct oc2gbts_mgr_instance *mgr) +{ int i; + uint32_t cause; + + if (!mgr->oc2gbts_ctrl.is_up) + return; + + LOGP(DTEMP, LOGL_DEBUG, "handle_alert_actions in state %s, crit_flags=0x%x\n", + oc2gbts_mgr_sensor_get_state(mgr->state.state), + mgr->oc2gbts_ctrl.crit_flags); + + for (i = 0; i < 32; i++) { + cause = 1 << i; + if (mgr->oc2gbts_ctrl.crit_flags & cause) { + switch(cause) { + case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Main power supply temperature is too high"); + break; + case S_MGR_TEMP_SOC_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-alert", "SoC temperature is too high"); + break; + case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-alert", "FPGA temperature is too high"); + break; + case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-alert", "RMS detector temperature is too high"); + break; + case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-alert", "OCXO temperature is too high"); + break; + case S_MGR_TEMP_TRX_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-alert", "TRX temperature is too high"); + break; + case S_MGR_TEMP_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-alert", "PA temperature is too high"); + break; + case S_MGR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply voltage is too high"); + break; + case S_MGR_SUPPLY_CRIT_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-alert", "Power supply voltage is too low"); + break; + case S_MGR_VSWR_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-alert", "VSWR is too high"); + break; + case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply consumption is too high"); + break; + case S_MGR_PWR_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-alert", "PA power consumption is too high"); + break; + default: + break; + } + } + } + return; +} + +void handle_warn_actions(struct oc2gbts_mgr_instance *mgr) +{ int i; + uint32_t cause; + + if (!mgr->oc2gbts_ctrl.is_up) + return; + + LOGP(DTEMP, LOGL_DEBUG, "handle_warn_actions in state %s, warn_flags=0x%x\n", + oc2gbts_mgr_sensor_get_state(mgr->state.state), + mgr->oc2gbts_ctrl.warn_flags); + + for (i = 0; i < 32; i++) { + cause = 1 << i; + if (mgr->oc2gbts_ctrl.warn_flags & cause) { + switch(cause) { + case S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Main power supply temperature is high"); + break; + case S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Main power supply temperature is low"); + break; + case S_MGR_TEMP_SOC_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL, "oc2g-oml-alert", "SoC temperature is high"); + break; + case S_MGR_TEMP_SOC_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL, "oc2g-oml-alert", "SoC temperature is low"); + break; + case S_MGR_TEMP_FPGA_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL, "oc2g-oml-alert", "FPGA temperature is high"); + break; + case S_MGR_TEMP_FPGA_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL, "oc2g-oml-alert", "FPGA temperature is low"); + break; + case S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL, "oc2g-oml-alert", "RMS detector temperature is high"); + break; + case S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL, "oc2g-oml-alert", "RMS detector temperature is low"); + break; + case S_MGR_TEMP_OCXO_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL, "oc2g-oml-alert", "OCXO temperature is high"); + break; + case S_MGR_TEMP_OCXO_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL, "oc2g-oml-alert", "OCXO temperature is low"); + break; + case S_MGR_TEMP_TRX_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL, "oc2g-oml-alert", "TRX temperature is high"); + break; + case S_MGR_TEMP_TRX_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL, "oc2g-oml-alert", "TRX temperature is low"); + break; + case S_MGR_TEMP_PA_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL, "oc2g-oml-alert", "PA temperature is high"); + break; + case S_MGR_TEMP_PA_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL, "oc2g-oml-alert", "PA temperature is low"); + break; + case S_MGR_SUPPLY_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply voltage is high"); + break; + case S_MGR_SUPPLY_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Power supply voltage is low"); + break; + case S_MGR_VSWR_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL, "oc2g-oml-alert", "VSWR is high"); + break; + case S_MGR_PWR_SUPPLY_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply consumption is high"); + break; + case S_MGR_PWR_PA_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL, "oc2g-oml-alert", "PA power consumption is high"); + break; + default: + break; + } + } + } + return; +} + +/** + * 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 oc2gbts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n"); + handle_ceased_actions(manager); + handle_normal_actions(manager->state.action_norm); +} + +static void execute_warning_act(struct oc2gbts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n"); + handle_warn_actions(manager); + handle_actions(manager->state.action_warn); +} + +/* Preventive timer call-back */ +static void preventive_timer_cb(void *_data) +{ + struct oc2gbts_mgr_instance *mgr = _data; + + /* Delete current preventive timer if possible */ + osmo_timer_del(&mgr->alarms.preventive_timer); + + LOGP(DTEMP, LOGL_DEBUG, "Preventive timer expired in %d sec, retry=%d\n", + mgr->alarms.preventive_duration, + mgr->alarms.preventive_retry); + + /* Turn on PA and clear action flag */ + if (mgr->state.action_comb & SENSOR_ACT_PA_OFF) { + mgr->state.action_comb &= ~SENSOR_ACT_PA_OFF; + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1)) + LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n"); + else + LOGP(DTEMP, LOGL_DEBUG, "Re-enable PA after preventive timer expired in %d sec\n", + mgr->alarms.preventive_duration); + } + } + + /* restart check sensor timer */ + osmo_timer_del(&sensor_ctrl_timer); + osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0); + + return; + +} + +static void execute_preventive_act(struct oc2gbts_mgr_instance *manager) +{ + struct oc2gbts_preventive_list *prevent_list, *prevent_list2; + + /* update LED pattern */ + select_led_pattern(manager); + + /* do nothing if the preventive action list is empty */ + if (llist_empty(&manager->alarms.list)) + return; + + llist_for_each_entry_safe(prevent_list, prevent_list2, &manager->alarms.list, list) { + /* Delete the timer in list and perform action*/ + if (prevent_list) { + /* Delete current preventive timer if possible */ + osmo_timer_del(&manager->alarms.preventive_timer); + + /* Start/restart preventive timer */ + if (prevent_list->param.sleep_sec) { + manager->alarms.preventive_timer.cb = preventive_timer_cb; + manager->alarms.preventive_timer.data = manager; + osmo_timer_schedule(&manager->alarms.preventive_timer, prevent_list->param.sleep_sec, 0); + + LOGP(DTEMP, LOGL_DEBUG,"Preventive timer scheduled for %d sec, preventive flags=0x%x\n", + prevent_list->param.sleep_sec, + prevent_list->action_flag); + } + /* Update active flags */ + manager->state.action_comb |= prevent_list->action_flag; + + /* Turn off PA */ + if (manager->state.action_comb & SENSOR_ACT_PA_OFF) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0)) + LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA\n"); + } + + /* Delete this preventive entry */ + llist_del(&prevent_list->list); + talloc_free(prevent_list); + LOGP(DTEMP, LOGL_DEBUG,"Deleted preventive entry from list, entries left=%d\n", + llist_count(&manager->alarms.list)); + + /* stay in last state is preventive active has exceed maximum number of retries */ + if (manager->alarms.preventive_retry > OC2GBTS_PREVENT_RETRY) + LOGP(DTEMP, LOGL_NOTICE, "Maximum number of preventive active exceed\n"); + else + /* increase retry counter */ + manager->alarms.preventive_retry++; + } + } + return; +} + +static void execute_critical_act(struct oc2gbts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n"); + handle_alert_actions(manager); + handle_actions(manager->state.action_crit); + +} + +static void oc2gbts_mgr_sensor_handle(struct oc2gbts_mgr_instance *manager, + int critical, int warning) +{ + int new_state = next_state(manager->state.state, critical, warning); + + /* run preventive action if it is possible */ + execute_preventive_act(manager); + + /* Nothing changed */ + if (new_state < 0) + return; + LOGP(DTEMP, LOGL_INFO, "Moving from state %s to %s.\n", + get_value_string(state_names, manager->state.state), + get_value_string(state_names, new_state)); + manager->state.state = new_state; + switch (manager->state.state) { + case STATE_NORMAL: + execute_normal_act(manager); + /* reset alarms */ + manager->alarms.temp_high = 0; + manager->alarms.temp_max = 0; + manager->alarms.vswr_high = 0; + manager->alarms.vswr_max = 0; + manager->alarms.supply_low = 0; + manager->alarms.supply_min = 0; + manager->alarms.supply_pwr_high = 0; + manager->alarms.supply_pwr_max = 0; + manager->alarms.pa_pwr_max = 0; + manager->alarms.pa_pwr_high = 0; + manager->state.action_comb = 0; + manager->alarms.preventive_retry = 0; + /* update LED pattern */ + select_led_pattern(manager); + break; + case STATE_WARNING_HYST: + /* do nothing? Maybe start to increase transmit power? */ + break; + case STATE_WARNING: + execute_warning_act(manager); + /* update LED pattern */ + select_led_pattern(manager); + break; + case STATE_CRITICAL: + execute_critical_act(manager); + /* update LED pattern */ + select_led_pattern(manager); + break; + }; +} + +static void schedule_preventive_action(struct oc2gbts_mgr_instance *mgr, int action, int duration) +{ + struct oc2gbts_preventive_list *prevent_list; + + /* add to pending list */ + prevent_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_preventive_list); + if (prevent_list) { + prevent_list->action_flag = action; + prevent_list->param.sleep_sec = duration; + prevent_list->param.sleep_usec = 0; + llist_add_tail(&prevent_list->list, &mgr->alarms.list); + LOGP(DTEMP, LOGL_DEBUG,"Added preventive action to list, duration=%d sec, total entries=%d\n", + prevent_list->param.sleep_sec, + llist_count(&mgr->alarms.list)); + } + return; +} + +static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + int temp, volt, vswr, power = 0; + int warn_thresh_passed = 0; + int crit_thresh_passed = 0; + int action = 0; + + LOGP(DTEMP, LOGL_INFO, "Going to check the temperature.\n"); + + /* Read the current supply temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the supply temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.supply_temp_limit.thresh_warn_min){ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is under %d\n", mgr->temp.supply_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM; + } + if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "Supply temperature is: %d\n", temp); + } + + /* Read the current SoC temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the SoC temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.soc_temp_limit.thresh_warn_min){ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is under %d\n", mgr->temp.soc_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MIN_ALARM; + } + if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SOC_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SOC_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "SoC temperature is: %d\n", temp); + } + + /* Read the current fpga temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the fpga temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.fpga_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is under %d\n", mgr->temp.fpga_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MIN_ALARM; + } + if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_FPGA_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_FPGA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "FPGA temperature is: %d\n", temp); + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + /* Read the current RMS detector temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the RMS detector temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.rmsdet_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is under %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM; + } + if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "RMS detector temperature is: %d\n", temp); + } + } + + /* Read the current OCXO temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the OCXO temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.ocxo_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is under %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MIN_ALARM; + } + if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_OCXO_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_OCXO_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "OCXO temperature is: %d\n", temp); + } + + /* Read the current TX temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the TX temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.tx_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is under %d\n", mgr->temp.tx_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MIN_ALARM; + } + if (temp > mgr->temp.tx_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_TRX_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_TRX_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "TX temperature is: %d\n", temp); + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + /* Read the current PA temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the PA temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.pa_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is under %d\n", mgr->temp.pa_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MIN_ALARM; + } + if (temp > mgr->temp.pa_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_PA_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_PA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "PA temperature is: %d\n", temp); + } + } + + /* Read the current main supply voltage */ + if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) { + rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_VOLTAGE, &volt); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the main supply voltage. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + if (volt > mgr->volt.supply_volt_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MAX_ALARM; + } + if (volt < mgr->volt.supply_volt_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->alarms.supply_low = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MIN_ALARM; + } + if (volt > mgr->volt.supply_volt_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MAX_ALARM; + } + + if (volt < mgr->volt.supply_volt_limit.thresh_crit_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_crit_min); + crit_thresh_passed = 1; + mgr->alarms.supply_min = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MIN_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MIN_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_NONE); + } + LOGP(DTEMP, LOGL_INFO, "Main supply voltage is: %d\n", volt); + } + } + + /* Read the main supply power consumption */ + if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) { + rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_POWER, &power); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the power supply current. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + power /= 1000000; + if (power > mgr->pwr.supply_pwr_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.supply_pwr_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_SUPPLY_WARN_MAX_ALARM; + } + if (power > mgr->pwr.supply_pwr_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_crit_max); + crit_thresh_passed = 1; + + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_SUPPLY_WARN_MAX_ALARM; + + if (oc2gbts_power_get(OC2GBTS_POWER_PA)) { + mgr->alarms.supply_pwr_max = 1; + /* schedule to turn off PA */ + action = SENSOR_ACT_PA_OFF; + /* repeat same alarm to BSC */ + handle_alert_actions(mgr); + } + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "Main supply current power consumption is: %d\n", power); + } + } else { + /* keep last state */ + if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM) { + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + /* Read the current PA power consumption */ + if (oc2gbts_power_get(OC2GBTS_POWER_PA)) { + rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, OC2GBTS_POWER_POWER, &power); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the PA power. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + power /= 1000000; + if (power > mgr->pwr.pa_pwr_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.pa_pwr_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_PA_WARN_MAX_ALARM; + } + if (power > mgr->pwr.pa_pwr_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.pa_pwr_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_PA_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_PA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "PA power consumption is: %d\n", power); + } + } else { + /* keep last state */ + if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_PA_CRIT_MAX_ALARM) { + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } + } + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + /* Read the current VSWR of powered ON PA*/ + if (oc2gbts_power_get(OC2GBTS_POWER_PA)) { + rc = oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the VSWR. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + if ((vswr > mgr->vswr.vswr_limit.thresh_warn_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_warn_max)) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.vswr_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_VSWR_WARN_MAX_ALARM; + } + if ((vswr > mgr->vswr.vswr_limit.thresh_crit_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_crit_max)) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.vswr_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_VSWR_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_VSWR_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "VSWR is: current = %d, last = %d\n", vswr, mgr->vswr.last_vswr); + + /* update last VSWR */ + mgr->vswr.last_vswr = vswr; + } + } else { + /* keep last state */ + if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_VSWR_CRIT_MAX_ALARM) { + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } + } + } + + select_led_pattern(mgr); + oc2gbts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed); +} + +static void sensor_ctrl_check_cb(void *_data) +{ + struct oc2gbts_mgr_instance *mgr = _data; + sensor_ctrl_check(mgr); + /* Check every minute? XXX make it configurable! */ + osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0); + LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n"); + /* TODO: do we want to notify if some sensors could not be read? */ + oc2gbts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR); +} + +int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr) +{ + int rc = 0; + + /* always enable PA GPIO for OC-2G */ + if (!oc2gbts_power_get(OC2GBTS_POWER_PA)) { + rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1); + if (!rc) + LOGP(DTEMP, LOGL_ERROR, "Failed to set GPIO for internal PA\n"); + } + + s_mgr = mgr; + sensor_ctrl_timer.cb = sensor_ctrl_check_cb; + sensor_ctrl_timer.data = s_mgr; + sensor_ctrl_check_cb(s_mgr); + return rc; +} + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c new file mode 100644 index 00000000..ef527394 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c @@ -0,0 +1,984 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_vty.c + * (C) 2014 by oc2gcom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso <anayuso@oc2gcom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <inttypes.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/logging.h> + +#include "oc2gbts_misc.h" +#include "oc2gbts_mgr.h" +#include "oc2gbts_temp.h" +#include "oc2gbts_power.h" +#include "oc2gbts_bid.h" +#include "oc2gbts_led.h" +#include "btsconfig.h" + +static struct oc2gbts_mgr_instance *s_mgr; + +static const char copyright[] = + "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n" + "(C) 2014 by Holger Hans Peter Freyther\r\n" + "(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n" + "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static int 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_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX_TEMP_NODE: + case LIMIT_PA_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA_PWR_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_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX_TEMP_NODE: + case LIMIT_PA_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA_PWR_NODE: + return 1; + default: + return 0; + } +} + +static struct vty_app_info vty_info = { + .name = "oc2gbts-mgr", + .version = PACKAGE_VERSION, + .go_parent_cb = go_to_parent, + .is_config_node = is_config_node, + .copyright = copyright, +}; + + +#define MGR_STR "Configure oc2gbts-mgr\n" + +static struct cmd_node mgr_node = { + MGR_NODE, + "%s(oc2gbts-mgr)# ", + 1, +}; + +static struct cmd_node act_norm_node = { + ACT_NORM_NODE, + "%s(actions-normal)# ", + 1, +}; + +static struct cmd_node act_warn_node = { + ACT_WARN_NODE, + "%s(actions-warn)# ", + 1, +}; + +static struct cmd_node act_crit_node = { + ACT_CRIT_NODE, + "%s(actions-critical)# ", + 1, +}; + +static struct cmd_node limit_supply_temp_node = { + LIMIT_SUPPLY_TEMP_NODE, + "%s(limit-supply-temp)# ", + 1, +}; + +static struct cmd_node limit_soc_node = { + LIMIT_SOC_NODE, + "%s(limit-soc)# ", + 1, +}; + +static struct cmd_node limit_fpga_node = { + LIMIT_FPGA_NODE, + "%s(limit-fpga)# ", + 1, +}; + +static struct cmd_node limit_rmsdet_node = { + LIMIT_RMSDET_NODE, + "%s(limit-rmsdet)# ", + 1, +}; + +static struct cmd_node limit_ocxo_node = { + LIMIT_OCXO_NODE, + "%s(limit-ocxo)# ", + 1, +}; + +static struct cmd_node limit_tx_temp_node = { + LIMIT_TX_TEMP_NODE, + "%s(limit-tx-temp)# ", + 1, +}; +static struct cmd_node limit_pa_temp_node = { + LIMIT_PA_TEMP_NODE, + "%s(limit-pa-temp)# ", + 1, +}; +static struct cmd_node limit_supply_volt_node = { + LIMIT_SUPPLY_VOLT_NODE, + "%s(limit-supply-volt)# ", + 1, +}; +static struct cmd_node limit_vswr_node = { + LIMIT_VSWR_NODE, + "%s(limit-vswr)# ", + 1, +}; +static struct cmd_node limit_supply_pwr_node = { + LIMIT_SUPPLY_PWR_NODE, + "%s(limit-supply-pwr)# ", + 1, +}; +static struct cmd_node limit_pa_pwr_node = { + LIMIT_PA_PWR_NODE, + "%s(limit-pa-pwr)# ", + 1, +}; + +static struct cmd_node limit_gps_fix_node = { + LIMIT_GPS_FIX_NODE, + "%s(limit-gps-fix)# ", + 1, +}; + +DEFUN(cfg_mgr, cfg_mgr_cmd, + "oc2gbts-mgr", + MGR_STR) +{ + vty->node = MGR_NODE; + return CMD_SUCCESS; +} + +static void write_volt_limit(struct vty *vty, const char *name, + struct oc2gbts_volt_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning min %d%s", + limit->thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " threshold critical min %d%s", + limit->thresh_crit_min, VTY_NEWLINE); +} + +static void write_vswr_limit(struct vty *vty, const char *name, + struct oc2gbts_vswr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); +} + +static void write_pwr_limit(struct vty *vty, const char *name, + struct oc2gbts_pwr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " threshold critical max %d%s", + limit->thresh_crit_max, 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 & SENSOR_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-on%s", + (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); +} + +static void write_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa-off%s", + (actions & SENSOR_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-off%s", + (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); +} + +static int config_write_mgr(struct vty *vty) +{ + vty_out(vty, "oc2gbts-mgr%s", VTY_NEWLINE); + + write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit); + write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit); + write_vswr_limit(vty, "limits vswr", &s_mgr->vswr.vswr_limit); + + write_norm_action(vty, "actions normal", s_mgr->state.action_norm); + write_action(vty, "actions warn", s_mgr->state.action_warn); + write_action(vty, "actions critical", s_mgr->state.action_crit); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +#define CFG_LIMIT_TEMP(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->temp.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit) +CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit) +CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit) +CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit) +CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit) +CFG_LIMIT_TEMP(tx_temp, "TX TEMP\n", LIMIT_TX_TEMP_NODE, tx_temp_limit) +CFG_LIMIT_TEMP(pa_temp, "PA TEMP\n", LIMIT_PA_TEMP_NODE, pa_temp_limit) +#undef CFG_LIMIT_TEMP + +#define CFG_LIMIT_VOLT(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->volt.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit) +#undef CFG_LIMIT_VOLT + +#define CFG_LIMIT_VSWR(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->vswr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VSWR(vswr, "VSWR\n", LIMIT_VSWR_NODE, vswr_limit) +#undef CFG_LIMIT_VSWR + +#define CFG_LIMIT_PWR(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->pwr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit) +CFG_LIMIT_PWR(pa_pwr, "PA PWR\n", LIMIT_PA_PWR_NODE, pa_pwr_limit) +#undef CFG_LIMIT_PWR + +#define CFG_LIMIT_GPS_FIX(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->gps.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit) +#undef CFG_LIMIT_GPS_FIX + +DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd, + "threshold warning min <0-48000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_volt_limit *limit = vty->index; + limit->thresh_warn_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd, + "threshold critical min <0-48000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct oc2gbts_volt_limit *limit = vty->index; + limit->thresh_crit_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd, + "threshold warning max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_vswr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd, + "threshold critical max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_vswr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd, + "threshold warning max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_pwr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd, + "threshold critical max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_pwr_limit *limit = vty->index; + limit->thresh_crit_max = 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->state.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 |= SENSOR_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 &= ~SENSOR_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 oc2gbts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_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 oc2gbts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_BTS_SRV_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 |= SENSOR_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 &= ~SENSOR_ACT_PA_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd, + "bts-service-off", + "Stop the systemd oc2gbts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_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 oc2gbts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(show_mgr, show_mgr_cmd, "show manager", + SHOW_STR "Display information about the manager") +{ + int temp, volt, current, power, vswr; + vty_out(vty, "Warning alarm flags: 0x%08x%s", + s_mgr->oc2gbts_ctrl.warn_flags, VTY_NEWLINE); + vty_out(vty, "Critical alarm flags: 0x%08x%s", + s_mgr->oc2gbts_ctrl.crit_flags, VTY_NEWLINE); + vty_out(vty, "Preventive action retried: %d%s", + s_mgr->alarms.preventive_retry, VTY_NEWLINE); + vty_out(vty, "Temperature control state: %s%s", + oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); + vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp); + vty_out(vty, " Main Supply : %4.2f Celcius%s", + temp/ 1000.0f, + VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp); + vty_out(vty, " SoC : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp); + vty_out(vty, " FPGA : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp); + vty_out(vty, " RMSDet : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + } + oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp); + vty_out(vty, " OCXO : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp); + vty_out(vty, " TX : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp); + vty_out(vty, " Power Amp : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + } + vty_out(vty, "Power Status%s", VTY_NEWLINE); + oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_VOLTAGE, &volt); + oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_CURRENT, ¤t); + oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_POWER, &power); + vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, + OC2GBTS_POWER_VOLTAGE, &volt); + oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, + OC2GBTS_POWER_CURRENT, ¤t); + oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, + OC2GBTS_POWER_POWER, &power); + vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + oc2gbts_power_get(OC2GBTS_POWER_PA) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + } + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + vty_out(vty, "VSWR Status%s", VTY_NEWLINE); + oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr); + vty_out(vty, " VSWR : %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +DEFUN(show_thresh, show_thresh_cmd, "show thresholds", + SHOW_STR "Display information about the thresholds") +{ + vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE); + vty_out(vty, " Main supply%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " SoC%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " FPGA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + vty_out(vty, " RMSDet%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE); + } + vty_out(vty, " OCXO%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_min, VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + vty_out(vty, " PA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_min, VTY_NEWLINE); + } + vty_out(vty, "Power limits%s", VTY_NEWLINE); + vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE); + vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + vty_out(vty, " PA power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_warn_max, VTY_NEWLINE); + } + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + vty_out(vty, "VSWR limits%s", VTY_NEWLINE); + vty_out(vty, " TX%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.vswr_limit.thresh_warn_max, VTY_NEWLINE); + } + vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(calibrate_clock, calibrate_clock_cmd, + "calibrate clock", + "Calibration commands\n" + "Calibrate clock against GPS PPS\n") +{ + if (oc2gbts_mgr_calib_run(s_mgr) < 0) { + vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(set_led_pattern, set_led_pattern_cmd, + "set led pattern <0-255>", + "Set LED pattern\n" + "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n") +{ + int pattern_id = atoi(argv[0]); + + if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) { + vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE); + return CMD_WARNING; + } + + led_set(s_mgr, pattern_id); + return CMD_SUCCESS; +} + +DEFUN(force_mgr_state, force_mgr_state_cmd, + "force manager state <0-255>", + "Force BTS manager state\n" + "Force BTS manager state for debugging purpose only\n") +{ + int state = atoi(argv[0]); + + if ((state < 0) || (state > STATE_CRITICAL)) { + vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE); + return CMD_WARNING; + } + + s_mgr->state.state = state; + return CMD_SUCCESS; +} + +#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \ + "limit temp " #name " " #criticity " " #min_max " <-200-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->temp.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_min, warning, min) +#undef LIMIT_TEMP + +#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \ + "limit " #name " " #criticity " " #min_max " <0-48000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->volt.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min) +#undef LIMIT_VOLT + +#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \ + "limit power " #name " " #criticity " " #min_max " <0-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->pwr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_crit_max, critical, max) +#undef LIMIT_PWR + +#define LIMIT_VSWR(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_vswr_##variable, limit_vswr_##variable##_cmd, \ + "limit vswr " #criticity " " #min_max " <1000-200000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->vswr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_crit_max, critical, max) +#undef LIMIT_VSWR + +#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \ + "limit gpsfix " #criticity " " #min_max " <0-365>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->gps.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max) +#undef LIMIT_GPSFIX + +static void register_limit(int limit, uint32_t unit) +{ + switch (unit) { + case MGR_LIMIT_TYPE_VOLT: + install_element(limit, &cfg_thresh_volt_warn_min_cmd); + install_element(limit, &cfg_thresh_volt_crit_min_cmd); + break; + case MGR_LIMIT_TYPE_VSWR: + install_element(limit, &cfg_thresh_vswr_warn_max_cmd); + install_element(limit, &cfg_thresh_vswr_crit_max_cmd); + break; + case MGR_LIMIT_TYPE_PWR: + install_element(limit, &cfg_thresh_pwr_warn_max_cmd); + install_element(limit, &cfg_thresh_pwr_crit_max_cmd); + break; + default: + break; + } +} + +static void register_normal_action(int act) +{ + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + 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); +} + +static void register_action(int act) +{ + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + 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); +} + +static void register_hidden_commands() +{ + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_min_cmd); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_min_cmd); + } + + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd); + + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + install_element(ENABLE_NODE, &limit_pwr_pa_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa_thresh_crit_max_cmd); + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + install_element(ENABLE_NODE, &limit_vswr_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_thresh_crit_max_cmd); + } + + install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd); +} + +int oc2gbts_mgr_vty_init(void) +{ + vty_init(&vty_info); + + install_element_ve(&show_mgr_cmd); + install_element_ve(&show_thresh_cmd); + + install_element(ENABLE_NODE, &calibrate_clock_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_supply_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_temp_cmd); + vty_install_default(LIMIT_SUPPLY_TEMP_NODE); + + install_node(&limit_soc_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_soc_temp_cmd); + vty_install_default(LIMIT_SOC_NODE); + + install_node(&limit_fpga_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd); + vty_install_default(LIMIT_FPGA_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + install_node(&limit_rmsdet_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd); + vty_install_default(LIMIT_RMSDET_NODE); + } + + install_node(&limit_ocxo_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd); + vty_install_default(LIMIT_OCXO_NODE); + + install_node(&limit_tx_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx_temp_cmd); + vty_install_default(LIMIT_TX_TEMP_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + install_node(&limit_pa_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa_temp_cmd); + vty_install_default(LIMIT_PA_TEMP_NODE); + } + + install_node(&limit_supply_volt_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_volt_cmd); + register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT); + vty_install_default(LIMIT_SUPPLY_VOLT_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + install_node(&limit_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_vswr_cmd); + register_limit(LIMIT_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + vty_install_default(LIMIT_VSWR_NODE); + } + + install_node(&limit_supply_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd); + register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR); + vty_install_default(LIMIT_SUPPLY_PWR_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + install_node(&limit_pa_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa_pwr_cmd); + vty_install_default(LIMIT_PA_PWR_NODE); + } + + install_node(&limit_gps_fix_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_gps_fix_cmd); + vty_install_default(LIMIT_GPS_FIX_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); + + /* install LED pattern command for debugging purpose */ + install_element_ve(&set_led_pattern_cmd); + install_element_ve(&force_mgr_state_cmd); + + register_hidden_commands(); + + return 0; +} + +int oc2gbts_mgr_parse_config(struct oc2gbts_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; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c new file mode 100644 index 00000000..bacf07bd --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c @@ -0,0 +1,381 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <limits.h> +#include <time.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> + +#include "oc2gbts_mgr.h" +#include "btsconfig.h" +#include "oc2gbts_misc.h" +#include "oc2gbts_par.h" +#include "oc2gbts_temp.h" +#include "oc2gbts_power.h" +#include "oc2gbts_bid.h" + +/********************************************************************* + * Temperature handling + *********************************************************************/ + +static const struct { + const char *name; + int has_max; + enum oc2gbts_temp_sensor sensor; + enum oc2gbts_par ee_par; +} temp_data[] = { + { + .name = "supply_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_SUPPLY, + .ee_par = OC2GBTS_PAR_TEMP_SUPPLY_MAX, + }, { + .name = "soc_temp", + .has_max = 0, + .sensor = OC2GBTS_TEMP_SOC, + .ee_par = OC2GBTS_PAR_TEMP_SOC_MAX, + }, { + .name = "fpga_temp", + .has_max = 0, + .sensor = OC2GBTS_TEMP_FPGA, + .ee_par = OC2GBTS_PAR_TEMP_FPGA_MAX, + + }, { + .name = "rmsdet_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_RMSDET, + .ee_par = OC2GBTS_PAR_TEMP_RMSDET_MAX, + }, { + .name = "ocxo_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_OCXO, + .ee_par = OC2GBTS_PAR_TEMP_OCXO_MAX, + }, { + .name = "tx_temp", + .has_max = 0, + .sensor = OC2GBTS_TEMP_TX, + .ee_par = OC2GBTS_PAR_TEMP_TX_MAX, + }, { + .name = "pa_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_PA, + .ee_par = OC2GBTS_PAR_TEMP_PA_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum oc2gbts_power_source sensor_source; + enum oc2gbts_power_type sensor_type; + enum oc2gbts_par ee_par; +} power_data[] = { + { + .name = "supply_volt", + .has_max = 1, + .sensor_source = OC2GBTS_POWER_SUPPLY, + .sensor_type = OC2GBTS_POWER_VOLTAGE, + .ee_par = OC2GBTS_PAR_VOLT_SUPPLY_MAX, + }, { + .name = "supply_pwr", + .has_max = 1, + .sensor_source = OC2GBTS_POWER_SUPPLY, + .sensor_type = OC2GBTS_POWER_POWER, + .ee_par = OC2GBTS_PAR_PWR_SUPPLY_MAX, + }, { + .name = "pa_pwr", + .has_max = 1, + .sensor_source = OC2GBTS_POWER_PA, + .sensor_type = OC2GBTS_POWER_POWER, + .ee_par = OC2GBTS_PAR_PWR_PA_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum oc2gbts_vswr_sensor sensor; + enum oc2gbts_par ee_par; +} vswr_data[] = { + { + .name = "vswr", + .has_max = 0, + .sensor = OC2GBTS_VSWR, + .ee_par = OC2GBTS_PAR_VSWR_MAX, + } +}; + +static const struct value_string power_unit_strs[] = { + { OC2GBTS_POWER_POWER, "W" }, + { OC2GBTS_POWER_VOLTAGE, "V" }, + { 0, NULL } +}; + +void oc2gbts_check_temp(int no_rom_write) +{ + int temp_old[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 = -99; + + if (temp_data[i].sensor == OC2GBTS_TEMP_PA && + !oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) + continue; + + rc = oc2gbts_par_get_int(temp_data[i].ee_par, &ret); + temp_old[i] = ret * 1000; + + oc2gbts_temp_get(temp_data[i].sensor, &temp_cur[i]); + if (temp_cur[i] < 0 && temp_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d)\n", temp_data[i].sensor); + continue; + } + + 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_cur[i] > temp_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "temperature: %d.%d C\n", temp_data[i].name, + temp_cur[i]/1000, temp_old[i]%1000); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(temp_data[i].ee_par, + temp_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max temp %d (%s)\n", temp_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void oc2gbts_check_power(int no_rom_write) +{ + int power_old[ARRAY_SIZE(power_data)]; + int power_cur[ARRAY_SIZE(power_data)]; + int i, rc; + int div_ratio; + + for (i = 0; i < ARRAY_SIZE(power_data); i++) { + int ret = 0; + + if (power_data[i].sensor_source == OC2GBTS_POWER_PA && + !oc2gbts_option_get(OC2GBTS_OPTION_PA)) + continue; + + rc = oc2gbts_par_get_int(power_data[i].ee_par, &ret); + switch(power_data[i].sensor_type) { + case OC2GBTS_POWER_VOLTAGE: + div_ratio = 1000; + break; + case OC2GBTS_POWER_POWER: + div_ratio = 1000000; + break; + default: + div_ratio = 1000; + } + power_old[i] = ret * div_ratio; + + oc2gbts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]); + if (power_cur[i] < 0 && power_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, power_data[i].sensor_type); + continue; + } + LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n", + power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (power_cur[i] > power_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "power: %d.%d %s\n", power_data[i].name, + power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(power_data[i].ee_par, + power_cur[i]/div_ratio); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max power %d (%s)\n", power_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void oc2gbts_check_vswr(int no_rom_write) +{ + int vswr_old[ARRAY_SIZE(vswr_data)]; + int vswr_cur[ARRAY_SIZE(vswr_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(vswr_data); i++) { + int ret = 0; + + if (vswr_data[i].sensor == OC2GBTS_VSWR && + (!oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + !oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL))) + continue; + + rc = oc2gbts_par_get_int(vswr_data[i].ee_par, &ret); + vswr_old[i] = ret * 1000; + + oc2gbts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]); + if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n", + vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000); + + if (vswr_cur[i] > vswr_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "vswr: %d.%d C\n", vswr_data[i].name, + vswr_cur[i]/1000, vswr_old[i]%1000); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(vswr_data[i].ee_par, + vswr_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max vswr %d (%s)\n", vswr_data[i].name, + rc, strerror(errno)); + } + } + } +} + +/********************************************************************* + * Hours handling + *********************************************************************/ +static time_t last_update; + +int oc2gbts_update_hours(int no_rom_write) +{ + time_t now = time(NULL); + int rc, op_hrs = 0; + + /* first time after start of manager program */ + if (last_update == 0) { + last_update = now; + + rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + /* create a new file anyway */ + if (!no_rom_write) + rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs); + + return rc; + } + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + return 0; + } + + if (now >= last_update + 3600) { + rc = oc2gbts_par_get_int(OC2GBTS_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_rom_write) { + rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs); + if (rc < 0) + return rc; + } + + last_update = now; + } + + return 0; +} + +/********************************************************************* + * Firmware reloading + *********************************************************************/ + +static const char *fw_sysfs[_NUM_FW] = { + [OC2GBTS_FW_DSP] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery", +}; + +int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type) +{ + int fd; + int rc; + + switch (type) { + case OC2GBTS_FW_DSP: + fd = open(fw_sysfs[type], O_WRONLY); + if (fd < 0) { + LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + fw_sysfs[type], strerror(errno)); + close(fd); + return fd; + } + rc = write(fd, "restart", 8); + if (rc < 8) { + LOGP(DFW, LOGL_ERROR, "short write during " + "fw write to %s\n", fw_sysfs[type]); + close(fd); + return -EIO; + } + close(fd); + default: + return -EINVAL; + } + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.h b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h new file mode 100644 index 00000000..78315679 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h @@ -0,0 +1,17 @@ +#ifndef _OC2GBTS_MISC_H +#define _OC2GBTS_MISC_H + +#include <stdint.h> + +void oc2gbts_check_temp(int no_rom_write); +void oc2gbts_check_power(int no_rom_write); +void oc2gbts_check_vswr(int no_rom_write); + +int oc2gbts_update_hours(int no_rom_write); + +enum oc2gbts_firmware_type { + OC2GBTS_FW_DSP, + _NUM_FW +}; + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c new file mode 100644 index 00000000..39f64aae --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c @@ -0,0 +1,123 @@ +/* Helper for netlink */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_nl.c + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <arpa/inet.h> +#include <netinet/ip.h> + +#include <sys/socket.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#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; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h new file mode 100644 index 00000000..340cf117 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_nl.h + * (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 <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c new file mode 100644 index 00000000..f3550243 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c @@ -0,0 +1,249 @@ +/* oc2gbts - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_par.c + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> + +#include "oc2gbts_par.h" + +const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1] = { + { OC2GBTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" }, + { OC2GBTS_PAR_TEMP_SOC_MAX, "temp-soc-max" }, + { OC2GBTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" }, + { OC2GBTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" }, + { OC2GBTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" }, + { OC2GBTS_PAR_TEMP_TX_MAX, "temp-tx-max" }, + { OC2GBTS_PAR_TEMP_PA_MAX, "temp-pa-max" }, + { OC2GBTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" }, + { OC2GBTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" }, + { OC2GBTS_PAR_PWR_PA_MAX, "pwr-pa-max" }, + { OC2GBTS_PAR_VSWR_MAX, "vswr-max" }, + { OC2GBTS_PAR_GPS_FIX, "gps-fix" }, + { OC2GBTS_PAR_SERNR, "serial-nr" }, + { OC2GBTS_PAR_HOURS, "hours-running" }, + { OC2GBTS_PAR_BOOTS, "boot-count" }, + { OC2GBTS_PAR_KEY, "key" }, + { 0, NULL } +}; + +int oc2gbts_par_is_int(enum oc2gbts_par par) +{ + switch (par) { + case OC2GBTS_PAR_TEMP_SUPPLY_MAX: + case OC2GBTS_PAR_TEMP_SOC_MAX: + case OC2GBTS_PAR_TEMP_FPGA_MAX: + case OC2GBTS_PAR_TEMP_RMSDET_MAX: + case OC2GBTS_PAR_TEMP_OCXO_MAX: + case OC2GBTS_PAR_TEMP_TX_MAX: + case OC2GBTS_PAR_TEMP_PA_MAX: + case OC2GBTS_PAR_VOLT_SUPPLY_MAX: + case OC2GBTS_PAR_VSWR_MAX: + case OC2GBTS_PAR_SERNR: + case OC2GBTS_PAR_HOURS: + case OC2GBTS_PAR_BOOTS: + case OC2GBTS_PAR_PWR_SUPPLY_MAX: + case OC2GBTS_PAR_PWR_PA_MAX: + return 1; + default: + return 0; + } +} + +FILE *oc2gbts_par_get_path(void *ctx, enum oc2gbts_par par, const char* mode) +{ + char *fpath; + FILE *fp; + + if (par >= _NUM_OC2GBTS_PAR) + return NULL; + + fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + if (!fpath) + return NULL; + + fp = fopen(fpath, mode); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + return fp; +} + +int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%d", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + return 0; +} + +int oc2gbts_par_set_int(enum oc2gbts_par par, int val) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + return 0; +} + +int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf, + unsigned int size) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "rb"); + if (fp == NULL) { + return -errno; + } + + rc = fread(buf, 1, size, fp); + + fclose(fp); + + return rc; +} + +int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf, + unsigned int size) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "wb"); + if (fp == NULL) { + return -errno; + } + + rc = fwrite(buf, 1, size, fp); + + fsync(fp); + fclose(fp); + + return rc; +} + +int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret) +{ + FILE *fp; + int rc; + + fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%ld", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +int oc2gbts_par_set_gps_fix(void *ctx, time_t val) +{ + FILE *fp; + int rc; + + fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%ld", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.h b/src/osmo-bts-oc2g/misc/oc2gbts_par.h new file mode 100644 index 00000000..588a3c32 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.h @@ -0,0 +1,43 @@ +#ifndef _OC2GBTS_PAR_H +#define _OC2GBTS_PAR_H + +#include <osmocom/core/utils.h> + +#define FACTORY_ROM_PATH "/mnt/rom/factory" +#define USER_ROM_PATH "/var/run/oc2gbts-mgr" +#define UPTIME_TMP_PATH "/tmp/uptime" + +enum oc2gbts_par { + OC2GBTS_PAR_TEMP_SUPPLY_MAX, + OC2GBTS_PAR_TEMP_SOC_MAX, + OC2GBTS_PAR_TEMP_FPGA_MAX, + OC2GBTS_PAR_TEMP_RMSDET_MAX, + OC2GBTS_PAR_TEMP_OCXO_MAX, + OC2GBTS_PAR_TEMP_TX_MAX, + OC2GBTS_PAR_TEMP_PA_MAX, + OC2GBTS_PAR_VOLT_SUPPLY_MAX, + OC2GBTS_PAR_PWR_SUPPLY_MAX, + OC2GBTS_PAR_PWR_PA_MAX, + OC2GBTS_PAR_VSWR_MAX, + OC2GBTS_PAR_GPS_FIX, + OC2GBTS_PAR_SERNR, + OC2GBTS_PAR_HOURS, + OC2GBTS_PAR_BOOTS, + OC2GBTS_PAR_KEY, + _NUM_OC2GBTS_PAR +}; + +extern const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1]; + +int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret); +int oc2gbts_par_set_int(enum oc2gbts_par par, int val); +int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf, + unsigned int size); +int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf, + unsigned int size); + +int oc2gbts_par_is_int(enum oc2gbts_par par); +int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret); +int oc2gbts_par_set_gps_fix(void *ctx, time_t val); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c new file mode 100644 index 00000000..4e2fc95a --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c @@ -0,0 +1,177 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#include "oc2gbts_power.h" + +static const char *power_enable_devs[_NUM_POWER_SOURCES] = { + [OC2GBTS_POWER_PA] = "/var/oc2g/pa-state/pa0/state", +}; + +static const char *power_sensor_devs[_NUM_POWER_SOURCES] = { + [OC2GBTS_POWER_SUPPLY] = "/var/oc2g/pwr-sense/main-supply/", + [OC2GBTS_POWER_PA] = "/var/oc2g/pwr-sense/pa0/", +}; + +static const char *power_sensor_type_str[_NUM_POWER_TYPES] = { + [OC2GBTS_POWER_POWER] = "power", + [OC2GBTS_POWER_VOLTAGE] = "voltage", + [OC2GBTS_POWER_CURRENT] = "current", +}; + +int oc2gbts_power_sensor_get( + enum oc2gbts_power_source source, + enum oc2gbts_power_type type, + int *power) +{ + char buf[PATH_MAX]; + char pwrstr[10]; + int fd, rc; + + if (source >= _NUM_POWER_SOURCES) + return -EINVAL; + + if (type >= _NUM_POWER_TYPES) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, pwrstr, sizeof(pwrstr)); + pwrstr[sizeof(pwrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *power = atoi(pwrstr); + return 0; +} + + +int oc2gbts_power_set( + enum oc2gbts_power_source source, + int en) +{ + int fd; + int rc; + + if (source != OC2GBTS_POWER_PA) { + return -EINVAL; + } + + fd = open(power_enable_devs[source], O_WRONLY); + if (fd < 0) { + return fd; + } + rc = write(fd, en?"1":"0", 2); + close( fd ); + + if (rc != 2) { + return -1; + } + + if (en) usleep(50*1000); + + return 0; +} + +int oc2gbts_power_get( + enum oc2gbts_power_source source) +{ + int fd; + int rc; + int retVal = 0; + char enstr[10]; + + fd = open(power_enable_devs[source], O_RDONLY); + if (fd < 0) { + return fd; + } + + rc = read(fd, enstr, sizeof(enstr)); + enstr[rc-1] = '\0'; + + close(fd); + + if (rc < 0) { + return rc; + } + if (rc == 0) { + return -EIO; + } + + rc = strcmp(enstr, "enabled"); + if(rc == 0) { + retVal = 1; + } + + return retVal; +} + +static const char *vswr_devs[_NUM_VSWR_SENSORS] = { + [OC2GBTS_VSWR] = "/var/oc2g/vswr/tx0/vswr", +}; + +int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr) +{ + char buf[PATH_MAX]; + char vswrstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, vswrstr, sizeof(vswrstr)); + vswrstr[sizeof(vswrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *vswr = atoi(vswrstr); + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.h b/src/osmo-bts-oc2g/misc/oc2gbts_power.h new file mode 100644 index 00000000..3229f243 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.h @@ -0,0 +1,36 @@ +#ifndef _OC2GBTS_POWER_H +#define _OC2GBTS_POWER_H + +enum oc2gbts_power_source { + OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_PA, + _NUM_POWER_SOURCES +}; + +enum oc2gbts_power_type { + OC2GBTS_POWER_POWER, + OC2GBTS_POWER_VOLTAGE, + OC2GBTS_POWER_CURRENT, + _NUM_POWER_TYPES +}; + +int oc2gbts_power_sensor_get( + enum oc2gbts_power_source source, + enum oc2gbts_power_type type, + int *volt); + +int oc2gbts_power_set( + enum oc2gbts_power_source source, + int en); + +int oc2gbts_power_get( + enum oc2gbts_power_source source); + +enum oc2gbts_vswr_sensor { + OC2GBTS_VSWR, + _NUM_VSWR_SENSORS +}; + +int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c new file mode 100644 index 00000000..59b795ac --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c @@ -0,0 +1,178 @@ +/* Systemd service wd notification for OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_swd.h" +#include <osmocom/core/logging.h> + +/* Needed for service watchdog notification */ +#include <systemd/sd-daemon.h> + +/* This is the period used to verify if all events have been registered to be allowed + to notify the systemd service watchdog +*/ +#define SWD_PERIOD 30 + +static void swd_start(struct oc2gbts_mgr_instance *mgr); +static void swd_process(struct oc2gbts_mgr_instance *mgr); +static void swd_close(struct oc2gbts_mgr_instance *mgr); +static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int reason); +static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop); +static void swd_loop_run(void *_data); + +enum swd_state { + SWD_INITIAL, + SWD_IN_PROGRESS, +}; + +enum swd_result { + SWD_FAIL_START, + SWD_FAIL_NOTIFY, + SWD_SUCCESS, +}; + +static void swd_start(struct oc2gbts_mgr_instance *mgr) +{ + swd_process(mgr); +} + +static void swd_process(struct oc2gbts_mgr_instance *mgr) +{ + int rc = 0, notify = 0; + + /* Did we get all needed conditions ? */ + if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) { + /* Ping systemd service wd if enabled */ + rc = sd_notify(0, "WATCHDOG=1"); + LOGP(DSWD, LOGL_INFO, "Watchdog notification attempt\n"); + notify = 1; + } + else { + LOGP(DSWD, LOGL_INFO, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks); + } + + if (rc < 0) { + LOGP(DSWD, LOGL_ERROR, + "Failed to notify system service watchdog: %d\n", rc); + swd_state_reset(mgr, SWD_FAIL_NOTIFY); + return; + } + else { + /* Did we notified the watchdog? */ + if (notify) { + mgr->swd.swd_events = 0; + /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */ + if (mgr->swd.swd_events != 0) + mgr->swd.swd_events = 0; + } + } + + swd_state_reset(mgr, SWD_SUCCESS); + return; +} + +static void swd_close(struct oc2gbts_mgr_instance *mgr) +{ +} + +static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome) +{ + if (mgr->swd.swd_from_loop) { + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0); + } + + mgr->swd.state = SWD_INITIAL; + swd_close(mgr); +} + +static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop) +{ + if (mgr->swd.state != SWD_INITIAL) { + LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n"); + return -1; + } + + mgr->swd.swd_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->swd.state = SWD_IN_PROGRESS; + swd_start(mgr); + return 0; +} + +static void swd_loop_run(void *_data) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = _data; + + LOGP(DSWD, LOGL_INFO, "Going to check for watchdog notification.\n"); + rc = swd_run(mgr, 1); + if (rc != 0) { + swd_state_reset(mgr, SWD_FAIL_START); + } +} + +/* 'swd_num_events' configures the number of events to be monitored before notifying the + systemd service watchdog. It must be in the range of [1,64]. Events are notified + through the function 'oc2gbts_swd_event' +*/ +int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events) +{ + /* Checks for a valid number of events to validate */ + if (swd_num_events < 1 || swd_num_events > 64) + return(-1); + + mgr->swd.state = SWD_INITIAL; + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0); + + if (swd_num_events == 64){ + mgr->swd.swd_eventmasks = 0xffffffffffffffffULL; + } + else { + mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1); + } + mgr->swd.swd_events = 0; + mgr->swd.num_events = swd_num_events; + + return 0; +} + +/* Notifies that the specified event 'swd_event' happened correctly; + the value must be in the range of [0,'swd_num_events'[ (see oc2gbts_swd_init). + For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. + WARNING: if this function can be used from multiple threads at the same time, + it must be protected with a kind of mutex to avoid loosing event notification. +*/ +int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event) +{ + /* Checks for a valid specified event (smaller than max possible) */ + if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events) + return(-1); + + mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event)); + + /* !!! Uncomment following line to debug events notification */ + LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event)); + + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.h b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h new file mode 100644 index 00000000..313712ac --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h @@ -0,0 +1,7 @@ +#ifndef _OC2GBTS_SWD_H +#define _OC2GBTS_SWD_H + +int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events); +int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c new file mode 100644 index 00000000..8425dda3 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#include <osmocom/core/utils.h> + +#include "oc2gbts_temp.h" + +static const char *temp_devs[_NUM_TEMP_SENSORS] = { + [OC2GBTS_TEMP_SUPPLY] = "/var/oc2g/temp/main-supply/temp", + [OC2GBTS_TEMP_SOC] = "/var/oc2g/temp/cpu/temp", + [OC2GBTS_TEMP_FPGA] = "/var/oc2g/temp/fpga/temp", + [OC2GBTS_TEMP_RMSDET] = "/var/oc2g/temp/rmsdet/temp", + [OC2GBTS_TEMP_OCXO] = "/var/oc2g/temp/ocxo/temp", + [OC2GBTS_TEMP_TX] = "/var/oc2g/temp/tx0/temp", + [OC2GBTS_TEMP_PA] = "/var/oc2g/temp/pa0/temp", +}; + +int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp) +{ + char buf[PATH_MAX]; + char tempstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]); + 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); + *temp = atoi(tempstr); + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.h b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h new file mode 100644 index 00000000..6d5dfca8 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h @@ -0,0 +1,26 @@ +#ifndef _OC2GBTS_TEMP_H +#define _OC2GBTS_TEMP_H + +enum oc2gbts_temp_sensor { + OC2GBTS_TEMP_SUPPLY, + OC2GBTS_TEMP_SOC, + OC2GBTS_TEMP_FPGA, + OC2GBTS_TEMP_RMSDET, + OC2GBTS_TEMP_OCXO, + OC2GBTS_TEMP_TX, + OC2GBTS_TEMP_PA, + _NUM_TEMP_SENSORS +}; + +enum oc2gbts_temp_type { + OC2GBTS_TEMP_INPUT, + OC2GBTS_TEMP_LOWEST, + OC2GBTS_TEMP_HIGHEST, + OC2GBTS_TEMP_FAULT, + _NUM_TEMP_TYPES +}; + +int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp); + + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c new file mode 100644 index 00000000..b71f0383 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c @@ -0,0 +1,158 @@ +/* oc2gbts-util - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + + +#include "oc2gbts_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 = oc2gbts_par_names; + + printf("oc2gbts-util [--void-warranty -r | -w value] param_name\n"); + printf("Possible param names:\n"); + + for (; par->str != NULL; par += 1) { + if (!oc2gbts_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 oc2gbts_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(oc2gbts_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 = oc2gbts_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 = oc2gbts_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 = oc2gbts_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); +} + diff --git a/src/osmo-bts-oc2g/oc2gbts.c b/src/osmo-bts-oc2g/oc2gbts.c new file mode 100644 index 00000000..012d705c --- /dev/null +++ b/src/osmo-bts-oc2g/oc2gbts.c @@ -0,0 +1,361 @@ +/* NuRAN Wireless OC-2G L1 API related definitions */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * based on: + * sysmobts.c + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <nrw/oc2g/oc2g.h> +#include <nrw/oc2g/gsml1const.h> +#include <nrw/oc2g/gsml1dbg.h> + +#include "oc2gbts.h" + +enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return L1P_T_REQ; + case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ; + case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ; + case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF; + case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ; + case GsmL1_PrimId_PhDataReq: return L1P_T_REQ; + case GsmL1_PrimId_MphTimeInd: return L1P_T_IND; + case GsmL1_PrimId_MphSyncInd: return L1P_T_IND; + case GsmL1_PrimId_PhConnectInd: return L1P_T_IND; + case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND; + case GsmL1_PrimId_PhDataInd: return L1P_T_IND; + case GsmL1_PrimId_PhRaInd: return L1P_T_IND; + default: return L1P_T_INVALID; + } +} + +const struct value_string oc2gbts_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 } +}; + +GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf; + case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf; + case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf; + case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf; + case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf; + case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf; + case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf; + case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf; + default: return -1; // Weak + } +} + +enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id) +{ + switch (id) { + case Oc2g_PrimId_SystemInfoReq: return L1P_T_REQ; + case Oc2g_PrimId_SystemInfoCnf: return L1P_T_CONF; + case Oc2g_PrimId_SystemFailureInd: return L1P_T_IND; + case Oc2g_PrimId_ActivateRfReq: return L1P_T_REQ; + case Oc2g_PrimId_ActivateRfCnf: return L1P_T_CONF; + case Oc2g_PrimId_DeactivateRfReq: return L1P_T_REQ; + case Oc2g_PrimId_DeactivateRfCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetTraceFlagsReq: return L1P_T_REQ; + case Oc2g_PrimId_Layer1ResetReq: return L1P_T_REQ; + case Oc2g_PrimId_Layer1ResetCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetCalibTblReq: return L1P_T_REQ; + case Oc2g_PrimId_SetCalibTblCnf: return L1P_T_CONF; + case Oc2g_PrimId_MuteRfReq: return L1P_T_REQ; + case Oc2g_PrimId_MuteRfCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetRxAttenReq: return L1P_T_REQ; + case Oc2g_PrimId_SetRxAttenCnf: return L1P_T_CONF; + case Oc2g_PrimId_IsAliveReq: return L1P_T_REQ; + case Oc2g_PrimId_IsAliveCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetMaxCellSizeReq: return L1P_T_REQ; + case Oc2g_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ; + case Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF; + default: return L1P_T_INVALID; + } +} + +const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1] = { + { Oc2g_PrimId_SystemInfoReq, "SYSTEM-INFO.req" }, + { Oc2g_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" }, + { Oc2g_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" }, + { Oc2g_PrimId_ActivateRfReq, "ACTIVATE-RF.req" }, + { Oc2g_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" }, + { Oc2g_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" }, + { Oc2g_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" }, + { Oc2g_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" }, + { Oc2g_PrimId_Layer1ResetReq, "LAYER1-RESET.req" }, + { Oc2g_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" }, + { Oc2g_PrimId_SetCalibTblReq, "SET-CALIB.req" }, + { Oc2g_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" }, + { Oc2g_PrimId_MuteRfReq, "MUTE-RF.req" }, + { Oc2g_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, + { Oc2g_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" }, + { Oc2g_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" }, + { Oc2g_PrimId_IsAliveReq, "IS-ALIVE.req" }, + { Oc2g_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" }, + { Oc2g_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" }, + { Oc2g_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" }, + { Oc2g_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" }, + { Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" }, + { 0, NULL } +}; + +Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id) +{ + switch (id) { + case Oc2g_PrimId_SystemInfoReq: return Oc2g_PrimId_SystemInfoCnf; + case Oc2g_PrimId_ActivateRfReq: return Oc2g_PrimId_ActivateRfCnf; + case Oc2g_PrimId_DeactivateRfReq: return Oc2g_PrimId_DeactivateRfCnf; + case Oc2g_PrimId_Layer1ResetReq: return Oc2g_PrimId_Layer1ResetCnf; + case Oc2g_PrimId_SetCalibTblReq: return Oc2g_PrimId_SetCalibTblCnf; + case Oc2g_PrimId_MuteRfReq: return Oc2g_PrimId_MuteRfCnf; + case Oc2g_PrimId_SetRxAttenReq: return Oc2g_PrimId_SetRxAttenCnf; + case Oc2g_PrimId_IsAliveReq: return Oc2g_PrimId_IsAliveCnf; + case Oc2g_PrimId_SetMaxCellSizeReq: return Oc2g_PrimId_SetMaxCellSizeCnf; + case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf; + default: return -1; // Weak + } +} + +const struct value_string oc2gbts_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 oc2gbts_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" }, + { GsmL1_Status_ClockError, "System clock error" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_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 +}; + +const struct value_string oc2gbts_rsl_ho_causes[] = { + { IPAC_HO_RQD_CAUSE_L_RXLEV_UL_H, "L_RXLEV_UL_H" }, + { IPAC_HO_RQD_CAUSE_L_RXLEV_DL_H, "L_RXLEV_DL_H" }, + { IPAC_HO_RQD_CAUSE_L_RXQUAL_UL_H, "L_RXQUAL_UL_H" }, + { IPAC_HO_RQD_CAUSE_L_RXQUAL_DL_H, "L_RXQUAL_DL_H" }, + { IPAC_HO_RQD_CAUSE_RXLEV_UL_IH, "RXLEV_UL_IH" }, + { IPAC_HO_RQD_CAUSE_RXLEV_DL_IH, "RXLEV_DL_IH" }, + { IPAC_HO_RQD_CAUSE_MAX_MS_RANGE, "MAX_MS_RANGE" }, + { IPAC_HO_RQD_CAUSE_POWER_BUDGET, "POWER_BUDGET" }, + { IPAC_HO_RQD_CAUSE_ENQUIRY, "ENQUIRY" }, + { IPAC_HO_RQD_CAUSE_ENQUIRY_FAILED, "ENQUIRY_FAILED" }, + { 0, NULL } +}; diff --git a/src/osmo-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h new file mode 100644 index 00000000..9eb87452 --- /dev/null +++ b/src/osmo-bts-oc2g/oc2gbts.h @@ -0,0 +1,92 @@ +#ifndef OC2GBTS_H +#define OC2GBTS_H + +#include <stdlib.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <nrw/oc2g/oc2g.h> +#include <nrw/oc2g/gsml1const.h> + +/* + * Depending on the firmware version either GsmL1_Prim_t or Oc2g_Prim_t + * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the + * bigger struct. + */ +#define OC2GBTS_PRIM_SIZE \ + (OSMO_MAX(sizeof(Oc2g_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, +}; + +enum oc2g_pedestal_mode{ + OC2G_PEDESTAL_OFF = 0, + OC2G_PEDESTAL_ON, +}; + +enum oc2g_led_control_mode{ + OC2G_LED_CONTROL_BTS = 0, + OC2G_LED_CONTROL_EXT, +}; + +enum oc2g_auto_pwr_adjust_mode{ + OC2G_TX_PWR_ADJ_NONE = 0, + OC2G_TX_PWR_ADJ_AUTO, +}; + +enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id); +const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1]; +GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id); + +enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id); +const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1]; +Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id); + +const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1]; +const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1]; + +const struct value_string oc2gbts_tracef_names[29]; +const struct value_string oc2gbts_tracef_docs[29]; + +const struct value_string oc2gbts_tch_pl_names[15]; + +const struct value_string oc2gbts_clksrc_names[10]; + +const struct value_string oc2gbts_dir_names[6]; + +const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX]; + +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]; + +/* OC2G default parameters */ +#define OC2G_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */ +#define OC2G_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */ +#define OC2G_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */ +#define OC2G_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */ +#define OC2G_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */ +#define OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */ +#define OC2G_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */ +#define OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */ + +#endif /* OC2GBTS_H */ diff --git a/src/osmo-bts-oc2g/oc2gbts_vty.c b/src/osmo-bts-oc2g/oc2gbts_vty.c new file mode 100644 index 00000000..4f7c45a1 --- /dev/null +++ b/src/osmo-bts-oc2g/oc2gbts_vty.c @@ -0,0 +1,733 @@ +/* VTY interface for NuRAN Wireless OC-2G */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/rate_ctr.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/ctrl/control_cmd.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/bts.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/rsl.h> + +#include "oc2gbts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(struct gsm_lchan *lchan); +extern int rsl_tx_preproc_meas_res(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; + +static const struct value_string oc2g_pedestal_mode_strs[] = { + { OC2G_PEDESTAL_OFF, "off" }, + { OC2G_PEDESTAL_ON, "on" }, + { 0, NULL } +}; + +static const struct value_string oc2g_led_mode_strs[] = { + { OC2G_LED_CONTROL_BTS, "bts" }, + { OC2G_LED_CONTROL_EXT, "external" }, + { 0, NULL } +}; + +static const struct value_string oc2g_auto_adj_pwr_strs[] = { + { OC2G_TX_PWR_ADJ_NONE, "none" }, + { OC2G_TX_PWR_ADJ_AUTO, "auto" }, + { 0, NULL } +}; + +/* configuration */ + +DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.oc2g.calib_path) + talloc_free(pinst->u.oc2g.calib_path); + + pinst->u.oc2g.calib_path = talloc_strdup(pinst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(oc2gbts_tracef_names, argv[1]); + pinst->u.oc2g.dsp_trace_f |= ~flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(oc2gbts_tracef_names, argv[1]); + pinst->u.oc2g.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + + +/* runtime */ + +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 oc2gl1_hdl *fl1h; + int i; + + if (!trx) + return CMD_WARNING; + + fl1h = trx_oc2gl1_hdl(trx); + + vty_out(vty, "Oc2g L1 DSP trace flags:%s", VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(oc2gbts_tracef_names); i++) { + const char *endis; + + if (oc2gbts_tracef_names[i].value == 0 && + oc2gbts_tracef_names[i].str == NULL) + break; + + if (fl1h->dsp_trace_f & oc2gbts_tracef_names[i].value) + endis = "enabled"; + else + endis = "disabled"; + + vty_out(vty, "DSP Trace %-15s %s%s", + oc2gbts_tracef_names[i].str, endis, + VTY_NEWLINE); + } + + return CMD_SUCCESS; + +} + +DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct oc2gl1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.oc2g.hdl; + flag = get_string_value(oc2gbts_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 phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct oc2gl1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.oc2g.hdl; + flag = get_string_value(oc2gbts_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 phy <0-0> instance <0-0> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct oc2gl1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!plink) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.oc2g.hdl; + + 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); + vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.minTxPower, VTY_NEWLINE); + vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.maxTxPower, 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 nr <0-1> tx-power <-110-100>", + TRX_STR + "TRX number \n" + "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(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; +} + +DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + "nominal-tx-power <0-40>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + int nominal_power = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + if (( nominal_power > 40 ) || ( nominal_power < 0 )) { + vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s", + nominal_power, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->nominal_power = nominal_power; + trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd, + "max-cell-size <0-166>", + "Set the maximum cell size in qbits\n") +{ + struct phy_instance *pinst = vty->index; + int cell_size = (uint8_t)atoi(argv[0]); + + if (( cell_size > 166 ) || ( cell_size < 0 )) { + vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s", + cell_size, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.max_cell_size = (uint8_t)cell_size; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd, + "pedestal-mode (on|off)", + "Set unused time-slot transmission in pedestal mode\n" + "Transmission pedestal mode can be (off, on)\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(oc2g_pedestal_mode_strs, argv[0]); + + if((val < OC2G_PEDESTAL_OFF) || (val > OC2G_PEDESTAL_ON)) { + vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.pedestal_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd, + "dsp-alive-period <0-60>", + "Set DSP alive timer period in second\n") +{ + struct phy_instance *pinst = vty->index; + uint8_t period = (uint8_t)atoi(argv[0]); + + if (( period > 60 ) || ( period < 0 )) { + vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s", + period, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.dsp_alive_period = period; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd, + "pwr-adj-mode (none|auto)", + "Set output power adjustment mode\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(oc2g_auto_adj_pwr_strs, argv[0]); + + if((val < OC2G_TX_PWR_ADJ_NONE) || (val > OC2G_TX_PWR_ADJ_AUTO)) { + vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd, + "tx-red-pwr-8psk <0-40>", + "Set reduction output power for 8-PSK scheme in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + if ((val > 40) || (val < 0)) { + vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", + val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.tx_pwr_red_8psk = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd, + "c0-idle-red-pwr <0-40>", + "Set reduction output power for C0 idle slot in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + if ((val > 40) || (val < 0)) { + vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", + val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.tx_c0_idle_pwr_red = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int trx_nr, ts_nr, lchan_nr; + uint8_t ho_cause; + uint8_t old_ho_cause; + + /* get BTS pointer */ + bts = gsm_bts_num(net, 0); + if (!bts) { + vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE); + return CMD_WARNING; + } + /* get TRX pointer */ + if (argc >= 1) { + trx_nr = atoi(argv[0]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + /* get TS pointer */ + if (argc >= 2) { + ts_nr = atoi(argv[1]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS %s%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[ts_nr]; + } + /* get lchan pointer */ + if (argc >= 3) { + lchan_nr = atoi(argv[2]); + if (lchan_nr >= TS_MAX_LCHAN) { + vty_out(vty, "%% can't find LCHAN %s%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + lchan = &ts->lchan[lchan_nr]; + } + + /* get HO cause */ + if (argc >= 4) { + ho_cause = get_string_value(oc2gbts_rsl_ho_causes, argv[3]); + if (ho_cause >= IPAC_HO_RQD_CAUSE_MAX) { + vty_out(vty, "%% can't find valid HO cause %s%s", argv[3], + VTY_NEWLINE); + return CMD_WARNING; + } + } else { + vty_out(vty, "%% HO cause is not provided %s", VTY_NEWLINE); + return CMD_WARNING; + } + /* TODO(oramadan) MERGE + /* store recorded HO causes * / + old_ho_cause = lchan->meas_preproc.rec_ho_causes; + + /* Apply new HO causes * / + lchan->meas_preproc.rec_ho_causes = 1 << (ho_cause - 1); + + /* Send measuremnt report to BSC * / + rsl_tx_preproc_meas_res(lchan); + + /* restore HO cause * / + lchan->meas_preproc.rec_ho_causes = old_ho_cause; + */ + + return CMD_SUCCESS; +} + +/* TODO(oramadan) MERGE +DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd, + "rtp-drift-threshold <0-10000>", + "RTP parameters\n" + "RTP timestamp drift threshold in ms\n") +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts_role_bts *btsb = bts_role_bts(bts); + + btsb->oc2g.rtp_drift_thres_ms = (unsigned int) atoi(argv[0]); + + return CMD_SUCCESS; +} +*/ + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + /* TODO(oramadan) MERGE + struct gsm_bts_role_bts *btsb = bts_role_bts(bts); + + vty_out(vty, " led-control-mode %s%s", + get_value_string(oc2g_led_mode_strs, btsb->oc2g.led_ctrl_mode), VTY_NEWLINE); + + vty_out(vty, " rtp-drift-threshold %d%s", + btsb->oc2g.rtp_drift_thres_ms, VTY_NEWLINE); + */ + +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.oc2g.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(oc2gbts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + if (pinst->u.oc2g.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.oc2g.calib_path, VTY_NEWLINE); + + vty_out(vty, " max-cell-size %d%s", + pinst->u.oc2g.max_cell_size, VTY_NEWLINE); + + vty_out(vty, " pedestal-mode %s%s", + get_value_string(oc2g_pedestal_mode_strs, pinst->u.oc2g.pedestal_mode) , VTY_NEWLINE); + + vty_out(vty, " dsp-alive-period %d%s", + pinst->u.oc2g.dsp_alive_period, VTY_NEWLINE); + + vty_out(vty, " pwr-adj-mode %s%s", + get_value_string(oc2g_auto_adj_pwr_strs, pinst->u.oc2g.tx_pwr_adj_mode), VTY_NEWLINE); + + vty_out(vty, " tx-red-pwr-8psk %d%s", + pinst->u.oc2g.tx_pwr_red_8psk, VTY_NEWLINE); + + vty_out(vty, " c0-idle-red-pwr %d%s", + pinst->u.oc2g.tx_c0_idle_pwr_red, 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, oc2gbts_tracef_names, + "phy <0-1> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs, + TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names, + "no phy <0-1> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs, + NO_STR TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_names, + "dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_docs, + DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_names, + "no dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_docs, + NO_STR DSP_TRACE_F_STR, + "\n", "", 0); + + trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts, + oc2gbts_rsl_ho_causes, + "trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (", + "|",")", VTY_DO_LOWER); + + install_element_ve(&show_dsp_trace_f_cmd); + install_element_ve(&show_sys_info_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, &loopback_cmd); + install_element(ENABLE_NODE, &no_loopback_cmd); + + install_element(ENABLE_NODE, &trigger_ho_cause_cmd); + + install_element(BTS_NODE, &cfg_bts_auto_band_cmd); + install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd); + /* TODO(oramadan) MERGE + install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd); + */ + + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd); + install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd); + install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd); + install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd); + install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd); + install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd); + + return 0; +} + +/* TODO(oramadan) MERGE +/* OC2G BTS control interface * / +CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_alert, "oc2g-oml-alert"); +static int set_oc2g_oml_alert(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int cause = atoi(cmd->value); + char *saveptr = NULL; + + alarm_sig_data.mo = &bts->mo; + cause = atoi(strtok_r(cmd->value, ",", &saveptr)); + alarm_sig_data.event_serverity = (cause >> 8) & 0x0F; + alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr); + memcpy(alarm_sig_data.spare, &cause, sizeof(int)); + LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text); + + /* dispatch OML alarm signal * / + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_ALARM, &alarm_sig_data); + + /* return with same alarm cause to MGR rather than OK string* / + cmd->reply = talloc_asprintf(cmd, "%d", cause); + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_ceased, "oc2g-oml-ceased"); +static int set_oc2g_oml_ceased(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int cause = atoi(cmd->value); + char *saveptr = NULL; + + alarm_sig_data.mo = &bts->mo; + cause = atoi(strtok_r(cmd->value, ",", &saveptr)); + alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr); + memcpy(alarm_sig_data.spare, &cause, sizeof(int)); + LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR ceased alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text); + + /* dispatch OML alarm signal * / + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_CEASED_ALARM, &alarm_sig_data); + */ + + /* return with same alarm cause to MGR rather than OK string* / + cmd->reply = talloc_asprintf(cmd, "%d", cause); + return CTRL_CMD_REPLY; +} +*/ + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + int rc = 0; + + + /* TODO(oramadan) MERGE + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_alert); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_ceased); + */ + + return 0; +} diff --git a/src/osmo-bts-oc2g/oml.c b/src/osmo-bts-oc2g/oml.c new file mode 100644 index 00000000..6cf7e1d5 --- /dev/null +++ b/src/osmo-bts-oc2g/oml.c @@ -0,0 +1,2088 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <nrw/oc2g/gsml1prim.h> +#include <nrw/oc2g/gsml1const.h> +#include <nrw/oc2g/gsml1types.h> +#include <nrw/oc2g/oc2g.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> + +#include "l1_if.h" +#include "oc2gbts.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_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +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 oc2gl1_hdl *gl1, + uint32_t hLayer3_uint32) +{ + HANDLE hLayer3; + prim->id = id; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + hLayer3 = (void*)hLayer3_uint32; + + switch (id) { + case GsmL1_PrimId_MphInitReq: + //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + 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; +} + +static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) +{ + 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(oc2gbts_l1prim_names, l1p->id), + get_value_string(oc2gbts_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(oc2gbts_l1prim_names, l1p->id), + get_value_string(oc2gbts_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[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + 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); +} + +static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_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(oc2gbts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-MUTE failure"); + } + + msgb_free(resp); + + return 0; +} + +static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_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(oc2gbts_l1status_names, ic->status)); + bts_shutdown(trx->bts, "MPH-INIT failure"); + } + + fl1h->hLayer1 = ic->hLayer1; + + /* 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); + + /* apply initial values for Tx power backoff for 8-PSK * / + trx->max_power_backoff_8psk = fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk; + l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk); + LOGP(DL1C, LOGL_INFO, "%s Applied initial 8-PSK Tx power backoff of %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk); + + /* apply initial values for Tx C0 idle slot power reduction * / + trx->c0_idle_power_red = fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red; + l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); + LOGP(DL1C, LOGL_INFO, "%s Applied initial C0 idle slot power reduction of %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); */ + + /* 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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int rc, oc2g_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); + } + + /* Update TRX band */ + rc = gsm_arfcn2band_rc(trx->arfcn, &trx->bts->band); + if (rc) { + /* FIXME: abort initialization? */ + LOGP(DL1C, LOGL_ERROR, "Could not pick GSM band " + "for ARFCN %u\n", trx->arfcn); + trx->bts->band = 0x00; + } + + oc2g_band = oc2gbts_select_oc2g_band(trx, trx->arfcn); + if (oc2g_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, + l1p_handle_for_trx(trx)); + dev_par = &mi_req->deviceParam; + dev_par->devType = GsmL1_DevType_TxdRxu; + dev_par->freqBand = oc2g_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 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = 0.0; + LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " + "TxPower % 2.2f dBm\n", dev_par->freqBand, 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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct msgb *msg; + + msg = l1p_msgb_alloc(); + prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, + l1p_handle_for_trx(trx)); + 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 oc2gl1_hdl *fl1h = trx_oc2gl1_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_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, 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) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (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: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + 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_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + 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, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_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(oc2gbts_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(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(oc2gbts_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 oc2gl1_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) +{ + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; +} + +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 oc2gl1_hdl *fl1h = trx_oc2gl1_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, l1p_handle_for_lchan(lchan)); + 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 = (HANDLE *)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: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * 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; + 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), (uint32_t)act_req->hLayer2, + get_value_string(oc2gbts_l1sapi_names, act_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gbts_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" }, + { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" }, + { 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(oc2gbts_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_txpower_backoff_8psk_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(oc2gbts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n", + cc->cfgParams.set8pskPowerReduction.u8PowerReduction); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(oc2gbts_sysprim_names, sysp->id), + get_value_string(oc2gbts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} + +static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(oc2gbts_sysprim_names, sysp->id), + get_value_string(oc2gbts_l1status_names, sac->status)); + + msgb_free(resp); + + 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, (uint32_t)cc->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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, + l1p_handle_for_lchan(lchan)); + 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 = (HANDLE)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(oc2gbts_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 oc2gl1_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, 0); + 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); +} + +int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_Set8pskPowerReduction; + conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL); +} + +int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_SetMaxCellSizeReq; + sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size; + + LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n", + gsm_trx_name(fl1h->phy_inst->trx), + cell_size); + + return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL); + +} + +int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_SetC0IdleSlotPowerReductionReq; + sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red; + + LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + red); + + return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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, + l1p_handle_for_lchan(lchan)); + + 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 = (HANDLE)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(oc2gbts_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 oc2gl1_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, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_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(oc2gbts_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(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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, l1p_handle_for_lchan(lchan)); + 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 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, deact_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_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); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + 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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + 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); +} + +int bts_model_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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + /* convert max TA to max cell size in qbits */ + uint8_t cell_size = bts->max_ta << 2; + + /* We do not need to check for L1 handle + * because the max cell size parameter can receive before MphInit */ + if (fl1h->phy_inst->u.oc2g.max_cell_size != cell_size) { + /* instruct L1 to apply max cell size */ + l1if_set_max_cell_size(fl1h, cell_size); + /* update current max cell size */ + fl1h->phy_inst->u.oc2g.max_cell_size = cell_size; + } + + /* 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); + + if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) { + /* update current Tx power backoff for 8-PSK */ + fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk = trx->max_power_backoff_8psk; + /* instruct L1 to apply Tx power backoff for 8 PSK */ + l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk); + } + + if (fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red != trx->c0_idle_power_red) { + /* update current C0 idle slot Tx power reduction */ + fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red = trx->c0_idle_power_red; + /* instruct L1 to apply C0 idle slot power reduction */ + l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); + } + } + + } + + /* 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_opstart(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 bts_model_lchan_deactivate_sacch(lchan); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_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_oc2gl1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + msgb_free(l1_msg); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts, 0); + + msgb_free(l1_msg); + + return 0; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + + rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); + if (rc) + cb_ts_connected(ts, rc); +} diff --git a/src/osmo-bts-oc2g/oml_router.c b/src/osmo-bts-oc2g/oml_router.c new file mode 100644 index 00000000..198d5e30 --- /dev/null +++ b/src/osmo-bts-oc2g/oml_router.c @@ -0,0 +1,132 @@ +/* Beginnings of an OML router */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "oml_router.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/msg_utils.h> + +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +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; +} diff --git a/src/osmo-bts-oc2g/oml_router.h b/src/osmo-bts-oc2g/oml_router.h new file mode 100644 index 00000000..4b22e9c5 --- /dev/null +++ b/src/osmo-bts-oc2g/oml_router.h @@ -0,0 +1,13 @@ +#pragma once + +struct gsm_bts; +struct osmo_fd; + +/** + * The default path oc2gbts will listen for incoming + * registrations for OML routing and sending. + */ +#define OML_ROUTER_PATH "/var/run/oc2gbts_oml_router" + + +int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-oc2g/tch.c b/src/osmo-bts-oc2g/tch.c new file mode 100644 index 00000000..1bd93e4b --- /dev/null +++ b/src/osmo-bts-oc2g/tch.c @@ -0,0 +1,545 @@ +/* Traffic channel support for NuRAN Wireless OC-2G BTS L1 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <nrw/oc2g/oc2g.h> +#include <nrw/oc2g/gsml1prim.h> +#include <nrw/oc2g/gsml1const.h> +#include <nrw/oc2g/gsml1types.h> + +#include "oc2gbts.h" +#include "l1_if.h" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_FR_BYTES); + memcpy(cur, l1_payload, GSM_FR_BYTES); + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + 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) +{ + /* new L1 can deliver bits like we need them */ + memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); + return GSM_FR_BYTES; +} + +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, + uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_EFR_BYTES); + memcpy(cur, l1_payload, GSM_EFR_BYTES); + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + return msg; +} + +static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + memcpy(l1_payload, rtp_payload, payload_len); + + return payload_len; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, 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); + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + 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(DL1P, 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); + + return GSM_HR_BYTES; +} + +static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t amr_if2_len = payload_len - 2; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + 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; + + return msg; +} + +/*! \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, uint8_t ft) +{ + memcpy(l1_payload, rtp_payload, payload_len); + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * 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. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + 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); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, + rtp_pl_len); + /* FIXME: detect and save EFR SID */ + break; + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + 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 -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +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, payload_type, payload_len, sid_first[9] = { 0 }; + 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) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + payload_len = data_ind->msgUnitParam.u8Size - 1; + + /* clear RTP marker if the marker has previously sent */ + if (!lchan->tch.dtx.is_speech_resume) + lchan->rtp_tx_marker = false; + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + case GsmL1_TchPlType_Efr: + 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; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->tch.dtx.is_speech_resume = true; + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->tch.dtx.is_speech_resume = true; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->tch.dtx.is_speech_resume = true; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_tch_pl_names, payload_type)); + break; + } + + LOGP(DL1P, LOGL_DEBUG, "%s %s lchan->rtp_tx_marker = %s, len=%u\n", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_tch_pl_names, payload_type), + lchan->rtp_tx_marker ? "true" : "false", + payload_len); + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type)); + return -EINVAL; +} + +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) +{ + 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; + int rc; + + 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: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-oc2g/utils.c b/src/osmo-bts-oc2g/utils.c new file mode 100644 index 00000000..cb65f45a --- /dev/null +++ b/src/osmo-bts-oc2g/utils.c @@ -0,0 +1,118 @@ +/* Helper utilities that are used in OMLs */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "utils.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#include "oc2gbts.h" +#include "l1_if.h" + +int band_oc2g2osmo(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_osmo2oc2g(struct gsm_bts_trx *trx, enum gsm_band osmo_band) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn) +{ + enum gsm_band band; + struct gsm_bts *bts = trx->bts; + int rc; + + if (!bts->auto_band) + return band_osmo2oc2g(trx, bts->band); + + /* + * We need to check what will happen now. + */ + rc = gsm_arfcn2band_rc(arfcn, &band); + if (rc) /* wrong ARFCN, give up */ + return -1; + + /* if we are already on the right band return */ + if (band == bts->band) + return band_osmo2oc2g(trx, bts->band); + + /* Check if it is GSM1800/GSM1900 */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return band_osmo2oc2g(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_osmo2oc2g(trx, band); + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850) + return band_osmo2oc2g(trx, GSM_BAND_1900); + + /* give up */ + return -1; +} diff --git a/src/osmo-bts-oc2g/utils.h b/src/osmo-bts-oc2g/utils.h new file mode 100644 index 00000000..f2f13e71 --- /dev/null +++ b/src/osmo-bts-oc2g/utils.h @@ -0,0 +1,13 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include <stdint.h> +#include "oc2gbts.h" + +struct gsm_bts_trx; + +int band_oc2g2osmo(GsmL1_FreqBand_t band); + +int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn); + +#endif diff --git a/src/osmo-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am new file mode 100644 index 00000000..43d9cd7d --- /dev/null +++ b/src/osmo-bts-octphy/Makefile.am @@ -0,0 +1,12 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OCTSDR2G_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) + +EXTRA_DIST = l1_if.h l1_oml.h l1_utils.h octphy_hw_api.h octpkt.h + +bin_PROGRAMS = osmo-bts-octphy + +COMMON_SOURCES = main.c l1_if.c l1_oml.c l1_utils.c l1_tch.c octphy_hw_api.c octphy_vty.c octpkt.c + +osmo_bts_octphy_SOURCES = $(COMMON_SOURCES) +osmo_bts_octphy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c new file mode 100644 index 00000000..f149c048 --- /dev/null +++ b/src/osmo-bts-octphy/l1_if.c @@ -0,0 +1,1806 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org> + * + * based on a copy of osmo-bts-sysmo/l1_if.c, which is + * Copyright (C) 2011-2014 by Harald Welte <laforge@gnumonks.org> + * Copyright (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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#include <linux/if_arp.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/socket.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/handover.h> + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" + +#include "octpkt.h" +#include <octphy/octvc1/main/octvc1_main_version.h> + +/* NOTE: The octphy GPRS frame number handling changed with + * OCTSDR-2G-02.07.00-B1314-BETA. From that version on, each ph_data_ind must + * subtract 3 from the frame number before passing the frame to the PCU */ +#define cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG 0x41c0522 + +#include <octphy/octpkt/octpkt_hdr.h> +#define OCTVC1_RC2STRING_DECLARE +#include <octphy/octvc1/octvc1_rc2string.h> +#define OCTVC1_ID2STRING_DECLARE +#include <octphy/octvc1/octvc1_id2string.h> +#include <octphy/octvc1/gsm/octvc1_gsm_evt_swap.h> +#define OCTVC1_OPT_DECLARE_DEFAULTS +#include <octphy/octvc1/gsm/octvc1_gsm_default.h> +#include <octphy/octvc1/main/octvc1_main_default.h> + +#define cPKTAPI_FIFO_ID_MSG 0xAAAA0001 + +/* maximum window of unacknowledged commands */ +#define UNACK_CMD_WINDOW 8 +/* maximum number of re-transmissions of a command */ +#define MAX_RETRANS 3 +/* timeout until which we expect PHY to respond */ +#define CMD_TIMEOUT 5 + +/* allocate a msgb for a Layer1 primitive */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc_headroom(1500, 24, "l1_prim"); + if (!msg) + return msg; + + msg->l2h = msg->data; + return msg; +} + +void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg, + struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd) +{ + octvc1_fill_msg_hdr(mh, msgb_l2len(msg), fl1h->session_id, + fl1h->next_trans_id++, 0 /* user_info */, + msg_type, 0, api_cmd); +} + +/* Map OSMOCOM BAND type to Octasic type */ +tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM +osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn) +{ + switch (osmo_band) { + case GSM_BAND_450: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_450; + case GSM_BAND_850: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_850; + case GSM_BAND_900: + if (arfcn == 0) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900; + else if (arfcn >= 955 && arfcn <= 974) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_R_900; + else if (arfcn >= 975 && arfcn <= 1023) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900; + else + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_P_900; + case GSM_BAND_1800: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_DCS_1800; + case GSM_BAND_1900: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_PCS_1900; + default: + return -EINVAL; + } +}; + +struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id) +{ + struct phy_instance *pinst; + + pinst = phy_instance_by_num(fl1h->phy_link, trx_id); + if (!pinst) + return NULL; + + return pinst->trx; +} + +struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, + tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id) +{ + unsigned int lchan_idx; + + OSMO_ASSERT(lch_id->byTimeslotNb < ARRAY_SIZE(trx->ts)); + if (lch_id->bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL) { + switch (lch_id->bySAPI) { + case cOCTVC1_GSM_SAPI_ENUM_FCCH: + case cOCTVC1_GSM_SAPI_ENUM_SCH: + case cOCTVC1_GSM_SAPI_ENUM_BCCH: + case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH: + case cOCTVC1_GSM_SAPI_ENUM_RACH: + lchan_idx = 4; + break; + case cOCTVC1_GSM_SAPI_ENUM_CBCH: + /* it is always index 2 (3rd element), whether in a + * combined CCCH+SDCCH4 or in a SDCCH8 */ + lchan_idx = 2; + break; + default: + lchan_idx = 0; + break; + } + } else + lchan_idx = lch_id->bySubChannelNb; + + OSMO_ASSERT(lchan_idx < ARRAY_SIZE(trx->ts[0].lchan)); + + return &trx->ts[lch_id->byTimeslotNb].lchan[lchan_idx]; +} + + +/* TODO: Unify with sysmobts? */ +struct wait_l1_conf { + /* list of wait_l1_conf in the phy handle */ + struct llist_head list; + /* expiration timer */ + struct osmo_timer_list timer; + /* primtivie / command ID */ + uint32_t prim_id; + /* transaction ID */ + uint32_t trans_id; + /* copy of the msgb containing the command */ + struct msgb *cmd_msg; + /* call-back to call on response */ + l1if_compl_cb *cb; + /* data to hand to call-back on response */ + void *cb_data; + /* number of re-transmissions so far */ + uint32_t num_retrans; +}; + +static void release_wlc(struct wait_l1_conf *wlc) +{ + osmo_timer_del(&wlc->timer); + msgb_free(wlc->cmd_msg); + talloc_free(wlc); +} + +static void l1if_req_timeout(void *data) +{ + struct wait_l1_conf *wlc = data; + + /* FIXME: Implement re-transmission of command on timer expiration */ + + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(octphy_cid_vals, wlc->prim_id)); + exit(23); +} + +/* FIXME: this should be in libosmocore */ +static struct llist_head *llist_first(struct llist_head *head) +{ + if (llist_empty(head)) + return NULL; + return head->next; +} + +static void check_refill_window(struct octphy_hdl *fl1h, struct wait_l1_conf *recent) +{ + struct wait_l1_conf *wlc; + int space = UNACK_CMD_WINDOW - fl1h->wlc_list_len; + int i; + + for (i = 0; i < space; i++) { + /* get head of queue */ + struct llist_head *first = llist_first(&fl1h->wlc_postponed); + struct msgb *msg; + if (!first) + break; + wlc = llist_entry(first, struct wait_l1_conf, list); + + /* remove from head of postponed queue */ + llist_del(&wlc->list); + fl1h->wlc_postponed_len--; + + /* add to window */ + llist_add_tail(&wlc->list, &fl1h->wlc_list); + fl1h->wlc_list_len++; + + if (wlc != recent) { + LOGP(DL1C, LOGL_INFO, "Txing formerly postponed " + "command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id); + } + msg = msgb_copy(wlc->cmd_msg, "Tx from wlc_postponed"); + /* queue for execution and response handling */ + if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); + llist_del(&wlc->list); + msgb_free(msg); + exit(24); + } + /* schedule a timer for CMD_TIMEOUT seconds. If PHY fails to + * respond, we terminate */ + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + + } +} + +/* send a request(command) to L1, scheduling a call-back to be executed + * on receiving the response*/ +int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + + /* assume that there is a VC1 Message header and that it + * contains a command ID in network byte order */ + tOCTVC1_MSG_HEADER *msg_hdr = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(msg_hdr->ul_Type_R_CmdId); + uint32_t cmd_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & cOCTVC1_MSG_ID_BIT_MASK; + + LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s, trans_id=%u)\n", + msgb_length(msg), octvc1_id2string(cmd_id), + ntohl(msg_hdr->ulTransactionId)); + + /* push the two common headers in front */ + octvocnet_push_ctl_hdr(msg, cOCTVC1_FIFO_ID_MGW_CONTROL, + cPKTAPI_FIFO_ID_MSG, fl1h->socket_id); + octpkt_push_common_hdr(msg, cOCTVOCNET_PKT_FORMAT_CTRL, 0, + cOCTPKT_HDR_CONTROL_PROTOCOL_TYPE_ENUM_OCTVOCNET); + + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cmd_msg = msg; + wlc->cb = cb; + wlc->cb_data = data; + wlc->prim_id = cmd_id; + wlc->trans_id = ntohl(msg_hdr->ulTransactionId); + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + + /* unconditionally add t to the tail of postponed commands */ + llist_add_tail(&wlc->list, &fl1h->wlc_postponed); + fl1h->wlc_postponed_len++; + + /* check if the unacknowledged window has some space to transmit */ + check_refill_window(fl1h, wlc); + + /* if any messages are in the queue, it must be at least 'our' message, + * as we always enqueue from the tail */ + if (fl1h->wlc_postponed_len) { + fl1h->stats.wlc_postponed++; + LOGP(DL1C, LOGL_INFO, "Postponed command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, cmd_id), wlc->trans_id); + } + + return 0; +} + +/* For OctPHY, this only about sending state changes to BSC */ +int l1if_activate_rf(struct gsm_bts_trx *trx, int on) +{ + int i; + if (on) { + /* 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 { + 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); + } + + return 0; +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + tOCTVC1_GSM_SAPI_ENUM sapi, uint8_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case cOCTVC1_GSM_SAPI_ENUM_BCCH: + cbits = 0x10; + break; + case cOCTVC1_GSM_SAPI_ENUM_CBCH: + cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */ + break; + case cOCTVC1_GSM_SAPI_ENUM_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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_SDCCH: + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH: + cbits = 0x12; + break; + case cOCTVC1_GSM_SAPI_ENUM_TCHF: + cbits = 0x01; + break; + case cOCTVC1_GSM_SAPI_ENUM_TCHH: + cbits = 0x02 + subCh; + break; + case cOCTVC1_GSM_SAPI_ENUM_FACCHF: + cbits = 0x01; + break; + case cOCTVC1_GSM_SAPI_ENUM_FACCHH: + cbits = 0x02 + subCh; + break; + case cOCTVC1_GSM_SAPI_ENUM_PDTCH: + case cOCTVC1_GSM_SAPI_ENUM_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 cOCTVC1_GSM_SAPI_ENUM_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; + } + return ((cbits << 3) | u8Tn); +} + +static void data_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req, + const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind) +{ + data_req->TrxId = rts_ind->TrxId; + data_req->LchId = rts_ind->LchId; + data_req->Data.ulFrameNumber = rts_ind->ulFrameNumber; + data_req->Data.ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_NONE; +} + +#if 0 +static void empty_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD * empty_req, + const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind) +{ + empty_req->TrxId = rts_ind->TrxId; + empty_req->LchId = rts_ind->LchId; + empty_req->ulFrameNumber = rts_ind->ulFrameNumber; +} +#endif + +/*********************************************************************** + * handle messages coming down from generic part + ***********************************************************************/ + + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *l1msg = NULL; + uint32_t u32Fn; + uint8_t u8Tn, subCh, sapi = 0; + uint8_t chan_nr, link_id; + int len; + int rc; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "L1SAP PH-DATA.req without msg. " + "Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + 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 = 0xf1; + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_SACCH; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_PTCCH; + } else { + sapi = cOCTVC1_GSM_SAPI_ENUM_PDTCH; + } + } else { + sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHF; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHH; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_BCCH; + } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_CBCH; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d chan_nr %d link_id %d\n", + l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); + rc = -EINVAL; + goto done; + } + + if (len) { + /* create new PHY primitive in l1msg, copying payload */ + + l1msg = l1p_msgb_alloc(); + if (!l1msg) { + LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-DATA.req msg alloc failed\n"); + rc = -ENOMEM; + goto done; + } + + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(l1msg, sizeof(*data_req)); + + l1if_fill_msg_hdr(&data_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req->TrxId.byTrxId = pinst->u.octphy.trx_id; + data_req->LchId.byTimeslotNb = u8Tn; + data_req->LchId.bySAPI = sapi; + data_req->LchId.bySubChannelNb = subCh; + data_req->LchId.byDirection = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + data_req->Data.ulFrameNumber = u32Fn; + data_req->Data.ulDataLength = msgb_l2len(msg); + memcpy(data_req->Data.abyDataContent, msg->l2h, msgb_l2len(msg)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + } else { + /* No data available, Don't send Empty frame to PHY */ + rc = 0; + goto done; + } + + rc = l1if_req_compl(fl1h, l1msg, NULL, NULL); +done: + return rc; +} + + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, sapi; + uint8_t chan_nr; + struct msgb *nmsg = NULL; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_TCHH; + } else { + subCh = 0xf1; + sapi = cOCTVC1_GSM_SAPI_ENUM_TCHF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* create new message and fill data */ + if (msg) { + nmsg = l1p_msgb_alloc(); + if (!nmsg) { + LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-TCH.req msg alloc failed\n"); + return -ENOMEM; + } + + msgb_pull(msg, sizeof(*l1sap)); + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(nmsg, sizeof(*data_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req); + + l1if_fill_msg_hdr(&data_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req->TrxId.byTrxId = pinst->u.octphy.trx_id; + data_req->LchId.byTimeslotNb = u8Tn; + data_req->LchId.bySAPI = sapi; + data_req->LchId.bySubChannelNb = subCh; + data_req->LchId.byDirection = + cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + data_req->Data.ulFrameNumber = u32Fn; + + l1if_tch_encode(lchan, + &data_req->Data.ulPayloadType, + data_req->Data.abyDataContent, + &data_req->Data.ulDataLength, + msg->data, msg->len); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + } else { + /* No data available, Don't send Empty frame to PHY */ + return 0; + } + + return l1if_req_compl(fl1h, nmsg, NULL, NULL); +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { +#pragma message ("Mode Modify is currently not supported for Octasic PHY (OS#3015)") + /* 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); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown L1SAP MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + } + + return rc; +} + +/* primitive from common part. We are taking ownership of msgb */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + 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, "L1SAP unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + msgb_free(msg); + + return rc; +} + +static int trx_close_all_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car = + (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car); + + /* we now know that the PHY link is connected */ + phy_link_state_set(fl1->phy_link, PHY_LINK_CONNECTED); + + msgb_free(resp); + + return 0; +} + +static int phy_link_trx_close_all(struct phy_link *plink) +{ + struct octphy_hdl *fl1h = plink->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac; + + cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *) + msgb_put(msg, sizeof(*cac)); + l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID); + + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac); + + return l1if_req_compl(fl1h, msg, trx_close_all_cb, NULL); +} + +int bts_model_phy_link_open(struct phy_link *plink) +{ + if (plink->u.octphy.hdl) + l1if_close(plink->u.octphy.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + plink->u.octphy.hdl = l1if_open(plink); + if (!plink->u.octphy.hdl) { + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return -1; + } + + /* do we need to iterate over the list of instances and do some + * instance-specific initialization? */ + + /* close all TRXs that might still exist in this link from + * previous execitions / sessions */ + phy_link_trx_close_all(plink); + + /* in the call-back to the above we will set the link state to + * connected */ + + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + LOGP(DL1C, LOGL_NOTICE, "model_init()\n"); + + bts->variant = BTS_OSMO_OCTPHY; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + /* FIXME: what is the nominal transmit power of the PHY/board? */ + bts->c0->nominal_power = 15; + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); +#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4) && defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8) + gsm_bts_set_feature(bts, BTS_FEAT_CBCH); +#endif + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + return 0; +} + +/*********************************************************************** + * handling of messages coming up from PHY + ***********************************************************************/ + +/* When the measurement indication is received from the phy, the phy will + * automatically stamp it with the frame number that matches the frame + * number of the SACCH channel that marks the end of the measurement + * period. (e.g. fn%104=90, on a TCH/H, TS0). However, the upper layers + * expect the frame number to be aligned to the next SACCH frame after, + * after the end of the measurement period that has just passed. (e.g. + * (fn%104=10, on a TCH/H, TS0). The following function remaps the frame + * number in order to match the higher layers expectations. + * See also: 3GPP TS 05.02 Clause 7 Table 1 of 9 Mapping of logical channels + * onto physical channels (see subclauses 6.3, 6.4, 6.5) */ +static uint32_t translate_tch_meas_rep_fn104_reverse(uint32_t fn) +{ + uint8_t new_fn_mod; + uint8_t fn_mod; + + fn_mod = fn % 104; + + switch (fn_mod) { + case 103: + new_fn_mod = 25; + break; + case 12: + new_fn_mod = 38; + break; + case 25: + new_fn_mod = 51; + break; + case 38: + new_fn_mod = 64; + break; + case 51: + new_fn_mod = 77; + break; + case 64: + new_fn_mod = 90; + break; + case 77: + new_fn_mod = 103; + break; + case 90: + new_fn_mod = 12; + break; + default: + /* No translation for frame numbers + * fall out of the raster */ + new_fn_mod = fn_mod; + } + + return (fn - fn_mod) + new_fn_mod; +} + +static unsigned int oct_meas2ber10k(const tOCTVC1_GSM_MEASUREMENT_INFO *m) +{ + if (m->usBERTotalBitCnt != 0) { + return (unsigned int)((m->usBERCnt * BER_10K) / m->usBERTotalBitCnt); + } else { + return 0; + } +} + +static int oct_meas2rssi_dBm(const tOCTVC1_GSM_MEASUREMENT_INFO *m) +{ + /* rssi is in q8 format */ + return (m->sRSSIDbm >> 8); +} + +static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + uint32_t fn, uint32_t data_len, + tOCTVC1_GSM_MEASUREMENT_INFO * 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; + + /* Update Timing offset for valid radio block */ + if (data_len != 0) { + /* burst timing in 1x */ + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->sBurstTiming4x*64; + } else { + /* FIXME, In current implementation, OCTPHY would send DATA_IND + * for all radio blocks (valid or invalid) But timing offset + * is only correct for valid block. so we need different + * counter to accumulate Timing offset.. even we add zero for + * invalid block.. timing offset average calucation would not + * correct. */ + l1sap.u.info.u.meas_ind.ta_offs_256bits = 0; + } + + l1sap.u.info.u.meas_ind.ber10k = oct_meas2ber10k(m); + + /* rssi is in q8 format */ + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) oct_meas2rssi_dBm(m); + + /* copy logical frame number to MEAS IND data structure */ + l1sap.u.info.u.meas_ind.fn = translate_tch_meas_rep_fn104_reverse(fn); + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + l1sap_up(trx, &l1sap); +} + +static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m) +{ + LOGP(DMEAS, ll, + "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits :%d, " + "BER Error Count %d , BER Toatal Bit count %d in last decoded frame\n", + m->sRSSIDbm, m->sBurstTiming, m->sBurstTiming4x, m->usBERCnt, + m->usBERTotalBitCnt); +} + +static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, trx_id); + struct osmo_phsap_prim l1sap; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != trx->bts->c0) + return 0; + + 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; + + l1sap_up(trx, &l1sap); + + return 0; +} + +static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, evt->TrxId.byTrxId); + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim *l1sap; + struct gsm_time g_time; + uint8_t chan_nr, link_id; + uint32_t fn; + int rc; + uint32_t t3p; + uint8_t ts_num, sc, sapi; + + struct msgb *resp_msg; + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req; + + /* Retrive the data */ + fn = evt->ulFrameNumber; + ts_num = (uint8_t) evt->LchId.byTimeslotNb; + sc = (uint8_t) evt->LchId.bySubChannelNb; + sapi = (uint8_t) evt->LchId.bySAPI; + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(octphy_l1sapi_names, sapi)); + + /* in case we need to forward primitive to common part */ + chan_nr = chan_nr_by_sapi(&trx->ts[ts_num], sapi, sc, ts_num, fn); + if (chan_nr) { + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + + 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 (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF + || sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + 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; + } + + l1sap_up(trx, l1sap); + + /* return '1' to indicate l1sap_up has taken msgb ownership */ + return 1; + } + + /* 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 = (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(resp_msg, sizeof(*data_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req); + + l1if_fill_msg_hdr(&data_req->Header, resp_msg, fl1, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req_from_rts_ind(data_req, evt); + + switch (sapi) { + /* TODO: SCH via L1SAP */ + case cOCTVC1_GSM_SAPI_ENUM_SCH: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + data_req->Data.ulDataLength = 4; + data_req->Data.abyDataContent[0] = + (bts->bsic << 2) | (g_time.t1 >> 9); + data_req->Data.abyDataContent[1] = (g_time.t1 >> 1); + data_req->Data.abyDataContent[2] = + (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + data_req->Data.abyDataContent[3] = (t3p & 1); + break; + case cOCTVC1_GSM_SAPI_ENUM_PRACH: +#if 0 + /* in case we decide to send an empty frame... */ + + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD + *empty_frame_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD + *) msgSendBuffer; + + empty_req_from_rts_ind(empty_frame_req, evt); + + /* send empty frame request */ + rc = Logical_Channel_Empty_Frame_Cmd(empty_frame_req); + if (cOCTVC1_RC_OK != rc) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, + "Sending Empty Frame Request Failed! (%s)\n", + octvc1_rc2string(rc)); + } + break; +#endif + default: + LOGPGT(DL1P, LOGL_ERROR, &g_time, "SAPI %s not handled via L1SAP!\n", + get_value_string(octphy_l1sapi_names, sapi)); +#if 0 + data_req->Data.ulDataLength = GSM_MACBLOCK_LEN; + memcpy(data_req->Data.abyDataContent, fill_frame, + GSM_MACBLOCK_LEN); +#endif + break; + } + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + + return l1if_req_compl(fl1, resp_msg, NULL, NULL); +} + +static int handle_ph_data_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, data_ind->TrxId.byTrxId); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + uint8_t *data; + uint16_t len; + int16_t snr; + int rc; + + uint8_t sapi = (uint8_t) data_ind->LchId.bySAPI; + uint8_t ts_num = (uint8_t) data_ind->LchId.byTimeslotNb; + uint8_t sc = (uint8_t) data_ind->LchId.bySubChannelNb; + + /* Need to combine two 16bit MSB and LSB to form 32bit FN */ + fn = data_ind->Data.ulFrameNumber; + + /* chan_nr and link_id */ + chan_nr = chan_nr_by_sapi(&trx->ts[ts_num], sapi, sc, ts_num, fn); + if (!chan_nr) { + LOGPFN(DL1C, LOGL_ERROR, fn, "Rx PH-DATA.ind for unknown L1 SAPI %s\n", + get_value_string(octphy_l1sapi_names, sapi)); + return ENOTSUP; + } + + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + + memset(&l1sap, 0, sizeof(l1sap)); + + /* uplink measurement */ + process_meas_res(trx, chan_nr, fn, data_ind->Data.ulDataLength, + &data_ind->MeasurementInfo); + + DEBUGPFN(DL1C, fn, "Rx PH-DATA.ind %s: %s data_len:%d \n", + get_value_string(octphy_l1sapi_names, sapi), + osmo_hexdump(data_ind->Data.abyDataContent, data_ind->Data.ulDataLength), + data_ind->Data.ulDataLength); + + /* check for TCH */ + if (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF || + sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, data_ind); + return rc; + } + + /* get data pointer and length */ + data = data_ind->Data.abyDataContent; + len = data_ind->Data.ulDataLength; + /* 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; + +#if (cOCTVC1_MAIN_VERSION_ID >= cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG) + if (sapi == cOCTVC1_GSM_SAPI_ENUM_PDTCH) { + /* FIXME::PCU is expecting encode frame number*/ + l1sap->u.data.fn = fn - 3; + } else + l1sap->u.data.fn = fn; +#else + l1sap->u.data.fn = fn; +#endif + + l1sap->u.data.rssi = oct_meas2rssi_dBm(&data_ind->MeasurementInfo); + l1sap->u.data.ber10k = oct_meas2ber10k(&data_ind->MeasurementInfo); + + /* burst timing in 1x but PCU is expecting 4X */ + l1sap->u.data.ta_offs_256bits = data_ind->MeasurementInfo.sBurstTiming4x*64; + snr = data_ind->MeasurementInfo.sSNRDb; + /* FIXME: better converion formulae for SnR -> C / I? + l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 10 / 256; + LOGP(DL1C, LOGL_ERROR, "SnR: raw %d, computed %d\n", snr, l1sap->u.data.lqual_cb); + */ + l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 100; + l1sap->u.data.pdch_presence_info = PRES_INFO_BOTH; /* FIXME: consider EDGE support */ + + l1sap_up(trx, l1sap); + + /* return '1' to indicate that l1sap_up has taken msgb ownership */ + return 1; +} + +static int handle_ph_rach_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, ra_ind->TrxId.byTrxId); + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo); + + if (ra_ind->ulMsgLength != 1) { + LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber, + "Rx PH-RACH.ind has lenghth %d > 1\n", ra_ind->ulMsgLength); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + .ra = ra_ind->abyMsg[0], + /* .acc_delay set below */ + .fn = ra_ind->ulFrameNumber, + .is_11bit = 0, + /* .burst_type remains unset */ + .rssi = oct_meas2rssi_dBm(&ra_ind->MeasurementInfo), + .ber10k = oct_meas2ber10k(&ra_ind->MeasurementInfo), + .acc_delay_256bits = ra_ind->MeasurementInfo.sBurstTiming4x * 64, + }; + + if (ra_ind->LchId.bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL && + ra_ind->LchId.bySAPI == cOCTVC1_GSM_SAPI_ENUM_RACH) { + rach_ind_param.chan_nr = 0x88; + } else { + struct gsm_lchan *lchan = get_lchan_by_lchid(trx, &ra_ind->LchId); + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + } + + /* check for under/overflow / sign */ + if (ra_ind->MeasurementInfo.sBurstTiming < 0) + rach_ind_param.acc_delay = 0; + else + rach_ind_param.acc_delay = ra_ind->MeasurementInfo.sBurstTiming; + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + 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); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind = rach_ind_param; + + l1sap_up(trx, l1sap); + + /* return '1' to indicate l1sap_up has taken msgb ownership */ + return 1; +} + +static int rx_gsm_trx_time_ind(struct msgb *msg) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *tind = + (tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *) msg->l2h; + + mOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT_SWAP(tind); + + return handle_mph_time_ind(fl1h, tind->TrxId.byTrxId, tind->ulFrameNumber); +} + +/* mark this message as RETRANSMIT of a previous msg */ +static void msg_set_retrans_flag(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + type_r_cmdid |= cOCTVC1_MSG_RETRANSMIT_FLAG; + mh->ul_Type_R_CmdId = htonl(type_r_cmdid); +} + +/* re-transmit all commands in the window that have a transaction ID lower than + * trans_id */ +static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id) +{ + struct wait_l1_conf *wlc; + int count = 0; + + LOGP(DL1C, LOGL_INFO, "Retransmitting up to trans_id=%u\n", trans_id); + + /* trans_id represents the trans_id of the just-received response, we + * therefore need to re-send any commands with a lower trans_id */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->trans_id <= trans_id) { + struct msgb *msg; + if (wlc->num_retrans >= MAX_RETRANS) { + LOGP(DL1C, LOGL_ERROR, "Command %s: maximum " + "number of retransmissions reached\n", + get_value_string(octphy_cid_vals, + wlc->prim_id)); + exit(24); + } + wlc->num_retrans++; + msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans"); + msg_set_retrans_flag(msg); + osmo_wqueue_enqueue(&fl1h->phy_wq, msg); + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + count++; + LOGP(DL1C, LOGL_INFO, "Re-transmitting %s " + "(trans_id=%u, attempt %u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id, wlc->num_retrans); + } + } + + return count; +} + +/* Receive a response (to a prior command) from the PHY */ +static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + struct llist_head *first; + uint32_t return_code = ntohl(mh->ulReturnCode); + struct octphy_hdl *fl1h = msg->dst; + struct wait_l1_conf *wlc = NULL; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "rx_octvc1_resp(msg_id=%s, trans_id=%u)\n", + octvc1_rc2string(msg_id), trans_id); + + /* check if the response is for the oldest (first) entry in wlc_list */ + first = llist_first(&fl1h->wlc_list); + if (first) { + wlc = llist_entry(first, struct wait_l1_conf, list); + if (wlc->trans_id == trans_id) { + /* process the received response */ + llist_del(&wlc->list); + fl1h->wlc_list_len--; + if (wlc->cb) { + /* call-back function must take msgb + * ownership. */ + rc = wlc->cb(fl1h, msg, wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + /* check if there are postponed wlcs and re-fill the window */ + check_refill_window(fl1h, NULL); + return rc; + } + } + + LOGP(DL1C, LOGL_NOTICE, "Sequence error: Rx response (cmd=%s, trans_id=%u) " + "for cmd != oldest entry in window (trans_id=%u)!!\n", + get_value_string(octphy_cid_vals, msg_id), trans_id, + wlc ? wlc->trans_id : 0); + + /* check if the response is for any of the other entries in wlc_list */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + /* it is assumed that all of the previous response + * message(s) have been lost, and we need to + * re-transmit older messages from the window */ + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_trans_id += rc; + /* do not process the received response, we rather wait + * for the in-order retransmissions to arrive */ + msgb_free(msg); + return 0; + } + } + + /* ignore unhandled responses that went ok, but let the user know about + * failing ones. */ + if (return_code != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, msg_id), trans_id); + } + msgb_free(msg); + return 0; + +} + +static int rx_gsm_clockmgr_status_ind(struct msgb *msg) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *evt = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *) msg->l2h; + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT_SWAP(evt); + + LOGP(DL1C, LOGL_NOTICE, "Rx ClkMgr Status Change Event: " + "%s -> %s\n", + get_value_string(octphy_clkmgr_state_vals, evt->ulPreviousState), + get_value_string(octphy_clkmgr_state_vals, evt->ulState)); + + fl1h->clkmgr_state = evt->ulState; + + return 0; +} + +static int rx_gsm_trx_status_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *) msg->l2h; + + mOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT_SWAP(evt); + + if (evt->ulStatus == cOCTVC1_GSM_TRX_STATUS_ENUM_RADIO_READY) + LOGP(DL1C, LOGL_INFO, "Rx TRX Status Event: READY\n"); + else + LOGP(DL1C, LOGL_ERROR, "Rx TRX Status Event: %u\n", + evt->ulStatus); + + return 0; +} + +/* DATA indication from PHY */ +static int rx_gsm_trx_lchan_data_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT_SWAP(evt); + + return handle_ph_data_ind(msg->dst, evt, msg); +} + +/* Ready-to-Send indication from PHY */ +static int rx_gsm_trx_rts_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT_SWAP(evt); + + return handle_ph_readytosend_ind(msg->dst, evt, msg); +} + +/* RACH receive indication from PHY */ +static int rx_gsm_trx_rach_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT_SWAP(evt); + + return handle_ph_rach_ind(msg->dst, evt, msg); +} + +/* Receive a notification (indication) from PHY */ +static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id) +{ + const char *evt_name = get_value_string(octphy_eid_vals, msg_id); + struct octphy_hdl *fl1h = msg->dst; + int rc = 0; + + if (!fl1h->opened) { + LOGP(DL1P, LOGL_NOTICE, "Rx NOTIF %s: Ignoring as PHY TRX " + "hasn't been re-opened yet\n", evt_name); + msgb_free(msg); + return 0; + } + + LOGP(DL1P, LOGL_DEBUG, "Rx NOTIF %s\n", evt_name); + + /* called functions MUST NOT take ownership of the msgb, + * as it is free()d below - unless they return 1 */ + switch (msg_id) { + case cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID: + rc = rx_gsm_trx_time_ind(msg); + break; + case cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EID: + rc = rx_gsm_clockmgr_status_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID: + rc = rx_gsm_trx_status_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID: + rc = rx_gsm_trx_lchan_data_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID: + rc = rx_gsm_trx_rts_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID: + rc = rx_gsm_trx_rach_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID: + LOGP(DL1P, LOGL_NOTICE, "Rx Unhandled event %s (%u)\n", + evt_name, msg_id); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "Rx Unknown event %s (%u)\n", + evt_name, msg_id); + } + + /* Special return value '1' means: do not free */ + if (rc != 1) + msgb_free(msg); + + return rc; +} + +static int rx_octvc1_event_msg(struct msgb *msg) +{ + tOCTVC1_EVENT_HEADER *eh = (tOCTVC1_EVENT_HEADER *) msg->l2h; + uint32_t event_id = ntohl(eh->ulEventId); + uint32_t length = ntohl(eh->ulLength); + /* DO NOT YET SWAP HEADER HERE, as downstream functions want to + * swap it */ + + /* OCTSDKAN5001 Chapter 6.1 */ + if (length < 12 || length > 1024) { + LOGP(DL1C, LOGL_ERROR, "Rx EVENT length %u invalid\n", length); + msgb_free(msg); + return -1; + } + + /* verify / ensure length */ + if (msgb_l2len(msg) < length) { + LOGP(DL1C, LOGL_ERROR, "Rx EVENT msgb_l2len(%u) < " + "event_msg_length (%u)\n", msgb_l2len(msg), length); + msgb_free(msg); + return -1; + } + + return rx_octvc1_notif(msg, event_id); +} + +/* Receive a supervisory message from the PHY */ +static int rx_octvc1_supv(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *rej; + uint32_t return_code = ntohl(mh->ulReturnCode); + uint32_t rejected_msg_id; + int rc; + + switch (msg_id) { + case cOCTVC1_CTRL_MSG_MODULE_REJECT_SID: + rej = (tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *) mh; + mOCTVC1_CTRL_MSG_MODULE_REJECT_SPV_SWAP(rej); + rejected_msg_id = (rej->ulRejectedCmdId >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (TID=%u, " + "ExpectedTID=0x%08x, RejectedCmdID=%s)\n", + trans_id, rej->ulExpectedTransactionId, + get_value_string(octphy_cid_vals, rejected_msg_id)); + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_supv += rc; + break; + default: + LOGP(DL1C, LOGL_NOTICE, "Rx unhandled supervisory msg_id " + "%u: ReturnCode:%u\n", msg_id, return_code); + break; + } + + return 0; +} + +static int rx_octvc1_ctrl_msg(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t length = ntohl(mh->ulLength); + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + uint32_t msg_type = (type_r_cmdid >> cOCTVC1_MSG_TYPE_BIT_OFFSET) & + cOCTVC1_MSG_TYPE_BIT_MASK; + uint32_t msg_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + uint32_t return_code = ntohl(mh->ulReturnCode); + const char *msg_name = get_value_string(octphy_cid_vals, msg_id); + + /* DO NOT YET SWAP HEADER HERE, as downstream functions want to + * swap it */ + + /* FIXME: OCTSDKAN5001 Chapter 3.1 states max size is 1024, but we see + * larger messages in practise */ + if (length < 24 || length > 2048) { + LOGP(DL1C, LOGL_ERROR, "Rx CTRL length %u invalid\n", length); + msgb_free(msg); + return -1; + } + + /* verify / ensure length */ + if (msgb_l2len(msg) < length) { + LOGP(DL1C, LOGL_ERROR, "Rx CTRL msgb_l2len(%u) < " + "ctrl_msg_length (%u)\n", msgb_l2len(msg), length); + msgb_free(msg); + return -1; + } + + LOGP(DL1P, LOGL_DEBUG, "Rx %s.resp (rc=%s(%x))\n", msg_name, + octvc1_rc2string(return_code), return_code); + + if (return_code != cOCTVC1_RC_OK) { + LOGP(DL1P, LOGL_ERROR, "%s failed, rc=%s\n", + msg_name, octvc1_rc2string(return_code)); + } + + /* called functions must take ownership of msgb */ + switch (msg_type) { + case cOCTVC1_MSG_TYPE_RESPONSE: + return rx_octvc1_resp(msg, msg_id, ntohl(mh->ulTransactionId)); + case cOCTVC1_MSG_TYPE_SUPERVISORY: + return rx_octvc1_supv(msg, msg_id, ntohl(mh->ulTransactionId)); + case cOCTVC1_MSG_TYPE_NOTIFICATION: + case cOCTVC1_MSG_TYPE_COMMAND: + LOGP(DL1C, LOGL_NOTICE, "Rx unhandled msg_type %s (%u)\n", + msg_name, msg_type); + msgb_free(msg); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "Rx unknown msg_type %s (%u)\n", + msg_name, msg_type); + msgb_free(msg); + } + + return 0; +} + +static int rx_octvc1_data_f_msg(struct msgb *msg) +{ + tOCTVOCNET_PKT_DATA_F_HEADER *datafh = + (tOCTVOCNET_PKT_DATA_F_HEADER *) msg->l2h; + uint32_t log_obj_port = ntohl(datafh->VocNetHeader.ulLogicalObjPktPort); + + msg->l2h = (uint8_t *) datafh + sizeof(*datafh); + + if (log_obj_port == + cOCTVOCNET_PKT_DATA_LOGICAL_OBJ_PKT_PORT_EVENT_SESSION) { + uint32_t sub_type = ntohl(datafh->ulSubType) & 0xF; + if (sub_type == cOCTVOCNET_PKT_SUBTYPE_API_EVENT) { + /* called function must take msgb ownership */ + return rx_octvc1_event_msg(msg); + } else { + LOGP(DL1C, LOGL_ERROR, "Unknown DATA_F " + "subtype 0x%x\n", sub_type); + } + } else { + LOGP(DL1C, LOGL_ERROR, "Unknown logical object pkt port 0x%x\n", + log_obj_port); + } + + msgb_free(msg); + return 0; +} + +/* main receive routine for messages coming up from OCTPHY */ +static int rx_octphy_msg(struct msgb *msg) +{ + tOCTVOCNET_PKT_CTL_HEADER *ctlh; + int rc = 0; + + /* we assume that the packets start right with the OCTPKT header + * and that the ethernet hardware header has already been + * stripped before */ + msg->l1h = msg->data; + + uint32_t ch = ntohl(*(uint32_t *) msg->data); + uint32_t format = (ch >> cOCTVOCNET_PKT_FORMAT_BIT_OFFSET) + & cOCTVOCNET_PKT_FORMAT_BIT_MASK; + uint32_t len = (ch >> cOCTVOCNET_PKT_LENGTH_BIT_OFFSET) + & cOCTVOCNET_PKT_LENGTH_MASK; + + if (len > msgb_length(msg)) { + LOGP(DL1C, LOGL_ERROR, "Received length (%u) < length " + "as per packet header (%u): %s\n", msgb_length(msg), + len, osmo_hexdump(msgb_data(msg), msgb_length(msg))); + msgb_free(msg); + return -1; + } + + /* we first need to decode the common OCTPKT header and dispatch + * based on contrl (command/resp) or data (event=indication) */ + switch (format) { + case cOCTVOCNET_PKT_FORMAT_CTRL: + ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4); + /* FIXME: check src/dest fifo, socket ID */ + msg->l2h = (uint8_t *) ctlh + sizeof(*ctlh); + /* called function must take msgb ownership */ + rc = rx_octvc1_ctrl_msg(msg); + break; + case cOCTVOCNET_PKT_FORMAT_F: + msg->l2h = msg->l1h + 4; + /* called function must take msgb ownership */ + rc = rx_octvc1_data_f_msg(msg); + break; + default: + LOGP(DL1C, LOGL_ERROR, "Rx Unknown pkt_format 0x%x\n", + format); + msgb_free(msg); + break; + } + + return rc; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ + /* configure some reasonable defaults, to be overridden by VTY */ + plink->u.octphy.rf_port_index = 0; + plink->u.octphy.rx_gain_db = 70; + plink->u.octphy.tx_atten_db = 0; + plink->u.octphy.over_sample_16x = true; +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + pinst->u.octphy.trx_id = pinst->num; +} + +/*********************************************************************** + * octphy socket / main loop integration + ***********************************************************************/ + +static int octphy_read_cb(struct osmo_fd *ofd) +{ + struct sockaddr_ll sll; + socklen_t sll_len = sizeof(sll); + int rc; + struct msgb *msg = msgb_alloc_headroom(1500, 24, "PHY Rx"); + + if (!msg) + return -ENOMEM; + + /* this is the fl1h over which the message was received */ + msg->dst = ofd->data; + + rc = recvfrom(ofd->fd, msg->data, msgb_tailroom(msg), 0, + (struct sockaddr *) &sll, &sll_len); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Error in recvfrom(): %s\n", + strerror(errno)); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + + return rx_octphy_msg(msg); +} + +static int octphy_write_cb(struct osmo_fd *fd, struct msgb *msg) +{ + struct octphy_hdl *fl1h = fd->data; + int rc; + + /* send the message down the socket */ + rc = sendto(fd->fd, msg->data, msgb_length(msg), 0, + (struct sockaddr *) &fl1h->phy_addr, + sizeof(fl1h->phy_addr)); + + /* core write uqueue takes care of free() */ + if (rc < 0) { + LOGP(DL1P, LOGL_ERROR, "Tx to PHY has failed: %s\n", + strerror(errno)); + } + + return rc; +} + +struct octphy_hdl *l1if_open(struct phy_link *plink) +{ + struct octphy_hdl *fl1h; + struct ifreq ifr; + int sfd, rc; + char *phy_dev = plink->u.octphy.netdev_name; + + fl1h = talloc_zero(plink, struct octphy_hdl); + if (!fl1h) + return NULL; + + INIT_LLIST_HEAD(&fl1h->wlc_list); + INIT_LLIST_HEAD(&fl1h->wlc_postponed); + fl1h->phy_link = plink; + + if (!phy_dev) { + LOGP(DL1C, LOGL_ERROR, "You have to specify a octphy net-device\n"); + talloc_free(fl1h); + return NULL; + } + + LOGP(DL1C, LOGL_NOTICE, "Opening L1 interface for OctPHY (%s)\n", + phy_dev); + + sfd = osmo_sock_packet_init(SOCK_DGRAM, cOCTPKT_HDR_ETHERTYPE, + phy_dev, OSMO_SOCK_F_NONBLOCK); + if (sfd < 0) { + LOGP(DL1C, LOGL_FATAL, "Error opening PHY socket: %s\n", + strerror(errno)); + talloc_free(fl1h); + return NULL; + } + + /* resolve the string device name to an ifindex */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, phy_dev, sizeof(ifr.ifr_name)); + rc = ioctl(sfd, SIOCGIFINDEX, &ifr); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Error using network device %s: %s\n", + phy_dev, strerror(errno)); + close(sfd); + talloc_free(fl1h); + return NULL; + } + + fl1h->session_id = rand(); + + /* set fl1h->phy_addr, which we use as sendto() destination */ + fl1h->phy_addr.sll_family = AF_PACKET; + fl1h->phy_addr.sll_protocol = htons(cOCTPKT_HDR_ETHERTYPE); + fl1h->phy_addr.sll_ifindex = ifr.ifr_ifindex; + fl1h->phy_addr.sll_hatype = ARPHRD_ETHER; + fl1h->phy_addr.sll_halen = ETH_ALEN; + /* plink->phy_addr.sll_addr is filled by bts_model_vty code */ + memcpy(fl1h->phy_addr.sll_addr, plink->u.octphy.phy_addr.sll_addr, + ETH_ALEN); + + /* Write queue / osmo_fd registration */ + osmo_wqueue_init(&fl1h->phy_wq, 10); + fl1h->phy_wq.write_cb = octphy_write_cb; + fl1h->phy_wq.read_cb = octphy_read_cb; + fl1h->phy_wq.bfd.fd = sfd; + fl1h->phy_wq.bfd.when = BSC_FD_READ; + fl1h->phy_wq.bfd.cb = osmo_wqueue_bfd_cb; + fl1h->phy_wq.bfd.data = fl1h; + rc = osmo_fd_register(&fl1h->phy_wq.bfd); + if (rc < 0) { + close(sfd); + talloc_free(fl1h); + return NULL; + } + + return fl1h; +} + +int l1if_close(struct octphy_hdl *fl1h) +{ + osmo_fd_unregister(&fl1h->phy_wq.bfd); + close(fl1h->phy_wq.bfd.fd); + talloc_free(fl1h); + + return 0; +} diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h new file mode 100644 index 00000000..09604822 --- /dev/null +++ b/src/osmo-bts-octphy/l1_if.h @@ -0,0 +1,117 @@ +#pragma once + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <linux/if_packet.h> + +#include <osmocom/core/write_queue.h> +#include <osmocom/core/timer.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> + +#include <octphy/octvc1/gsm/octvc1_gsm_api.h> + +#define BER_10K 10000 + +struct octphy_hdl { + /* MAC address of the PHY */ + struct sockaddr_ll phy_addr; + + /* packet socket to talk with PHY */ + struct osmo_wqueue phy_wq; + + /* address parameters of the PHY */ + uint32_t session_id; + uint32_t next_trans_id; + uint32_t socket_id; + + /* clock manager state */ + uint32_t clkmgr_state; + + struct { + struct { + char *name; + char *description; + char *version; + } app; + struct { + char *platform; + char *version; + } system; + } info; + + /* This is a list of outstanding commands sent to the PHY, for which we + * currently still wait for a response. Represented by 'struct + * wait_l1_conf' in l1_if.c - Octasic calls this the 'Unacknowledged + * Command Window' */ + struct llist_head wlc_list; + int wlc_list_len; + struct { + /* messages retransmitted due to discontinuity of transaction + * ID in responses from PHY */ + uint32_t retrans_cmds_trans_id; + /* messages retransmitted due to supervisory messages by PHY */ + uint32_t retrans_cmds_supv; + /* number of commands/wlcs that we ever had to postpone */ + uint32_t wlc_postponed; + } stats; + + /* This is a list of wait_la_conf that OsmoBTS wanted to transmit to + * the PHY, but which couldn't yet been sent as the unacknowledged + * command window was full. */ + struct llist_head wlc_postponed; + int wlc_postponed_len; + + /* back pointer to the PHY link */ + struct phy_link *phy_link; + + struct osmo_timer_list alive_timer; + uint32_t alive_prim_cnt; + + /* were we already (re)opened after OsmoBTS start */ + int opened; +}; + +void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg, + struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd); + +typedef int l1if_compl_cb(struct octphy_hdl *fl1, struct msgb *l1_msg, void *data); + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data); + +#include <octphy/octvc1/gsm/octvc1_gsm_api.h> +struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, + tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id); + +struct octphy_hdl *l1if_open(struct phy_link *plink); +int l1if_close(struct octphy_hdl *hdl); + +int l1if_trx_open(struct gsm_bts_trx *trx); +int l1if_trx_close_all(struct gsm_bts *bts); +int l1if_enable_events(struct gsm_bts_trx *trx); + +int l1if_activate_rf(struct gsm_bts_trx *trx, int on); + +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT * + data_ind); + +struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id); + +struct msgb *l1p_msgb_alloc(void); + +/* tch.c */ +void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, + uint8_t *data, uint32_t *len, const uint8_t *rtp_pl, + unsigned int rtp_pl_len); + +tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM +osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn); diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c new file mode 100644 index 00000000..d44f7211 --- /dev/null +++ b/src/osmo-bts-octphy/l1_oml.c @@ -0,0 +1,1825 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org> + * + * based on a copy of osmo-bts-sysmo/l1_oml.c, which is + * Copyright (C) 2011 by Harald Welte <laforge@gnumonks.org> + * Copyright (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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> + +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" +#include "btsconfig.h" + +#include <octphy/octvc1/octvc1_rc2string.h> +#include <octphy/octvc1/gsm/octvc1_gsm_api_swap.h> +#include <octphy/octvc1/gsm/octvc1_gsm_default.h> +#include <octphy/octvc1/gsm/octvc1_gsm_id.h> +#include <octphy/octvc1/main/octvc1_main_default.h> +#include <octphy/octvc1/main/octvc1_main_version.h> + +bool no_fw_check = 0; + +#define LOGPTRX(byTrxId, level, fmt, args...) \ + LOGP(DL1C, level, "(byTrxId %u) " fmt, byTrxId, ## args) + +/* Map OSMOCOM logical channel type to OctPHY Logical channel type */ +static tOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM pchan_to_logChComb[_GSM_PCHAN_MAX] = +{ + [GSM_PCHAN_NONE] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY, + [GSM_PCHAN_CCCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH, + [GSM_PCHAN_CCCH_SDCCH4] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_SACCHC4, + [GSM_PCHAN_TCH_F] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF, + [GSM_PCHAN_TCH_H] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH, + [GSM_PCHAN_SDCCH8_SACCH8C] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_SACCHC8, + // TODO - watch out below two!!! + [GSM_PCHAN_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF, + [GSM_PCHAN_TCH_F_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF, +#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4 + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4, +#endif +#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8 + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8, +#endif + [GSM_PCHAN_UNKNOWN] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY +}; + +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; + tOCTVC1_GSM_SAPI_ENUM sapi; + tOCTVC1_GSM_DIRECTION_ENUM dir; + enum sapi_cmd_type type; + int (*callback) (struct gsm_lchan * lchan, int status); +}; + +struct sapi_dir { + tOCTVC1_GSM_SAPI_ENUM sapi; + tOCTVC1_GSM_DIRECTION_ENUM dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_FCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_BCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_RACH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir tchf_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir tchh_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir sdcch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir cbch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_CBCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + /* Does the CBCH really have a SACCH in Downlink */ + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, +}; + +static const struct sapi_dir pdtch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + uint32_t 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 uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +extern uint8_t rach_detected_LA_g; +extern uint8_t rach_detected_Other_g; + +static int opstart_compl(struct gsm_abis_mo *mo) +{ + /* TODO: Send NACK in case of error! */ + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* 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 == 7) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(mo); +} + +static +tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM 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 cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + return (tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM) 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 cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; + } + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; +} + +static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) +{ + /* common for the SIGN, V1 and EFR: */ + int i; + p_Config->byCmiPhase = 0; + p_Config->byInitRate = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; + /* 4 AMR active codec set */ + for (i = 0; i < cOCTVC1_GSM_RATE_LIST_SIZE; i++) + p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; +} + +static void lchan2lch_par(struct gsm_lchan *lchan, + tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) +{ + 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) { + clear_amr_params(p_Config); + } + break; + + case GSM48_CMODE_SPEECH_V1: + clear_amr_params(p_Config); + break; + + case GSM48_CMODE_SPEECH_EFR: + clear_amr_params(p_Config); + break; + + case GSM48_CMODE_SPEECH_AMR: + p_Config->byCmiPhase = 1; /* FIXME? */ + p_Config->byInitRate = + (tOCTVC1_GSM_AMR_CODEC_MODE_ENUM) + amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < cOCTVC1_GSM_RATE_LIST_SIZE; j++) + p_Config->abyRate[j] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; + + j = 0; + if (mr_conf->m4_75) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_4_75; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m5_15) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_15; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m5_90) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_90; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m6_70) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_6_70; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m7_40) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_40; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m7_95) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_95; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m10_2) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_10_2; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m12_2) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_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; + + } +} + +/*********************************************************************** + * CORE SAPI QUEUE HANDLING + ***********************************************************************/ + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status); +static void sapi_queue_send(struct gsm_lchan *lchan); + +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 lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h; + struct gsm_bts_trx *trx; + struct gsm_lchan *lchan; + uint8_t sapi; + uint8_t direction; + uint8_t status; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar); + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan activation\n"); + return -EINVAL; + } + + lchan = get_lchan_by_lchid(trx, &ar->LchId); + sapi = ar->LchId.bySAPI; + direction = ar->LchId.byDirection; + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, direction)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s\n", + get_value_string(octphy_l1sapi_names, sapi)); + status = LCHAN_SAPI_S_ERROR; + } else { + status = LCHAN_SAPI_S_ASSIGNED; + } + + switch (direction) { + case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS: + lchan->sapis_dl[sapi] = status; + break; + case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS: + lchan->sapis_ul[sapi] = status; + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unknown direction %d\n", + ar->LchId.byDirection); + break; + } + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + sapi_queue_dispatch(lchan, ar->Header.ulReturnCode); + +err: + msgb_free(resp); + + return 0; +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac; + + lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*lac)); + l1if_fill_msg_hdr(&lac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID); + + lac->TrxId.byTrxId = pinst->u.octphy.trx_id; + lac->LchId.byTimeslotNb = lchan->ts->nr; + lac->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan); + lac->LchId.bySAPI = cmd->sapi; + lac->LchId.byDirection = cmd->dir; + + lac->Config.byTimingAdvance = lchan->rqd_ta; + lac->Config.byBSIC = lchan->ts->trx->bts->bsic; + + lchan2lch_par(lchan, &lac->Config); + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac); + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, cmd->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, cmd->dir)); + + return l1if_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + + +static tOCTVC1_GSM_CIPHERING_ID_ENUM rsl2l1_ciph[] = { + [0] = cOCTVC1_GSM_CIPHERING_ID_ENUM_UNUSED, + [1] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_0, + [2] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_1, + [3] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_2, + [4] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_3 +}; + +static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *pcr = + (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *) resp->l2h; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP_SWAP(pcr); + + if (pcr->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "Error: Cipher Request Failed!\n\n"); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); + exit(-1); + } + + trx = trx_by_l1h(fl1, pcr->TrxId.byTrxId); + if (!trx) { + LOGPTRX(pcr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during cipher mode activation\n"); + return -EINVAL; + } + + OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr); + ts = &trx->ts[pcr->PchId.byTimeslotNb]; + /* for some strange reason the response does not tell which + * sub-channel, only th request contains this information :( */ + lchan = &ts->lchan[(unsigned long) data]; + + /* TODO: This state machine should be shared accross BTS models? */ + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + } + + 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, pcr->Header.ulReturnCode); + +err: + msgb_free(resp); + return 0; +} + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *pcc; + + pcc = (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *) + msgb_put(msg, sizeof(*pcc)); + l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID); + + pcc->TrxId.byTrxId = pinst->u.octphy.trx_id; + pcc->PchId.byTimeslotNb = lchan->ts->nr; + pcc->ulSubchannelNb = lchan_to_GsmL1_SubCh_t(lchan); + pcc->ulDirection = cmd->dir; + pcc->Config.ulCipherId = rsl2l1_ciph[lchan->encr.alg_id]; + memcpy(pcc->Config.abyKey, lchan->encr.key, lchan->encr.key_len); + + LOGP(DL1C, LOGL_INFO, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), pcc->Config.ulCipherId, + get_value_string(octphy_dir_names, pcc->ulDirection)); + + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD_SWAP(pcc); + + /* we have to save the lchan number in this strange way, as the + * PHY does not return the ulSubchannelNr in the response to + * this command */ + return l1if_req_compl(fl1h, msg, set_ciph_compl_cb, (void *)(unsigned long) lchan->nr); +} + + +/** + * 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 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); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != cOCTVC1_RC_OK && 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 == cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir == cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS) { + 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 lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + 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 == cOCTVC1_GSM_SAPI_ENUM_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); + } + + /* 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 int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *ldr = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h; + struct gsm_bts_trx *trx; + struct gsm_lchan *lchan; + struct sapi_cmd *cmd; + uint8_t status; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr); + trx = trx_by_l1h(fl1, ldr->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ldr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan deactivation\n"); + return -EINVAL; + } + + lchan = get_lchan_by_lchid(trx, &ldr->LchId); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, ldr->LchId.byDirection)); + + if (ldr->Header.ulReturnCode == cOCTVC1_RC_OK) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI), + ldr->LchId.byTimeslotNb); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, + "Error deactivating L1 SAPI %s on TS %u\n", + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI), + ldr->LchId.byTimeslotNb); + status = LCHAN_SAPI_S_ERROR; + } + + switch (ldr->LchId.byDirection) { + case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS: + lchan->sapis_dl[ldr->LchId.bySAPI] = status; + break; + case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS: + lchan->sapis_ul[ldr->LchId.bySAPI] = status; + break; + } + + 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 != ldr->LchId.bySAPI || + cmd->dir != ldr->LchId.byDirection || + 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, + ldr->LchId.bySAPI, ldr->LchId.byDirection); + goto err; + } + + sapi_queue_dispatch(lchan, status); + +err: + msgb_free(resp); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *ldc; + + ldc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*ldc)); + l1if_fill_msg_hdr(&ldc->Header, msg, fl1h,cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID); + + ldc->TrxId.byTrxId = pinst->u.octphy.trx_id; + ldc->LchId.byTimeslotNb = lchan->ts->nr; + ldc->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan); + ldc->LchId.byDirection = cmd->dir; + ldc->LchId.bySAPI = cmd->sapi; + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(ldc); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, cmd->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, cmd->dir)); + + return l1if_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); + +} + +/** + * 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 = 0; + 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: + /* TODO: Mode modif not supported by OctPHY currently */ + /* 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, cOCTVC1_GSM_SAPI_ENUM_SACCH, + cOCTVC1_GSM_ID_DIRECTION_ENUM_TX_BTS_MS); + res |= + check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH, + cOCTVC1_GSM_ID_DIRECTION_ENUM_RX_BTS_MS); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + res = 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); +} + +/* we regularly check if the 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 octphy_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +/*********************************************************************** + * RSL DEACTIVATE SACCH + ***********************************************************************/ + +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); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(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 bts_model_lchan_deactivate_sacch(lchan); +} + + +/*********************************************************************** + * RSL CHANNEL RELEASE + ***********************************************************************/ + +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 bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + enqueue_rel_marker(lchan); + 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) + return 0; + lchan_deactivate(lchan); + return 0; +} + + +/*********************************************************************** + * SET CIPHERING + ***********************************************************************/ + +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 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 = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + else + dir = cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + + +/*********************************************************************** + * RSL MODE MODIFY + ***********************************************************************/ + +/* Mode modify is currently not supported by OctPHY */ +static void enqueue_sapi_logchpar_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_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + + +/* Mode modify is currently not supported by OctPHY */ +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction); + + return 0; +} + +/* Mode modify is currently not supported by OctPHY */ +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, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS); + tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS); + + /* FIXME: update encryption */ + + return 0; +} + + +/*********************************************************************** + * LCHAN / SAPI ACTIVATION + ***********************************************************************/ + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + if (status != cOCTVC1_RC_OK) { + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, + RSL_ERR_EQUIPMENT_FAIL); + 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); + + 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 phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + DEBUGP(DL1C, "lchan_act called\n"); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == cOCTVC1_GSM_SAPI_ENUM_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); + } + + lchan_init_lapdm(lchan); + + return 0; +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + lchan_activate(lchan); + return 0; +} + +#define talloc_replace(dst, ctx, src) \ + do { \ + if (dst) \ + talloc_free(dst); \ + dst = talloc_strdup(ctx, (const char *) src); \ + } while (0) + +static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data) +{ + tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr = + (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP_SWAP(aisr); + + LOGP(DL1C, LOGL_INFO, "Rx APP-INFO-SYSTEM.resp (platform='%s', version='%s')\n", + aisr->szPlatform, aisr->szVersion); + +#if OCTPHY_MULTI_TRX == 1 + LOGP(DL1C, LOGL_INFO, "Note: compiled with multi-trx support.\n"); +#else + LOGP(DL1C, LOGL_INFO, "Note: compiled without multi-trx support.\n"); +#endif + + talloc_replace(fl1h->info.system.platform, fl1h, aisr->szPlatform); + talloc_replace(fl1h->info.system.version, fl1h, aisr->szVersion); + + msgb_free(resp); + + return 0; +} + +int l1if_check_app_sys_version(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *ais; + + ais = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *) + msgb_put(msg, sizeof(*ais)); + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_DEF(ais); + l1if_fill_msg_hdr(&ais->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_SWAP(ais); + + LOGP(DL1C, LOGL_INFO, "Tx APP-INFO-SYSTEM.req\n"); + + return l1if_req_compl(fl1h, msg, app_info_sys_compl_cb, pinst); +} + +static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, + void *data) +{ + char ver_hdr[32]; + struct phy_instance *pinst = data; + tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air = + (tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h; + + snprintf(ver_hdr, sizeof(ver_hdr), "%02i.%02i.%02i-B%i", + cOCTVC1_MAIN_VERSION_MAJOR, cOCTVC1_MAIN_VERSION_MINOR, + cOCTVC1_MAIN_VERSION_MAINTENANCE, cOCTVC1_MAIN_VERSION_BUILD); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP_SWAP(air); + + LOGP(DL1C, LOGL_INFO, + "Rx APP-INFO.resp (name='%s', desc='%s', ver='%s', ver_hdr='%s')\n", + air->szName, air->szDescription, air->szVersion, ver_hdr); + + /* Check if the firmware version of the DSP matches the header files + * that were used to compile osmo-bts */ + if (strcmp(air->szVersion, ver_hdr) != 0) { + LOGP(DL1C, LOGL_ERROR, + "Invalid header-file-version / dsp-firmware-version combination\n"); + LOGP(DL1C, LOGL_ERROR, + "Expected firmware version: %s\n", ver_hdr); + LOGP(DL1C, LOGL_ERROR, + "Actual firmware version: %s\n", air->szVersion); + + if (!no_fw_check) { + LOGP(DL1C, LOGL_ERROR, + "use option -I to override the check (not recommened)\n"); + LOGP(DL1C, LOGL_ERROR, + "exiting...\n"); + exit(1); + } + } + + talloc_replace(fl1h->info.app.name, fl1h, air->szName); + talloc_replace(fl1h->info.app.description, fl1h, air->szDescription); + talloc_replace(fl1h->info.app.version, fl1h, air->szVersion); + OSMO_ASSERT(strlen(ver_hdr) < sizeof(pinst->version)); + osmo_strlcpy(pinst->version, ver_hdr, strlen(ver_hdr)); + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + msgb_free(resp); + + return 0; +} + +int l1if_check_app_version(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *ai; + + ai = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *) msgb_put(msg, sizeof(*ai)); + mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_DEF(ai); + l1if_fill_msg_hdr(&ai->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_SWAP(ai); + + LOGP(DL1C, LOGL_INFO, "Tx APP-INFO.req\n"); + + return l1if_req_compl(fl1h, msg, app_info_compl_cb, pinst); +} + +static int trx_close_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *car = + (tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_CLOSE_RSP_SWAP(car); + + LOGP(DL1C, LOGL_INFO, "Rx TRX-CLOSE.conf(%u)\n", car->TrxId.byTrxId); + + msgb_free(resp); + + return 0; +} + +static int trx_close(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct octphy_hdl *fl1h = plink->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *cac; + + cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *) + msgb_put(msg, sizeof(*cac)); + l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_CLOSE_CID); + + cac->TrxId.byTrxId = pinst->u.octphy.trx_id; + + LOGP(DL1C, LOGL_INFO, "Tx TRX-CLOSE.req(%u)\n", cac->TrxId.byTrxId); + + mOCTVC1_GSM_MSG_TRX_CLOSE_CMD_SWAP(cac); + + return l1if_req_compl(fl1h, msg, trx_close_cb, NULL); +} + +/* call-back once the TRX_OPEN_CID response arrives */ +static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data) +{ + struct gsm_bts_trx *trx; + + tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or = + (tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or); + trx = trx_by_l1h(fl1h, or->TrxId.byTrxId); + if (!trx) { + LOGPTRX(or->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during TRX opening procedure -- abort\n"); + exit(1); + } + + LOGP(DL1C, LOGL_INFO, "TRX-OPEN.resp(trx=%u) = %s\n", + trx->nr, octvc1_rc2string(or->Header.ulReturnCode)); + + /* FIXME: check for ulReturnCode == OK */ + if (or->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "TRX-OPEN failed: %s\n", + octvc1_rc2string(or->Header.ulReturnCode)); + msgb_free(resp); + exit(1); + } + + msgb_free(resp); + + opstart_compl(&trx->mo); + + octphy_hw_get_pcb_info(fl1h); + octphy_hw_get_rf_port_info(fl1h, 0); + octphy_hw_get_rf_ant_rx_config(fl1h, 0, 0); + octphy_hw_get_rf_ant_tx_config(fl1h, 0, 0); + octphy_hw_get_rf_ant_rx_config(fl1h, 0, 1); + octphy_hw_get_rf_ant_tx_config(fl1h, 0, 1); + octphy_hw_get_clock_sync_info(fl1h); + fl1h->opened = 1; + + return 0; +} + +int l1if_trx_open(struct gsm_bts_trx *trx) +{ + /* putting it all together */ + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_OPEN_CMD *oc; + + oc = (tOCTVC1_GSM_MSG_TRX_OPEN_CMD *) msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_OPEN_CID); + oc->ulRfPortIndex = plink->u.octphy.rf_port_index; + oc->TrxId.byTrxId = pinst->u.octphy.trx_id; + oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn); + oc->Config.usArfcn = trx->arfcn; + +#if OCTPHY_MULTI_TRX == 1 + if (pinst->u.octphy.trx_id) + oc->Config.usCentreArfcn = plink->u.octphy.center_arfcn; + else { + oc->Config.usCentreArfcn = trx->arfcn; + plink->u.octphy.center_arfcn = trx->arfcn; + } + oc->Config.usBcchArfcn = trx->bts->c0->arfcn; +#endif + oc->Config.usTsc = trx->bts->bsic & 0x7; + oc->RfConfig.ulRxGainDb = plink->u.octphy.rx_gain_db; + /* FIXME: compute this based on nominal transmit power, etc. */ + if (plink->u.octphy.tx_atten_flag) { + oc->RfConfig.ulTxAttndB = plink->u.octphy.tx_atten_db; + } else { + /* Take the Tx Attn received in set radio attribures + * x4 is for the value in db */ + oc->RfConfig.ulTxAttndB = (trx->max_power_red) << 2; + } + +#if OCTPHY_USE_ANTENNA_ID == 1 + oc->RfConfig.ulTxAntennaId = plink->u.octphy.tx_ant_id; + oc->RfConfig.ulRxAntennaId = plink->u.octphy.rx_ant_id; +#endif + +#if OCTPHY_MULTI_TRX == 1 + LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, " + "center=%u, tsc=%u, rx_gain=%u, tx_atten=%u)\n", + oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn, + oc->Config.usCentreArfcn, oc->Config.usTsc, oc->RfConfig.ulRxGainDb, + oc->RfConfig.ulTxAttndB); +#else + LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, " + "tsc=%u, rx_gain=%u, tx_atten=%u)\n", + oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn, + oc->Config.usTsc, oc->RfConfig.ulRxGainDb, + oc->RfConfig.ulTxAttndB); +#endif + + mOCTVC1_GSM_MSG_TRX_OPEN_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL); +} + +#if OCTPHY_USE_16X_OVERSAMPLING == 1 +static int over_sample_16x_modif_compl_cb(struct octphy_hdl *fl1, + struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_RSP *mcr = + (tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_RSP*) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_RSP_SWAP(mcr); + + LOGP(DL1C, LOGL_INFO, "Rx OVER-SAMPLE-16x-MODIFY.conf\n"); + + msgb_free(resp); + + return 0; +} + +static int l1if_over_sample_16x_modif(struct gsm_bts_trx *trx) +{ + /* NOTE: The 16x oversampling mode should always be enabled. Single- + * TRX operation will work with standard 4x oversampling, but multi- + * TRX requires 16x oversampling */ + + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD *mc; + + mc = (tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD*) msgb_put(msg, + sizeof + (*mc)); + mOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD_DEF(mc); + l1if_fill_msg_hdr(&mc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CID); + + if (plink->u.octphy.over_sample_16x == true) + mc->ulOversample16xEnableFlag = 1; + else + mc->ulOversample16xEnableFlag = 0; + + mOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD_SWAP(mc); + + LOGP(DL1C, LOGL_INFO, "Tx OVER-SAMPLE-16x-MODIF.req\n"); + + return l1if_req_compl(fl1h, msg, over_sample_16x_modif_compl_cb, 0); +} +#endif + +uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx) +{ + return 0; +} + +static int trx_init(struct gsm_bts_trx *trx) +{ + 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); */ + } + + l1if_check_app_version(trx); + l1if_check_app_sys_version(trx); + +#if OCTPHY_USE_16X_OVERSAMPLING == 1 + l1if_over_sample_16x_modif(trx); +#endif + + return l1if_trx_open(trx); +} + +/*********************************************************************** + * PHYSICAL CHANNE ACTIVATION + ***********************************************************************/ + +static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_abis_mo *mo; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during physical channel activation -- abort\n"); + exit(1); + } + + ts_nr = ar->PchId.byTimeslotNb; + OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts)); + + ts = &trx->ts[ts_nr]; + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n", + ts->trx->nr, ts->nr, ts->pchan, + octvc1_rc2string(ar->Header.ulReturnCode)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, + "PCHAN-ACT failed: %s\n\n", + octvc1_rc2string(ar->Header.ulReturnCode)); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); + exit(-1); + } + + trx = ts->trx; + mo = &trx->ts[ar->PchId.byTimeslotNb].mo; + + msgb_free(resp); + + return opstart_compl(mo); +} + +static int ts_connect_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb * cb, void *data) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + + oc = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD*) + msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID); + + oc->TrxId.byTrxId = pinst->u.octphy.trx_id; + oc->PchId.byTimeslotNb = ts->nr; + oc->ulChannelType = pchan_to_logChComb[pchan]; + + /* TODO: how should we know the payload type here? Also, why + * would the payload type have to be the same for both halves of + * a TCH/H ? */ + switch (oc->ulChannelType) { + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF: + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF: + oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE; + break; + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH: + oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE; + break; + } + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.req(trx=%u, ts=%u, chcomb=%u)\n", + ts->trx->nr, ts->nr, pchan); + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, cb, data); +} + +/* Dynamic timeslots: Disconnect callback, reports completed disconnection + * to higher layers */ +static int ts_disconnect_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during ts disconnection\n"); + return -EINVAL; + } + + ts_nr = ar->PchId.byTimeslotNb; + ts = &trx->ts[ts_nr]; + + cb_ts_disconnected(ts); + + return 0; +} + +/* Dynamic timeslots: Connect callback, reports completed disconnection to + * higher layers */ +static int ts_connect_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id while connecting ts\n"); + return -EINVAL; + } + + ts_nr = ar->PchId.byTimeslotNb; + OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts)); + + ts = &trx->ts[ts_nr]; + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n", + ts->trx->nr, ts->nr, ts->pchan, + octvc1_rc2string(ar->Header.ulReturnCode)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, + "PCHAN-ACT failed: %s\n\n", + octvc1_rc2string(ar->Header.ulReturnCode)); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); + exit(-1); + } + + msgb_free(resp); + + cb_ts_connected(ts, 0); + + return 0; +} + +/*********************************************************************** + * BTS MODEL CALLBACKS + ***********************************************************************/ + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + /* TODO: How to do this ? */ + return 0; +} + +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; +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + int i; + for (i = 0; i < bts->num_trx; i++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i); + l1if_activate_rf(trx, 1); + } + return 0; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + int rc; + + struct gsm_bts_trx *trx; + struct phy_instance *pinst; + struct octphy_hdl *fl1h; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + + trx = ((struct gsm_bts_trx *)obj); + pinst = trx_phy_instance(trx); + fl1h = pinst->phy_link->u.octphy.hdl; + + if (mo->procedure_pending) { + LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " + "pending procedure on TRX %d\n", trx->nr); + return 0; + } + mo->procedure_pending = 1; + switch (adm_state) { + case NM_STATE_LOCKED: + + pinst->u.octphy.trx_locked = 1; + + /* Stop heartbeat check */ + osmo_timer_del(&fl1h->alive_timer); + + bts_model_trx_deact_rf(trx); + + /* Close TRX */ + rc = bts_model_trx_close(trx); + if (rc != 0) { + LOGP(DL1C, LOGL_ERROR, + "Cannot close TRX %d, it is already closed.\n", + trx->nr); + } + break; + + case NM_STATE_UNLOCKED: + + if (pinst->u.octphy.trx_locked) { + pinst->u.octphy.trx_locked = 0; + l1if_activate_rf(trx, 1); + } + + break; + + default: + break; + } + + mo->procedure_pending = 0; + break; + + default: + /* blindly accept all state changes */ + break; + } + + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + return l1if_activate_rf(trx, 0); +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + /* FIXME: close only one TRX */ + return trx_close(trx); +} + + +/* 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) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + /*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */ + + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + } + 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 = -1; + struct gsm_bts_trx_ts *ts; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_connect_as(ts, ts->pchan, pchan_act_compl_cb, NULL); + 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_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ +#pragma message ("Implement bts_model_change_power based on TRX_MODIFY_RF_CID (OS#3016)") + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *oc = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + + oc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID); + + oc->TrxId.byTrxId = pinst->u.octphy.trx_id; + oc->PchId.byTimeslotNb = ts->nr; + + LOGP(DL1C, LOGL_INFO, "PCHAN-DEACT.req(trx=%u, ts=%u, chcomb=%u)\n", + ts->trx->nr, ts->nr, ts->pchan); + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + if (as_pchan == GSM_PCHAN_TCH_F_PDCH + || as_pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(as_pchan)); + exit(1); + return; + } + + rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); + if (rc) + cb_ts_connected(ts, rc); +} diff --git a/src/osmo-bts-octphy/l1_oml.h b/src/osmo-bts-octphy/l1_oml.h new file mode 100644 index 00000000..4729df5b --- /dev/null +++ b/src/osmo-bts-octphy/l1_oml.h @@ -0,0 +1,18 @@ +#pragma once + +#include "l1_if.h" + +/* channel control */ +int l1if_rsl_chan_act(struct gsm_lchan *lchan); +int l1if_rsl_chan_rel(struct gsm_lchan *lchan); +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); +int l1if_rsl_mode_modify(struct gsm_lchan *lchan); + +int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink); + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, + const uint8_t * attr_ids, unsigned int num_attr_ids); + +int lchan_activate(struct gsm_lchan *lchan); diff --git a/src/osmo-bts-octphy/l1_tch.c b/src/osmo-bts-octphy/l1_tch.c new file mode 100644 index 00000000..df0469dd --- /dev/null +++ b/src/osmo-bts-octphy/l1_tch.c @@ -0,0 +1,283 @@ +/* Traffic Channel (TCH) part of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org> + * + * based on a copy of osmo-bts-sysmo/l1_tch.c, which is + * Copyright (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/bits.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/l1sap.h> + +#include "l1_if.h" + +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, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* 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; + + 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 + */ +int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + /* 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); + return GSM_FR_BYTES; +} + +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, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, 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); + + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(cur, GSM_HR_BYTES); + + 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(DL1P, 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); + + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES); + + return GSM_HR_BYTES; +} + + +/* brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT * + data_ind) +{ + uint32_t payload_type = data_ind->Data.ulPayloadType; + uint8_t *payload = data_ind->Data.abyDataContent; + + uint32_t fn = data_ind->Data.ulFrameNumber; + uint16_t b_total = data_ind->MeasurementInfo.usBERTotalBitCnt; + uint16_t b_error = data_ind->MeasurementInfo.usBERCnt; + uint16_t ber10k = b_total ? BER_10K * b_error / b_total : 0; + int16_t lqual_cb = 0; /* FIXME: check min_qual_norm! */ + + uint8_t payload_len; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = + &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (data_ind->Data.ulDataLength < 1) { + LOGPFN(DL1P, LOGL_DEBUG, fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, + data_ind->Data.ulFrameNumber, + ber10k, lqual_cb); + } + + payload_len = data_ind->Data.ulDataLength; + + switch (payload_type) { + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE: + if (lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, fn, "%s Rx Payload Type %d is unsupported\n", + gsm_lchan_name(lchan), payload_type); + break; + } + + LOGPFN(DL1P, LOGL_DEBUG, fn, "%s Rx codec frame (%u): %s\n", gsm_lchan_name(lchan), + payload_len, osmo_hexdump(payload, payload_len)); + + switch (payload_type) { + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: + rmsg = l1_to_rtppayload_fr(payload, payload_len); + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE: + rmsg = l1_to_rtppayload_hr(payload, payload_len); + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_efr(payload, payload_len); + break; +#endif + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_amr(payload, payload_len, + &lchan->tch.amr_mr); +#else + LOGPFN(DL1P, LOGL_ERROR, fn, "OctPHY only supports FR!\n"); + return -1; +#endif + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, + data_ind->Data.ulFrameNumber, + ber10k, lqual_cb); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, fn, "%s Rx Payload Type %d incompatible with lchan\n", + gsm_lchan_name(lchan), payload_type); + return -EINVAL; +} + +#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, uint32_t *payload_type, + uint8_t *data, uint32_t *len, const uint8_t *rtp_pl, + unsigned int rtp_pl_len) +{ + uint8_t *l1_payload; + int rc = -1; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + l1_payload = &data[0]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + } else { + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE; + /* Not supported currently */ + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + } + break; + case GSM48_CMODE_SPEECH_EFR: + /* Not supported currently */ + case GSM48_CMODE_SPEECH_AMR: + /* Not supported currently */ + LOGP(DRTP, LOGL_ERROR, "OctPHY only supports FR!\n"); + 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; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); +} diff --git a/src/osmo-bts-octphy/l1_utils.c b/src/osmo-bts-octphy/l1_utils.c new file mode 100644 index 00000000..8a8e1554 --- /dev/null +++ b/src/osmo-bts-octphy/l1_utils.c @@ -0,0 +1,141 @@ +/* Layer 1 (PHY) Utilities of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include "l1_utils.h" +#include <octphy/octvc1/gsm/octvc1_gsm_api.h> +#include <octphy/octvc1/gsm/octvc1_gsm_id.h> +#include <octphy/octvc1/main/octvc1_main_id.h> +#include <octphy/octvc1/hw/octvc1_hw_api.h> + +const struct value_string octphy_l1sapi_names[23] = +{ + { cOCTVC1_GSM_SAPI_ENUM_IDLE, "IDLE" }, + { cOCTVC1_GSM_SAPI_ENUM_FCCH, "FCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SCH, "SCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SACCH, "SACCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SDCCH, "SDCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_BCCH, "BCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH,"PCH_AGCH" }, + { cOCTVC1_GSM_SAPI_ENUM_CBCH, "CBCH" }, + { cOCTVC1_GSM_SAPI_ENUM_RACH, "RACH" }, + { cOCTVC1_GSM_SAPI_ENUM_TCHF, "TCH/F" }, + { cOCTVC1_GSM_SAPI_ENUM_FACCHF, "FACCH/F" }, + { cOCTVC1_GSM_SAPI_ENUM_TCHH, "TCH/H" }, + { cOCTVC1_GSM_SAPI_ENUM_FACCHH, "FACCH/H" }, + { cOCTVC1_GSM_SAPI_ENUM_NCH, "NCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PDTCH, "PDTCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PACCH, "PACCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PBCCH, "PBCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PAGCH, "PAGCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PPCH, "PPCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PNCH, "PNCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PTCCH, "PTCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PRACH, "PRACH" }, + { 0, NULL } +}; + +const struct value_string octphy_dir_names[5] = +{ + { cOCTVC1_GSM_DIRECTION_ENUM_NONE, "None" }, + { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS, "TX_BTS_MS(DL)" }, + { cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "RX_BTS_MS(UL)" }, + { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS | cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "BOTH" }, + { 0, NULL } +}; + +const struct value_string octphy_clkmgr_state_vals[8] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, "UNINITIALIZED" }, + +/* Note: Octasic renamed cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED to + * cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE. The following ifdef + * statement ensures that older headers still work. */ +#ifdef cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED, "UNUSED" }, +#else + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "IDLE" }, +#endif + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, "NO_EXT_CLOCK" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "LOCKED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED, "UNLOCKED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "ERROR" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "DISABLED" }, + { 0, NULL } +}; + +const struct value_string octphy_cid_vals[37] = { + { cOCTVC1_GSM_MSG_TRX_OPEN_CID, "TRX-OPEN" }, + { cOCTVC1_GSM_MSG_TRX_CLOSE_CID, "TRX-CLOSE" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_CID, "TRX-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_INFO_CID, "TRX-INFO" }, + { cOCTVC1_GSM_MSG_TRX_RESET_CID, "TRX-RESET" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_CID, "TRX-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_LIST_CID, "TRX-LIST" }, + { cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID, "TRX-CLOSE-ALL" }, + { cOCTVC1_GSM_MSG_TRX_START_RECORD_CID, "RECORD-START" }, + { cOCTVC1_GSM_MSG_TRX_STOP_RECORD_CID, "RECORD-STOP" }, + { cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-ACT" }, + { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-DEACT" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_LOGICAL_CHANNEL_CID, "LCHAN-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_INFO_LOGICAL_CHANNEL_CID, "LCHAN-INFO" }, + { cOCTVC1_GSM_MSG_TRX_LIST_LOGICAL_CHANNEL_CID, "LCHAN-LIST" }, + { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID, + "LCHAN-EMPTY-FRAME" }, + { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID, "LCHAN-DATA" }, + { cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-ACT" }, + { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-DEACT" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_PHYSICAL_CHANNEL_CID, "PCHAN-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_RESET_PHYSICAL_CHANNEL_CID, "PCHAN-RESET" }, + { cOCTVC1_GSM_MSG_TRX_LIST_PHYSICAL_CHANNEL_CID, "PCHAN-LIST" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CID, "PCHAN-INFO" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID, + "PCHAN-CIPH-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CIPHERING_CID, + "PCHAN-CIPH-INFO" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_MEASUREMENT_CID, + "PCHAN-MEASUREMENT" }, + { cOCTVC1_GSM_MSG_TRX_INFO_RF_CID, "RF-INFO" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_RF_CID, "RF-MODIFY" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_LIST_CID, "TAP-FILTER-LIST" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_INFO_CID, "TAP-FILTER-INFO" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_STATS_CID, "TAP-FILTER-STATS" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_MODIFY_CID, "TAP-FILTER-MODIFY" }, + { cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID, "MAIN_MSG_APP_INFO" }, + { cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID, "MAIN_MSG_APP_INFO_SYSTEM" }, + { cOCTVC1_GSM_MSG_TRX_START_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID, + "LCHAN-RAW-DATA-START" }, + { cOCTVC1_GSM_MSG_TRX_STOP_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID, + "LCHAN-RAW-DATA-STOP" }, + { 0, NULL } +}; + +const struct value_string octphy_eid_vals[7] = { + { cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID, "TIME.ind" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID, "TRX-STATUS-CHG.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID, + "LCHAN-DATA.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID, + "LCHAN-RTS.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID, + "LCHAN-RACH.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID, + "LCHAN-RAW-DATA.ind" }, + { 0, NULL } +}; diff --git a/src/osmo-bts-octphy/l1_utils.h b/src/osmo-bts-octphy/l1_utils.h new file mode 100644 index 00000000..d1a87170 --- /dev/null +++ b/src/osmo-bts-octphy/l1_utils.h @@ -0,0 +1,9 @@ +#pragma once + +#include <osmocom/core/utils.h> + +const struct value_string octphy_l1sapi_names[23]; +const struct value_string octphy_dir_names[5]; +const struct value_string octphy_clkmgr_state_vals[8]; +const struct value_string octphy_cid_vals[37]; +const struct value_string octphy_eid_vals[7]; diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c new file mode 100644 index 00000000..928a4c81 --- /dev/null +++ b/src/osmo-bts-octphy/main.c @@ -0,0 +1,101 @@ +/* Main program of osmo-bts for OCTPHY-2G */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org> + * + * based on a copy of osmo-bts-sysmo/main.c, which is + * Copyright (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/l1sap.h> + +#include "l1_if.h" + +#define RF_LOCK_PATH "/var/lock/bts_rf_lock" + +extern int pcu_direct; +extern bool no_fw_check; + +int bts_model_print_help() +{ + printf(" -I --no-fw-check Override firmware version check\n"); + return 0; +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* specific to this hardware */ + { "no-fw-check", 0, 0, 'I' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "I", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'I': + no_fw_check = true; + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c new file mode 100644 index 00000000..6da038b1 --- /dev/null +++ b/src/osmo-bts-octphy/octphy_hw_api.c @@ -0,0 +1,404 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2015 Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmo-bts/logging.h> + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" + +#include <octphy/octvc1/octvc1_rc2string.h> +#include <octphy/octvc1/hw/octvc1_hw_api.h> +#include <octphy/octvc1/hw/octvc1_hw_api_swap.h> + +/* Chapter 12.1 */ +static int get_pcb_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_HW_MSG_PCB_INFO_RSP *pir = + (tOCTVC1_HW_MSG_PCB_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_PCB_INFO_RSP_SWAP(pir); + + LOGP(DL1C, LOGL_INFO, "HW-PCB-INFO.resp: Name=%s %s, Serial=%s, " + "FileName=%s, InfoSource=%u, InfoState=%u, GpsName=%s, " + "WiFiName=%s\n", pir->szName, pir->ulDeviceId ? "SEC" : "PRI", + pir->szSerial, pir->szFilename, pir->ulInfoSource, + pir->ulInfoState, pir->szGpsName, pir->szWifiName); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.1 */ +int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_PCB_INFO_CMD *pic; + + pic = (tOCTVC1_HW_MSG_PCB_INFO_CMD *) msgb_put(msg, sizeof(*pic)); + + l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_PCB_INFO_CID); + + mOCTVC1_HW_MSG_PCB_INFO_CMD_SWAP(pic); + + return l1if_req_compl(fl1h, msg, get_pcb_info_compl_cb, NULL); +} + +/* Chapter 12.9 */ +static int rf_port_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *pir = + (tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_RSP_SWAP(pir); + + LOGP(DL1C, LOGL_INFO, "RF-PORT-INFO.resp Idx=%u, InService=%u, " + "hOwner=0x%x, Id=%u, FreqMin=%u, FreqMax=%u\n", + pir->ulPortIndex, pir->ulInService, pir->hOwner, + pir->ulPortInterfaceId, pir->ulFrequencyMinKhz, + pir->ulFrequencyMaxKhz); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.9 */ +int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *pic; + + pic = (tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *) msgb_put(msg, sizeof(*pic)); + + l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_CID); + + pic->ulPortIndex = index; + + mOCTVC1_HW_MSG_RF_PORT_INFO_CMD_SWAP(pic); + + return l1if_req_compl(fl1h, msg, rf_port_info_compl_cb, NULL); +} + +/* Chapter 12.10 */ +static int rf_port_stats_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + struct octphy_hw_get_cb_data *get_cb_data; + + tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr = + (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_STATS_RSP_SWAP(psr); + + LOGP(DL1C, LOGL_INFO, "RF-PORT-STATS.resp Idx=%u RadioStandard=%s, " + "Rx(Bytes=%u, Overflow=%u, AvgBps=%u, Period=%uus, Freq=%u) " + "Tx(Bytes=%i, Underflow=%u, AvgBps=%u, Period=%uus, Freq=%u)\n", + psr->ulPortIndex, + get_value_string(radio_std_vals, psr->ulRadioStandard), + psr->RxStats.ulRxByteCnt, psr->RxStats.ulRxOverflowCnt, + psr->RxStats.ulRxAverageBytePerSecond, + psr->RxStats.ulRxAveragePeriodUs, +#if OCTPHY_USE_FREQUENCY == 1 + psr->RxStats.Frequency.ulValue, +#else + psr->RxStats.ulFrequencyKhz, +#endif + psr->TxStats.ulTxByteCnt, psr->TxStats.ulTxUnderflowCnt, + psr->TxStats.ulTxAverageBytePerSecond, + psr->TxStats.ulTxAveragePeriodUs, +#if OCTPHY_USE_FREQUENCY == 1 + psr->TxStats.Frequency.ulValue); +#else + psr->TxStats.ulFrequencyKhz); +#endif + + get_cb_data = (struct octphy_hw_get_cb_data*) data; + get_cb_data->cb(resp,get_cb_data->data); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.10 */ +int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index, + struct octphy_hw_get_cb_data *cb_data) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *) msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_STATS_CID); + + psc->ulPortIndex = index; + psc->ulResetStatsFlag = cOCT_FALSE; + + mOCTVC1_HW_MSG_RF_PORT_STATS_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_port_stats_compl_cb, cb_data); +} + +static const struct value_string rx_gain_mode_vals[] = { + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_MGC, "Manual" }, + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_FAST_ATK, "Automatic (fast)" }, + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_SLOW_ATK, "Automatic (slow)" }, + { 0, NULL } +}; + +/* Chapter 12.13 */ +static int rf_ant_rx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *arc = + (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP_SWAP(arc); + + LOGP(DL1C, LOGL_INFO, "ANT-RX-CONFIG.resp(Port=%u, Ant=%u): %s, " + "Gain %d dB, GainCtrlMode=%s\n", + arc->ulPortIndex, arc->ulAntennaIndex, +#ifdef OCTPHY_USE_RX_CONFIG + arc->RxConfig.ulEnableFlag ? "Enabled" : "Disabled", + arc->RxConfig.lRxGaindB/512, + get_value_string(rx_gain_mode_vals, arc->RxConfig.ulRxGainMode)); +#else + arc->ulEnableFlag ? "Enabled" : "Disabled", + arc->lRxGaindB/512, + get_value_string(rx_gain_mode_vals, arc->ulRxGainMode)); +#endif + msgb_free(resp); + return 0; +} + +/* Chapter 12.13 */ +int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *) + msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CID); + + psc->ulPortIndex = port_idx; + psc->ulAntennaIndex = ant_idx; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_ant_rx_compl_cb, NULL); + +} + +/* Chapter 12.14 */ +static int rf_ant_tx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *atc = + (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP_SWAP(atc); + + LOGP(DL1C, LOGL_INFO, "ANT-TX-CONFIG.resp(Port=%u, Ant=%u): %s, " + "Gain %d dB\n", + atc->ulPortIndex, atc->ulAntennaIndex, +#ifdef OCTPHY_USE_TX_CONFIG + atc->TxConfig.ulEnableFlag? "Enabled" : "Disabled", + atc->TxConfig.lTxGaindB/512); +#else + atc->ulEnableFlag ? "Enabled" : "Disabled", + atc->lTxGaindB/512); + +#endif + msgb_free(resp); + return 0; +} + +/* Chapter 12.14 */ +int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *) + msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CID); + + psc->ulPortIndex = port_idx; + psc->ulAntennaIndex = ant_idx; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_ant_tx_compl_cb, NULL); + +} + +static const struct value_string clocksync_source_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ, "1 Hz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_10MHZ, "10 MHz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_30_72MHZ, "30.72 MHz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ_EXT, "1 Hz (ext)"}, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_NONE, "None" }, + { 0, NULL } +}; + +#if OCTPHY_USE_CLK_SOURCE_SELECTION == 1 +static const struct value_string clocksync_sel_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_AUTOSELECT, + "Autoselect" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_CONFIG_FILE, + "Config File" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_HOST_APPLICATION, + "Host Application" }, + { 0, NULL } +}; +#endif + +/* Chapter 12.15 */ +static int get_clock_sync_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *cir = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP_SWAP(cir); + + LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-INFO.resp Reference=%s ", + get_value_string(clocksync_source_vals, cir->ulClkSourceRef)); + +#if OCTPHY_USE_CLK_SOURCE_SELECTION == 1 + LOGPC(DL1C, LOGL_INFO, "Selection=%s)\n", + get_value_string(clocksync_sel_vals, cir->ulClkSourceSelection)); +#else + LOGPC(DL1C, LOGL_INFO, "Clock Drift= %u Us\n", + cir->ulMaxDriftDurationUs); +#endif + + msgb_free(resp); + return 0; +} + +/* Chapter 12.15 */ +int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *cic; + + cic = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *) + msgb_put(msg, sizeof(*cic)); + l1if_fill_msg_hdr(&cic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CID); + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD_SWAP(cic); + + return l1if_req_compl(fl1h, msg, get_clock_sync_compl_cb, NULL); +} + +/* Chapter 12.16 */ +static int get_clock_sync_stats_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + struct octphy_hw_get_cb_data *get_cb_data; + + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP_SWAP(csr); + + LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-STATS.resp"); + LOGPC(DL1C, LOGL_INFO, " State=%s,", + get_value_string(clocksync_state_vals, csr->ulState)); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR == 1 + LOGPC(DL1C, LOGL_INFO, " ClockError=%d,", csr->lClockError); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES == 1 + LOGPC(DL1C, LOGL_INFO, " DroppedCycles=%d,", csr->lDroppedCycles); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ == 1 + LOGPC(DL1C, LOGL_INFO, " PllFreqHz=%u,", csr->ulPllFreqHz); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ == 1 + LOGPC(DL1C, LOGL_INFO, " PllFract=%u,", csr->ulPllFractionalFreqHz); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT == 1 + LOGPC(DL1C, LOGL_INFO, " SlipCnt=%u,", csr->ulSlipCnt); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT == 1 + LOGPC(DL1C, LOGL_INFO, " SyncLosses=%u,", csr->ulSyncLossCnt); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT == 1 + LOGPC(DL1C, LOGL_INFO, " SyncLosses=%u,", csr->ulSyncLosseCnt); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE == 1 + LOGPC(DL1C, LOGL_INFO, " SourceState=%u,", csr->ulSourceState); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1 + LOGPC(DL1C, LOGL_INFO, " CLOCK-SYNC-MGR-STATS.resp State=%s,", + get_value_string(clocksync_dac_vals, csr->ulDacState)); +#endif + LOGPC(DL1C, LOGL_INFO, " LOCK-SYNC-MGR-USR-PROCESS.resp State=%s,", + get_value_string(usr_process_id, csr->ulOwnerProcessUid)); + LOGPC(DL1C, LOGL_INFO, " DacValue=%u,", csr->ulDacValue); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US == 1 + LOGPC(DL1C, LOGL_INFO, " DriftElapseTime=%u Us,", + csr->ulDriftElapseTimeUs); +#endif + LOGPC(DL1C, LOGL_INFO, "\n"); + + get_cb_data = (struct octphy_hw_get_cb_data*) data; + get_cb_data->cb(resp,get_cb_data->data); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.16 */ +int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h, + struct octphy_hw_get_cb_data *cb_data) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *csc; + + csc = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *) + msgb_put(msg, sizeof(*csc)); + l1if_fill_msg_hdr(&csc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CID); + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD_SWAP(csc); + + return l1if_req_compl(fl1h, msg, get_clock_sync_stats_cb, cb_data); +} + diff --git a/src/osmo-bts-octphy/octphy_hw_api.h b/src/osmo-bts-octphy/octphy_hw_api.h new file mode 100644 index 00000000..625fe864 --- /dev/null +++ b/src/osmo-bts-octphy/octphy_hw_api.h @@ -0,0 +1,84 @@ +#pragma once + +#include <stdint.h> +#include "l1_if.h" +#include <octphy/octvc1/hw/octvc1_hw_api.h> + +static const struct value_string radio_std_vals[] = { + { cOCTVC1_RADIO_STANDARD_ENUM_GSM, "GSM" }, + { cOCTVC1_RADIO_STANDARD_ENUM_UMTS, "UMTS" }, + { cOCTVC1_RADIO_STANDARD_ENUM_LTE, "LTE" }, + { cOCTVC1_RADIO_STANDARD_ENUM_INVALID, "INVALID" }, + { 0, NULL } +}; + +static const struct value_string clocksync_state_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, + "Uninitialized" }, +/* Note: Octasic renamed cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED to + * cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE. The following ifdef + * statement ensures that older headers still work. */ +#ifdef cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED, "Unused" }, +#else + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "Idle" }, +#endif + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, + "No External Clock" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "Locked" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED,"Unlocked" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "Error" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "Disabled" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOSS_EXT_CLOCK, + "Loss of Ext Clock" }, + { 0, NULL } +}; + +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1 +static const struct value_string clocksync_dac_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_UNUSED, "Unused" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_MASTER, "Master" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_SLAVE, "Slave" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_FREE_RUNNING, "Free_Run"}, + { 0, NULL } +}; +#endif + +static const struct value_string usr_process_id[] = { + { cOCTVC1_USER_ID_PROCESS_ENUM_INVALID, "Invalid" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_MAIN_APP, "MainApp" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_MAIN_ROUTER, "MainRouter" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DL_0, "DL"}, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULIM_0, "ULIM" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULOM_0, "ULOM" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_SCHED_0, "SCHED" }, +#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DECOMB + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DECOMB, "DECOMB"}, +#endif +#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULEQ + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULEQ, "ULEQ" }, +#endif +#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_TEST + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_TEST, "TEST"}, +#endif + { 0, NULL } +}; + +typedef void octphy_hw_get_cb(struct msgb *resp, void *data); + +struct octphy_hw_get_cb_data { + octphy_hw_get_cb* cb; + void *data; +}; + +int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h); +int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index); +int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index, + struct octphy_hw_get_cb_data *cb_data); +int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx); +int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx); +int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h); +int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h, + struct octphy_hw_get_cb_data *cb_data); diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c new file mode 100644 index 00000000..d250a957 --- /dev/null +++ b/src/osmo-bts-octphy/octphy_vty.c @@ -0,0 +1,466 @@ +/* VTY interface for osmo-bts OCTPHY integration */ + +/* (C) 2015-2016 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/macaddr.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/vty.h> + +#include "l1_if.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR + +#define OCT_STR "OCTPHY Um interface\n" + +static struct gsm_bts *vty_bts; + +/* configuration */ + +DEFUN(cfg_phy_hwaddr, cfg_phy_hwaddr_cmd, + "octphy hw-addr HWADDR", + OCT_STR "Configure the hardware addess of the OCTPHY\n" + "hardware address in aa:bb:cc:dd:ee:ff format\n") +{ + struct phy_link *plink = vty->index; + int rc; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + rc = osmo_macaddr_parse(plink->u.octphy.phy_addr.sll_addr, argv[0]); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_netdev, cfg_phy_netdev_cmd, + "octphy net-device NAME", + OCT_STR "Configure the hardware device towards the OCTPHY\n" + "Ethernet device name\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (plink->u.octphy.netdev_name) + talloc_free(plink->u.octphy.netdev_name); + plink->u.octphy.netdev_name = talloc_strdup(plink, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_rf_port_idx, cfg_phy_rf_port_idx_cmd, + "octphy rf-port-index <0-255>", + OCT_STR "Configure the RF Port for this TRX\n" + "RF Port Index\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.rf_port_index = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#if OCTPHY_USE_ANTENNA_ID == 1 +DEFUN(cfg_phy_rx_ant_id, cfg_phy_rx_ant_id_cmd, + "octphy rx-ant-id <0-1>", + OCT_STR "Configure the RX Antenna for this TRX\n" "RX Antenna Id\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.rx_ant_id = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_ant_id, cfg_phy_tx_ant_id_cmd, + "octphy tx-ant-id <0-1>", + OCT_STR "Configure the TX Antenna for this TRX\n" "TX Antenna Id\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.tx_ant_id = atoi(argv[0]); + + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_phy_rx_gain_db, cfg_phy_rx_gain_db_cmd, + "octphy rx-gain <0-73>", + OCT_STR "Configure the Rx Gain in dB\n" + "Rx gain in dB\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.rx_gain_db = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_atten_db, cfg_phy_tx_atten_db_cmd, + "octphy tx-attenuation (oml|<0-359>)", + OCT_STR "Set attenuation on transmitted RF\n" + "Use tx-attenuation according to OML instructions from BSC\n" + "Fixed tx-attenuation in quarter-dB\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(argv[0], "oml") == 0) { + plink->u.octphy.tx_atten_flag = false; + } else { + plink->u.octphy.tx_atten_db = atoi(argv[0]); + plink->u.octphy.tx_atten_flag = true; + } + + return CMD_SUCCESS; +} + +#if OCTPHY_USE_16X_OVERSAMPLING == 1 +DEFUN(cfg_phy_over_sample_16x, cfg_phy_over_sample_16x_cmd, + "octphy over-sample-16x <0-1>", + OCT_STR "Configure 16x over sampling rate for this TRX (restart required)\n" + "Over Sampling Rate\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if(atoi(argv[0])) + plink->u.octphy.over_sample_16x = true; + else + plink->u.octphy.over_sample_16x = false; + + return CMD_SUCCESS; +} +#endif + +void show_rf_port_stats_cb(struct msgb *resp, void *data) +{ + struct vty *vty = (struct vty*) data; + tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr; + + if (sizeof(tOCTVC1_HW_MSG_RF_PORT_STATS_RSP) != msgb_l2len(resp)) { + vty_out(vty, + "invalid msgb size (%d bytes, expected %zu bytes)%s", + msgb_l2len(resp), + sizeof(tOCTVC1_HW_MSG_RF_PORT_STATS_RSP), VTY_NEWLINE); + return; + } + + psr = (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) msgb_l2(resp); + + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "RF-PORT-STATS:%s", VTY_NEWLINE); + vty_out(vty, "Idx=%d%s", psr->ulPortIndex, VTY_NEWLINE); + vty_out(vty, "RadioStandard=%s%s", + get_value_string(radio_std_vals, psr->ulRadioStandard), + VTY_NEWLINE); + vty_out(vty, "Rx Bytes=%u%s", psr->RxStats.ulRxByteCnt, VTY_NEWLINE); + vty_out(vty, "Rx Overflow=%u%s", psr->RxStats.ulRxOverflowCnt, + VTY_NEWLINE); + vty_out(vty, "Rx AvgBps=%u%s", psr->RxStats.ulRxAverageBytePerSecond, + VTY_NEWLINE); + vty_out(vty, "Rx Period=%u%s", psr->RxStats.ulRxAveragePeriodUs, + VTY_NEWLINE); +#if OCTPHY_USE_FREQUENCY == 1 + vty_out(vty, "Rx Freq=%u%s", psr->RxStats.Frequency.ulValue, VTY_NEWLINE); +#else + vty_out(vty, "Rx Freq=%u%s", psr->RxStats.ulFrequencyKhz, VTY_NEWLINE); +#endif + vty_out(vty, "Tx Bytes=%u%s", psr->TxStats.ulTxByteCnt, VTY_NEWLINE); + vty_out(vty, "Tx Underflow=%u%s", psr->TxStats.ulTxUnderflowCnt, + VTY_NEWLINE); + vty_out(vty, "Tx AvgBps=%u%s", psr->TxStats.ulTxAverageBytePerSecond, + VTY_NEWLINE); + vty_out(vty, "Tx Period=%u%s", psr->TxStats.ulTxAveragePeriodUs, + VTY_NEWLINE); +#if OCTPHY_USE_FREQUENCY == 1 + vty_out(vty, "Tx Freq=%u%s", psr->TxStats.Frequency.ulValue, VTY_NEWLINE); +#else + vty_out(vty, "Tx Freq=%u%s", psr->TxStats.ulFrequencyKhz, VTY_NEWLINE); +#endif +} + +DEFUN(show_rf_port_stats, show_rf_port_stats_cmd, + "show phy <0-255> rf-port-stats <0-1>", + "Show statistics for the RF Port\n" + "RF Port Number\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink = phy_link_by_num(phy_nr); + static struct octphy_hw_get_cb_data cb_data; + + cb_data.cb = show_rf_port_stats_cb; + cb_data.data = vty; + + octphy_hw_get_rf_port_stats(plink->u.octphy.hdl, atoi(argv[1]), + &cb_data); + + return CMD_SUCCESS; +} + +void show_clk_sync_stats_cb(struct msgb *resp, void *data) +{ + struct vty *vty = (struct vty*) data; + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr; + + if (sizeof(tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP) != + msgb_l2len(resp)) { + vty_out(vty, + "invalid msgb size (%d bytes, expected %zu bytes)%s", + msgb_l2len(resp), + sizeof(tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP), + VTY_NEWLINE); + return; + } + + csr = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) msgb_l2(resp); + + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "CLOCK-SYNC-MGR-STATS:%s", VTY_NEWLINE); + vty_out(vty, "State=%s%s", + get_value_string(clocksync_state_vals, csr->ulState), + VTY_NEWLINE); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR == 1 + vty_out(vty, "ClockError=%d%s", csr->lClockError, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES == 1 + vty_out(vty, "DroppedCycles=%d%s", csr->lDroppedCycles, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ == 1 + vty_out(vty, "PllFreqHz=%u%s", csr->ulPllFreqHz, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ == 1 + vty_out(vty, "PllFract=%u%s", csr->ulPllFractionalFreqHz, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT == 1 + vty_out(vty, "SlipCnt=%u%s", csr->ulSlipCnt, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT == 1 + vty_out(vty, "SyncLosses=%u%s", csr->ulSyncLossCnt, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT == 1 + vty_out(vty, "SyncLosses=%u%s", csr->ulSyncLosseCnt, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE == 1 + vty_out(vty, "SourceState=%u%s", csr->ulSourceState, VTY_NEWLINE); +#endif + vty_out(vty, "DacValue=%u%s", csr->ulDacValue, VTY_NEWLINE); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1 + vty_out(vty, "CLOCK-SYNC-MGR-STATS.resp State=%s%s", + get_value_string(clocksync_dac_vals, csr->ulDacState), + VTY_NEWLINE); +#endif + vty_out(vty, "LOCK-SYNC-MGR-USR-PROCESS.resp State=%s%s", + get_value_string(usr_process_id, csr->ulOwnerProcessUid), + VTY_NEWLINE); + vty_out(vty, "DacValue=%u%s", csr->ulDacValue, VTY_NEWLINE); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US == 1 + vty_out(vty, "DriftElapseTime=%u Us%s", csr->ulDriftElapseTimeUs, + VTY_NEWLINE); +#endif +} + +DEFUN(show_clk_sync_stats, show_clk_sync_stats_cmd, + "show phy <0-255> clk-sync-stats", + "Obtain statistics for the Clock Sync Manager\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink = phy_link_by_num(phy_nr); + static struct octphy_hw_get_cb_data cb_data; + + cb_data.cb = show_clk_sync_stats_cb; + cb_data.data = vty; + + octphy_hw_get_clock_sync_stats(plink->u.octphy.hdl, &cb_data); + return CMD_SUCCESS; +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ + if (plink->u.octphy.netdev_name) + vty_out(vty, " octphy net-device %s%s", + plink->u.octphy.netdev_name, VTY_NEWLINE); + + vty_out(vty, " octphy hw-addr %02x:%02x:%02x:%02x:%02x:%02x%s", + plink->u.octphy.phy_addr.sll_addr[0], + plink->u.octphy.phy_addr.sll_addr[1], + plink->u.octphy.phy_addr.sll_addr[2], + plink->u.octphy.phy_addr.sll_addr[3], + plink->u.octphy.phy_addr.sll_addr[4], + plink->u.octphy.phy_addr.sll_addr[5], + VTY_NEWLINE); + vty_out(vty, " octphy rx-gain %u%s", plink->u.octphy.rx_gain_db, + VTY_NEWLINE); + + if (plink->u.octphy.tx_atten_flag) { + vty_out(vty, " octphy tx-attenuation %u%s", + plink->u.octphy.tx_atten_db, VTY_NEWLINE); + } else + vty_out(vty, " octphy tx-attenuation oml%s", VTY_NEWLINE); + + vty_out(vty, " octphy rf-port-index %u%s", plink->u.octphy.rf_port_index, + VTY_NEWLINE); + +#if OCTPHY_USE_ANTENNA_ID == 1 + vty_out(vty, " octphy tx-ant-id %u%s", plink->u.octphy.tx_ant_id, + VTY_NEWLINE); + + vty_out(vty, " octphy rx-ant-id %u%s", plink->u.octphy.rx_ant_id, + VTY_NEWLINE); +#endif +#if OCTPHY_USE_16X_OVERSAMPLING == 1 + vty_out(vty, " octphy over-sample-16x %u%s", plink->u.octphy.over_sample_16x, + VTY_NEWLINE); +#endif +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ +} + +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) +{ +} + +DEFUN(show_sys_info, show_sys_info_cmd, + "show phy <0-255> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct octphy_hdl *fl1h; + + if (!plink) { + vty_out(vty, "Cannot find PHY number %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = plink->u.octphy.hdl; + + vty_out(vty, "System Platform: '%s', Version: '%s'%s", + fl1h->info.system.platform, fl1h->info.system.version, + VTY_NEWLINE); + vty_out(vty, "Application Name: '%s', Description: '%s', Version: '%s'%s", + fl1h->info.app.name, fl1h->info.app.description, + fl1h->info.app.version, VTY_NEWLINE); + + return CMD_SUCCESS; +} + + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element(PHY_NODE, &cfg_phy_hwaddr_cmd); + install_element(PHY_NODE, &cfg_phy_netdev_cmd); + install_element(PHY_NODE, &cfg_phy_rf_port_idx_cmd); +#if OCTPHY_USE_ANTENNA_ID == 1 + install_element(PHY_NODE, &cfg_phy_rx_ant_id_cmd); + install_element(PHY_NODE, &cfg_phy_tx_ant_id_cmd); +#endif + install_element(PHY_NODE, &cfg_phy_rx_gain_db_cmd); + install_element(PHY_NODE, &cfg_phy_tx_atten_db_cmd); +#if OCTPHY_USE_16X_OVERSAMPLING == 1 + install_element(PHY_NODE, &cfg_phy_over_sample_16x_cmd); +#endif + install_element_ve(&show_rf_port_stats_cmd); + install_element_ve(&show_clk_sync_stats_cmd); + install_element_ve(&show_sys_info_cmd); + + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-octphy/octpkt.c b/src/osmo-bts-octphy/octpkt.c new file mode 100644 index 00000000..d96d93d8 --- /dev/null +++ b/src/osmo-bts-octphy/octpkt.c @@ -0,0 +1,158 @@ +/* Utility routines for dealing with OCTPKT/OCTVC1 in msgb */ + +/* Copyright (c) 2015 Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <errno.h> +#include <unistd.h> + +#include <arpa/inet.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> + +#include <osmo-bts/gsm_data.h> + +#include <octphy/octpkt/octpkt_hdr.h> +#include <octphy/octpkt/octpkt_hdr_swap.h> +#include <octphy/octvc1/octvocnet_pkt.h> +#include <octphy/octvc1/octvc1_msg.h> +#include <octphy/octvc1/octvc1_msg_swap.h> +#include <octphy/octvc1/gsm/octvc1_gsm_api.h> + +#include "l1_if.h" +#include "octpkt.h" + +/* push a common header (1 dword) to the start of a msgb */ +void octpkt_push_common_hdr(struct msgb *msg, uint8_t format, + uint8_t trace, uint32_t ptype) +{ + uint32_t ch; + uint32_t *chptr; + uint32_t tot_len = msgb_length(msg) + sizeof(ch); + + ch = ((format & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_OFFSET) | + ((trace & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_OFFSET) | + ((ptype & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_OFFSET) | + (tot_len & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_LENGTH_BIT_MASK); + + chptr = (uint32_t *) msgb_push(msg, sizeof(ch)); + *chptr = htonl(ch); +} + +/* push a control header (3 dwords) to the start of a msgb. This format + * is used for command and response packets */ +void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id, + uint32_t src_fifo_id, uint32_t socket_id) +{ + tOCTVOCNET_PKT_CTL_HEADER *ch; + + ch = (tOCTVOCNET_PKT_CTL_HEADER *) msgb_push(msg, sizeof(*ch)); + + ch->ulDestFifoId = htonl(dest_fifo_id); + ch->ulSourceFifoId = htonl(src_fifo_id); + ch->ulSocketId = htonl(socket_id); +} + +/* common msg_header shared by all control messages. host byte order! */ +void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, + uint32_t sess_id, uint32_t trans_id, + uint32_t user_info, uint32_t msg_type, + uint32_t flags, uint32_t api_cmd) +{ + uint32_t type_r_cmdid; + type_r_cmdid = ((msg_type & cOCTVC1_MSG_TYPE_BIT_MASK) + << cOCTVC1_MSG_TYPE_BIT_OFFSET) | + ((api_cmd & cOCTVC1_MSG_ID_BIT_MASK) + << cOCTVC1_MSG_ID_BIT_OFFSET); + /* Resync? Flags? */ + + mh->ulLength = len; + mh->ulTransactionId = trans_id; + mh->ul_Type_R_CmdId = type_r_cmdid; + mh->ulSessionId = sess_id; + mh->ulReturnCode = 0; + mh->ulUserInfo = user_info; +} + +#include <sys/ioctl.h> +#include <net/if.h> +#include <sys/socket.h> +#include <linux/if_packet.h> +#include <net/ethernet.h> + +/*! \brief Initialize a packet socket + * \param[in] tye Socket type like SOCK_RAW or SOCK_DGRAM + * \param[in] proto The link-layer protocol in network byte order + * \param[in] bind_dev The name of the interface to bind to (if any) + * \param[in] flags flags like \ref OSMO_SOCK_F_BIND + * + * This function creates a new packet socket of \a type and \a proto + * and optionally bnds to it, if stated in the \a flags parameter. + */ +int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev, + unsigned int flags) +{ + int sfd, rc, on = 1; + + if (flags & OSMO_SOCK_F_CONNECT) + return -EINVAL; + + sfd = socket(AF_PACKET, type, proto); + if (sfd < 0) + return -1; + + if (flags & OSMO_SOCK_F_NONBLOCK) { + if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) { + perror("cannot set this socket unblocking"); + close(sfd); + return -EINVAL; + } + } + + if (bind_dev) { + struct sockaddr_ll sa; + struct ifreq ifr; + + /* resolve the string device name to an ifindex */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, bind_dev, sizeof(ifr.ifr_name)); + rc = ioctl(sfd, SIOCGIFINDEX, &ifr); + if (rc < 0) + goto err; + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(proto); + sa.sll_ifindex = ifr.ifr_ifindex; + /* according to the packet(7) man page, bind() will only + * use sll_protocol nad sll_ifindex */ + rc = bind(sfd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) + goto err; + } + + return sfd; +err: + close(sfd); + return -1; +} diff --git a/src/osmo-bts-octphy/octpkt.h b/src/osmo-bts-octphy/octpkt.h new file mode 100644 index 00000000..fcffec02 --- /dev/null +++ b/src/osmo-bts-octphy/octpkt.h @@ -0,0 +1,22 @@ +#pragma once +#include <stdint.h> + +/* push a common header (1 dword) to the start of a msgb */ +void octpkt_push_common_hdr(struct msgb *msg, uint8_t format, + uint8_t trace, uint32_t ptype); + +/* common msg_header shared by all control messages */ +void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, + uint32_t sess_id, uint32_t trans_id, + uint32_t user_info, uint32_t msg_type, + uint32_t flags, uint32_t api_cmd); + +/* push a control header (3 dwords) to the start of a msgb. This format + * is used for command and response packets */ +void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id, + uint32_t src_fifo_id, uint32_t socket_id); + +int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev, + unsigned int flags); + +int tx_trx_open(struct gsm_bts_trx *trx); diff --git a/src/osmo-bts-omldummy/Makefile.am b/src/osmo-bts-omldummy/Makefile.am new file mode 100644 index 00000000..5a4ce7c5 --- /dev/null +++ b/src/osmo-bts-omldummy/Makefile.am @@ -0,0 +1,8 @@ +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl + +bin_PROGRAMS = osmo-bts-omldummy + +osmo_bts_omldummy_SOURCES = main.c bts_model.c +osmo_bts_omldummy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) diff --git a/src/osmo-bts-omldummy/bts_model.c b/src/osmo-bts-omldummy/bts_model.c new file mode 100644 index 00000000..c0114015 --- /dev/null +++ b/src/osmo-bts-omldummy/bts_model.c @@ -0,0 +1,222 @@ +/* (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/codec/codec.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> + +/* TODO: check if dummy method is sufficient, else implement */ +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +/* TODO: check if dummy method is sufficient, else implement */ +int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr, + int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + 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; +} + +static uint8_t vbts_set_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + + llist_for_each_entry(trx, &bts->trx_list, list) { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + + for (tn = 0; tn < TRX_NR_TS; tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + + /* report availability of trx to the bts. this will trigger the rsl connection */ + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + } + return 0; +} + +static uint8_t vbts_set_trx(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts) +{ + return 0; +} + +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 = vbts_set_bts(obj); + break; + case NM_MT_SET_RADIO_ATTR: + cause = vbts_set_trx(obj); + break; + case NM_MT_SET_CHAN_ATTR: + cause = vbts_set_ts(obj); + break; + } + return oml_fom_ack_nack(msg, cause); +} + +/* MO: TS 12.21 Managed Object */ +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: + case NM_OC_CHANNEL: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_BTS: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + 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) +{ + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + bts->variant = BTS_OSMO_OMLDUMMY; + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + return 0; +} + +void bts_model_print_help() +{ +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + bts_shutdown(bts, "Abis close"); +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + return -ENOTSUP; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + return; +} + +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + return 0; +} diff --git a/src/osmo-bts-omldummy/main.c b/src/osmo-bts-omldummy/main.c new file mode 100644 index 00000000..3f1d58c5 --- /dev/null +++ b/src/osmo-bts-omldummy/main.c @@ -0,0 +1,53 @@ + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> + + +int main(int argc, char **argv) +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct e1inp_line *line; + int i; + + char *dst_host = argv[1]; + int site_id = atoi(argv[2]); + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 10*1024); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) + exit(1); + bts->ip_access.site_id = site_id; + bts->ip_access.bts_id = 0; + + /* Additional TRXs */ + for (i = 1; i < 8; i++) { + trx = gsm_bts_trx_alloc(bts); + if (!trx) + exit(1); + } + + if (bts_init(bts) < 0) + exit(1); + //btsb = bts_role_bts(bts); + abis_init(bts); + + + line = abis_open(bts, dst_host, "OMLdummy"); + if (!line) + exit(2); + + while (1) { + osmo_select_main(0); + } + + return EXIT_SUCCESS; +} diff --git a/src/osmo-bts-omldummy/respawn.sh b/src/osmo-bts-omldummy/respawn.sh new file mode 100755 index 00000000..b025d433 --- /dev/null +++ b/src/osmo-bts-omldummy/respawn.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +while [ -f /etc/passwd ]; do + ./osmo-bts-omldummy $* + sleep 1 +done diff --git a/src/osmo-bts-sysmo/Makefile.am b/src/osmo-bts-sysmo/Makefile.am new file mode 100644 index 00000000..4901ea3c --- /dev/null +++ b/src/osmo-bts-sysmo/Makefile.am @@ -0,0 +1,43 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(SYSMOBTS_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) + +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 \ + misc/sysmobts-layer1.h \ + l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h + +bin_PROGRAMS = osmo-bts-sysmo osmo-bts-sysmo-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 + +osmo_bts_sysmo_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_sysmo_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +osmo_bts_sysmo_remote_SOURCES = $(COMMON_SOURCES) l1_transp_fwd.c +osmo_bts_sysmo_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) + +if ENABLE_SYSMOBTS_CALIB +bin_PROGRAMS = sysmobts-calib + +sysmobts_calib_SOURCES = misc/sysmobts-calib.c misc/sysmobts-layer1.c +sysmobts_calib_LDADD = -lrt $(COMMON_LDADD) +endif + +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) $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c +sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-sysmo/calib_file.c b/src/osmo-bts-sysmo/calib_file.c new file mode 100644 index 00000000..2f723dd0 --- /dev/null +++ b/src/osmo-bts-sysmo/calib_file.c @@ -0,0 +1,475 @@ +/* sysmocom femtobts L1 calibration file routines*/ + +/* (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1const.h> + +#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, + + }, +}; + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) +static const unsigned int arrsize_by_band[] = { + [GsmL1_FreqBand_850] = 124, + [GsmL1_FreqBand_900] = 194, + [GsmL1_FreqBand_1800] = 374, + [GsmL1_FreqBand_1900] = 299 +}; + +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; +} +#endif /* API 2.4.0 */ + +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]; + + 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) + int i; + 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) +{ +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) + eeprom_Error_t eerr; + int i; + 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]; + } +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0) + rx->u8DspMajVer = rx_cal.u8DspMajVer; + rx->u8DspMinVer = rx_cal.u8DspMinVer; + rx->u8FpgaMajVer = rx_cal.u8FpgaMajVer; + rx->u8FpgaMinVer = rx_cal.u8FpgaMinVer; +#endif + } 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]; +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0) + tx->u8DspMajVer = tx_cal.u8DspMajVer; + tx->u8DspMinVer = tx_cal.u8DspMinVer; + tx->u8FpgaMajVer = tx_cal.u8FpgaMajVer; + tx->u8FpgaMinVer = tx_cal.u8FpgaMinVer; +#endif + } +#endif + + return 0; +} + +/* 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; +} + +/* 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; + char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path; + int rc; + + msg = sysp_msgb_alloc(); + + if (calib_path) + rc = calib_file_read(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; + char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path; + + LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded (src: %s)\n", + calib_files[st->last_file_idx].fname, + 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 diff --git a/src/osmo-bts-sysmo/calib_fixup.c b/src/osmo-bts-sysmo/calib_fixup.c new file mode 100644 index 00000000..29dd34dd --- /dev/null +++ b/src/osmo-bts-sysmo/calib_fixup.c @@ -0,0 +1,101 @@ +/* AUTOGENERATED, DO NOT EDIT */ + +#include <stdint.h> + +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 }, +}; diff --git a/src/osmo-bts-sysmo/eeprom.c b/src/osmo-bts-sysmo/eeprom.c new file mode 100644 index 00000000..472b78e2 --- /dev/null +++ b/src/osmo-bts-sysmo/eeprom.c @@ -0,0 +1,1804 @@ +// $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 <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> + +#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 header version + */ +#define EEPROM_HDR_V1 1 +#define EEPROM_HDR_V2 2 + +/** + * 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_ASSY = 0x3400 ///< Assembly information +} eeprom_SID_t; + +/**************************************************************************** + * Private types * + ****************************************************************************/ + +/** + * TX calibration table (common part) V1 + */ +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) V1 + */ +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; + +/** + * TX calibration table (common part) V2 + */ +typedef struct +{ + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + 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_CfgTxCalV2_t; + +/** + * RX calibration table (common part) V2 + */ +typedef struct +{ + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer ; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + 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_CfgRxCalV2_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; + + /** EEPROM Format V2 */ + 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_CfgTxCalV2_t gsm850TxCalV2; + uint16_t __gsm850TxCalMemV2[124]; + + /** GSM-850 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t gsm850RxuCalV2; + uint16_t __gsm850RxuCalMemV2[124]; + + /** GSM-850 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t gsm850RxdCalV2; + uint16_t __gsm850RxdCalMemV2[124]; + + /** GSM-900 TX Calibration Table */ + eeprom_CfgTxCalV2_t gsm900TxCalV2; + uint16_t __gsm900TxCalMemV2[194]; + + /** GSM-900 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t gsm900RxuCalV2; + uint16_t __gsm900RxuCalMemV2[194]; + + /** GSM-900 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t gsm900RxdCalV2; + uint16_t __gsm900RxdCalMemV2[194]; + + /** DCS-1800 TX Calibration Table */ + eeprom_CfgTxCalV2_t dcs1800TxCalV2; + uint16_t __dcs1800TxCalMemV2[374]; + + /** DCS-1800 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t dcs1800RxuCalV2; + uint16_t __dcs1800RxuCalMemV2[374]; + + /** DCS-1800 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t dcs1800RxdCalV2; + uint16_t __dcs1800RxdCalMemV2[374]; + + /** PCS-1900 TX Calibration Table */ + eeprom_CfgTxCalV2_t pcs1900TxCalV2; + uint16_t __pcs1900TxCalMemV2[299]; + + /** PCS-1900 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t pcs1900RxuCalV2; + uint16_t __pcs1900RxuCalMemV2[299]; + + /** PCS-1900 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t pcs1900RxdCalV2; + uint16_t __pcs1900RxdCalMemV2[299]; + + /** Assembly information */ + struct + { + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + char szSn[16]; ///< System serial number + char szPartNum[20]; ///< System part number + uint8_t u8TsID ; ///< Test station ID + uint8_t u8TstVer ; ///< Test version + uint8_t u8PaType; ///< PA type (0: None, 1-254 supported, 255 ; Unknown) + uint8_t u8PaBand; ///< PA GSM band (0: Unknown, 1: 850 MHz, 2: 900 MHz, 4: 1800 MHz, 8: 1900 MHz) + uint8_t u8PaMajVer; ///< PA major version + uint8_t u8PaMinVer; ///< PA minor version + } __attribute__((packed)) assyInfo; + } __attribute__((packed)) v2; + } __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 = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) ) + { + 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 EEPROM_HDR_V1: + case EEPROM_HDR_V2: + { + // Get a copy of the EEPROM section + err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), 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 = EEPROM_HDR_V2; + + // 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 EEPROM_HDR_V2: + { + 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 + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), 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 EEPROM_HDR_V1: + case EEPROM_HDR_V2: + { + // Get a copy of the EEPROM section + err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), 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 = EEPROM_HDR_V2; + + // 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 EEPROM_HDR_V2: + { + 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 + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), 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; + eeprom_CfgTxCalV2_t *pCfgTxCalV2 = 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 EEPROM_HDR_V1: + { + 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; + } + + //DSP firmware version + pTxCal->u8DspMajVer = 0; + pTxCal->u8DspMinVer = 0; + + //FPGA firmware version + pTxCal->u8FpgaMajVer = 0; + pTxCal->u8FpgaMinVer = 0; + + break; + } + + case EEPROM_HDR_V2: + { + + switch ( iBand ) + { + case 0: + nArfcn = 124; + sId = EEPROM_SID_GSM850_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.gsm850TxCalV2; + size = sizeof(ee->cfg.v2.gsm850TxCalV2) + sizeof(ee->cfg.v2.__gsm850TxCalMemV2); + break; + case 1: + nArfcn = 194; + sId = EEPROM_SID_GSM900_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.gsm900TxCalV2; + size = sizeof(ee->cfg.v2.gsm900TxCalV2) + sizeof(ee->cfg.v2.__gsm900TxCalMemV2); + break; + case 2: + nArfcn = 374; + sId = EEPROM_SID_DCS1800_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.dcs1800TxCalV2; + size = sizeof(ee->cfg.v2.dcs1800TxCalV2) + sizeof(ee->cfg.v2.__dcs1800TxCalMemV2); + break; + case 3: + nArfcn = 299; + sId = EEPROM_SID_PCS1900_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.pcs1900TxCalV2; + size = sizeof(ee->cfg.v2.pcs1900TxCalV2) + sizeof(ee->cfg.v2.__pcs1900TxCalMemV2); + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + + // Validate the ID + if ( pCfgTxCalV2->u16SectionID != sId ) + { + PERROR( "Uninitialised data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&pCfgTxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCalV2->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)pCfgTxCalV2->sfixTxGainGmsk[i] * 0.03125f; + } + pTxCal->fTx8PskCorr = (float)pCfgTxCalV2->sfixTx8PskCorr * 0.001953125f; + for ( i = 0; i < 31; i++ ) + { + pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCalV2->sfixTxExtAttCorr[i] * 0.001953125f; + } + for ( i = 0; i < nArfcn; i++ ) + { + pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCalV2->sfixTxRollOffCorr[i] * 0.001953125f; + } + + //DSP firmware version + pTxCal->u8DspMajVer = pCfgTxCalV2->u8DspMajVer; + pTxCal->u8DspMinVer = pCfgTxCalV2->u8DspMinVer; + + //FPGA firmware version + pTxCal->u8FpgaMajVer = pCfgTxCalV2->u8FpgaMajVer; + pTxCal->u8FpgaMinVer = pCfgTxCalV2->u8FpgaMinVer; + + 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_CfgTxCalV2_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 = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V2: + { + int32_t fixVal; + + switch ( iBand ) + { + case 0: + nArfcn = 124; + sId = EEPROM_SID_GSM850_TXCAL; + pCfgTxCal = &ee.cfg.v2.gsm850TxCalV2; + size = sizeof(ee.cfg.v2.gsm850TxCalV2) + sizeof(ee.cfg.v2.__gsm850TxCalMemV2); + break; + case 1: + nArfcn = 194; + sId = EEPROM_SID_GSM900_TXCAL; + pCfgTxCal = &ee.cfg.v2.gsm900TxCalV2; + size = sizeof(ee.cfg.v2.gsm900TxCalV2) + sizeof(ee.cfg.v2.__gsm900TxCalMemV2); + break; + case 2: + nArfcn = 374; + sId = EEPROM_SID_DCS1800_TXCAL; + pCfgTxCal = &ee.cfg.v2.dcs1800TxCalV2; + size = sizeof(ee.cfg.v2.dcs1800TxCalV2) + sizeof(ee.cfg.v2.__dcs1800TxCalMemV2); + break; + case 3: + nArfcn = 299; + sId = EEPROM_SID_PCS1900_TXCAL; + pCfgTxCal = &ee.cfg.v2.pcs1900TxCalV2; + size = sizeof(ee.cfg.v2.pcs1900TxCalV2) + sizeof(ee.cfg.v2.__pcs1900TxCalMemV2); + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + pCfgTxCal->u16SectionID = sId; + pCfgTxCal->u16Crc = 0; + pCfgTxCal->u32Time = time(NULL); + + //DSP firmware version + pCfgTxCal->u8DspMajVer = pTxCal->u8DspMajVer; + pCfgTxCal->u8DspMinVer = pTxCal->u8DspMinVer; + + //FPGA firmware version + pCfgTxCal->u8FpgaMajVer = pTxCal->u8FpgaMajVer; + pCfgTxCal->u8FpgaMinVer = pTxCal->u8FpgaMinVer; + + // 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 + ((uint8_t*)pCfgTxCal - (uint8_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; + eeprom_CfgRxCalV2_t *pCfgRxCalV2 = NULL; + + + if (!ee) + { + PERROR( "Reading cached content failed.\n" ); + return EEPROM_ERR_DEVICE; + } + + switch ( ee->hdr.u16Version ) + { + case EEPROM_HDR_V1: + { + 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; + } + + //DSP firmware version + pRxCal->u8DspMajVer = 0; + pRxCal->u8DspMinVer = 0; + + //FPGA firmware version + pRxCal->u8FpgaMajVer = 0; + pRxCal->u8FpgaMinVer = 0; + + break; + } + + case EEPROM_HDR_V2: + { + switch ( iBand ) + { + case 0: + nArfcn = 124; + if ( iUplink ) + { + sId = EEPROM_SID_GSM850_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm850RxuCalV2; + size = sizeof(ee->cfg.v2.gsm850RxuCalV2) + sizeof(ee->cfg.v2.__gsm850RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM850_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm850RxdCalV2; + size = sizeof(ee->cfg.v2.gsm850RxdCalV2) + sizeof(ee->cfg.v2.__gsm850RxdCalMemV2); + } + break; + case 1: + nArfcn = 194; + if ( iUplink ) + { + sId = EEPROM_SID_GSM900_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm900RxuCalV2; + size = sizeof(ee->cfg.v2.gsm900RxuCalV2) + sizeof(ee->cfg.v2.__gsm900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM900_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm900RxdCalV2; + size = sizeof(ee->cfg.v2.gsm900RxdCalV2) + sizeof(ee->cfg.v2.__gsm900RxdCalMemV2); + } + break; + case 2: + nArfcn = 374; + if ( iUplink ) + { + sId = EEPROM_SID_DCS1800_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxuCalV2; + size = sizeof(ee->cfg.v2.dcs1800RxuCalV2) + sizeof(ee->cfg.v2.__dcs1800RxuCalMemV2); + } + else + { + sId = EEPROM_SID_DCS1800_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxdCalV2; + size = sizeof(ee->cfg.v2.dcs1800RxdCalV2) + sizeof(ee->cfg.v2.__dcs1800RxdCalMemV2); + } + break; + case 3: + nArfcn = 299; + if ( iUplink ) + { + sId = EEPROM_SID_PCS1900_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxuCalV2; + size = sizeof(ee->cfg.v2.pcs1900RxuCalV2) + sizeof(ee->cfg.v2.__pcs1900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_PCS1900_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxdCalV2; + size = sizeof(ee->cfg.v2.pcs1900RxdCalV2) + sizeof(ee->cfg.v2.__pcs1900RxdCalMemV2); + } + break; + + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + // Validate the ID + if ( pCfgRxCalV2->u16SectionID != sId ) + { + PERROR( "Uninitialized data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&pCfgRxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCalV2->u16Crc ) + { + PERROR( "Parity error - Band %d\n", iBand ); + return EEPROM_ERR_PARITY; + } + + // Expand the IQ imbalance mode (0:off, 1:on, 2:auto) + pRxCal->u8IqImbalMode = pCfgRxCalV2->u16IqImbalMode; + + // Expand the IQ imbalance compensation + for ( i = 0; i < 4; i++ ) + { + pRxCal->u16IqImbalCorr[i] = pCfgRxCalV2->u16IqImbalCorr[i]; + } + + // Expand the External RX gain + pRxCal->fExtRxGain = (float)pCfgRxCalV2->sfixExtRxGain * 0.001953125; + + // Expand the Mixer gain error compensation + pRxCal->fRxMixGainCorr = (float)pCfgRxCalV2->sfixRxMixGainCorr * 0.001953125; + + // 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)pCfgRxCalV2->sfixRxLnaGainCorr[i] * 0.001953125; + } + + // Expand the Frequency roll-off compensation + for ( i = 0; i < nArfcn; i++ ) + { + pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCalV2->sfixRxRollOffCorr[i] * 0.001953125; + } + + //DSP firmware version + pRxCal->u8DspMajVer = pCfgRxCalV2->u8DspMajVer; + pRxCal->u8DspMinVer = pCfgRxCalV2->u8DspMinVer; + + //FPGA firmware version + pRxCal->u8FpgaMajVer = pCfgRxCalV2->u8FpgaMajVer; + pRxCal->u8FpgaMinVer = pCfgRxCalV2->u8FpgaMinVer; + + 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_CfgRxCalV2_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 = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V2: + { + int32_t fixVal; + + switch ( iBand ) + { + case 0: + nArfcn = 124; + if ( iUplink ) + { + sId = EEPROM_SID_GSM850_RXUCAL; + pCfgRxCal = &ee.cfg.v2.gsm850RxuCalV2; + size = sizeof(ee.cfg.v2.gsm850RxuCalV2) + sizeof(ee.cfg.v2.__gsm850RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM850_RXDCAL; + pCfgRxCal = &ee.cfg.v2.gsm850RxdCalV2; + size = sizeof(ee.cfg.v2.gsm850RxdCalV2) + sizeof(ee.cfg.v2.__gsm850RxdCalMemV2); + } + break; + case 1: + nArfcn = 194; + if ( iUplink ) + { + sId = EEPROM_SID_GSM900_RXUCAL; + pCfgRxCal = &ee.cfg.v2.gsm900RxuCalV2; + size = sizeof(ee.cfg.v2.gsm900RxuCalV2) + sizeof(ee.cfg.v2.__gsm900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM900_RXDCAL; + pCfgRxCal = &ee.cfg.v2.gsm900RxdCalV2; + size = sizeof(ee.cfg.v2.gsm900RxdCalV2) + sizeof(ee.cfg.v2.__gsm900RxdCalMemV2); + } + break; + case 2: + nArfcn = 374; + if ( iUplink ) + { + sId = EEPROM_SID_DCS1800_RXUCAL; + pCfgRxCal = &ee.cfg.v2.dcs1800RxuCalV2; + size = sizeof(ee.cfg.v2.dcs1800RxuCalV2) + sizeof(ee.cfg.v2.__dcs1800RxuCalMemV2); + } + else + { + sId = EEPROM_SID_DCS1800_RXDCAL; + pCfgRxCal = &ee.cfg.v2.dcs1800RxdCalV2; + size = sizeof(ee.cfg.v2.dcs1800RxdCalV2) + sizeof(ee.cfg.v2.__dcs1800RxdCalMemV2); + } + break; + case 3: + nArfcn = 299; + if ( iUplink ) + { + sId = EEPROM_SID_PCS1900_RXUCAL; + pCfgRxCal = &ee.cfg.v2.pcs1900RxuCalV2; + size = sizeof(ee.cfg.v2.pcs1900RxuCalV2) + sizeof(ee.cfg.v2.__pcs1900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_PCS1900_RXDCAL; + pCfgRxCal = &ee.cfg.v2.pcs1900RxdCalV2; + size = sizeof(ee.cfg.v2.pcs1900RxdCalV2) + sizeof(ee.cfg.v2.__pcs1900RxdCalMemV2); + } + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + pCfgRxCal->u16SectionID = sId; + pCfgRxCal->u16Crc = 0; + pCfgRxCal->u32Time = time(NULL); + + //DSP firmware version + pCfgRxCal->u8DspMajVer = pRxCal->u8DspMajVer; + pCfgRxCal->u8DspMinVer = pRxCal->u8DspMinVer; + + //FPGA firmware version + pCfgRxCal->u8FpgaMajVer = pRxCal->u8FpgaMajVer; + pCfgRxCal->u8FpgaMinVer = pRxCal->u8FpgaMinVer; + + // 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 + ((uint8_t*)pCfgRxCal - (uint8_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; +} diff --git a/src/osmo-bts-sysmo/eeprom.h b/src/osmo-bts-sysmo/eeprom.h new file mode 100644 index 00000000..f75e54f9 --- /dev/null +++ b/src/osmo-bts-sysmo/eeprom.h @@ -0,0 +1,304 @@ +/*************************************************************************** + * + * **** 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 <stdint.h> + +/**************************************************************************** + * 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 +{ + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + 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 +{ + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + 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__ diff --git a/src/osmo-bts-sysmo/femtobts.c b/src/osmo-bts-sysmo/femtobts.c new file mode 100644 index 00000000..480fe06b --- /dev/null +++ b/src/osmo-bts-sysmo/femtobts.c @@ -0,0 +1,370 @@ +/* sysmocom femtobts L1 API related definitions */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1dbg.h> + +#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" }, +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0) + { GsmL1_Status_ClockError, "Clock error" }, +#endif + { 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 +}; diff --git a/src/osmo-bts-sysmo/femtobts.h b/src/osmo-bts-sysmo/femtobts.h new file mode 100644 index 00000000..9163ebbf --- /dev/null +++ b/src/osmo-bts-sysmo/femtobts.h @@ -0,0 +1,110 @@ +#ifndef FEMTOBTS_H +#define FEMTOBTS_H + +#include <stdlib.h> +#include <osmocom/core/utils.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1const.h> + +#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_chcomb_names[8]; +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 */ diff --git a/src/osmo-bts-sysmo/hw_misc.c b/src/osmo-bts-sysmo/hw_misc.c new file mode 100644 index 00000000..6aa3b83f --- /dev/null +++ b/src/osmo-bts-sysmo/hw_misc.c @@ -0,0 +1,113 @@ +/* Misc HW routines for Sysmocom BTS */ + +/* (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/utils.h> + +#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 diff --git a/src/osmo-bts-sysmo/hw_misc.h b/src/osmo-bts-sysmo/hw_misc.h new file mode 100644 index 00000000..c4838dbf --- /dev/null +++ b/src/osmo-bts-sysmo/hw_misc.h @@ -0,0 +1,12 @@ +#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 diff --git a/src/osmo-bts-sysmo/l1_fwd.h b/src/osmo-bts-sysmo/l1_fwd.h new file mode 100644 index 00000000..55397920 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_fwd.h @@ -0,0 +1,5 @@ +#define L1FWD_L1_PORT 9999 +#define L1FWD_SYS_PORT 9998 +#define L1FWD_TCH_PORT 9997 +#define L1FWD_PDTCH_PORT 9996 + diff --git a/src/osmo-bts-sysmo/l1_fwd_main.c b/src/osmo-bts-sysmo/l1_fwd_main.c new file mode 100644 index 00000000..bc9fc21c --- /dev/null +++ b/src/osmo-bts-sysmo/l1_fwd_main.c @@ -0,0 +1,236 @@ +/* Sysmocom femtobts L1 proxy */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/application.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> + +#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; + void *ctx = talloc_named_const(NULL, 0, "l1_fwd"); + + printf("sizeof(GsmL1_Prim_t) = %zu\n", sizeof(GsmL1_Prim_t)); + printf("sizeof(SuperFemto_Prim_t) = %zu\n", sizeof(SuperFemto_Prim_t)); + + osmo_init_logging2(ctx, &bts_log_info); + + /* + * 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(ctx, 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(ctx, 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); +} diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c new file mode 100644 index 00000000..87cf25a0 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_if.c @@ -0,0 +1,1899 @@ +/* Interface handler for Sysmocom L1 */ + +/* (C) 2011-2016 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/lapdm.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/tx_power.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> + +#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" + +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 */ + HANDLE conf_hLayer3; /* layer 3 handle 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 HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + + +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]; + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + 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; +} + +/* 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, bool use_cache) +{ + struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGP(DL1C, LOGL_FATAL, "PH-DATA.req without msg. " + "Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + 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_CBCH(chan_nr)) { + sapi = GsmL1_Sapi_Cbch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = l1sap_fn2ccch_block(u32Fn); + if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ")) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + /* empty frame */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + + 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], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + 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)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* 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); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* 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 */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + 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], nmsg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(nmsg); + return -ENOBUFS; + } + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + 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 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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); + 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; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + 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; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct femtol1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1); + 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) { + msgb_free(msg); + 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; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Cbch: + cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */ + 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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + 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: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + 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 = femtol1_hdl_trx(fl1); + 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], 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 = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + 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); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + 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; + 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) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + 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, + uint32_t fn, 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_256bits = m->i16BurstTiming * 64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + 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 = femtol1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct msgb *sap_msg; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + int rc = 0; + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + if (!chan_nr) { + LOGPFN(DL1C, LOGL_ERROR, data_ind->u32Fn, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + + process_meas_res(trx, chan_nr, fn, &data_ind->measParam); + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + 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; + } + + /* fill L1SAP header */ + sap_msg = l1sap_msgb_alloc(data_ind->msgUnitParam.u8Size); + l1sap = msgb_l1sap_prim(sap_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, sap_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = (int8_t) (data_ind->measParam.fRssi); + if (!pcu_direct) { /* FIXME: if pcu_direct=1, then this is not set, what to do in pcu_tx_data_ind() in this case ?*/ + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming * 64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + } + /* copy data from L1 primitive to L1SAP primitive */ + sap_msg->l2h = msgb_put(sap_msg, data_ind->msgUnitParam.u8Size); + memcpy(sap_msg->l2h, data_ind->msgUnitParam.u8Buffer, + data_ind->msgUnitParam.u8Size); + + + msgb_free(l1p_msg); + + 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 = femtol1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */ + if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) { + msgb_free(l1p_msg); + return 0; + } + + dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1C, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", + ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + }; + + lchan = l1if_hLayer_to_lchan(trx, ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = 0x88; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to osmo-bts-sysmo */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + 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 = rach_ind_param; + + 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; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + 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) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(femtol1_hdl_trx(fl1h), 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) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(femtol1_hdl_trx(fl1h), 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); +} + +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; +} + +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 +} + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0) +/* + * 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; +} +#endif /* 2.2.0 */ + +/* 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); + + 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) + struct gsm_bts_trx *trx = hdl->phy_inst->trx; + if (has_external_atten(hdl)) { + LOGP(DL1C, LOGL_INFO, "Using external attenuator.\n"); + sysp->u.activateRfReq.rfTrx.u8UseExtAtten = 1; + sysp->u.activateRfReq.rfTrx.fMaxTxPower = + (float) get_p_trxout_target_mdBm(trx, 0) / 1000; + } +#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); +} + +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); + } +} + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) +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) +{ + + 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) + const uint8_t unmuted[8] = { 0,0,0,0,0,0,0,0 }; + struct gsm_bts_trx *trx = hdl->phy_inst->trx; + int i; + LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n"); + /* always acknowledge an un-MUTE (which is a no-op if MUTE is not supported */ + if (!memcmp(mute, unmuted, ARRAY_SIZE(unmuted))) { + bts_update_status(BTS_STATUS_RF_MUTE, mute[0]); + oml_mo_rf_lock_chg(&trx->mo, mute, 1); + for (i = 0; i < ARRAY_SIZE(unmuted); ++i) + mute_handle_ts(&trx->ts[i], mute[i]); + return 0; + } + return -ENOTSUP; +#else + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + 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); + + 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)); + + if (l1if_dsp_ver(fl1h) < L1_VER_SHIFT(5,3,3)) + fl1h->rtp_hr_jumble_needed = true; + + /* 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) */ + int 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_get_type(&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_get_trx(&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; +} + +/* Set the clock calibration to the value read from the eeprom. */ +static void clk_cal_use_eeprom(struct femtol1_hdl *hdl) +{ + struct phy_instance *pinst = hdl->phy_inst; + eeprom_RfClockCal_t rf_clk; + int rc; + + if (!pinst->u.sysmobts.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); +} + +struct femtol1_hdl *l1if_open(struct phy_instance *pinst) +{ + 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(pinst, struct femtol1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.sysmobts.dsp_trace_f; + fl1h->clk_src = pinst->u.sysmobts.clk_src; + fl1h->clk_cal = pinst->u.sysmobts.clk_cal; + clk_cal_use_eeprom(fl1h); + get_hwinfo_eeprom(fl1h); +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) + if (fl1h->clk_src == SuperFemto_ClkSrcId_None) { + 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 + if (fl1h->clk_src == SF_CLKSRC_NONE) + 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; + } + + l1if_reset(fl1h); + + 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->phy_inst->u.sysmobts.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 + +static void set_power_param(struct trx_power_params *out, + int trx_p_max_out_dBm, + int int_pa_nominal_gain_dB) +{ + out->trx_p_max_out_mdBm = to_mdB(trx_p_max_out_dBm); + out->pa.nominal_gain_mdB = to_mdB(int_pa_nominal_gain_dB); +} + +static void fill_trx_power_params(struct gsm_bts_trx *trx, + struct phy_instance *pinst) +{ + struct femtol1_hdl *fl1h = pinst->u.sysmobts.hdl; + + switch (fl1h->hw_info.model_nr) { + case 1020: + set_power_param(&trx->power_params, 23, 10); + break; + case 1100: + set_power_param(&trx->power_params, 23, 17); + break; + case 2050: + set_power_param(&trx->power_params, 37, 0); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "Unknown/Unsupported " + "sysmoBTS Model Number %u\n", + fl1h->hw_info.model_nr); + /* fall-through */ + case 0xffff: + /* sysmoBTS 1002 without any setting in EEPROM */ + LOGP(DL1C, LOGL_NOTICE, "Assuming 1002 for sysmoBTS " + "Model number %u\n", fl1h->hw_info.model_nr); + case 1002: + set_power_param(&trx->power_params, 23, 0); + } +} + + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + struct femtol1_hdl *hdl; + struct gsm_bts *bts; + + OSMO_ASSERT(pinst); + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.sysmobts.hdl = l1if_open(pinst); + if (!pinst->u.sysmobts.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + fill_trx_power_params(pinst->trx, pinst); + + bts = pinst->trx->bts; + if (pinst->trx == bts->c0) { + int rc; + rc = get_p_max_out_mdBm(bts->c0); + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal " + "transmit power. Assuming 23dBm.\n"); + } + bts->c0->nominal_power = rc; + } + + hdl = pinst->u.sysmobts.hdl; + osmo_strlcpy(bts->sub_model, sysmobts_model(hdl->hw_info.model_nr, hdl->hw_info.trx_nr), sizeof(bts->sub_model)); + snprintf(pinst->version, sizeof(pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u", + hdl->hw_info.ver_major, hdl->hw_info.ver_minor, + hdl->hw_info.dsp_version[0], hdl->hw_info.dsp_version[1], hdl->hw_info.dsp_version[2], + hdl->hw_info.fpga_version[0], hdl->hw_info.fpga_version[1], hdl->hw_info.fpga_version[2]); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + return 0; +} diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h new file mode 100644 index 00000000..1b214be7 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_if.h @@ -0,0 +1,171 @@ +#ifndef _FEMTO_L1_H +#define _FEMTO_L1_H + +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/timer.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/phy_link.h> + +#include <sysmocom/femtobts/gsml1prim.h> + +#include <stdbool.h> + +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; /* currently operational DSP trace flags */ + int clk_cal; + uint8_t clk_src; + struct llist_head wlc_list; + + struct phy_instance *phy_inst; /* Reference to PHY instance */ + + 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; + bool rtp_hr_jumble_needed; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; + + /* for l1_fwd */ + void *priv; +}; + + +#define L1_VER_SHIFT(x,y,z) ((x << 16) | (y << 8) | (z)) + +static inline uint32_t l1if_dsp_ver(struct femtol1_hdl *fl1h) +{ + const uint8_t *v = fl1h->hw_info.dsp_version; + return L1_VER_SHIFT(v[0], v[1], v[2]); +} + +static inline uint32_t l1if_fpga_ver(struct femtol1_hdl *fl1h) +{ + const uint8_t *v = fl1h->hw_info.fpga_version; + return L1_VER_SHIFT(v[0], v[1], v[2]); +} + +#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(struct phy_instance *pinst); +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 */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +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, uint32_t fn); + +/* 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); +int get_clk_cal(struct femtol1_hdl *hdl); + +/* 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); + +static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.sysmobts.hdl; +} + +static inline struct gsm_bts_trx *femtol1_hdl_trx(struct femtol1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} +#endif /* _FEMTO_L1_H */ diff --git a/src/osmo-bts-sysmo/l1_transp.h b/src/osmo-bts-sysmo/l1_transp.h new file mode 100644 index 00000000..b2967f93 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _FEMTOL1_TRANSPH_ +#define _FEMTOL1_TRANSPH_ + +#include <osmocom/core/msgb.h> + +/* 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 */ diff --git a/src/osmo-bts-sysmo/l1_transp_fwd.c b/src/osmo-bts-sysmo/l1_transp_fwd.c new file mode 100644 index 00000000..87c230bb --- /dev/null +++ b/src/osmo-bts-sysmo/l1_transp_fwd.c @@ -0,0 +1,152 @@ +/* Interface handler for Sysmocom L1 (forwarding) */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> + +#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; +} diff --git a/src/osmo-bts-sysmo/l1_transp_hw.c b/src/osmo-bts-sysmo/l1_transp_hw.c new file mode 100644 index 00000000..01bc2005 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_transp_hw.c @@ -0,0 +1,329 @@ +/* Interface handler for Sysmocom L1 (real hardware) */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <assert.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> + +#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)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + 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, "[%d] unable to open %s for reading: %s\n", + q, rd_devnames[q], 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, "[%d] unable to open %s for writing: %s\n", + q, wr_devnames[q], 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; +} diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c new file mode 100644 index 00000000..221e8e8a --- /dev/null +++ b/src/osmo-bts-sysmo/main.c @@ -0,0 +1,199 @@ +/* Main program for Sysmocom BTS */ + +/* (C) 2011-2015 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/l1sap.h> + +#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 bts_model_init(struct gsm_bts *bts) +{ + struct stat st; + static struct osmo_fd accept_fd, read_fd; + int rc; + + bts->variant = BTS_OSMO_SYSMO; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + 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 (stat(SYSMOBTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +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); +} + +void bts_model_print_help() +{ + printf( + " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for " + "PDCH dchannel directly\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 +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* specific to this hardware */ + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + pinst->u.sysmobts.clk_use_eeprom = 1; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-calib.c b/src/osmo-bts-sysmo/misc/sysmobts-calib.c new file mode 100644 index 00000000..a111d1d5 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-calib.c @@ -0,0 +1,537 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <unistd.h> +#include <math.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1types.h> + +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/core/utils.h> + +#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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c new file mode 100644 index 00000000..4b34f50e --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c @@ -0,0 +1,800 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> + +#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 +#define COMPILED_MAJOR (FEMTOBTS_API_VERSION >> 16) +#define COMPILED_MINOR ((FEMTOBTS_API_VERSION >> 8) & 0xff) +#define COMPILED_BUILD (FEMTOBTS_API_VERSION & 0xff) +#else +#define BOARD_REV(x) x.u.systemInfoCnf.boardVersion.rev +#define BOARD_OPT(x) x.u.systemInfoCnf.boardVersion.option +#define COMPILED_MAJOR (SUPERFEMTO_API_VERSION >> 16) +#define COMPILED_MINOR ((SUPERFEMTO_API_VERSION >> 8) & 0xff) +#define COMPILED_BUILD (SUPERFEMTO_API_VERSION & 0xff) +#endif + + printf("Compiled against: v%u.%u.%u\n", + COMPILED_MAJOR, COMPILED_MINOR, COMPILED_BUILD); + printf("Running 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)); + + if (COMPILED_MAJOR != INFO_DSP(prim).major || COMPILED_MINOR != INFO_DSP(prim).minor) { + printf("WARNING! WARNING! WARNING! WARNING! WARNING\n"); + printf("You might run this against an incompatible firmware.\n"); + printf("Continuing anyway but the result might be broken\n"); + } +#undef INFO_DSP +#undef INFO_FPGA +#undef BOARD_REV +#undef BOARD_OPT +#undef COMPILED_MAJOR +#undef COMPILED_MINOR +#undef COMPILED_BUILD + 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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.h b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h new file mode 100644 index 00000000..e7d59c94 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h @@ -0,0 +1,45 @@ +#ifndef SYSMOBTS_LAYER_H +#define SYSMOBTS_LAYER_H + +#include <sysmocom/femtobts/superfemto.h> + +#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 diff --git a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h new file mode 100644 index 00000000..b7a27fb7 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h @@ -0,0 +1,44 @@ +#ifndef _SYSMOBTS_EEPROM_H +#define _SYSMOBTS_EEPROM_H + +#include <stdint.h> + +struct sysmobts_net_cfg { + uint8_t mode; /* 0 */ + uint32_t ip; /* 1 - 4 */ + uint32_t mask; /* 5 - 8 */ + uint32_t gw; /* 9 - 12 */ + uint32_t dns; /* 13 - 16 */ +} __attribute__((packed)); + +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 boot_state[48]; /* 37-84 */ + uint8_t _pad1[18]; /* 85-102 */ + struct sysmobts_net_cfg net_cfg;/* 103-119 */ + uint8_t crc; /* 120 */ + uint8_t gpg_key[128]; /* 121-249 */ +} __attribute__((packed)); + +enum sysmobts_model_number { + MODEL_SYSMOBTS_1002 = 1002, + MODEL_SYSMOBTS_1020 = 1020, + MODEL_SYSMOBTS_2050 = 2050, +}; + +enum sysmobts_net_mode { + NET_MODE_DHCP, + NET_MODE_STATIC, +}; + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c new file mode 100644 index 00000000..a0080738 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c @@ -0,0 +1,336 @@ +/* Main program for SysmoBTS management daemon */ + +/* (C) 2012 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/msgb.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/ports.h> + +#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_get_type(&bts_type); + if (rc < 0) { + fprintf(stderr, "Failed to get model number.\n"); + return -1; + } + + rc = sysmobts_get_trx(&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: + case SIGTERM: + 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), +}; + +int main(int argc, char **argv) +{ + int rc; + struct ctrl_connection *ccon; + + tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); + msgb_talloc_ctx_init(tall_mgr_ctx, 0); + + srand(time(NULL)); + + osmo_init_logging2(tall_mgr_ctx, &mgr_log_info); + if (classify_bts() != 0) + exit(2); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_handler); + signal(SIGTERM, &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_mgr_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 */ + ccon = osmo_ctrl_conn_alloc(tall_mgr_ctx, NULL); + rc = -1; + if (ccon) { + ccon->write_queue.bfd.data = ccon; + rc = osmo_sock_init_ofd(&ccon->write_queue.bfd, AF_INET, + SOCK_STREAM, IPPROTO_TCP, + "localhost", OSMO_CTRL_PORT_BTS, + OSMO_SOCK_F_CONNECT); + } + if (rc < 0) + LOGP(DLCTRL, LOGL_ERROR, "Can't connect to CTRL @ localhost:%u\n", + OSMO_CTRL_PORT_BTS); + else + LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to locahost:%u\n", + OSMO_CTRL_PORT_BTS); + + sysmobts_mgr_temp_init(&manager, ccon); + + 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); + } +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.h b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h new file mode 100644 index 00000000..88f4e245 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h @@ -0,0 +1,122 @@ +#ifndef _SYSMOBTS_MGR_H +#define _SYSMOBTS_MGR_H + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> + +#include <gps.h> + +#include <stdint.h> + +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, + struct ctrl_connection *ctrl); +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 diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c new file mode 100644 index 00000000..12961e3f --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c @@ -0,0 +1,384 @@ +/* (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "sysmobts_misc.h" +#include "sysmobts_par.h" +#include "sysmobts_mgr.h" +#include "btsconfig.h" + +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/serial.h> + +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#ifdef BUILD_SBTS2050 +#include <sysmocom/femtobts/sbts2050_header.h> + +#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 diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c new file mode 100644 index 00000000..b0b5edd8 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c @@ -0,0 +1,547 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_misc.h" +#include "osmo-bts/msg_utils.h" + +#include <osmocom/core/logging.h> +#include <osmocom/core/select.h> + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipa.h> + +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 inline int compat_gps_read(struct gps_data_t *data) +{ +/* API break in gpsd 6bba8b329fc7687b15863d30471d5af402467802 */ +#if GPSD_API_MAJOR_VERSION >= 7 && GPSD_API_MINOR_VERSION >= 0 + return gps_read(data, NULL, 0); +#else + return gps_read(data); +#endif +} + +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 = compat_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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c new file mode 100644 index 00000000..48a03124 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c @@ -0,0 +1,186 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_misc.h" +#include "misc/sysmobts_nl.h" +#include "misc/sysmobts_par.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <arpa/inet.h> + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <string.h> + +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 */ + model_name = sysmobts_model(sysmobts_bts_type(), sysmobts_trx_number()); + 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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c new file mode 100644 index 00000000..1be56ac2 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c @@ -0,0 +1,321 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_misc.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include <stdlib.h> + +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 osmo-bts-sysmo"); + } +} + +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 osmo-bts-sysmo"); + } +} + +/** + * 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, + struct ctrl_connection *ctrl, int critical, + int warning) +{ + int new_state = next_state(manager->state, critical, warning); + struct ctrl_cmd *rep; + char *oml_alert = NULL; + + /* 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); + oml_alert = "Temperature Warning"; + break; + case STATE_CRITICAL: + execute_critical_act(manager); + oml_alert = "Temperature Critical"; + break; + }; + + if (!oml_alert) + return; + + rep = ctrl_cmd_create(tall_mgr_ctx, CTRL_TYPE_SET); + if (!rep) { + LOGP(DTEMP, LOGL_ERROR, "OML alert creation failed for %s.\n", + oml_alert); + return; + } + + rep->id = talloc_asprintf(rep, "%d", rand()); + rep->variable = "oml-alert"; + rep->value = oml_alert; + LOGP(DTEMP, LOGL_ERROR, "OML alert sent: %d\n", + ctrl_cmd_send(&ctrl->write_queue, rep)); + talloc_free(rep); +} + +static void temp_ctrl_check(struct ctrl_connection *ctrl) +{ + 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, ctrl, crit_thresh_passed, + warn_thresh_passed); +} + +static void temp_ctrl_check_cb(void *ctrl) +{ + temp_ctrl_check(ctrl); + /* 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, + struct ctrl_connection *ctrl) +{ + s_mgr = mgr; + temp_ctrl_timer.cb = temp_ctrl_check_cb; + temp_ctrl_timer.data = ctrl; + temp_ctrl_check_cb(ctrl); + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c new file mode 100644 index 00000000..444ee7c3 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c @@ -0,0 +1,531 @@ +/* (C) 2014 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso <anayuso@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/logging.h> + +#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 <laforge@gnumonks.org>\r\n" + "(C) 2014 by Holger Hans Peter Freyther\r\n" + "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static int 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 osmo-bts-sysmo.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 osmo-bts-sysmo.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 osmo-bts-sysmo.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 osmo-bts-sysmo.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); + + /* 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); + + install_node(&limit_digital_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_digital_cmd); + register_limit(LIMIT_DIGITAL_NODE); + + install_node(&limit_board_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_board_cmd); + register_limit(LIMIT_BOARD_NODE); + + install_node(&limit_pa_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa_cmd); + register_limit(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); + + install_node(&act_crit_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_critical_cmd); + register_action(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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.c b/src/osmo-bts-sysmo/misc/sysmobts_misc.c new file mode 100644 index 00000000..d996d644 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.c @@ -0,0 +1,275 @@ +/* (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <limits.h> +#include <time.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> + +#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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.h b/src/osmo-bts-sysmo/misc/sysmobts_misc.h new file mode 100644 index 00000000..06166cf6 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.h @@ -0,0 +1,65 @@ +#ifndef _SYSMOBTS_MISC_H +#define _SYSMOBTS_MISC_H + +#include <stdint.h> + +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 diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_nl.c new file mode 100644 index 00000000..67aa6636 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.c @@ -0,0 +1,120 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <arpa/inet.h> +#include <netinet/ip.h> + +#include <sys/socket.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#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; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.h b/src/osmo-bts-sysmo/misc/sysmobts_nl.h new file mode 100644 index 00000000..84f4d9cc --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.h @@ -0,0 +1,24 @@ +/* + * (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 <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.c b/src/osmo-bts-sysmo/misc/sysmobts_par.c new file mode 100644 index 00000000..de81fff5 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_par.c @@ -0,0 +1,382 @@ +/* sysmobts - access to hardware related parameters */ + +/* (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/crc8gen.h> +#include <osmocom/core/utils.h> + +#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" + +static const struct osmo_crc8gen_code crc8_ccit = { + .bits = 8, + .poly = 0x83, + .init = 0xFF, + .remainder = 0x00, +}; + +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; +} + +int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg) +{ + struct sysmobts_eeprom *ee = get_eeprom(0); + ubit_t bits[sizeof(*cfg) * 8]; + uint8_t crc; + int rc; + + if (!ee) + return -EIO; + + /* convert the net_cfg to unpacked bits */ + rc = osmo_pbit2ubit(bits, (uint8_t *) &ee->net_cfg, sizeof(bits)); + if (rc != sizeof(bits)) + return -EFAULT; + /* compute the crc and compare */ + crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits)); + if (crc != ee->crc) { + fprintf(stderr, "Computed CRC(%d) wanted CRC(%d)\n", crc, ee->crc); + return -EBADMSG; + } + /* return the actual data */ + *cfg = ee->net_cfg; + return 0; +} + +int sysmobts_get_type(int *bts_type) +{ + return sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, bts_type); +} + +int sysmobts_get_trx(int *trx_number) +{ + return sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, trx_number); +} + +char *sysmobts_model(int bts_type, int trx_num) +{ + switch(bts_type) { + case 0: + case 0xffff: + case 1002: + return "sysmoBTS 1002"; + case 2050: + switch(trx_num) { + case 0: + return "sysmoBTS 2050 (master)"; + case 1: + return "sysmoBTS 2050 (slave)"; + default: + return "sysmoBTS 2050 (unknown)"; + } + default: + return "Unknown"; + } +} + +int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg) +{ + struct sysmobts_eeprom *ee = get_eeprom(1); + ubit_t bits[sizeof(*cfg) * 8]; + int rc; + + if (!ee) + return -EIO; + + /* convert the net_cfg to unpacked bits */ + rc = osmo_pbit2ubit(bits, (uint8_t *) cfg, sizeof(bits)); + if (rc != sizeof(bits)) + return -EFAULT; + /* compute and store the result */ + ee->net_cfg = *cfg; + ee->crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits)); + return set_eeprom(ee); +} + +osmo_static_assert(offsetof(struct sysmobts_eeprom, trx_nr) == 36, offset_36); +osmo_static_assert(offsetof(struct sysmobts_eeprom, boot_state) == 37, offset_37); +osmo_static_assert(offsetof(struct sysmobts_eeprom, _pad1) == 85, offset_85); +osmo_static_assert(offsetof(struct sysmobts_eeprom, net_cfg.mode) == 103, offset_103); +osmo_static_assert((offsetof(struct sysmobts_eeprom, net_cfg.ip) & 0x3) == 0, ip_32bit_aligned); +osmo_static_assert(offsetof(struct sysmobts_eeprom, gpg_key) == 121, offset_121); diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.h b/src/osmo-bts-sysmo/misc/sysmobts_par.h new file mode 100644 index 00000000..52bf67df --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_par.h @@ -0,0 +1,38 @@ +#ifndef _SYSMOBTS_PAR_H +#define _SYSMOBTS_PAR_H + +#include <osmocom/core/utils.h> + +struct sysmobts_net_cfg; + +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_get_net(struct sysmobts_net_cfg *cfg); +int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg); +int sysmobts_get_type(int *bts_type); +int sysmobts_get_trx(int *trx_number); +char *sysmobts_model(int bts_type, int trx_num); +int sysmobts_par_is_int(enum sysmobts_par par); + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_util.c b/src/osmo-bts-sysmo/misc/sysmobts_util.c new file mode 100644 index 00000000..c9930d8f --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_util.c @@ -0,0 +1,256 @@ +/* sysmobts-util - access to hardware related parameters */ + +/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "sysmobts_par.h" +#include "sysmobts_eeprom.h" + +enum act { + ACT_GET, + ACT_SET, + ACT_NET_GET, + ACT_NET_SET, +}; + +static enum act action; +static char *write_arg; +static int void_warranty; + + +static struct in_addr net_ip = { 0, }, net_dns = { 0, }, net_gw = { 0, }, net_mask = { 0, }; +static uint8_t net_mode = 0; + +static void print_help() +{ + const struct value_string *par = sysmobts_par_names; + + printf("sysmobts-util [--void-warranty -r | -w value] param_name\n"); + printf("sysmobts-util --net-read\n"); + printf("sysmobts-util --net-write --mode INT --ip IP_STR --gw IP_STR --dns IP_STR --net-mask IP_STR\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' }, + { "ip", 1, 0, 241 }, + { "gw", 1, 0, 242 }, + { "dns", 1, 0, 243 }, + { "net-mask", 1, 0, 244 }, + { "mode", 1, 0, 245 }, + { "net-read", 0, 0, 246 }, + { "net-write", 0, 0, 247 }, + { 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; + case 246: + action = ACT_NET_GET; + break; + case 247: + action = ACT_NET_SET; + break; + case 245: + net_mode = atoi(optarg); + break; + case 244: + inet_aton(optarg, &net_mask); + break; + case 243: + inet_aton(optarg, &net_dns); + break; + case 242: + inet_aton(optarg, &net_gw); + break; + case 241: + inet_aton(optarg, &net_ip); + break; + default: + printf("Unknown option %d/%c\n", c, c); + return -1; + } + } + + return 0; +} + +static const char *make_addr(uint32_t saddr) +{ + struct in_addr addr; + addr.s_addr = ntohl(saddr); + return inet_ntoa(addr); +} + +static void dump_net_cfg(struct sysmobts_net_cfg *net_cfg) +{ + if (net_cfg->mode == NET_MODE_DHCP) { + printf("IP=dhcp\n"); + printf("DNS=\n"); + printf("GATEWAY=\n"); + printf("NETMASK=\n"); + } else { + printf("IP=%s\n", make_addr(net_cfg->ip)); + printf("GATEWAY=%s\n", make_addr(net_cfg->gw)); + printf("DNS=%s\n", make_addr(net_cfg->dns)); + printf("NETMASK=%s\n", make_addr(net_cfg->mask)); + } +} + +static int handle_net(void) +{ + struct sysmobts_net_cfg net_cfg; + int rc; + + switch (action) { + case ACT_NET_GET: + rc = sysmobts_par_get_net(&net_cfg); + if (rc != 0) { + fprintf(stderr, "Error %d\n", rc); + exit(rc); + } + dump_net_cfg(&net_cfg); + break; + case ACT_NET_SET: + memset(&net_cfg, 0, sizeof(net_cfg)); + net_cfg.mode = net_mode; + net_cfg.ip = htonl(net_ip.s_addr); + net_cfg.mask = htonl(net_mask.s_addr); + net_cfg.gw = htonl(net_gw.s_addr); + net_cfg.dns = htonl(net_dns.s_addr); + printf("Going to write\n"); + dump_net_cfg(&net_cfg); + + rc = sysmobts_par_set_net(&net_cfg); + if (rc != 0) { + fprintf(stderr, "Error %d\n", rc); + exit(rc); + } + break; + default: + printf("Unhandled action %d\n", action); + } + 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 (action > ACT_SET) + return handle_net(); + + if (optind >= argc && action <+ ACT_NET_GET) { + 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); +} + diff --git a/src/osmo-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c new file mode 100644 index 00000000..ea7527dd --- /dev/null +++ b/src/osmo-bts-sysmo/oml.c @@ -0,0 +1,1963 @@ +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> +#include <sysmocom/femtobts/superfemto.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> + +#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_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +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, + HANDLE hLayer3) +{ + prim->id = id; + + /* for some reason the hLayer1 and hlayer3 fields are 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; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + 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; +} + +static HANDLE l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static HANDLE l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static HANDLE l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +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[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + 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 msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int femto_band; + int initial_mdBm = power_ramp_initial_power_mdBm(trx); + + 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, + l1p_handle_for_trx(trx)); + 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 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = ((float) initial_mdBm) / 1000; + 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); + trx->power_params.p_total_cur_mdBm = trx->power_params.ramp.max_initial_pout_mdBm; + + /* 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, + l1p_handle_for_trx(trx)); + 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_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + DEBUGP(DL1C, "%s pchan=%s ts_connect_as(%s) logChComb=%s\n", + gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan), + gsm_pchan_name(pchan), get_value_string(femtobts_chcomb_names, + cr->logChComb)); + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, 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) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (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_TCH_F_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + 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_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + 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, l1p_handle_for_lchan(lchan)); + 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: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * 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; + 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; + case GsmL1_Sapi_Ptcch: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->ptcch.u8Bsic); + break; + case GsmL1_Sapi_Prach: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->prach.u8Bsic); + break; + 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, + l1p_handle_for_lchan(lchan)); + 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, 0); + 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, + l1p_handle_for_lchan(lchan)); + + 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, l1p_handle_for_lchan(lchan)); + 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); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + 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 bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + 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); +} + +int bts_model_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_PRES_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_opstart(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 bts_model_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); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts, 0); + + return 0; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + + rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); + if (rc) + cb_ts_connected(ts, rc); +} diff --git a/src/osmo-bts-sysmo/oml_router.c b/src/osmo-bts-sysmo/oml_router.c new file mode 100644 index 00000000..f3d08373 --- /dev/null +++ b/src/osmo-bts-sysmo/oml_router.c @@ -0,0 +1,129 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "oml_router.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/msg_utils.h> + +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +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; +} diff --git a/src/osmo-bts-sysmo/oml_router.h b/src/osmo-bts-sysmo/oml_router.h new file mode 100644 index 00000000..55f0681d --- /dev/null +++ b/src/osmo-bts-sysmo/oml_router.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/osmo-bts-sysmo/sysmobts_ctrl.c b/src/osmo-bts-sysmo/sysmobts_ctrl.c new file mode 100644 index 00000000..21df88e5 --- /dev/null +++ b/src/osmo-bts-sysmo/sysmobts_ctrl.c @@ -0,0 +1,274 @@ +/* Control Interface for sysmoBTS */ + +/* (C) 2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> + +#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 = 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; + + 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 bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + 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; +} diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c new file mode 100644 index 00000000..b105bf4d --- /dev/null +++ b/src/osmo-bts-sysmo/sysmobts_vty.c @@ -0,0 +1,542 @@ +/* VTY interface for sysmoBTS */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/rate_ctr.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/rsl.h> + +#include "femtobts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(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_phy_clkcal_eeprom, cfg_phy_clkcal_eeprom_cmd, + "clock-calibration eeprom", + "Use the eeprom clock calibration value\n") +{ + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_clkcal_def, cfg_phy_clkcal_def_cmd, + "clock-calibration default", + "Set the clock calibration value\n" "Default Clock DAC value\n") +{ + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 0; + pinst->u.sysmobts.clk_cal = 0xffff; + + return CMD_SUCCESS; +} + +#ifdef HW_SYSMOBTS_V1 +DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd, + "clock-calibration <0-4095>", + "Set the clock calibration value\n" "Clock DAC value\n") +{ + unsigned int clkcal = atoi(argv[0]); + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 0; + pinst->u.sysmobts.clk_cal = clkcal & 0xfff; + + return CMD_SUCCESS; +} +#else +DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd, + "clock-calibration <-4095-4095>", + "Set the clock calibration value\n" "Offset in PPB\n") +{ + int clkcal = atoi(argv[0]); + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 0; + pinst->u.sysmobts.clk_cal = clkcal; + + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_phy_clksrc, cfg_phy_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 phy_instance *pinst = vty->index; + int rc; + + rc = get_string_value(femtobts_clksrc_names, argv[0]); + if (rc < 0) + return CMD_WARNING; + + pinst->u.sysmobts.clk_src = rc; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.sysmobts.calib_path) + talloc_free(pinst->u.sysmobts.calib_path); + + pinst->u.sysmobts.calib_path = talloc_strdup(pinst, 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; + + trx->bts->ul_power_target = atoi(argv[0]); + + 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; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(femtobts_tracef_names, argv[1]); + pinst->u.sysmobts.dsp_trace_f |= ~flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(femtobts_tracef_names, argv[1]); + pinst->u.sysmobts.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + +/* runtime */ + +DEFUN(show_phy_clksrc, show_trx_clksrc_cmd, + "show phy <0-255> clock-source", + SHOW_TRX_STR "Display the clock source for this TRX") +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst = vty_get_phy_instance(vty, phy_nr, 0); + + if (!pinst) + return CMD_WARNING; + + vty_out(vty, "PHY Clock Source: %s%s", + get_value_string(femtobts_clksrc_names, + pinst->u.sysmobts.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 phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct femtol1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.sysmobts.hdl; + 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 phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct femtol1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.sysmobts.hdl; + 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 phy <0-255> instance <0-255> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct femtol1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.sysmobts.hdl; + + 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) +{ +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + if (trx->nominal_power != get_p_max_out_mdBm(trx)) + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power, + VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.sysmobts.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(femtobts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + + if (pinst->u.sysmobts.clk_use_eeprom) + vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE); + else + vty_out(vty, " clock-calibration %d%s", + pinst->u.sysmobts.clk_cal, VTY_NEWLINE); + if (pinst->u.sysmobts.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.sysmobts.calib_path, VTY_NEWLINE); + if (pinst->u.sysmobts.clk_src) + vty_out(vty, " clock-source %s%s", + get_value_string(femtobts_clksrc_names, + pinst->u.sysmobts.clk_src), 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); + + cfg_phy_dsp_trace_f_cmd.string = + vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + "dsp-trace-flag (", "|", ")", + VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = + vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + DSP_TRACE_F_STR, "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = + vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + "no dsp-trace-flag (", "|", ")", + VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = + vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + NO_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_ul_power_target_cmd); + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clkcal_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clkcal_eeprom_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clkcal_def_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clksrc_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + + return 0; +} diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c new file mode 100644 index 00000000..54e73136 --- /dev/null +++ b/src/osmo-bts-sysmo/tch.c @@ -0,0 +1,684 @@ +/* Traffic channel support for Sysmocom BTS L1 */ + +/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <sysmocom/femtobts/superfemto.h> +#include <sysmocom/femtobts/gsml1prim.h> +#include <sysmocom/femtobts/gsml1const.h> +#include <sysmocom/femtobts/gsml1types.h> + +#include "femtobts.h" +#include "l1_if.h" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-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 */ + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + 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 gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-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 */ + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + 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 */ + +#ifdef USE_L1_RTP_MODE +/* change the bit-order of each unaligned field inside the HR codec + * payload from little-endian bit-ordering to bit-endian and vice-versa. + * This is required on all sysmoBTS DSP versions < 5.3.3 in order to + * be compliant with ETSI TS 101 318 Chapter 5.2 */ +static void hr_jumble(uint8_t *dst, const uint8_t *src) +{ + /* Table 2 / Section 5.2.1 of ETSI TS 101 381 */ + const int p_unvoiced[] = + { 5, 11, 9, 8, 1, 2, 7, 7, 5, 7, 7, 5, 7, 7, 5, 7, 7, 5 }; + /* Table 3 / Section 5.2.1 of ETSI TS 101 381 */ + const int p_voiced[] = + { 5, 11, 9, 8, 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 }; + + int base, i, j, l, si, di; + const int *p; + + memset(dst, 0x00, GSM_HR_BYTES); + + p = (src[4] & 0x30) ? p_voiced : p_unvoiced; + + base = 0; + for (i = 0; i < 18; i++) { + l = p[i]; + for (j = 0; j < l; j++) { + si = base + j; + di = base + l - j - 1; + + if (src[si >> 3] & (1 << (7 - (si & 7)))) + dst[di >> 3] |= (1 << (7 - (di & 7))); + } + + base += l; + } +} +#endif /* USE_L1_RTP_MODE */ + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); +#ifdef USE_L1_RTP_MODE + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + if (fl1h->rtp_hr_jumble_needed) + hr_jumble(cur, l1_payload); + else + memcpy(cur, l1_payload, GSM_HR_BYTES); +#else /* USE_L1_RTP_MODE */ + memcpy(cur, l1_payload, GSM_HR_BYTES); + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(cur, GSM_HR_BYTES); +#endif /* USE_L1_RTP_MODE */ + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + 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, struct gsm_lchan *lchan) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + +#ifdef USE_L1_RTP_MODE + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + if (fl1h->rtp_hr_jumble_needed) + hr_jumble(l1_payload, rtp_payload); + else + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); +#else /* USE_L1_RTP_MODE */ + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + /* 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, "L1P-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(DL1P, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx); + cmr = AMR_CMR_NONE; + } else { + cmr = amr_mrc->bts_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; +} + +/*! \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, uint8_t ft) +{ +#ifdef USE_L1_RTP_MODE + memcpy(l1_payload, 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 */ + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * 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. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + 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); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len, lchan); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + 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); + /* FIXME: detect and save EFR SID */ + break; +#endif + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + 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 -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +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, payload_type, payload_len, sid_first[9] = { 0 }; + 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) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + 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; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%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, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; +#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; +#endif + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + /* FIXME: what about GsmL1_TchPlType_Amr_SidBad? not well documented. */ + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%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, uint32_t fn) +{ + 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; + int rc; + + 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: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-sysmo/utils.c b/src/osmo-bts-sysmo/utils.c new file mode 100644 index 00000000..0e3ef273 --- /dev/null +++ b/src/osmo-bts-sysmo/utils.c @@ -0,0 +1,116 @@ +/* + * Helper utilities that are used in OML + * + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include "utils.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#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; + int rc; + + if (!bts->auto_band) + return band_osmo2femto(trx, bts->band); + + /* + * We need to check what will happen now. + */ + rc = gsm_arfcn2band_rc(arfcn, &band); + if (rc) /* wrong ARFCN, give up */ + return -1; + + /* 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; +} diff --git a/src/osmo-bts-sysmo/utils.h b/src/osmo-bts-sysmo/utils.h new file mode 100644 index 00000000..45908d50 --- /dev/null +++ b/src/osmo-bts-sysmo/utils.h @@ -0,0 +1,12 @@ +#ifndef SYSMOBTS_UTILS_H +#define SYSMOBTS_UTILS_H + +#include <stdint.h> +#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); +#endif diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am new file mode 100644 index 00000000..19222405 --- /dev/null +++ b/src/osmo-bts-trx/Makefile.am @@ -0,0 +1,10 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOCODING_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOCODING_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl + +EXTRA_DIST = trx_if.h l1_if.h loops.h + +bin_PROGRAMS = osmo-bts-trx + +osmo_bts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler_trx.c trx_vty.c loops.c +osmo_bts_trx_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c new file mode 100644 index 00000000..da1b554f --- /dev/null +++ b/src/osmo-bts-trx/l1_if.c @@ -0,0 +1,782 @@ +/* + * layer 1 primitive handling and interface + * + * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu> + * Copyright (C) 2015 Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/abis_nm.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/scheduler.h> + +#include "l1_if.h" +#include "trx_if.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_CCCH_SDCCH4_CBCH] = 5, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 3, + [GSM_PCHAN_SDCCH8_SACCH8C] = 7, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 7, + [GSM_PCHAN_PDCH] = 13, + /* [GSM_PCHAN_TCH_F_PDCH] not needed here, see trx_set_ts_as_pchan() */ + [GSM_PCHAN_UNKNOWN] = 0, +}; + +struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst) +{ + struct trx_l1h *l1h; + l1h = talloc_zero(tall_ctx, struct trx_l1h); + l1h->phy_inst = pinst; + trx_if_init(l1h); + return l1h; +} + +static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct gsm_bts_trx *trx = pinst->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_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + if (!pinst->u.osmotrx.sw_act_reported) { + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + pinst->u.osmotrx.sw_act_reported = true; + } + + for (tn = 0; tn < TRX_NR_TS; 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 < TRX_NR_TS; 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; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + check_transceiver_availability_trx(l1h, avail); + } + return 0; +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + /* FIXME: perform whatever is needed (if any) to set proper PCH/AGCH allocation according to + 3GPP TS 44.018 Table 10.5.2.11.1 using num_agch(lchan->ts->trx, "TRX L1"); function */ + return 0; + } + /* set lchan inactive */ + lchan_set_state(lchan, LCHAN_S_NONE); + + return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan), + LID_DEDIC, 0); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan), + LID_SACCH, 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_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_sent) { + trx_if_cmd_settsc(l1h, l1h->config.tsc); + l1h->config.tsc_sent = 1; + } + if (!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; + } + if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) { + trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb); + l1h->config.maxdlynb_sent = 1; + } + + for (tn = 0; tn < TRX_NR_TS; 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; + l1h->config.maxdlynb_sent = 0; + for (tn = 0; tn < TRX_NR_TS; tn++) + l1h->config.slottype_sent[tn] = 0; + } + + return 0; +} + +int l1if_provision_transceiver(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + 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; + l1h->config.maxdlynb_sent = 0; + for (tn = 0; tn < TRX_NR_TS; 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 phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + /* 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[CCCH_LCHAN]); + + /* 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 phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + enum gsm_phys_chan_config pchan = trx->ts[0].pchan; + + /* close all logical channels and reset timeslots */ + trx_sched_reset(&l1h->l1s); + + /* deactivate lchan for CCCH */ + if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 || + pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) { + lchan_set_state(&trx->ts[0].lchan[CCCH_LCHAN], 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) +{ + bts_shutdown(bts, "Abis close"); +} + +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; + uint8_t bsic = bts->bsic; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + 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 phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + 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_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + uint8_t tn = ts->nr; + uint16_t tsc = ts->tsc; + 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); + } + + /* ignore disabled slots */ + if (!(l1h->config.slotmask & (1 << tn))) + return NM_NACK_RES_NOTAVAIL; + + /* set physical channel. For dynamic timeslots, the caller should have + * decided on a more specific PCHAN type already. */ + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + rc = trx_sched_set_pchan(&l1h->l1s, tn, pchan); + if (rc) + return NM_NACK_RES_NOTAVAIL; + + /* activate lchan for CCCH */ + if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 || + pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) { + ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; + lchan_set_state(&ts->lchan[CCCH_LCHAN], 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; +} + +static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan; + + /* For dynamic timeslots, pick the pchan type that should currently be + * active. This should only be called during init, PDCH transitions + * will call trx_set_ts_as_pchan() directly. */ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + OSMO_ASSERT((ts->flags & TS_F_PDCH_PENDING_MASK) == 0); + pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH + : GSM_PCHAN_TCH_F; + break; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want); + pchan = ts->dyn.pchan_is; + break; + default: + pchan = ts->pchan; + break; + } + + return trx_set_ts_as_pchan(ts, pchan); +} + + +/* + * 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->l1s, 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->l1s, chan_nr, 0, + lchan->encr.alg_id - 1, lchan->encr.key, + lchan->encr.key_len); + } + trx_sched_set_cipher(&l1h->l1s, 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 phy_instance *pinst = l1h->phy_inst; + 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(pinst->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); +} + + +static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, int16_t toa256, + float ber, float rssi, uint32_t fn) +{ + 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_256bits = toa256; + l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000); + l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1); + l1sap->u.info.u.meas_ind.fn = fn; +} + +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, int16_t toa256) +{ + 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; + + LOGPFN(DMEAS, LOGL_DEBUG, fn, "RX UL measurement for %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " + "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa256=%d\n", + gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.current), + rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa256); + + l1if_fill_meas_res(&l1sap, chan_nr, toa256, ber, rssi, fn); + + 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 phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + struct msgb *msg = l1sap->oph.msg; + uint8_t chan_nr; + 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->l1s, l1sap); + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_tch_req(&l1h->l1s, 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + 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; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot activate" + " chan_nr 0x%02x\n", chan_nr); + break; + } + + /* trx_chan_desc[] in scheduler.c uses the RSL_CHAN_OSMO_PDCH cbits + * (0xc0) to indicate the need for PDTCH and PTCCH SAPI activation. + * However, 0xc0 is a cbits pattern exclusively used for Osmocom style + * dyn TS (a non-standard RSL Chan Activ mod); hence, for IPA style dyn + * TS, the chan_nr will never reflect 0xc0 and we would omit the + * PDTCH,PTTCH SAPIs. To properly de-/activate the PDTCH SAPIs in + * scheduler.c, make sure the 0xc0 cbits are set for de-/activating PDTCH + * lchans, i.e. both Osmocom and IPA style dyn TS. (For Osmocom style dyn + * TS, the chan_nr typically already reflects 0xc0, while it doesn't for + * IPA style.) */ + if (lchan->type == GSM_LCHAN_PDTCH) + chan_nr = RSL_CHAN_OSMO_PDCH | (chan_nr & ~RSL_CHAN_NR_MASK); + + /* activate dedicated channel */ + trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_DEDIC, 1); + /* activate associated channel */ + trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_SACCH, 1); + /* set mode */ + trx_sched_set_mode(&l1h->l1s, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_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->l1s, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_mode[3].mode, + amr_get_initial_mode(lchan), + 0); + break; + } + /* here, type == PRIM_INFO_DEACTIVATE */ + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " + "chan_nr 0x%02x\n", chan_nr); + break; + } + /* deactivate associated channel */ + bts_model_lchan_deactivate_sacch(lchan); + if (!l1sap->u.info.u.act_req.sacch_only) { + /* deactivate dedicated channel */ + lchan_deactivate(lchan); + /* confirm only on dedicated channel */ + mph_info_chan_confirm(l1h, chan_nr, + PRIM_INFO_DEACTIVATE, 0); + } + 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; + LOGP(DOML, LOGL_DEBUG, "bts_model_opstart: %s received\n", + get_value_string(abis_nm_obj_class_names, mo->obj_class)); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + /* activate transceiver */ + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + 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, NM_AVSTATE_OK); + 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; +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ +#warning "implement bts_model_change_power\n" + LOGP(DL1C, LOGL_NOTICE, "Setting TRX output power not supported!\n"); + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + /* no action required, signal completion right away. */ + cb_ts_disconnected(ts); + return 0; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + LOGP(DL1C, LOGL_DEBUG, "%s bts_model_ts_connect(as_pchan=%s)\n", + gsm_ts_name(ts), gsm_pchan_name(as_pchan)); + + rc = trx_set_ts_as_pchan(ts, as_pchan); + if (rc) + cb_ts_connected(ts, rc); + + LOGP(DL1C, LOGL_NOTICE, "%s bts_model_ts_connect(as_pchan=%s) success," + " calling cb_ts_connected()\n", + gsm_ts_name(ts), gsm_pchan_name(as_pchan)); + cb_ts_connected(ts, 0); +} diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h new file mode 100644 index 00000000..165f9d81 --- /dev/null +++ b/src/osmo-bts-trx/l1_if.h @@ -0,0 +1,82 @@ +#ifndef L1_IF_H_TRX +#define L1_IF_H_TRX + +#include <osmo-bts/scheduler.h> +#include <osmo-bts/phy_link.h> +#include "trx_if.h" + +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; + uint8_t rxgain; + int rxgain_sent; + + int power_valid; + uint8_t power; + int power_oml; + int power_sent; + + int maxdly_valid; + int maxdly; + int maxdly_sent; + + int maxdlynb_valid; + int maxdlynb; + int maxdlynb_sent; + + uint8_t slotmask; + + int slottype_valid[TRX_NR_TS]; + uint8_t slottype[TRX_NR_TS]; + int slottype_sent[TRX_NR_TS]; +}; + +struct trx_l1h { + struct llist_head trx_ctrl_list; + /* Latest RSPed cmd, used to catch duplicate RSPs from sent retransmissions */ + struct trx_ctrl_msg *last_acked; + + //struct gsm_bts_trx *trx; + struct phy_instance *phy_inst; + + 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 ho_rach_detect[TRX_NR_TS][TS_MAX_LCHAN]; + + struct l1sched_trx l1s; +}; + +struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst); +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); +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, int16_t toa256); + +static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx->role_bts.l1h; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + return &l1h->l1s; +} + +#endif /* L1_IF_H_TRX */ diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c new file mode 100644 index 00000000..926b4c6f --- /dev/null +++ b/src/osmo-bts-trx/loops.c @@ -0,0 +1,340 @@ +/* Loop control for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/l1sap.h> +#include <osmocom/core/bits.h> + +#include "trx_if.h" +#include "l1_if.h" +#include "loops.h" + +/* + * MS Power loop + */ + +static int ms_power_diff(struct gsm_lchan *lchan, uint8_t chan_nr, int8_t diff) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + enum gsm_band band = trx->bts->band; + uint16_t arfcn = trx->arfcn; + int8_t new_power; + + new_power = lchan->ms_power_ctrl.current - (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 (arfcn >= 512 && 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_ctrl.current + MS_LOWER_MAX) + new_power = lchan->ms_power_ctrl.current + MS_LOWER_MAX; + else if (new_power < lchan->ms_power_ctrl.current - MS_RAISE_MAX) + new_power = lchan->ms_power_ctrl.current - MS_RAISE_MAX; + + if (lchan->ms_power_ctrl.current == new_power) { + LOGP(DLOOP, LOGL_INFO, "Keeping MS new_power of trx=%u " + "chan_nr=0x%02x at control level %d (%d dBm)\n", + trx->nr, chan_nr, new_power, + ms_pwr_dbm(band, 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", + trx->nr, chan_nr, lchan->ms_power_ctrl.current, + ms_pwr_dbm(band, lchan->ms_power_ctrl.current), new_power, + ms_pwr_dbm(band, new_power)); + + lchan->ms_power_ctrl.current = new_power; + + return 0; +} + +static int ms_power_val(struct l1sched_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 gsm_lchan *lchan, + uint8_t chan_nr, struct l1sched_chan_state *chan_state) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct phy_instance *pinst = trx_phy_instance(trx); + 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", + trx->nr, chan_nr); + return ms_power_diff(lchan, chan_nr, 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, + pinst->phy_link->u.osmotrx.trx_target_rssi, lchan->ms_power_ctrl.current, + ms_pwr_dbm(trx->bts->band, lchan->ms_power_ctrl.current), + trx->nr, chan_nr); + ms_power_diff(lchan, chan_nr, pinst->phy_link->u.osmotrx.trx_target_rssi - rssi); + + return 0; +} + + +/* 90% of one bit duration in 1/256 symbols: 256*0.9 */ +#define TOA256_9OPERCENT 230 + +/* + * Timing Advance loop + */ + +int ta_val(struct gsm_lchan *lchan, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int16_t toa256) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + + /* 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.toa256_sum += toa256; + if (++(chan_state->meas.toa_num) < 16) + return 0; + + /* complete set */ + toa256 = chan_state->meas.toa256_sum / chan_state->meas.toa_num; + + /* check for change of TOA */ + if (toa256 < -TOA256_9OPERCENT && lchan->rqd_ta > 0) { + LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " + "early (%d), now lowering TA from %d to %d\n", + trx->nr, chan_nr, toa256, lchan->rqd_ta, + lchan->rqd_ta - 1); + lchan->rqd_ta--; + } else if (toa256 > TOA256_9OPERCENT && lchan->rqd_ta < 63) { + LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " + "late (%d), now raising TA from %d to %d\n", + trx->nr, chan_nr, toa256, 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 (%d), keeping current TA of %d\n", + trx->nr, chan_nr, toa256, lchan->rqd_ta); + + chan_state->meas.toa_num = 0; + chan_state->meas.toa256_sum = 0; + + return 0; +} + +int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa256) +{ + struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] + .lchan[l1sap_chan2ss(chan_nr)]; + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + + if (pinst->phy_link->u.osmotrx.trx_ms_power_loop) + ms_power_val(chan_state, rssi); + + if (pinst->phy_link->u.osmotrx.trx_ta_loop) + ta_val(lchan, chan_nr, chan_state, toa256); + + return 0; +} + +int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state) +{ + struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] + .lchan[l1sap_chan2ss(chan_nr)]; + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + + if (pinst->phy_link->u.osmotrx.trx_ms_power_loop) + ms_power_clock(lchan, chan_nr, chan_state); + + /* count the number of SACCH clocks */ + chan_state->meas.clock++; + + return 0; +} + +int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float ber) +{ + struct gsm_bts_trx *trx = l1t->trx; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)] + .lchan[l1sap_chan2ss(chan_nr)]; + + /* 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; + + /* 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, 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.bts_mode[chan_state->dl_cmr-1].threshold) { + 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, 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.bts_mode[chan_state->dl_cmr].threshold + - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].hysteresis) { + 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, trx->nr, chan_nr); + chan_state->dl_cmr++; + } + + return 0; + } + + return 0; +} + +int trx_loop_amr_set(struct l1sched_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; +} diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h new file mode 100644 index 00000000..f9e69c84 --- /dev/null +++ b/src/osmo-bts-trx/loops.h @@ -0,0 +1,27 @@ +#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 2 + +/* + * loops api + */ + +int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa); + +int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state); + +int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float ber); + +int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop); + +#endif /* _TRX_LOOPS_H */ diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c new file mode 100644 index 00000000..9529190e --- /dev/null +++ b/src/osmo-bts-trx/main.c @@ -0,0 +1,151 @@ +/* Main program for OsmoBTS-TRX */ + +/* (C) 2011-2015 by Harald Welte <laforge@gnumonks.org> + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sched.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/bits.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/control_if.h> +#include <osmo-bts/scheduler.h> + +#include "l1_if.h" +#include "trx_if.h" + +/* dummy, since no direct dsp support */ +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +void bts_model_print_help() +{ +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "", + long_options, &option_idx); + + if (c == -1) + break; + + switch (c) { + default: + num_errors++; + break; + } + } + + return num_errors; +} + +int bts_model_init(struct gsm_bts *bts) +{ + bts->variant = BTS_OSMO_TRX; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + /* FIXME: this needs to be overridden with the real hardrware + * value */ + bts->c0->nominal_power = 23; + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_CBCH); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ + plink->u.osmotrx.local_ip = talloc_strdup(plink, "127.0.0.1"); + plink->u.osmotrx.remote_ip = talloc_strdup(plink, "127.0.0.1"); + plink->u.osmotrx.base_port_local = 5800; + plink->u.osmotrx.base_port_remote = 5700; + plink->u.osmotrx.clock_advance = 20; + plink->u.osmotrx.rts_advance = 5; + plink->u.osmotrx.trx_ta_loop = true; + plink->u.osmotrx.trx_ms_power_loop = false; + plink->u.osmotrx.trx_target_rssi = -10; +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + struct trx_l1h *l1h; + l1h = trx_l1h_alloc(tall_bts_ctx, pinst); + pinst->u.osmotrx.hdl = l1h; + + l1h->config.power_oml = 1; +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c new file mode 100644 index 00000000..fa3aed22 --- /dev/null +++ b/src/osmo-bts-trx/scheduler_trx.c @@ -0,0 +1,1635 @@ +/* Scheduler worker functions for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <inttypes.h> +#include <sys/timerfd.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer_compat.h> +#include <osmocom/codec/codec.h> +#include <osmocom/codec/ecu.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/a5.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +#include "l1_if.h" +#include "trx_if.h" +#include "loops.h" + +extern void *tall_bts_ctx; + +/* Maximum size of a EGPRS message in bytes */ +#define EGPRS_0503_MAX_BYTES 155 + + +/* Compute the bit error rate in 1/10000 units */ +static inline uint16_t compute_ber10k(int n_bits_total, int n_errors) +{ + if (n_bits_total == 0) + return 10000; + else + return 10000 * n_errors / n_bits_total; +} + +/* + * TX on downlink + */ + +/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting IDLE\n"); + + if (nbits) + *nbits = GSM_BURST_LEN; + + return NULL; +} + +/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */ +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n"); + + if (nbits) + *nbits = GSM_BURST_LEN; + + /* BURST BYPASS */ + + return (ubit_t *) _sched_fcch_burst; +} + +/* obtain a to-be-transmitted SCH (synchronization channel) burst */ +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + static ubit_t bits[GSM_BURST_LEN], burst[78]; + uint8_t sb_info[4]; + struct gsm_time t; + uint8_t t3p, bsic; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n"); + + /* BURST BYPASS */ + + /* create SB info from GSM time and BSIC */ + gsm_fn2gsmtime(&t, fn); + t3p = t.t3 / 10; + bsic = l1t->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 */ + gsm0503_sch_encode(burst, sb_info); + + /* compose burst */ + memset(bits, 0, 3); + memcpy(bits + 3, burst, 39); + memcpy(bits + 42, _sched_sch_train, 64); + memcpy(bits + 106, burst + 39, 39); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + return bits; +} + +/* obtain a to-be-transmitted data (SACCH/SDCCH) burst */ +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + uint8_t link_id = trx_chan_desc[chan].link_id; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; + static ubit_t bits[GSM_BURST_LEN]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* send clock information to loops process */ + if (L1SAP_IS_LINK_SACCH(link_id)) + trx_loop_sacch_clock(l1t, chan_nr, &l1ts->chan_state[chan]); + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg) + goto got_msg; + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n"); + +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) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! " + "(len=%d)\n", msgb_l2len(msg)); + /* free message */ + msgb_free(msg); + goto no_msg; + } + + /* BURST BYPASS */ + + /* handle loss detection of SACCH */ + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { + /* count and send BFI */ + if (++(l1ts->chan_state[chan].lost_frames) > 1) { + /* TODO: Should we pass old TOA here? Otherwise we risk + * unnecessary decreasing TA */ + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, + 456, 456, -110, 0); + /* FIXME: use actual values for BER etc */ + _sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0, + -110, 0, 0, 10000, + PRES_INFO_INVALID); + } + } + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + gsm0503_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, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + +/* obtain a to-be-transmitted PDTCH (packet data) burst */ +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; + enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type; + static ubit_t bits[EGPRS_BURST_LEN]; + int rc = 0; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg) + goto got_msg; + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n"); + +no_msg: + /* free burst memory */ + if (*bursts_p) { + talloc_free(*bursts_p); + *bursts_p = NULL; + } + return NULL; + +got_msg: + /* BURST BYPASS */ + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, + GSM0503_EGPRS_BURSTS_NBITS); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); + if (rc < 0) + rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); + + /* check validity of message */ + if (rc < 0) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! " + "(len=%ld)\n", msg->tail - msg->l2h); + /* free message */ + msgb_free(msg); + goto no_msg; + } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) { + *burst_type = TRX_BURST_8PSK; + } else { + *burst_type = TRX_BURST_GMSK; + } + + /* free message */ + msgb_free(msg); + +send_burst: + /* compose burst */ + if (*burst_type == TRX_BURST_8PSK) { + burst = *bursts_p + bid * 348; + memset(bits, 1, 9); + memcpy(bits + 9, burst, 174); + memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78); + memcpy(bits + 261, burst + 174, 174); + memset(bits + 435, 1, 9); + + if (nbits) + *nbits = EGPRS_BURST_LEN; + } else { + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + } + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + +/* determine if the FN is transmitting a CMR (1) or not (0) */ +static inline int fn_is_codec_mode_request(uint32_t fn) +{ + return (((fn + 4) % 26) >> 2) & 1; +} + +/* common section for generation of TCH bursts (TCH/H and TCH/F) */ +static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, + struct msgb **_msg_facch) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[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 + && ++(chan_state->lost_frames) > 5) { + uint8_t tch_data[GSM_FR_BYTES]; + int len; + + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Missing TCH bursts detected, sending BFI\n"); + + /* 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, GSM_FR_BYTES); + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode1; + memset(tch_data, 0, GSM_EFR_BYTES); + len = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], AMR_BAD); + if (len < 2) + break; + memset(tch_data + 2, 0, len - 2); + _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len); + break; + default: +inval_mode1: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + len = 0; + } + if (len) + _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len); + } + + /* get frame and unlink from queue */ + msg1 = _sched_dequeue_prim(l1t, tn, fn, chan); + msg2 = _sched_dequeue_prim(l1t, 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) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "TCH twice, please FIX!\n"); + msgb_free(msg2); + } else + msg_facch = msg2; + } + } else { + msg_facch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive != PRIM_TCH) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "FACCH twice, please FIX!\n"); + 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) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "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 cmr_codec; + int cmr, ft, i; + enum osmo_amr_type ft_codec; + enum osmo_amr_quality bfi; + int8_t sti, cmi; + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, " + "because we are not in speech mode\n"); + goto free_bad_msg; + } + + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (chan != TRXC_TCHF) /* HR */ + len = 15; + else + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode2; + len = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch), + &cmr_codec, &cmi, &ft_codec, + &bfi, &sti); + 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) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "Codec (FT = %d) of RTP frame not in list\n", ft_codec); + goto free_bad_msg; + } + if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) " + " of RTP cannot be changed now, but in next frame\n", ft_codec); + goto free_bad_msg; + } + chan_state->dl_ft = ft; + if (bfi == AMR_BAD) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad AMR frame'\n"); + goto free_bad_msg; + } + break; + default: +inval_mode2: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + goto free_bad_msg; + } + if (len < 0) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n"); + goto free_bad_msg; + } + if (msgb_l2len(msg_tch) != len) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with " + "invalid length! (expecting %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; +} + +/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */ +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t tch_mode = chan_state->tch_mode; + ubit_t *burst, **bursts_p = &chan_state->dl_bursts; + static ubit_t bits[GSM_BURST_LEN]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch); + + /* BURST BYPASS */ + + /* allocate 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) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n"); + goto send_burst; + } + + /* encode bursts (prioritize FACCH) */ + if (msg_facch) + gsm0503_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. + */ + gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2, + msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn), + chan_state->codec, chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + else + gsm0503_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, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + +/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */ +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t tch_mode = chan_state->tch_mode; + ubit_t *burst, **bursts_p = &chan_state->dl_bursts; + static ubit_t bits[GSM_BURST_LEN]; + + /* 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(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch); + + /* check for FACCH alignment */ + if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on " + "even frames, please fix RTS!\n"); + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* BURST BYPASS */ + + /* allocate 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) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n"); + goto send_burst; + } + + /* encode bursts (prioritize FACCH) */ + if (msg_facch) { + gsm0503_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. */ + gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2, + msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn), + chan_state->codec, chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + else + gsm0503_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, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + + +/* + * RX on uplink (indication to upper layer) + */ + +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + uint8_t chan_nr; + struct osmo_phsap_prim l1sap; + int n_errors, n_bits_total; + uint8_t ra; + int rc; + + chan_nr = trx_chan_desc[chan].chan_nr | tn; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received RACH toa=%d\n", toa256); + + /* decode */ + rc = gsm0503_rach_decode_ber(&ra, bits + 8 + 41, l1t->trx->bts->bsic, &n_errors, &n_bits_total); + if (rc) { + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad AB frame\n"); + 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; + l1sap.u.rach_ind.acc_delay = (toa256 >= 0) ? toa256/256 : 0; + l1sap.u.rach_ind.fn = fn; + + /* 11bit RACH is not supported for osmo-trx */ + l1sap.u.rach_ind.is_11bit = 0; + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0; + l1sap.u.rach_ind.rssi = rssi; + l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors); + l1sap.u.rach_ind.acc_delay_256bits = toa256; + + /* forward primitive */ + l1sap_up(l1t->trx, &l1sap); + + return 0; +} + +/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */ +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[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; + int32_t *toa256_sum = &chan_state->toa256_sum; + uint8_t *toa_num = &chan_state->toa_num; + uint8_t l2[GSM_MACBLOCK_LEN], l2_len; + int n_errors, n_bits_total; + uint16_t ber10k; + int rc; + + /* handle RACH, if handover RACH detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256); + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received Data, bid=%u\n", bid); + + /* allocate 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; + *toa256_sum = 0; + *toa_num = 0; + } + + /* update mask + RSSI */ + *mask |= (1 << bid); + *rssi_sum += rssi; + (*rssi_num)++; + *toa256_sum += toa256; + (*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(l1t, trx_chan_desc[chan].chan_nr | tn, + chan_state, rssi, toa256); + } + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete data (%u/%u)\n", + *first_fn, (*first_fn) % l1ts->mf_period); + + /* we require first burst to have correct FN */ + if (!(*mask & 0x1)) { + *mask = 0x0; + return 0; + } + } + *mask = 0x0; + + /* decode */ + rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total); + if (rc) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n", + *first_fn, (*first_fn) % l1ts->mf_period); + l2_len = 0; + } else + l2_len = GSM_MACBLOCK_LEN; + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn, + n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num); + ber10k = compute_ber10k(n_bits_total, n_errors); + return _sched_compose_ph_data_ind(l1t, tn, *first_fn, chan, l2, l2_len, + *rssi_sum / *rssi_num, + 4 * (*toa256_sum) / *toa_num, 0, ber10k, + PRES_INFO_UNKNOWN); +} + +/*! \brief a single PDTCH burst was received by the PHY, process it */ +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[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; + int32_t *toa256_sum = &chan_state->toa256_sum; + uint8_t *toa_num = &chan_state->toa_num; + uint8_t l2[EGPRS_0503_MAX_BYTES]; + int n_errors, n_bursts_bits, n_bits_total; + uint16_t ber10k; + int rc; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received PDTCH bid=%u\n", bid); + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, + GSM0503_EGPRS_BURSTS_NBITS); + if (!*bursts_p) + return -ENOMEM; + } + + /* clear burst */ + if (bid == 0) { + memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS); + *mask = 0x0; + *first_fn = fn; + *rssi_sum = 0; + *rssi_num = 0; + *toa256_sum = 0; + *toa_num = 0; + } + + /* update mask + rssi */ + *mask |= (1 << bid); + *rssi_sum += rssi; + (*rssi_num)++; + *toa256_sum += toa256; + (*toa_num)++; + + /* copy burst to buffer of 4 bursts */ + if (nbits == EGPRS_BURST_LEN) { + burst = *bursts_p + bid * 348; + memcpy(burst, bits + 9, 174); + memcpy(burst + 174, bits + 261, 174); + n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS; + } else { + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS; + } + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + } + *mask = 0x0; + + /* + * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we + * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS, + * then we incur decoding overhead of 31 bits on the Type 3 EGPRS + * header, which is tolerable. + */ + rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits, + NULL, &n_errors, &n_bits_total); + + if ((nbits == GSM_BURST_LEN) && (rc < 0)) { + rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL, + &n_errors, &n_bits_total); + } + + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn, + n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num); + + if (rc <= 0) { + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad PDTCH (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + return 0; + } + ber10k = compute_ber10k(n_bits_total, n_errors); + return _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 3) % GSM_HYPERFRAME, chan, + l2, rc, *rssi_sum / *rssi_num, 4 * (*toa256_sum) / *toa_num, 0, + ber10k, PRES_INFO_BOTH); +} + +/*! \brief a single TCH/F burst was received by the PHY, process it */ +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[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; + 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; + bool bfi_flag = false; + struct gsm_lchan *lchan = + get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn); + + /* handle rach, if handover rach detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256); + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/F, bid=%u\n", bid); + + /* allocate 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; + *first_fn = fn; + } + + /* 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) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + } + *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 = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total); + if (rc >= 0) + lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */ + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + rc = gsm0503_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 = gsm0503_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(l1t, + 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 != GSM_MACBLOCK_LEN && rc >= 4) { + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->ul_cmr], + chan_state->codec[chan_state->ul_ft], AMR_GOOD); + } + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n", + tch_mode); + return -EINVAL; + } + memcpy(*bursts_p, *bursts_p + 464, 464); + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn, + n_errors, n_bits_total, rssi, toa256); + + /* Check if the frame is bad */ + if (rc < 0) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + bfi_flag = true; + goto bfi; + } + if (rc < 4) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) " + "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc); + bfi_flag = true; + goto bfi; + } + + /* FACCH */ + if (rc == GSM_MACBLOCK_LEN) { + uint16_t ber10k = compute_ber10k(n_bits_total, n_errors); + _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, + tch_data + amr, GSM_MACBLOCK_LEN, rssi, 4 * toa256, 0, + ber10k, PRES_INFO_UNKNOWN); +bfi: + if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR */ + if (lchan->tch.dtx.ul_sid) { + /* DTXu: pause in progress. Push empty payload to upper layers */ + rc = 0; + goto compose_l1sap; + } + + /* Perform error concealment if possible */ + rc = osmo_ecu_fr_conceal(&lchan->ecu_state.fr, tch_data); + if (rc) { + memset(tch_data, 0, GSM_FR_BYTES); + tch_data[0] = 0xd0; + } + + rc = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + memset(tch_data, 0, GSM_EFR_BYTES); + tch_data[0] = 0xc0; + rc = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], + AMR_BAD); + if (rc < 2) + break; + memset(tch_data + 2, 0, rc - 2); + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "TCH mode %u invalid, please fix!\n", tch_mode); + return -EINVAL; + } + } + } + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) + return 0; + + /* Reset ECU with a good frame */ + if (!bfi_flag && tch_mode == GSM48_CMODE_SPEECH_V1) + osmo_ecu_fr_reset(&lchan->ecu_state.fr, tch_data); + + /* TCH or BFI */ +compose_l1sap: + return _sched_compose_tch_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, + tch_data, rc); +} + +/*! \brief a single TCH/H burst was received by the PHY, process it */ +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[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; + 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; + struct gsm_lchan *lchan = + get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn); + /* 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 + */ + int fn_is_odd = (((fn + 26 - 10) % 26) >> 2) & 1; + + /* handle RACH, if handover RACH detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256); + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/H, bid=%u\n", bid); + + /* allocate 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; + *first_fn = fn; + } + + /* 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) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + } + *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 = gsm0503_tch_hr_decode(tch_data, *bursts_p, + fn_is_odd, &n_errors, &n_bits_total); + if (rc) /* DTXu */ + lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan); + 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 = gsm0503_tch_ahs_decode(tch_data + 2, *bursts_p, + fn_is_odd, fn_is_odd, 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(l1t, + 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 != GSM_MACBLOCK_LEN && rc >= 4) { + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->ul_cmr], + chan_state->codec[chan_state->ul_ft], AMR_GOOD); + } + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "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 uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn, + n_errors, n_bits_total, rssi, toa256); + + /* Check if the frame is bad */ + if (rc < 0) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + goto bfi; + } + if (rc < 4) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) " + "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc); + goto bfi; + } + + /* FACCH */ + if (rc == GSM_MACBLOCK_LEN) { + chan_state->ul_ongoing_facch = 1; + uint16_t ber10k = compute_ber10k(n_bits_total, n_errors); + _sched_compose_ph_data_ind(l1t, tn, + (fn + GSM_HYPERFRAME - 10 - ((fn % 26) >= 19)) % GSM_HYPERFRAME, chan, + tch_data + amr, GSM_MACBLOCK_LEN, rssi, toa256/64, 0, + ber10k, PRES_INFO_UNKNOWN); +bfi: + if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* HR */ + if (lchan->tch.dtx.ul_sid) { + /* DTXu: pause in progress. Push empty payload to upper layers */ + rc = 0; + goto compose_l1sap; + } + tch_data[0] = 0x70; /* F = 0, FT = 111 */ + memset(tch_data + 1, 0, 14); + rc = 15; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], + AMR_BAD); + if (rc < 2) + break; + memset(tch_data + 2, 0, rc - 2); + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "TCH mode %u invalid, please fix!\n", tch_mode); + return -EINVAL; + } + } + } + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) + return 0; + +compose_l1sap: + /* 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 subtracted to get correct + * start of frame. + */ + return _sched_compose_tch_ind(l1t, tn, + (fn + GSM_HYPERFRAME - 10 - ((fn%26)==19) - ((fn%26)==20)) % GSM_HYPERFRAME, + chan, tch_data, rc); +} + +/* schedule all frames of all TRX for given FN */ +static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + const ubit_t *bits; + uint8_t gain; + uint16_t nbits = 0; + + /* send time indication */ + l1if_mph_time_ind(bts, fn); + + /* process every TRX */ + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + struct l1sched_trx *l1t = &l1h->l1s; + + /* advance frame number, so the transceiver has more + * time until it must be transmitted. */ + fn = (fn + plink->u.osmotrx.clock_advance) % GSM_HYPERFRAME; + + /* we don't schedule, if power is off */ + if (!trx_if_powered(l1h)) + continue; + + /* process every TS of TRX */ + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + /* ready-to-send */ + _sched_rts(l1t, tn, + (fn + plink->u.osmotrx.rts_advance) % GSM_HYPERFRAME); + /* get burst for FN */ + bits = _sched_dl_burst(l1t, tn, fn, &nbits); + if (!bits) { + /* if no bits, send no burst */ + continue; + } else + gain = 0; + if (nbits) + trx_if_send_burst(l1h, tn, fn, gain, bits, nbits); + } + } + + return 0; +} + +/* + * TRX frame clock handling + * + * In a "normal" synchronous PHY layer, we would be polled every time + * the PHY needs data for a given frame number. However, the + * OpenBTS-inherited TRX protocol works differently: We (L1) must + * autonomously send burst data based on our own clock, and every so + * often (currently every ~ 216 frames), we get a clock indication from + * the TRX. + * + * We're using a MONOTONIC timerfd interval timer for the 4.615ms frame + * intervals, and then compute + send the 8 bursts for that frame. + * + * Upon receiving a clock indication from the TRX, we compensate + * accordingly: If we were transmitting too fast, we're delaying the + * next interval timer accordingly. If we were too slow, we immediately + * send burst data for the missing frame numbers. + */ + +/*! clock state of a given TRX */ +struct osmo_trx_clock_state { + /*! number of FN periods without TRX clock indication */ + uint32_t fn_without_clock_ind; + struct { + /*! last FN we processed based on FN period timer */ + uint32_t fn; + /*! time at which we last processed FN */ + struct timespec tv; + } last_fn_timer; + struct { + /*! last FN we received a clock indication for */ + uint32_t fn; + /*! time at which we received the last clock indication */ + struct timespec tv; + } last_clk_ind; + /*! Osmocom FD wrapper for timerfd */ + struct osmo_fd fn_timer_ofd; +}; + +/* TODO: This must go and become part of the phy_link */ +static struct osmo_trx_clock_state g_clk_s = { .fn_timer_ofd.fd = -1 }; + +/*! duration of a GSM frame in nano-seconds. (120ms/26) */ +#define FRAME_DURATION_nS 4615384 +/*! duration of a GSM frame in micro-seconds (120s/26) */ +#define FRAME_DURATION_uS (FRAME_DURATION_nS/1000) +/*! maximum number of 'missed' frame periods we can tolerate of OS doesn't schedule us*/ +#define MAX_FN_SKEW 50 +/*! maximum number of frame periods we can tolerate without TRX Clock Indication*/ +#define TRX_LOSS_FRAMES 400 + +/*! compute the number of micro-seconds difference elapsed between \a last and \a now */ +static inline int64_t compute_elapsed_us(const struct timespec *last, const struct timespec *now) +{ + struct timespec elapsed; + + timespecsub(now, last, &elapsed); + return (int64_t)(elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000); +} + +/*! compute the number of frame number intervals elapsed between \a last and \a now */ +static inline int compute_elapsed_fn(const uint32_t last, const uint32_t now) +{ + int elapsed_fn = (now + GSM_HYPERFRAME - last) % GSM_HYPERFRAME; + if (elapsed_fn >= 135774) + elapsed_fn -= GSM_HYPERFRAME; + return elapsed_fn; +} + +/*! normalise given 'struct timespec', i.e. carry nanoseconds into seconds */ +static inline void normalize_timespec(struct timespec *ts) +{ + ts->tv_sec += ts->tv_nsec / 1000000000; + ts->tv_nsec = ts->tv_nsec % 1000000000; +} + +/*! Increment a GSM frame number modulo GSM_HYPERFRAME */ +#define INCREMENT_FN(fn) (fn) = (((fn) + 1) % GSM_HYPERFRAME) + +extern int quit; + +/*! this is the timerfd-callback firing for every FN to be processed */ +static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct gsm_bts *bts = ofd->data; + struct osmo_trx_clock_state *tcs = &g_clk_s; + struct timespec tv_now; + uint64_t expire_count; + int64_t elapsed_us, error_us; + int rc, i; + + if (!(what & BSC_FD_READ)) + return 0; + + /* read from timerfd: number of expirations of periodic timer */ + rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count)); + if (rc < 0 && errno == EAGAIN) + return 0; + OSMO_ASSERT(rc == sizeof(expire_count)); + + if (expire_count > 1) { + LOGP(DL1C, LOGL_NOTICE, "FN timer expire_count=%"PRIu64": We missed %"PRIu64" timers\n", + expire_count, expire_count-1); + } + + /* check if transceiver is still alive */ + if (tcs->fn_without_clock_ind++ == TRX_LOSS_FRAMES) { + LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n"); + goto no_clock; + } + + /* compute actual elapsed time and resulting OS scheduling error */ + clock_gettime(CLOCK_MONOTONIC, &tv_now); + elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now); + error_us = elapsed_us - FRAME_DURATION_uS; +#ifdef DEBUG_CLOCK + printf("%s(): %09ld, elapsed_us=%05" PRId64 ", error_us=%-d: fn=%d\n", __func__, + tv_now.tv_nsec, elapsed_us, error_us, tcs->last_fn_timer.fn+1); +#endif + tcs->last_fn_timer.tv = tv_now; + + /* if someone played with clock, or if the process stalled */ + if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) { + LOGP(DL1C, LOGL_ERROR, "PC clock skew: elapsed_us=%" PRId64 ", error_us=%" PRId64 "\n", + elapsed_us, error_us); + goto no_clock; + } + + /* call trx_sched_fn() for all expired FN */ + for (i = 0; i < expire_count; i++) { + INCREMENT_FN(tcs->last_fn_timer.fn); + trx_sched_fn(bts, tcs->last_fn_timer.fn); + } + + return 0; + +no_clock: + osmo_timerfd_disable(&tcs->fn_timer_ofd); + transceiver_available = 0; + + bts_shutdown(bts, "No clock from osmo-trx"); + + return -1; +} + +/*! reset clock with current fn and schedule it. Called when trx becomes + * available or when max clock skew is reached */ +static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs, + struct timespec *tv_now, const struct timespec *interval, uint32_t fn) +{ + tcs->last_fn_timer.fn = fn; + /* call trx cheduler function for new 'last' FN */ + trx_sched_fn(bts, tcs->last_fn_timer.fn); + + /* schedule first FN clock timer */ + osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_fn_timer_cb, bts); + osmo_timerfd_schedule(&tcs->fn_timer_ofd, NULL, interval); + + tcs->last_fn_timer.tv = *tv_now; + tcs->last_clk_ind.tv = *tv_now; + tcs->last_clk_ind.fn = fn; + + return 0; +} + +/*! called every time we receive a clock indication from TRX */ +int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) +{ + struct osmo_trx_clock_state *tcs = &g_clk_s; + struct timespec tv_now; + int elapsed_fn; + int64_t elapsed_us, elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk; + unsigned int fn_caught_up = 0; + const struct timespec interval = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_nS }; + + if (quit) + return 0; + + /* reset lost counter */ + tcs->fn_without_clock_ind = 0; + + clock_gettime(CLOCK_MONOTONIC, &tv_now); + + /* 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); + + return trx_setup_clock(bts, tcs, &tv_now, &interval, fn); + } + + /* calculate elapsed time +fn since last timer */ + elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now); + elapsed_fn = compute_elapsed_fn(tcs->last_fn_timer.fn, fn); +#ifdef DEBUG_CLOCK + printf("%s(): LAST_TIMER %9ld, elapsed_us=%7d, elapsed_fn=%+3d\n", __func__, + tv_now.tv_nsec, elapsed_us, elapsed_fn); +#endif + /* negative elapsed_fn values mean that we've already processed + * more FN based on the local interval timer than what the TRX + * now reports in the clock indication. Positive elapsed_fn + * values mean we still have a backlog to process */ + + /* calculate elapsed time +fn since last clk ind */ + elapsed_us_since_clk = compute_elapsed_us(&tcs->last_clk_ind.tv, &tv_now); + elapsed_fn_since_clk = compute_elapsed_fn(tcs->last_clk_ind.fn, fn); + /* error (delta) between local clock since last CLK and CLK based on FN clock at TRX */ + error_us_since_clk = elapsed_us_since_clk - (FRAME_DURATION_uS * elapsed_fn_since_clk); + LOGP(DL1C, LOGL_INFO, "TRX Clock Ind: elapsed_us=%7"PRId64", " + "elapsed_fn=%3"PRId64", error_us=%+5"PRId64"\n", + elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk); + + /* TODO: put this computed error_us_since_clk into some filter + * function and use that to adjust our regular timer interval to + * compensate for clock drift between the PC clock and the + * TRX/SDR clock */ + + tcs->last_clk_ind.tv = tv_now; + tcs->last_clk_ind.fn = fn; + + /* 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", tcs->last_fn_timer.fn, fn); + return trx_setup_clock(bts, tcs, &tv_now, &interval, fn); + } + + LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %" PRId64 "us (elapsed_fn=%d)\n", + elapsed_fn * FRAME_DURATION_uS - elapsed_us, elapsed_fn); + + /* too many frames have been processed already */ + if (elapsed_fn < 0) { + struct timespec first = interval; + /* set clock to the time or last FN should have been + * transmitted. */ + first.tv_nsec += (0 - elapsed_fn) * FRAME_DURATION_nS; + normalize_timespec(&first); + LOGP(DL1C, LOGL_NOTICE, "We were %d FN faster than TRX, compensating\n", -elapsed_fn); + /* set time to the time our next FN has to be transmitted */ + osmo_timerfd_schedule(&tcs->fn_timer_ofd, &first, &interval); + return 0; + } + + /* transmit what we still need to transmit */ + while (fn != tcs->last_fn_timer.fn) { + INCREMENT_FN(tcs->last_fn_timer.fn); + trx_sched_fn(bts, tcs->last_fn_timer.fn); + fn_caught_up++; + } + + if (fn_caught_up) { + LOGP(DL1C, LOGL_NOTICE, "We were %d FN slower than TRX, compensated\n", elapsed_fn); + tcs->last_fn_timer.tv = tv_now; + } + + return 0; +} + +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +{ + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (activate) + trx_if_cmd_handover(l1h, tn, ss); + else + trx_if_cmd_nohandover(l1h, tn, ss); +} diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c new file mode 100644 index 00000000..abe6846d --- /dev/null +++ b/src/osmo-bts-trx/trx_if.c @@ -0,0 +1,838 @@ +/* + * OpenBTS-style TRX interface/protocol handling + * + * This file contains the BTS-side implementation of the OpenBTS-style + * UDP TRX protocol. It manages the clock, control + burst-data UDP + * sockets and their respective protocol encoding/parsing. + * + * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu> + * Copyright (C) 2016-2017 Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include <netinet/in.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/bits.h> + +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/scheduler.h> + +#include "l1_if.h" +#include "trx_if.h" + +/* enable to print RSSI level graph */ +//#define TOA_RSSI_DEBUG + +int transceiver_available = 0; + +#define TRX_MAX_BURST_LEN 512 + +/* + * socket helper functions + */ + +/*! convenience wrapper to open socket + fill in osmo_fd */ +static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local, + uint16_t port_local, const char *host_remote, uint16_t port_remote, + int (*cb)(struct osmo_fd *fd, unsigned int what)) +{ + int rc; + + /* Init */ + ofd->fd = -1; + ofd->cb = cb; + ofd->data = priv; + + /* Listen / Binds + Connect */ + rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host_local, port_local, + host_remote, port_remote, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); + if (rc < 0) + return rc; + + return 0; +} + +/* close socket + unregister osmo_fd */ +static void trx_udp_close(struct osmo_fd *ofd) +{ + if (ofd->fd >= 0) { + osmo_fd_unregister(ofd); + close(ofd->fd); + ofd->fd = -1; + } +} + + +/* + * TRX clock socket + */ + +/* get clock from clock socket */ +static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct phy_link *plink = ofd->data; + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + char buf[1500]; + int len; + uint32_t fn; + + OSMO_ASSERT(pinst); + + 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; + } + + if (sscanf(buf, "IND CLOCK %u", &fn) != 1) { + LOGP(DTRX, LOGL_ERROR, "Unable to parse '%s'\n", buf); + return 0; + } + + LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn); + + if (fn >= GSM_HYPERFRAME) { + fn %= GSM_HYPERFRAME; + LOGP(DTRX, LOGL_ERROR, "Indicated clock's FN is not wrapping " + "correctly, correcting to fn=%u\n", fn); + } + + /* inform core TRX clock handling code that a FN has been received */ + trx_sched_clock(pinst->trx->bts, fn); + + return 0; +} + + +/* + * TRX ctrl socket + */ + +/* send first ctrl message and start timer */ +static void trx_ctrl_send(struct trx_l1h *l1h) +{ + struct trx_ctrl_msg *tcm; + char buf[1500]; + int len; + + /* get first command */ + if (llist_empty(&l1h->trx_ctrl_list)) + return; + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); + + len = snprintf(buf, sizeof(buf), "CMD %s%s%s", tcm->cmd, tcm->params_len ? " ":"", tcm->params); + OSMO_ASSERT(len < sizeof(buf)); + + LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to %s\n", buf, phy_instance_name(l1h->phy_inst)); + /* send command */ + send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0); + + /* start timer */ + 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; + struct trx_ctrl_msg *tcm = NULL; + + /* get first command */ + OSMO_ASSERT(!llist_empty(&l1h->trx_ctrl_list)); + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); + + LOGP(DTRX, LOGL_NOTICE, "No satisfactory response from transceiver for %s (CMD %s%s%s)\n", + phy_instance_name(l1h->phy_inst), + tcm->cmd, tcm->params_len ? " ":"", tcm->params); + + trx_ctrl_send(l1h); +} + +void trx_if_init(struct trx_l1h *l1h) +{ + l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb; + l1h->trx_ctrl_timer.data = l1h; +} + +/*! Send a new TRX control command. + * \param[inout] l1h TRX Layer1 handle to which to send command + * \param[in] criticial + * \param[in] cmd zero-terminated string containing command + * \param[in] fmt Format string (+ variable list of arguments) + * \returns 0 on success; negative on error + * + * The new ocommand will be added to the end of the control command + * queue. + */ +static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd, + const char *fmt, ...) +{ + struct trx_ctrl_msg *tcm; + struct trx_ctrl_msg *prev = NULL; + va_list ap; + int pending; + + if (!transceiver_available && + !(!strcmp(cmd, "POWEROFF") || !strcmp(cmd, "POWERON"))) { + LOGP(DTRX, LOGL_ERROR, "CTRL %s ignored: No clock from " + "transceiver, please fix!\n", cmd); + return -EIO; + } + + pending = !llist_empty(&l1h->trx_ctrl_list); + + /* create message */ + tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg); + if (!tcm) + return -ENOMEM; + snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "%s", cmd); + tcm->cmd[sizeof(tcm->cmd)-1] = '\0'; + tcm->cmd_len = strlen(tcm->cmd); + if (fmt && fmt[0]) { + va_start(ap, fmt); + vsnprintf(tcm->params, sizeof(tcm->params) - 1, fmt, ap); + va_end(ap); + tcm->params[sizeof(tcm->params)-1] = '\0'; + tcm->params_len = strlen(tcm->params); + } else { + tcm->params[0] ='\0'; + tcm->params_len = 0; + } + tcm->critical = critical; + + /* Avoid adding consecutive duplicate messages, eg: two consecutive POWEROFF */ + if(pending) + prev = llist_entry(l1h->trx_ctrl_list.prev, struct trx_ctrl_msg, list); + + if (!pending || + !(strcmp(tcm->cmd, prev->cmd) == 0 && strcmp(tcm->params, prev->params) == 0)) { + LOGP(DTRX, LOGL_INFO, "Enqueuing TRX control command 'CMD %s%s%s'\n", + tcm->cmd, tcm->params_len ? " ":"", tcm->params); + llist_add_tail(&tcm->list, &l1h->trx_ctrl_list); + } + + /* send message, if we didn't already have pending messages */ + if (!pending) + trx_ctrl_send(l1h); + + return 0; +} + +/*! Send "POWEROFF" command to TRX */ +int trx_if_cmd_poweroff(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (pinst->num == 0) + return trx_ctrl_cmd(l1h, 1, "POWEROFF", ""); + else + return 0; +} + +/*! Send "POWERON" command to TRX */ +int trx_if_cmd_poweron(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (pinst->num == 0) + return trx_ctrl_cmd(l1h, 1, "POWERON", ""); + else + return 0; +} + +/*! Send "SETTSC" command to TRX */ +int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (pinst->phy_link->u.osmotrx.use_legacy_setbsic) + return 0; + + return trx_ctrl_cmd(l1h, 1, "SETTSC", "%d", tsc); +} + +/*! Send "SETBSIC" command to TRX */ +int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic) + return 0; + + return trx_ctrl_cmd(l1h, 1, "SETBSIC", "%d", bsic); +} + +/*! Send "SETRXGAIN" command to TRX */ +int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db) +{ + return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db); +} + +/*! Send "SETPOWER" command to TRX */ +int trx_if_cmd_setpower(struct trx_l1h *l1h, int db) +{ + return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db); +} + +/*! Send "SETMAXDLY" command to TRX, i.e. maximum delay for RACH bursts */ +int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly) +{ + return trx_ctrl_cmd(l1h, 0, "SETMAXDLY", "%d", dly); +} + +/*! Send "SETMAXDLYNB" command to TRX, i.e. maximum delay for normal bursts */ +int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly) +{ + return trx_ctrl_cmd(l1h, 0, "SETMAXDLYNB", "%d", dly); +} + +/*! Send "SETSLOT" command to TRX: Configure Channel Combination for TS */ +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); +} + +/*! Send "RXTUNE" command to TRX: Tune Receiver to given ARFCN */ +int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn) +{ + struct phy_instance *pinst = l1h->phy_inst; + uint16_t freq10; + + if (pinst->trx->bts->band == GSM_BAND_1900) + arfcn |= ARFCN_PCS; + + freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */ + if (freq10 == 0xffff) { + LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", + arfcn & ~ARFCN_FLAG_MASK); + return -ENOTSUP; + } + + return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100); +} + +/*! Send "TXTUNE" command to TRX: Tune Transmitter to given ARFCN */ +int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn) +{ + struct phy_instance *pinst = l1h->phy_inst; + uint16_t freq10; + + if (pinst->trx->bts->band == GSM_BAND_1900) + arfcn |= ARFCN_PCS; + + freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */ + if (freq10 == 0xffff) { + LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", + arfcn & ~ARFCN_FLAG_MASK); + return -ENOTSUP; + } + + return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100); +} + +/*! Send "HANDOVER" command to TRX: Enable handover RACH Detection on timeslot/sub-slot */ +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); +} + +/*! Send "NOHANDOVER" command to TRX: Disable handover RACH Detection on timeslot/sub-slot */ +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); +} + +struct trx_ctrl_rsp { + char cmd[50]; + char params[100]; + int status; +}; + +static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp) +{ + char *p, *k; + + if (strncmp(buf_in, "RSP ", 4)) + goto parse_err; + + /* Get the RSP cmd name */ + if (!(p = strchr(buf_in + 4, ' '))) + goto parse_err; + + if (p - buf_in >= sizeof(rsp->cmd)) { + LOGP(DTRX, LOGL_ERROR, "cmd buffer too small %lu >= %lu\n", + p - buf_in, sizeof(rsp->cmd)); + goto parse_err; + } + + rsp->cmd[0] = '\0'; + strncat(rsp->cmd, buf_in + 4, p - buf_in - 4); + + /* Now comes the status code of the response */ + p++; + if (sscanf(p, "%d", &rsp->status) != 1) + goto parse_err; + + /* Now copy back the parameters */ + k = strchr(p, ' '); + if (k) + k++; + else + k = p + strlen(p); + + if (strlen(k) >= sizeof(rsp->params)) { + LOGP(DTRX, LOGL_ERROR, "params buffer too small %lu >= %lu\n", + strlen(k), sizeof(rsp->params)); + goto parse_err; + } + rsp->params[0] = '\0'; + strcat(rsp->params, k); + return 0; + +parse_err: + LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n", + buf_in); + return -1; +} + +static bool cmd_matches_rsp(struct trx_ctrl_msg *tcm, struct trx_ctrl_rsp *rsp) +{ + if (strcmp(tcm->cmd, rsp->cmd)) + return false; + + /* For SETSLOT we also need to check if it's the response for the + specific timeslot. For other commands such as SETRXGAIN, it is + expected that they can return different values */ + if (strcmp(tcm->cmd, "SETSLOT") == 0 && strcmp(tcm->params, rsp->params)) + return false; + + return true; +} + +/* -EINVAL: unrecoverable error, exit BTS + * N > 0: try sending originating command again after N seconds + * 0: Done with response, get originating command out from send queue + */ +static int trx_ctrl_rx_rsp(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp, bool critical) +{ + struct phy_instance *pinst = l1h->phy_inst; + + /* If TRX fails, try again after 1 sec */ + if (strcmp(rsp->cmd, "POWERON") == 0) { + if (rsp->status == 0) { + if (pinst->phy_link->state != PHY_LINK_CONNECTED) + phy_link_state_set(pinst->phy_link, PHY_LINK_CONNECTED); + return 0; + } else { + LOGP(DTRX, LOGL_NOTICE, + "transceiver (%s) rejected POWERON command (%d), re-trying in a few seconds\n", + phy_instance_name(pinst), rsp->status); + if (pinst->phy_link->state != PHY_LINK_SHUTDOWN) + phy_link_state_set(pinst->phy_link, PHY_LINK_SHUTDOWN); + return 5; + } + } + + if (rsp->status) { + LOGP(DTRX, critical ? LOGL_FATAL : LOGL_NOTICE, + "transceiver (%s) rejected TRX command with response: '%s%s%s %d'\n", + phy_instance_name(pinst), rsp->cmd, rsp->params[0] != '\0' ? " ":"", + rsp->params, rsp->status); + if (critical) + return -EINVAL; + } + return 0; +} + +/*! Get + parse response from TRX ctrl socket */ +static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct trx_l1h *l1h = ofd->data; + struct phy_instance *pinst = l1h->phy_inst; + char buf[1500]; + struct trx_ctrl_rsp rsp; + int len, rc; + struct trx_ctrl_msg *tcm; + + len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); + if (len <= 0) + return len; + buf[len] = '\0'; + + if (parse_rsp(buf, len, &rsp) < 0) + return 0; + + 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)) { + /* RSP from a retransmission, skip it */ + if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, &rsp)) { + LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP " + "from old CMD '%s'\n", buf); + return 0; + } + 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 response matches command */ + if (!cmd_matches_rsp(tcm, &rsp)) { + /* RSP from a retransmission, skip it */ + if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, &rsp)) { + LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP " + "from old CMD '%s'\n", buf); + return 0; + } + LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE, + "Response message '%s' does not match command " + "message 'CMD %s%s%s'\n", + buf, tcm->cmd, tcm->params_len ? " ":"", tcm->params); + goto rsp_error; + } + + /* check for response code */ + rc = trx_ctrl_rx_rsp(l1h, &rsp, tcm->critical); + if (rc == -EINVAL) + goto rsp_error; + + /* re-schedule last cmd in rc seconds time */ + if (rc > 0) { + osmo_timer_schedule(&l1h->trx_ctrl_timer, rc, 0); + return 0; + } + + /* remove command from list, save it to last_acked and removed previous last_acked */ + llist_del(&tcm->list); + talloc_free(l1h->last_acked); + l1h->last_acked = tcm; + + trx_ctrl_send(l1h); + + return 0; + +rsp_error: + bts_shutdown(pinst->trx->bts, "TRX-CTRL-MSG: CRITICAL"); + /* keep tcm list, so process is stopped */ + return -EIO; +} + + +/* + * TRX burst data socket + */ + +static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct trx_l1h *l1h = ofd->data; + uint8_t buf[TRX_MAX_BURST_LEN]; + int len; + uint8_t tn; + int8_t rssi; + int16_t toa256 = 0; + uint32_t fn; + sbit_t bits[EGPRS_BURST_LEN]; + int i, burst_len = GSM_BURST_LEN; + + len = recv(ofd->fd, buf, sizeof(buf), 0); + if (len <= 0) { + return len; + } else if (len == EGPRS_BURST_LEN + 10) { + burst_len = EGPRS_BURST_LEN; + /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */ + } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) { + 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]; + toa256 = ((int16_t)(buf[6] << 8) | buf[7]); + + /* copy and convert bits {254..0} to sbits {-127..127} */ + for (i = 0; i < burst_len; 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 >= GSM_HYPERFRAME) { + LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn); + return -EINVAL; + } + + LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa256=%d\n", + tn, fn, rssi, toa256); + +#ifdef TOA_RSSI_DEBUG + char deb[128]; + + sprintf(deb, "| 0 " + " | rssi=%4d toa=%5d fn=%u", rssi, toa256, fn); + deb[1 + (128 + rssi) / 4] = '*'; + fprintf(stderr, "%s\n", deb); +#endif + + /* feed received burst into scheduler code */ + trx_sched_ul_burst(&l1h->l1s, tn, fn, bits, burst_len, rssi, toa256); + + return 0; +} + +/*! Send burst data for given FN/timeslot to TRX + * \param[inout] l1h TRX Layer1 handle referring to TX + * \param[in] tn Timeslot Number (0..7) + * \param[in] fn GSM Frame Number + * \param[in] pwr Transmit Power to use + * \param[in] bits Unpacked bits to be transmitted + * \param[in] nbits Number of \a bits + * \returns 0 on success; negative on error */ +int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, + const ubit_t *bits, uint16_t nbits) +{ + uint8_t buf[TRX_MAX_BURST_LEN]; + + if ((nbits != GSM_BURST_LEN) && (nbits != EGPRS_BURST_LEN)) { + LOGP(DTRX, LOGL_ERROR, "Tx burst length %u invalid\n", nbits); + return -1; + } + + 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, nbits); + + /* 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, nbits + 6, 0); + } else + LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, transceiver " + "offline.\n"); + + return 0; +} + + +/* + * open/close + */ + +/*! flush (delete) all 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); + } + talloc_free(l1h->last_acked); +} + +/*! close the TRX for given handle (data + control socket) */ +void trx_if_close(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + LOGP(DTRX, LOGL_NOTICE, "Close transceiver for %s\n", + phy_instance_name(pinst)); + + trx_if_flush(l1h); + + /* close sockets */ + trx_udp_close(&l1h->trx_ofd_ctrl); + trx_udp_close(&l1h->trx_ofd_data); +} + +/*! compute UDP port number used for TRX protocol */ +static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data) +{ + struct phy_link *plink = pinst->phy_link; + uint16_t inc = 1; + + if (is_data) + inc = 2; + + if (remote) + return plink->u.osmotrx.base_port_remote + (pinst->num << 1) + inc; + else + return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc; +} + +/*! open a TRX interface. creates contro + data sockets */ +static int trx_if_open(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + int rc; + + LOGP(DTRX, LOGL_NOTICE, "Open transceiver for %s\n", + phy_instance_name(pinst)); + + /* initialize ctrl queue */ + INIT_LLIST_HEAD(&l1h->trx_ctrl_list); + + /* open sockets */ + rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl, + plink->u.osmotrx.local_ip, + compute_port(pinst, 0, 0), + plink->u.osmotrx.remote_ip, + compute_port(pinst, 1, 0), trx_ctrl_read_cb); + if (rc < 0) + goto err; + rc = trx_udp_open(l1h, &l1h->trx_ofd_data, + plink->u.osmotrx.local_ip, + compute_port(pinst, 0, 1), + plink->u.osmotrx.remote_ip, + compute_port(pinst, 1, 1), trx_data_read_cb); + if (rc < 0) + goto err; + + /* enable all slots */ + l1h->config.slotmask = 0xff; + + /* FIXME: why was this only for TRX0 ? */ + //if (l1h->trx->nr == 0) + trx_if_cmd_poweroff(l1h); + + return 0; + +err: + trx_if_close(l1h); + return rc; +} + +/*! close the control + burst data sockets for one phy_instance */ +static void trx_phy_inst_close(struct phy_instance *pinst) +{ + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + trx_if_close(l1h); + trx_sched_exit(&l1h->l1s); +} + +/*! open the control + burst data sockets for one phy_instance */ +static int trx_phy_inst_open(struct phy_instance *pinst) +{ + struct trx_l1h *l1h; + int rc; + + l1h = pinst->u.osmotrx.hdl; + if (!l1h) + return -EINVAL; + + rc = trx_sched_init(&l1h->l1s, pinst->trx); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Cannot initialize scheduler for phy " + "instance %d\n", pinst->num); + return -EIO; + } + + rc = trx_if_open(l1h); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Cannot open TRX interface for phy " + "instance %d\n", pinst->num); + trx_phy_inst_close(pinst); + return -EIO; + } + + return 0; +} + +/*! open the PHY link using TRX protocol */ +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst; + int rc; + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + /* open the shared/common clock socket */ + rc = trx_udp_open(plink, &plink->u.osmotrx.trx_ofd_clk, + plink->u.osmotrx.local_ip, + plink->u.osmotrx.base_port_local, + plink->u.osmotrx.remote_ip, + plink->u.osmotrx.base_port_remote, + trx_clk_read_cb); + if (rc < 0) { + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return -1; + } + + /* open the individual instances with their ctrl+data sockets */ + llist_for_each_entry(pinst, &plink->instances, list) { + if (trx_phy_inst_open(pinst) < 0) + goto cleanup; + } + /* FIXME: is there better way to check/report TRX availability? */ + transceiver_available = 1; + return 0; + +cleanup: + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->u.osmotrx.hdl) { + trx_if_close(pinst->u.osmotrx.hdl); + pinst->u.osmotrx.hdl = NULL; + } + } + trx_udp_close(&plink->u.osmotrx.trx_ofd_clk); + return -1; +} + +/*! determine if the TRX for given handle is powered up */ +int trx_if_powered(struct trx_l1h *l1h) +{ + return l1h->config.poweron; +} diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h new file mode 100644 index 00000000..206f5e54 --- /dev/null +++ b/src/osmo-bts-trx/trx_if.h @@ -0,0 +1,35 @@ +#ifndef TRX_IF_H +#define TRX_IF_H + +extern int transceiver_available; + +struct trx_l1h; + +struct trx_ctrl_msg { + struct llist_head list; + char cmd[28]; + char params[100]; + int cmd_len; + int params_len; + int critical; +}; + +void trx_if_init(struct trx_l1h *l1h); +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_setmaxdlynb(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_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, + const ubit_t *bits, uint16_t nbits); +int trx_if_powered(struct trx_l1h *l1h); + +#endif /* TRX_IF_H */ diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c new file mode 100644 index 00000000..e9710acd --- /dev/null +++ b/src/osmo-bts-trx/trx_vty.c @@ -0,0 +1,606 @@ +/* VTY interface for sysmoBTS */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <inttypes.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/socket.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/scheduler.h> + +#include "l1_if.h" +#include "trx_if.h" +#include "loops.h" + +#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n" + +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; + + if (!transceiver_available) { + vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE); + } else { + vty_out(vty, "transceiver is connected%s", VTY_NEWLINE); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + char *sname = osmo_sock_get_name(NULL, pinst->phy_link->u.osmotrx.trx_ofd_clk.fd); + l1h = pinst->u.osmotrx.hdl; + vty_out(vty, "TRX %d %s%s", trx->nr, sname, VTY_NEWLINE); + talloc_free(sname); + 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); + } + + return CMD_SUCCESS; +} + + +static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst) +{ + uint8_t tn; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + vty_out(vty, "PHY Instance %s%s", + phy_instance_name(pinst), VTY_NEWLINE); + + if (l1h->config.rxgain_valid) + vty_out(vty, " rx-gain : %d dB%s", + l1h->config.rxgain, VTY_NEWLINE); + else + vty_out(vty, " rx-gain : undefined%s", VTY_NEWLINE); + if (l1h->config.power_valid) + vty_out(vty, " tx-attenuation : %d dB%s", + l1h->config.power, VTY_NEWLINE); + else + vty_out(vty, " tx-attenuation : 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); + if (l1h->config.maxdlynb_valid) + vty_out(vty, " maxdlynb : %d%s", l1h->config.maxdlynb, + VTY_NEWLINE); + else + vty_out(vty, " maxdlynb : undefined%s", VTY_NEWLINE); + for (tn = 0; tn < TRX_NR_TS; 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); + } +} + +static void show_phy_single(struct vty *vty, struct phy_link *plink) +{ + struct phy_instance *pinst; + + vty_out(vty, "PHY %u%s", plink->num, VTY_NEWLINE); + + llist_for_each_entry(pinst, &plink->instances, list) + show_phy_inst_single(vty, pinst); +} + +DEFUN(show_phy, show_phy_cmd, "show phy", + SHOW_STR "Display information about the available PHYs") +{ + int i; + + for (i = 0; i < 255; i++) { + struct phy_link *plink = phy_link_by_num(i); + if (!plink) + break; + show_phy_single(vty, plink); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd, + "osmotrx ms-power-loop <-127-127>", OSMOTRX_STR + "Enable MS power control loop\nTarget RSSI value (transceiver specific, " + "should be 6dB or more above noise floor)\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_target_rssi = atoi(argv[0]); + plink->u.osmotrx.trx_ms_power_loop = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd, + "no osmotrx ms-power-loop", + NO_STR OSMOTRX_STR "Disable MS power control loop\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_ms_power_loop = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd, + "osmotrx timing-advance-loop", OSMOTRX_STR + "Enable timing advance control loop\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_ta_loop = true; + + return CMD_SUCCESS; +} +DEFUN(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd, + "no osmotrx timing-advance-loop", + NO_STR OSMOTRX_STR "Disable timing advance control loop\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_ta_loop = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd, + "osmotrx maxdly <0-31>", + OSMOTRX_STR + "Set the maximum acceptable delay of an Access Burst (in GSM symbols)." + " Access Burst is the first burst a mobile transmits in order to establish" + " a connection and it is used to estimate Timing Advance (TA) which is" + " then applied to Normal Bursts to compensate for signal delay due to" + " distance. So changing this setting effectively changes maximum range of" + " the cell, because if we receive an Access Burst with a delay higher than" + " this value, it will be ignored and connection is dropped.\n" + "GSM symbols (approx. 1.1km per symbol)\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + 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_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd, + "osmotrx maxdlynb <0-31>", + OSMOTRX_STR + "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)." + " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!" + " During normal operation, Normal Bursts delay are controled by a Timing" + " Advance control loop and thus Normal Bursts arrive to a BTS with no more" + " than a couple GSM symbols, which is already taken into account in osmo-trx." + " So changing this setting will have no effect in production installations" + " except increasing osmo-trx CPU load. This setting is only useful when" + " testing with a transmitter which can't precisely synchronize to the BTS" + " downlink signal, like e.g. R&S CMD57.\n" + "GSM symbols (approx. 1.1km per symbol)\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdlynb = atoi(argv[0]); + l1h->config.maxdlynb_valid = 1; + l1h->config.maxdlynb_sent = 0; + l1if_provision_transceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_slotmask, cfg_phyinst_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 phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + uint8_t tn; + + l1h->config.slotmask = 0; + for (tn = 0; tn < TRX_NR_TS; tn++) + if (argv[tn][0] == '1') + l1h->config.slotmask |= (1 << tn); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd, + "osmotrx power (on|off)", + OSMOTRX_STR + "Change TRX state\n" + "Turn it ON or OFF\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (strcmp(argv[0], "on")) + vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h), VTY_NEWLINE); + else { + vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h), VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd, + "osmotrx fn-advance <0-30>", + OSMOTRX_STR + "Set the number of frames to be transmitted to transceiver in advance " + "of current FN\n" + "Advance in frames\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.clock_advance = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd, + "osmotrx rts-advance <0-30>", + OSMOTRX_STR + "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") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.rts_advance = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd, + "osmotrx rx-gain <0-50>", + OSMOTRX_STR + "Set the receiver gain in dB\n" + "Gain in dB\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + 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_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd, + "osmotrx tx-attenuation <0-50>", + OSMOTRX_STR + "Set the transmitter attenuation\n" + "Fixed attenuation in dB, overriding OML\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + 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_phyinst_tx_atten_oml, cfg_phyinst_tx_atten_oml_cmd, + "osmotrx tx-attenuation oml", + OSMOTRX_STR + "Set the transmitter attenuation\n" + "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + 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_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd, + "no osmotrx rx-gain", + NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.rxgain_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_tx_atten, cfg_phyinst_no_tx_atten_cmd, + "no osmotrx tx-attenuation", + NO_STR OSMOTRX_STR "Unset the transmitter attenuation\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.power_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd, + "no osmotrx maxdly", + NO_STR OSMOTRX_STR + "Unset the maximum delay of GSM symbols\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdly_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd, + "no osmotrx maxdlynb", + NO_STR OSMOTRX_STR + "Unset the maximum delay of GSM symbols\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdlynb_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_transc_ip, cfg_phy_transc_ip_cmd, + "osmotrx ip HOST", + OSMOTRX_STR + "Set local and remote IP address\n" + "IP address (for both OsmoBtsTrx and OsmoTRX)\n") +{ + struct phy_link *plink = vty->index; + + osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[0]); + osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_osmotrx_ip, cfg_phy_osmotrx_ip_cmd, + "osmotrx ip (local|remote) A.B.C.D", + OSMOTRX_STR + "Set IP address\n" "Local IP address (BTS)\n" + "Remote IP address (OsmoTRX)\n" "IP address\n") +{ + struct phy_link *plink = vty->index; + + if (!strcmp(argv[0], "local")) + osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[1]); + else if (!strcmp(argv[0], "remote")) + osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[1]); + else + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd, + "osmotrx base-port (local|remote) <0-65535>", + OSMOTRX_STR "Set base UDP port number\n" "Local UDP port\n" + "Remote UDP port\n" "UDP base port number\n") +{ + struct phy_link *plink = vty->index; + + if (!strcmp(argv[0], "local")) + plink->u.osmotrx.base_port_local = atoi(argv[1]); + else + plink->u.osmotrx.base_port_remote = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd, + "osmotrx legacy-setbsic", OSMOTRX_STR + "Use SETBSIC to configure transceiver (use ONLY with OpenBTS Transceiver!)\n") +{ + struct phy_link *plink = vty->index; + plink->u.osmotrx.use_legacy_setbsic = true; + + vty_out(vty, "%% You have enabled SETBSIC, which is not supported by OsmoTRX " + "but only useful if you want to interface with legacy OpenBTS Transceivers%s", + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd, + "no osmotrx legacy-setbsic", + NO_STR OSMOTRX_STR "Disable Legacy SETBSIC to configure transceiver\n") +{ + struct phy_link *plink = vty->index; + plink->u.osmotrx.use_legacy_setbsic = false; + + return CMD_SUCCESS; +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ + if (plink->u.osmotrx.local_ip) + vty_out(vty, " osmotrx ip local %s%s", + plink->u.osmotrx.local_ip, VTY_NEWLINE); + if (plink->u.osmotrx.remote_ip) + vty_out(vty, " osmotrx ip remote %s%s", + plink->u.osmotrx.remote_ip, VTY_NEWLINE); + + if (plink->u.osmotrx.trx_ms_power_loop) + vty_out(vty, " osmotrx ms-power-loop %d%s", plink->u.osmotrx.trx_target_rssi, VTY_NEWLINE); + else + vty_out(vty, " no osmotrx ms-power-loop%s", VTY_NEWLINE); + vty_out(vty, " %sosmotrx timing-advance-loop%s", (plink->u.osmotrx.trx_ta_loop) ? "" : "no ", VTY_NEWLINE); + + if (plink->u.osmotrx.base_port_local) + vty_out(vty, " osmotrx base-port local %"PRIu16"%s", + plink->u.osmotrx.base_port_local, VTY_NEWLINE); + if (plink->u.osmotrx.base_port_remote) + vty_out(vty, " osmotrx base-port remote %"PRIu16"%s", + plink->u.osmotrx.base_port_remote, VTY_NEWLINE); + + vty_out(vty, " osmotrx fn-advance %d%s", + plink->u.osmotrx.clock_advance, VTY_NEWLINE); + vty_out(vty, " osmotrx rts-advance %d%s", + plink->u.osmotrx.rts_advance, VTY_NEWLINE); + + if (plink->u.osmotrx.use_legacy_setbsic) + vty_out(vty, " osmotrx legacy-setbsic%s", VTY_NEWLINE); +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (l1h->config.rxgain_valid) + vty_out(vty, " osmotrx rx-gain %d%s", + l1h->config.rxgain, VTY_NEWLINE); + if (l1h->config.power_valid) { + if (l1h->config.power_oml) + vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE); + else + vty_out(vty, " osmotrx tx-attenuation %d%s", + l1h->config.power, VTY_NEWLINE); + } + if (l1h->config.maxdly_valid) + vty_out(vty, " osmotrx maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE); + if (l1h->config.maxdlynb_valid) + vty_out(vty, " osmotrx maxdlynb %d%s", l1h->config.maxdlynb, 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); +} + +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_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element_ve(&show_transceiver_cmd); + install_element_ve(&show_phy_cmd); + + install_element(PHY_NODE, &cfg_phy_ms_power_loop_cmd); + install_element(PHY_NODE, &cfg_phy_no_ms_power_loop_cmd); + install_element(PHY_NODE, &cfg_phy_timing_advance_loop_cmd); + install_element(PHY_NODE, &cfg_phy_no_timing_advance_loop_cmd); + install_element(PHY_NODE, &cfg_phy_base_port_cmd); + install_element(PHY_NODE, &cfg_phy_fn_advance_cmd); + install_element(PHY_NODE, &cfg_phy_rts_advance_cmd); + install_element(PHY_NODE, &cfg_phy_transc_ip_cmd); + install_element(PHY_NODE, &cfg_phy_osmotrx_ip_cmd); + install_element(PHY_NODE, &cfg_phy_setbsic_cmd); + install_element(PHY_NODE, &cfg_phy_no_setbsic_cmd); + + install_element(PHY_INST_NODE, &cfg_phyinst_rxgain_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_oml_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_rxgain_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_tx_atten_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_slotmask_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_power_on_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_maxdly_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdly_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_maxdlynb_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdlynb_cmd); + + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-virtual/Makefile.am b/src/osmo-bts-virtual/Makefile.am new file mode 100644 index 00000000..070efed6 --- /dev/null +++ b/src/osmo-bts-virtual/Makefile.am @@ -0,0 +1,10 @@ +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl + +noinst_HEADERS = l1_if.h osmo_mcast_sock.h virtual_um.h + +bin_PROGRAMS = osmo-bts-virtual + +osmo_bts_virtual_SOURCES = main.c bts_model.c virtualbts_vty.c scheduler_virtbts.c l1_if.c virtual_um.c osmo_mcast_sock.c +osmo_bts_virtual_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) diff --git a/src/osmo-bts-virtual/bts_model.c b/src/osmo-bts-virtual/bts_model.c new file mode 100644 index 00000000..b971af5c --- /dev/null +++ b/src/osmo-bts-virtual/bts_model.c @@ -0,0 +1,176 @@ +/* (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/codec/codec.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> + +/* TODO: check if dummy method is sufficient, else implement */ +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +/* TODO: check if dummy method is sufficient, else implement */ +int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr, + int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + 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; +} + +static uint8_t vbts_set_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + + llist_for_each_entry(trx, &bts->trx_list, list) { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + + for (tn = 0; tn < TRX_NR_TS; tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + + /* report availability of trx to the bts. this will trigger the rsl connection */ + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + } + return 0; +} + +static uint8_t vbts_set_trx(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + int rc; + + rc = trx_sched_set_pchan(&pinst->u.virt.sched, ts->nr, ts->pchan); + if (rc) + return NM_NACK_RES_NOTAVAIL; + + return 0; +} + +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 = vbts_set_bts(obj); + break; + case NM_MT_SET_RADIO_ATTR: + cause = vbts_set_trx(obj); + break; + case NM_MT_SET_CHAN_ATTR: + cause = vbts_set_ts(obj); + break; + } + return oml_fom_ack_nack(msg, cause); +} + +/* MO: TS 12.21 Managed Object */ +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: + case NM_OC_CHANNEL: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_BTS: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + 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) +{ + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} diff --git a/src/osmo-bts-virtual/l1_if.c b/src/osmo-bts-virtual/l1_if.c new file mode 100644 index 00000000..d0c368ee --- /dev/null +++ b/src/osmo-bts-virtual/l1_if.c @@ -0,0 +1,461 @@ +/* Virtual BTS layer 1 primitive handling and interface + * + * Copyright (C) 2015-2017 Harald Welte <laforge@gnumonks.org> + * Copyright (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/rsl.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/scheduler.h> +#include "virtual_um.h" + +extern int vbts_sched_start(struct gsm_bts *bts); + +static struct phy_instance *phy_instance_by_arfcn(struct phy_link *plink, uint16_t arfcn) +{ + struct phy_instance *pinst; + + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->trx && pinst->trx->arfcn == arfcn) + return pinst; + } + + return NULL; +} + +static 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); +/** + * Callback to handle incoming messages from the MS. + * The incoming message should be GSM_TAP encapsulated. + * TODO: implement all channels + */ +static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg) +{ + struct phy_link *plink = (struct phy_link *)vui->priv; + struct phy_instance *pinst; + if (!msg) { + pinst = phy_instance_by_num(plink, 0); + bts_shutdown(pinst->trx->bts, "VirtPHY read socket died\n"); + return; + } + + struct gsmtap_hdr *gh = msgb_l1(msg); + uint32_t fn = ntohl(gh->frame_number); /* frame number of the rcv msg */ + uint16_t arfcn = ntohs(gh->arfcn); /* arfcn of the cell we currently camp on */ + uint8_t gsmtap_chantype = gh->sub_type; /* gsmtap channel type */ + uint8_t signal_dbm = gh->signal_dbm; /* signal strength in dBm */ + //uint8_t snr = gh->snr_db; /* signal noise ratio in dB */ + uint8_t subslot = gh->sub_slot; /* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */ + uint8_t timeslot = gh->timeslot; /* tdma timeslot to send in (0-7) */ + uint8_t rsl_chantype; /* rsl chan type (8.58, 9.3.1) */ + uint8_t link_id; /* rsl link id tells if this is an ssociated or dedicated link */ + uint8_t chan_nr; /* encoded rsl channel type, timeslot and mf subslot */ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + /* get rid of l1 gsmtap hdr */ + msg->l2h = msgb_pull(msg, sizeof(*gh)); + + /* convert gsmtap chan to RSL chan and link id */ + chantype_gsmtap2rsl(gsmtap_chantype, &rsl_chantype, &link_id); + chan_nr = rsl_enc_chan_nr(rsl_chantype, subslot, timeslot); + + /* ... or not uplink */ + if (!(arfcn & GSMTAP_ARFCN_F_UPLINK)) { + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignoring incoming msg - no uplink flag\n"); + goto nomessage; + } + + /* Generally ignore all msgs that are either not received with the right ARFCN... */ + pinst = phy_instance_by_arfcn(plink, arfcn & GSMTAP_ARFCN_MASK); + if (!pinst) + goto nomessage; + + /* switch case with removed ACCH flag */ + switch ((gsmtap_chantype & ~GSMTAP_CHANNEL_ACCH) & 0xff) { + case GSMTAP_CHANNEL_RACH: + /* generate primitive for upper layer + * see 04.08 - 3.3.1.3.1: the IMMEDIATE_ASSIGNMENT coming back from the network has to be + * sent with the same ra reference as in the CHANNEL_REQUEST that was received */ + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, msg); + + l1sap.u.rach_ind.chan_nr = chan_nr; + /* TODO: 11bit RACH */ + l1sap.u.rach_ind.ra = msgb_pull_u8(msg); /* directly after gh hdr comes ra */ + l1sap.u.rach_ind.acc_delay = 0; /* probably not used in virt um */ + l1sap.u.rach_ind.is_11bit = 0; + l1sap.u.rach_ind.fn = fn; + /* we don't rally know which RACH bursrt type the virtual MS is using, as this field is not + * part of information present in the GSMTAP header. So we simply report all of them as 0 */ + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GSMTAP_CHANNEL_TCH_F: + case GSMTAP_CHANNEL_TCH_H: +#if 0 + /* TODO: handle voice messages */ + if (!facch && ! tch_acch) { + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); + } +#endif + case GSMTAP_CHANNEL_SDCCH4: + case GSMTAP_CHANNEL_SDCCH8: + case GSMTAP_CHANNEL_PACCH: + case GSMTAP_CHANNEL_PDCH: + case GSMTAP_CHANNEL_PTCCH: + 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 = link_id; + l1sap.u.data.fn = fn; + l1sap.u.data.rssi = 0; /* Radio Signal Strength Indicator. Best -> 0 */ + l1sap.u.data.ber10k = 0; /* Bit Error Rate in 0.01%. Best -> 0 */ + l1sap.u.data.ta_offs_256bits = 0; /* Burst time of arrival in quarter bits. Probably used for Timing Advance calc. Best -> 0 */ + l1sap.u.data.lqual_cb = 10 * signal_dbm; /* Link quality in centiBel = 10 * dB. */ + l1sap.u.data.pdch_presence_info = PRES_INFO_BOTH; + l1if_process_meas_res(pinst->trx, timeslot, fn, chan_nr, 0, 0, 0, 0); + break; + case GSMTAP_CHANNEL_AGCH: + case GSMTAP_CHANNEL_PCH: + case GSMTAP_CHANNEL_BCCH: + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type downlink only!\n"); + goto nomessage; + case GSMTAP_CHANNEL_SDCCH: + case GSMTAP_CHANNEL_CCCH: + case GSMTAP_CHANNEL_CBCH51: + case GSMTAP_CHANNEL_CBCH52: + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type not supported!\n"); + goto nomessage; + default: + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type unknown\n"); + goto nomessage; + } + + /* forward primitive, lsap takes ownership of the msgb. */ + l1sap_up(pinst->trx, &l1sap); + DEBUGPFN(DL1P, fn, "Message forwarded to layer 2.\n"); + return; + +nomessage: + talloc_free(msg); +} + +/* called by common part once OML link is established */ +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +/* called by bts_main to initialize physical link */ +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst; + + //OSMO_ASSERT(plink->type == PHY_LINK_T_VIRTUAL); + + if (plink->u.virt.virt_um) + virt_um_destroy(plink->u.virt.virt_um); + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + plink->u.virt.virt_um = virt_um_init(plink, plink->u.virt.ms_mcast_group, plink->u.virt.ms_mcast_port, + plink->u.virt.bts_mcast_group, plink->u.virt.bts_mcast_port, + virt_um_rcv_cb); + if (!plink->u.virt.virt_um) { + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return -1; + } + /* set back reference to plink */ + plink->u.virt.virt_um->priv = plink; + + /* iterate over list of PHY instances and initialize the scheduler */ + llist_for_each_entry(pinst, &plink->instances, list) { + trx_sched_init(&pinst->u.virt.sched, pinst->trx); + /* Only start the scheduler for the transceiver on C0. + * If we have multiple tranceivers, CCCH is always on C0 + * and has to be auto active */ + /* Other TRX are activated via OML by a PRIM_INFO_MODIFY + * / PRIM_INFO_ACTIVATE */ + if (pinst->trx && pinst->trx == pinst->trx->bts->c0) { + vbts_sched_start(pinst->trx->bts); + /* init lapdm layer 3 callback for the trx on timeslot 0 == BCCH */ + lchan_init_lapdm(&pinst->trx->ts[0].lchan[CCCH_LCHAN]); + /* FIXME: This is probably the wrong location to set the CCCH to active... the OML link def. needs to be reworked and fixed. */ + pinst->trx->ts[0].lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; + lchan_set_state(&pinst->trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); + } + } + + /* this will automatically update the MO state of all associated TRX objects */ + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + return 0; +} + + +/* + * primitive handling + */ + +/* enable ciphering */ +static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct phy_instance *pinst = trx_phy_instance(trx); + struct l1sched_trx *sched = &pinst->u.virt.sched; + + /* ciphering already enabled in both directions */ + if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) + return -EINVAL; + + if (!downlink) { + /* set uplink */ + trx_sched_set_cipher(sched, 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(sched, chan_nr, 0, + lchan->encr.alg_id - 1, lchan->encr.key, + lchan->encr.key_len); + } + trx_sched_set_cipher(sched, 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 gsm_bts_trx *trx, 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(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); +} + + +static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta, + float ber, float rssi, uint32_t fn) +{ + 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_256bits = (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); + l1sap->u.info.u.meas_ind.fn = fn; +} + +static 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; + + DEBUGPFN(DMEAS, fn, "RX L1 frame %s 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), 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, fn); + + 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 phy_instance *pinst = trx_phy_instance(trx); + struct l1sched_trx *sched = &pinst->u.virt.sched; + 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(sched, l1sap); + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_tch_req(sched, 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(lchan, chan_nr, 0); + if (l1sap->u.info.u.ciph_req.downlink) + l1if_set_ciphering(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]; + /* we receive a channel activation request from the BSC, + * e.g. as a response to a channel req on RACH */ + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot activate" + " chan_nr 0x%02x\n", chan_nr); + break; + } + /* activate dedicated channel */ + trx_sched_set_lchan(sched, chan_nr, LID_DEDIC, 1); + /* activate associated channel */ + trx_sched_set_lchan(sched, chan_nr, LID_SACCH, 1); + /* set mode */ + trx_sched_set_mode(sched, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_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(lchan, chan_nr, 0); + l1if_set_ciphering(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(trx, chan_nr, + PRIM_INFO_ACTIVATE, 0); + break; + } + if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + /* change mode */ + trx_sched_set_mode(sched, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_mode[3].mode, + amr_get_initial_mode(lchan), + 0); + break; + } + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " + "chan_nr 0x%02x\n", chan_nr); + break; + } + /* deactivate associated channel */ + trx_sched_set_lchan(sched, 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(sched, chan_nr, 0x00, 0); + /* confirm only on dedicated channel */ + mph_info_chan_confirm(trx, 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; +} diff --git a/src/osmo-bts-virtual/l1_if.h b/src/osmo-bts-virtual/l1_if.h new file mode 100644 index 00000000..6a843b37 --- /dev/null +++ b/src/osmo-bts-virtual/l1_if.h @@ -0,0 +1,20 @@ +#pragma once + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/scheduler.h> + +#include "virtual_um.h" + +struct vbts_l1h { + struct gsm_bts_trx *trx; + struct l1sched_trx l1s; + struct virt_um_inst *virt_um; +}; + +struct vbts_l1h *l1if_open(struct gsm_bts_trx *trx); +void l1if_close(struct vbts_l1h *l1h); +void l1if_reset(struct vbts_l1h *l1h); + +int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn); + +int vbts_sched_start(struct gsm_bts *bts); diff --git a/src/osmo-bts-virtual/main.c b/src/osmo-bts-virtual/main.c new file mode 100644 index 00000000..aa1c608e --- /dev/null +++ b/src/osmo-bts-virtual/main.c @@ -0,0 +1,144 @@ +/* Main program for Virtual OsmoBTS */ + +/* (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/phy_link.h> +#include "virtual_um.h" + +/* dummy, since no direct dsp support */ +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + bts->variant = BTS_OSMO_VIRTUAL; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_CBCH); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + return 0; +} + +void bts_model_print_help() +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* specific to this hardware */ + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ + plink->u.virt.bts_mcast_group = DEFAULT_BTS_MCAST_GROUP; + plink->u.virt.bts_mcast_port = DEFAULT_BTS_MCAST_PORT; + plink->u.virt.ms_mcast_group = DEFAULT_MS_MCAST_GROUP; + plink->u.virt.ms_mcast_port = DEFAULT_MS_MCAST_PORT; +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -ENOTSUP; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.c b/src/osmo-bts-virtual/osmo_mcast_sock.c new file mode 100644 index 00000000..c0f0af58 --- /dev/null +++ b/src/osmo-bts-virtual/osmo_mcast_sock.c @@ -0,0 +1,113 @@ +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> +#include <unistd.h> +#include "osmo_mcast_sock.h" + +/* server socket is what we use for transmission. It is not subscribed + * to a multicast group or locally bound, but it is just a normal UDP + * socket that's connected to the remote mcast group + port */ +int mcast_server_sock_setup(struct osmo_fd *ofd, const char* tx_mcast_group, + uint16_t tx_mcast_port, bool loopback) +{ + int rc; + unsigned int flags = OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_UDP_REUSEADDR; + + if (!loopback) + flags |= OSMO_SOCK_F_NO_MCAST_LOOP; + + /* setup mcast server socket */ + rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + tx_mcast_group, tx_mcast_port, flags); + if (rc < 0) { + perror("Failed to create Multicast Server Socket"); + return rc; + } + + return 0; +} + +/* the client socket is what we use for reception. It is a UDP socket + * that's bound to the GSMTAP UDP port and subscribed to the respective + * multicast group */ +int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data) +{ + int rc; + unsigned int flags = OSMO_SOCK_F_BIND | OSMO_SOCK_F_NO_MCAST_ALL | OSMO_SOCK_F_UDP_REUSEADDR; + + ofd->cb = fd_rx_cb; + ofd->when = BSC_FD_READ; + ofd->data = osmo_fd_data; + + /* Create mcast client socket */ + rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + NULL, mcast_port, flags); + if (rc < 0) { + perror("Could not create mcast client socket"); + return rc; + } + + /* Configure and join the multicast group */ + rc = osmo_sock_mcast_subscribe(ofd->fd, mcast_group); + if (rc < 0) { + perror("Failed to join to mcast goup"); + osmo_fd_close(ofd); + return rc; + } + + return 0; +} + +struct mcast_bidir_sock * +mcast_bidir_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, + const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data) +{ + struct mcast_bidir_sock *bidir_sock = talloc(ctx, struct mcast_bidir_sock); + int rc; + + if (!bidir_sock) + return NULL; + + rc = mcast_client_sock_setup(&bidir_sock->rx_ofd, rx_mcast_group, rx_mcast_port, + fd_rx_cb, osmo_fd_data); + if (rc < 0) { + talloc_free(bidir_sock); + return NULL; + } + rc = mcast_server_sock_setup(&bidir_sock->tx_ofd, tx_mcast_group, tx_mcast_port, loopback); + if (rc < 0) { + osmo_fd_close(&bidir_sock->rx_ofd); + talloc_free(bidir_sock); + return NULL; + } + return bidir_sock; + +} + +int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, + unsigned int data_len) +{ + return send(bidir_sock->tx_ofd.fd, data, data_len, 0); +} + +int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len) +{ + return recv(bidir_sock->rx_ofd.fd, buf, buf_len, 0); +} + +void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock) +{ + osmo_fd_close(&bidir_sock->tx_ofd); + osmo_fd_close(&bidir_sock->rx_ofd); + talloc_free(bidir_sock); +} diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.h b/src/osmo-bts-virtual/osmo_mcast_sock.h new file mode 100644 index 00000000..aa2013c6 --- /dev/null +++ b/src/osmo-bts-virtual/osmo_mcast_sock.h @@ -0,0 +1,29 @@ +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <netinet/in.h> +#include <osmocom/core/select.h> + +struct mcast_bidir_sock { + struct osmo_fd tx_ofd; + struct osmo_fd rx_ofd; +}; + +struct mcast_bidir_sock *mcast_bidir_sock_setup(void *ctx, + const char *tx_mcast_group, uint16_t tx_mcast_port, + const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data); + +int mcast_server_sock_setup(struct osmo_fd *ofd, const char *tx_mcast_group, + uint16_t tx_mcast_port, bool loopback); + +int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data); + +int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, unsigned int data_len); +int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len); +void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock); + diff --git a/src/osmo-bts-virtual/scheduler_virtbts.c b/src/osmo-bts-virtual/scheduler_virtbts.c new file mode 100644 index 00000000..25f65839 --- /dev/null +++ b/src/osmo-bts-virtual/scheduler_virtbts.c @@ -0,0 +1,619 @@ +/* Scheduler worker functiosn for Virtua OsmoBTS */ + +/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/gsm/rsl.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> +#include "virtual_um.h" +#include "l1_if.h" + +#define MODULO_HYPERFRAME 0 + +static const char *gsmtap_hdr_stringify(const struct gsmtap_hdr *gh) +{ + static char buf[256]; + snprintf(buf, sizeof(buf), "(ARFCN=%u, ts=%u, ss=%u, type=%u/%u)", + gh->arfcn & GSMTAP_ARFCN_MASK, gh->timeslot, gh->sub_slot, gh->type, gh->sub_type); + return buf; +} + +/** + * Send a message over the virtual um interface. + * This will at first wrap the msg with a GSMTAP header and then write it to the declared multicast socket. + * TODO: we might want to remove unused argument uint8_t tn + */ +static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, struct msgb *msg) +{ + const struct trx_chan_desc *chdesc = &trx_chan_desc[chan]; + struct msgb *outmsg; /* msg to send with gsmtap header prepended */ + uint16_t arfcn = l1t->trx->arfcn; /* ARFCN of the tranceiver the message is send with */ + uint8_t signal_dbm = 63; /* signal strength, 63 is best */ + uint8_t snr = 63; /* signal noise ratio, 63 is best */ + uint8_t *data = msgb_l2(msg); /* data to transmit (whole message without l1 header) */ + uint8_t data_len = msgb_l2len(msg); /* length of data */ + uint8_t rsl_chantype; /* RSL chan type (TS 08.58, 9.3.1) */ + uint8_t subslot; /* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */ + uint8_t timeslot; /* TDMA timeslot to send in (0-7) */ + uint8_t gsmtap_chantype; /* the GSMTAP channel */ + + rsl_dec_chan_nr(chdesc->chan_nr, &rsl_chantype, &subslot, ×lot); + /* the timeslot is not encoded in the chan_nr of the chdesc, and so has to be overwritten */ + timeslot = tn; + /* in Osmocom, AGCH is only sent on ccch block 0. no idea why. this seems to cause false GSMTAP channel + * types for agch and pch. */ + if (rsl_chantype == RSL_CHAN_PCH_AGCH && + l1sap_fn2ccch_block(fn) >= num_agch(l1t->trx, "PH-DATA-REQ")) + gsmtap_chantype = GSMTAP_CHANNEL_PCH; + else + gsmtap_chantype = chantype_rsl2gsmtap(rsl_chantype, chdesc->link_id); /* the logical channel type */ + +#if MODULO_HYPERFRAME + /* Restart fn after every superframe (26 * 51 frames) to simulate hyperframe overflow each 6 seconds. */ + fn %= 26 * 51; +#endif + + outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, fn, signal_dbm, snr, data, data_len); + + if (outmsg) { + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + struct gsmtap_hdr *gh = (struct gsmtap_hdr *)msgb_data(outmsg); + int rc; + + rc = virt_um_write_msg(pinst->phy_link->u.virt.virt_um, outmsg); + if (rc < 0) + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "%s GSMTAP msg could not send to virtual Um\n", gsmtap_hdr_stringify(gh)); + else if (rc == 0) + bts_shutdown(l1t->trx->bts, "VirtPHY write socket died\n"); + else + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, + "%s Sending GSMTAP message to virtual Um\n", gsmtap_hdr_stringify(gh)); + } else + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "GSMTAP msg could not be created!\n"); + + /* free incoming message */ + msgb_free(msg); +} + +/* + * TX on downlink + */ + +/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + return NULL; +} + +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + return NULL; +} + +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + return NULL; +} + +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg; + + if (bid > 0) + return NULL; + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (!msg) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + return NULL; + } + + /* check validity of message */ + if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n", + msgb_l2len(msg)); + /* free message */ + msgb_free(msg); + return NULL; + } + + /* transmit the msg received on dl from bsc to layer1 (virt Um) */ + tx_to_virt_um(l1t, tn, fn, chan, msg); + + return NULL; +} + +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg = NULL; /* make GCC happy */ + + if (bid > 0) + return NULL; + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (!msg) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + return NULL; + } + + tx_to_virt_um(l1t, tn, fn, chan, msg); + + return NULL; +} + +static void tx_tch_common(struct l1sched_trx *l1t, 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 l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + struct osmo_phsap_prim *l1sap; +#if 0 + /* handle loss detection of received TCH frames */ + if (rsl_cmode == RSL_CMOD_SPD_SPEECH + && ++(chan_state->lost_frames) > 5) { + uint8_t tch_data[GSM_FR_BYTES]; + int len; + + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "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, GSM_FR_BYTES); + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode1; + memset(tch_data, 0, GSM_EFR_BYTES); + len = GSM_EFR_BYTES; + 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); + _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); + break; + default: +inval_mode1: + LOGP(DL1P, LOGL_ERROR, "TCH mode invalid, please " + "fix!\n"); + len = 0; + } + if (len) + _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); + } +#endif + + /* get frame and unlink from queue */ + msg1 = _sched_dequeue_prim(l1t, tn, fn, chan); + msg2 = _sched_dequeue_prim(l1t, 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) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "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) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "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) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "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; +#if 0 + uint8_t bfi, cmr_codec, ft_codec; + int cmr, ft, i; +#endif + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, " + "because we are not in speech mode\n"); + 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) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad HR frame'\n"); + goto free_bad_msg; + } + break; + } + len = GSM_FR_BYTES; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] >> 4) != 0xd) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad FR frame'\n"); + goto free_bad_msg; + } + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode2; + len = GSM_EFR_BYTES; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] >> 4) != 0xc) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad EFR frame'\n"); + goto free_bad_msg; + } + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ +#if 0 + 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) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "Codec (FT = %d) of RTP frame not in list. ", ft_codec); + goto free_bad_msg; + } + if (codec_mode_request && chan_state->dl_ft != ft) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Codec (FT = %d) of RTP cannot be changed now, but in " + "next frame\n", ft_codec); + goto free_bad_msg; + } + chan_state->dl_ft = ft; + if (bfi) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad AMR frame'\n"); + goto free_bad_msg; + } +#else + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "AMR not supported!\n"); + goto free_bad_msg; +#endif + break; + default: +inval_mode2: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + goto free_bad_msg; + } + if (len < 0) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n"); + goto free_bad_msg; + } + if (msgb_l2len(msg_tch) != len) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "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; +} + +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + + if (bid > 0) + return NULL; + + tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch, + (((fn + 4) % 26) >> 2) & 1); + + /* no message at all */ + if (!msg_tch && !msg_facch) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + goto send_burst; + } + + if (msg_facch) { + tx_to_virt_um(l1t, tn, fn, chan, msg_facch); + msgb_free(msg_tch); + } else + tx_to_virt_um(l1t, tn, fn, chan, msg_tch); + +send_burst: + + return NULL; +} + +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + //uint8_t tch_mode = chan_state->tch_mode; + + /* send burst, if we already got a frame */ + if (bid > 0) + return NULL; + + /* get TCH and/or FACCH */ + tx_tch_common(l1t, 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)) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on " + "even frames, please fix RTS!\n"); + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* no message at all */ + if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + goto send_burst; + } + + if (msg_facch) { + tx_to_virt_um(l1t, tn, fn, chan, msg_facch); + msgb_free(msg_tch); + } else if (msg_tch) + tx_to_virt_um(l1t, tn, fn, chan, msg_tch); + +send_burst: + return NULL; +} + + +/*********************************************************************** + * RX on uplink (indication to upper layer) + ***********************************************************************/ + +/* we don't use those functions, as we feed the MAC frames from GSMTAP + * directly into the L1SAP, bypassing the TDMA multiplex logic oriented + * towards receiving bursts */ + +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +/*! \brief a single burst was received by the PHY, process it */ +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +{ +} + +/*********************************************************************** + * main scheduler function + ***********************************************************************/ + +#define RTS_ADVANCE 5 /* about 20ms */ +#define FRAME_DURATION_uS 4615 + +static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn) +{ + struct gsm_bts_trx *trx; + + /* send time indication */ + /* update model with new frame number, lot of stuff happening, measurements of timeslots */ + /* saving GSM time in BTS model, and more */ + l1if_mph_time_ind(bts, fn); + + /* advance the frame number? */ + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct l1sched_trx *l1t = &pinst->u.virt.sched; + int tn; + uint16_t nbits; + + /* do for each of the 8 timeslots */ + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + /* Generate RTS indication to higher layers */ + /* This will basically do 2 things (check l1_if:bts_model_l1sap_down): + * 1) Get pending messages from layer 2 (from the lapdm queue) + * 2) Process the messages + * --> Handle and process non-transparent RSL-Messages (activate channel, ) + * --> Forward transparent RSL-DATA-Messages to the ms by appending them to + * the l1-dl-queue */ + _sched_rts(l1t, tn, (fn + RTS_ADVANCE) % GSM_HYPERFRAME); + /* schedule transmit backend functions */ + /* Process data in the l1-dlqueue and forward it + * to MS */ + /* the returned bits are not used here, the routines called will directly forward their + * bits to the virt Um */ + _sched_dl_burst(l1t, tn, fn, &nbits); + } + } + + return 0; +} + +static void vbts_fn_timer_cb(void *data) +{ + struct gsm_bts *bts = data; + struct timeval tv_now; + struct timeval *tv_clock = &bts->vbts.tv_clock; + int32_t elapsed_us; + + gettimeofday(&tv_now, NULL); + + /* check how much time elapsed till the last timer callback call. + * this value should be about 4.615 ms (a bit greater) as this is the scheduling interval */ + elapsed_us = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + + (tv_now.tv_usec - tv_clock->tv_usec); + + /* not so good somehow a lot of time passed between two timer callbacks */ + if (elapsed_us > 2 *FRAME_DURATION_uS) + LOGP(DL1P, LOGL_NOTICE, "vbts_fn_timer_cb after %d us\n", elapsed_us); + + /* schedule the current frame/s (fn = frame number) + * this loop will be called at least once, but can also be executed + * multiple times if more than one frame duration (4615us) passed till the last callback */ + while (elapsed_us > FRAME_DURATION_uS / 2) { + const struct timeval tv_frame = { + .tv_sec = 0, + .tv_usec = FRAME_DURATION_uS, + }; + timeradd(tv_clock, &tv_frame, tv_clock); + /* increment the frame number in the BTS model instance */ + bts->vbts.last_fn = (bts->vbts.last_fn + 1) % GSM_HYPERFRAME; + vbts_sched_fn(bts, bts->vbts.last_fn); + elapsed_us -= FRAME_DURATION_uS; + } + + /* re-schedule the timer */ + /* timer is set to frame duration - elapsed time to guarantee that this cb method will be + * periodically executed every 4.615ms */ + osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS - elapsed_us); +} + +int vbts_sched_start(struct gsm_bts *bts) +{ + LOGP(DL1P, LOGL_NOTICE, "starting VBTS scheduler\n"); + + memset(&bts->vbts.fn_timer, 0, sizeof(bts->vbts.fn_timer)); + bts->vbts.fn_timer.cb = vbts_fn_timer_cb; + bts->vbts.fn_timer.data = bts; + + gettimeofday(&bts->vbts.tv_clock, NULL); + /* trigger the first timer after 4615us (a frame duration) */ + osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS); + + return 0; +} diff --git a/src/osmo-bts-virtual/virtual_um.c b/src/osmo-bts-virtual/virtual_um.c new file mode 100644 index 00000000..fd0940f0 --- /dev/null +++ b/src/osmo-bts-virtual/virtual_um.c @@ -0,0 +1,100 @@ +/* Routines for a Virtual Um interface over GSMTAP/UDP */ + +/* (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/select.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include "osmo_mcast_sock.h" +#include "virtual_um.h" +#include <unistd.h> + +/** + * Virtual UM interface file descriptor callback. + * Should be called by select.c when the fd is ready for reading. + */ +static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct virt_um_inst *vui = ofd->data; + + if (what & BSC_FD_READ) { + struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, "Virtual UM Rx"); + int rc; + + /* read message from fd into message buffer */ + rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg)); + if (rc > 0) { + msgb_put(msg, rc); + msg->l1h = msgb_data(msg); + /* call the l1 callback function for a received msg */ + vui->recv_cb(vui, msg); + } else if (rc == 0) { + vui->recv_cb(vui, NULL); + osmo_fd_close(ofd); + } else + perror("Read from multicast socket"); + + } + + return 0; +} + +struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)) +{ + struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst); + vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port, + rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui); + if (!vui->mcast_sock) { + perror("Unable to create VirtualUm multicast socket"); + talloc_free(vui); + return NULL; + } + vui->recv_cb = recv_cb; + + return vui; + +} + +void virt_um_destroy(struct virt_um_inst *vui) +{ + mcast_bidir_sock_close(vui->mcast_sock); + talloc_free(vui); +} + +/** + * Write msg to to multicast socket and free msg afterwards + */ +int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg) +{ + int rc; + + rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) + perror("Writing to multicast socket"); + msgb_free(msg); + + return rc; +} diff --git a/src/osmo-bts-virtual/virtual_um.h b/src/osmo-bts-virtual/virtual_um.h new file mode 100644 index 00000000..ac098dd4 --- /dev/null +++ b/src/osmo-bts-virtual/virtual_um.h @@ -0,0 +1,31 @@ +#pragma once + +#include <osmocom/core/select.h> +#include <osmocom/core/msgb.h> +#include "osmo_mcast_sock.h" + +/* We use multicast group addresses from the 239.192.0.0/14 rage, as + * those are designated by RFC2365 as "IPv4 Organization Local Scope, + * "... the space from which an organization should allocate sub- + * ranges when defining scopes for private use." */ + +#define VIRT_UM_MSGB_SIZE 256 +#define DEFAULT_MS_MCAST_GROUP "239.193.23.1" +#define DEFAULT_MS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */ +#define DEFAULT_BTS_MCAST_GROUP "239.193.23.2" +#define DEFAULT_BTS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */ + +struct virt_um_inst { + void *priv; + struct mcast_bidir_sock *mcast_sock; + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg); +}; + +struct virt_um_inst *virt_um_init( + void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)); + +void virt_um_destroy(struct virt_um_inst *vui); + +int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg); diff --git a/src/osmo-bts-virtual/virtualbts_vty.c b/src/osmo-bts-virtual/virtualbts_vty.c new file mode 100644 index 00000000..323222b4 --- /dev/null +++ b/src/osmo-bts-virtual/virtualbts_vty.c @@ -0,0 +1,185 @@ +/* VTY interface for virtual OsmoBTS */ + +/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/rate_ctr.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/vty.h> +#include "virtual_um.h" + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR + +static struct gsm_bts *vty_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) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ + if (plink->u.virt.mcast_dev) + vty_out(vty, " virtual-um net-device %s%s", + plink->u.virt.mcast_dev, VTY_NEWLINE); + if (strcmp(plink->u.virt.ms_mcast_group, DEFAULT_BTS_MCAST_GROUP)) + vty_out(vty, " virtual-um ms-multicast-group %s%s", + plink->u.virt.ms_mcast_group, VTY_NEWLINE); + if (plink->u.virt.ms_mcast_port != DEFAULT_BTS_MCAST_PORT) + vty_out(vty, " virtual-um ms-udp-port %u%s", + plink->u.virt.ms_mcast_port, VTY_NEWLINE); + if (strcmp(plink->u.virt.bts_mcast_group, DEFAULT_MS_MCAST_GROUP)) + vty_out(vty, " virtual-um bts-multicast-group %s%s", + plink->u.virt.bts_mcast_group, VTY_NEWLINE); + if (plink->u.virt.bts_mcast_port != DEFAULT_MS_MCAST_PORT) + vty_out(vty, " virtual-um bts-udp-port %u%s", + plink->u.virt.bts_mcast_port, VTY_NEWLINE); + +} + +#define VUM_STR "Virtual Um layer\n" + +DEFUN(cfg_phy_ms_mcast_group, cfg_phy_ms_mcast_group_cmd, + "virtual-um ms-multicast-group GROUP", + VUM_STR "Configure the MS multicast group\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(plink, &plink->u.virt.ms_mcast_group, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_ms_mcast_port, cfg_phy_ms_mcast_port_cmd, + "virtual-um ms-udp-port <0-65535>", + VUM_STR "Configure the MS UDP port\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.virt.ms_mcast_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_bts_mcast_group, cfg_phy_bts_mcast_group_cmd, + "virtual-um bts-multicast-group GROUP", + VUM_STR "Configure the BTS multicast group\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(plink, &plink->u.virt.bts_mcast_group, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_bts_mcast_port, cfg_phy_bts_mcast_port_cmd, + "virtual-um bts-udp-port <0-65535>", + VUM_STR "Configure the BTS UDP port\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.virt.bts_mcast_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_mcast_dev, cfg_phy_mcast_dev_cmd, + "virtual-um net-device NETDEV", + VUM_STR "Configure the network device\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(plink, &plink->u.virt.mcast_dev, argv[0]); + + return CMD_SUCCESS; +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element(PHY_NODE, &cfg_phy_ms_mcast_group_cmd); + install_element(PHY_NODE, &cfg_phy_ms_mcast_port_cmd); + install_element(PHY_NODE, &cfg_phy_bts_mcast_group_cmd); + install_element(PHY_NODE, &cfg_phy_bts_mcast_port_cmd); + install_element(PHY_NODE, &cfg_phy_mcast_dev_cmd); + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 00000000..1eb28d6f --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,45 @@ +SUBDIRS = paging cipher agch misc handover tx_power power meas + +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 $@ + diff --git a/tests/agch/Makefile.am b/tests/agch/Makefile.am new file mode 100644 index 00000000..0c4fce45 --- /dev/null +++ b/tests/agch/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS) +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) diff --git a/tests/agch/agch_test.c b/tests/agch/agch_test.c new file mode 100644 index 00000000..e6c56d97 --- /dev/null +++ b/tests/agch/agch_test.c @@ -0,0 +1,240 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <inttypes.h> +#include <unistd.h> + +static struct gsm_bts *bts; + +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"); + bts->agch_queue.max_length = 32; + + bts->agch_queue.low_level = 30; + bts->agch_queue.high_level = 30; + bts->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 %"PRIu64", merged %"PRIu64", rejected %"PRIu64", " + "ag-res %"PRIu64", non-res %"PRIu64"\n", + count, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count, + bts->agch_queue.max_length, bts->agch_queue.length, + bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs, + bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs, + bts->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 %"PRIu64", merged %"PRIu64", rejected %"PRIu64", " + "ag-res %"PRIu64", non-res %"PRIu64"\n", + multiframes, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count, + bts->agch_queue.max_length, bts->agch_queue.length, + bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs, + bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs, + bts->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) +{ + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + test_agch_queue_length_computation(); + test_agch_queue(); + printf("Success\n"); + + return 0; +} + diff --git a/tests/agch/agch_test.ok b/tests/agch/agch_test.ok new file mode 100644 index 00000000..a506f451 --- /dev/null +++ b/tests/agch/agch_test.ok @@ -0,0 +1,23 @@ +Testing 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 101, dropped 0, merged 198, rejected 421, ag-res 0, non-res 0 +AGCH drained: multiframes 4, imm.ass 2, imm.ass.rej 8 (refs 26), queue limit 32, occupied 0, dropped 92, merged 198, rejected 421, ag-res 3, non-res 6 +Success diff --git a/tests/cipher/Makefile.am b/tests/cipher/Makefile.am new file mode 100644 index 00000000..3c23718e --- /dev/null +++ b/tests/cipher/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS) +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) diff --git a/tests/cipher/cipher_test.c b/tests/cipher/cipher_test.c new file mode 100644 index 00000000..9d78a880 --- /dev/null +++ b/tests/cipher/cipher_test.c @@ -0,0 +1,85 @@ +/* (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/gsm_data.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> + +#include <errno.h> +#include <unistd.h> + +static struct gsm_bts *bts; + +#define ASSERT_TRUE(rc) \ + if (!(rc)) { \ + printf("Assert failed in %s:%d.\n", \ + __FILE__, __LINE__); \ + abort(); \ + } + +static void test_cipher_parsing(void) +{ + int i; + + bts->support.ciphers = 0; + + /* always support A5/0 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x0) == -ENOTSUP); + ASSERT_TRUE(bts_supports_cipher(bts, 0x1) == 1); /* A5/0 */ + for (i = 2; i <= 8; ++i) { + ASSERT_TRUE(bts_supports_cipher(bts, i) == 0); + } + + /* checking default A5/1 to A5/3 support */ + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + ASSERT_TRUE(bts_supports_cipher(bts, 0x0) == -ENOTSUP); + ASSERT_TRUE(bts_supports_cipher(bts, 0x1) == 1); /* A5/0 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x2) == 1); /* A5/1 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x3) == 1); /* A5/2 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x4) == 1); /* A5/3 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x5) == 0); /* A5/4 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x6) == 0); /* A5/5 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x7) == 0); /* A5/6 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x8) == 0); /* A5/7 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x9) == -ENOTSUP); +} + +int main(int argc, char **argv) +{ + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + test_cipher_parsing(); + printf("Success\n"); + + return 0; +} + diff --git a/tests/cipher/cipher_test.ok b/tests/cipher/cipher_test.ok new file mode 100644 index 00000000..35821117 --- /dev/null +++ b/tests/cipher/cipher_test.ok @@ -0,0 +1 @@ +Success diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am new file mode 100644 index 00000000..966ea469 --- /dev/null +++ b/tests/handover/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_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) diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c new file mode 100644 index 00000000..a805554e --- /dev/null +++ b/tests/handover/handover_test.c @@ -0,0 +1,283 @@ +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sched.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/backtrace.h> +#include <osmocom/abis/abis.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/handover.h> + +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 }; +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) +{ + void *tall_bts_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"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + osmo_stderr_target->categories[DHO].loglevel = LOGL_DEBUG; + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to init BTS\n"); + exit(1); + } + + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to alloc TRX structure\n"); + exit(1); + } + if (bts_trx_init(trx) < 0) { + fprintf(stderr, "unable to init TRX\n"); + exit(1); + } + + 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]; + lchan->type = GSM_LCHAN_SDCCH; + l1sap_chan_act(lchan->ts->trx, 0x09, NULL); + lchan = &trx->ts[2].lchan[0]; + lchan->type = GSM_LCHAN_TCH_F; + 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 < bts->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_init(struct gsm_bts_trx *trx) { 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; } +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) { return 0; } +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) { return; } +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { return 0; } +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return 0; } diff --git a/tests/handover/handover_test.ok b/tests/handover/handover_test.ok new file mode 100644 index 00000000..35821117 --- /dev/null +++ b/tests/handover/handover_test.ok @@ -0,0 +1 @@ +Success diff --git a/tests/meas/Makefile.am b/tests/meas/Makefile.am new file mode 100644 index 00000000..d8fa1182 --- /dev/null +++ b/tests/meas/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) +noinst_PROGRAMS = meas_test +noinst_HEADERS = sysmobts_fr_samples.h meas_testcases.h +EXTRA_DIST = meas_test.ok + +meas_test_SOURCES = meas_test.c +meas_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/meas/meas_test.c b/tests/meas/meas_test.c new file mode 100644 index 00000000..b2bf80e2 --- /dev/null +++ b/tests/meas/meas_test.c @@ -0,0 +1,675 @@ +#include <stdio.h> +#include <stdint.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/rsl.h> + +static struct gsm_bts *bts; +struct gsm_bts_trx *trx; + +struct fn_sample { + uint32_t fn; + uint8_t ts; + uint8_t ss; + int rc; +}; + +#include "sysmobts_fr_samples.h" +#include "meas_testcases.h" + + +void test_fn_sample(struct fn_sample *s, unsigned int len, uint8_t pchan, uint8_t tsmap) +{ + int rc; + struct gsm_lchan *lchan; + unsigned int i; + unsigned int delta = 0; + uint8_t tsmap_result = 0; + uint32_t fn_prev = 0; + struct gsm_time gsm_time; + + + printf("\n\n"); + printf("===========================================================\n"); + + for (i = 0; i < len; i++) { + + lchan = &trx->ts[s[i].ts].lchan[s[i].ss]; + trx->ts[s[i].ts].pchan = pchan; + lchan->meas.num_ul_meas = 1; + + rc = lchan_meas_check_compute(lchan, s[i].fn); + if (rc) { + gsm_fn2gsmtime(&gsm_time, s[i].fn); + fprintf(stdout, "Testing: ts[%i]->lchan[%i], fn=%u=>%s, fn%%104=%u, rc=%i, delta=%i\n", s[i].ts, + s[i].ss, s[i].fn, osmo_dump_gsmtime(&gsm_time), s[i].fn % 104, rc, s[i].fn - fn_prev); + fn_prev = s[i].fn; + tsmap_result |= (1 << s[i].ts); + } else + delta++; + + /* If the test data set provides a return + * code, we check that as well */ + if (s[i].rc != -1) + OSMO_ASSERT(s[i].rc == rc); + } + + /* Make sure that we exactly trigger on the right frames + * timeslots must match exactlty to what we expect */ + OSMO_ASSERT(tsmap_result == tsmap); +} + +static void reset_lchan_meas(struct gsm_lchan *lchan) +{ + lchan->state = LCHAN_S_ACTIVE; + memset(&lchan->meas, 0, sizeof(lchan->meas)); +} + +static void test_meas_compute(const struct meas_testcase *mtc) +{ + struct gsm_lchan *lchan; + unsigned int i; + unsigned int fn = 0; + + printf("\n\n"); + printf("===========================================================\n"); + printf("Measurement Compute Test: %s\n", mtc->name); + + lchan = &trx->ts[mtc->ts].lchan[0]; + lchan->ts->pchan = mtc->pchan; + reset_lchan_meas(lchan); + + /* feed uplink measurements into the code */ + for (i = 0; i < mtc->ulm_count; i++) { + lchan_new_ul_meas(lchan, (struct bts_ul_meas *) &mtc->ulm[i], fn); + fn += 1; + } + + /* compute the results */ + OSMO_ASSERT(lchan_meas_check_compute(lchan, mtc->final_fn) == mtc->res.success); + if (!mtc->res.success) { + OSMO_ASSERT(!(lchan->meas.flags & LC_UL_M_F_RES_VALID)); + } else { + OSMO_ASSERT(lchan->meas.flags & (LC_UL_M_F_RES_VALID|LC_UL_M_F_OSMO_EXT_VALID)); + printf("number of measurements: %u\n", mtc->ulm_count); + printf("parameter | actual | expected\n"); + printf("meas.ext.toa256_min | %6d | %6d\n", + lchan->meas.ext.toa256_min, mtc->res.toa256_min); + printf("meas.ext.toa256_max | %6d | %6d\n", + lchan->meas.ext.toa256_max, mtc->res.toa256_max); + printf("meas.ms_toa256 | %6d | %6d\n", + lchan->meas.ms_toa256, mtc->res.toa256_mean); + printf("meas.ext.toa256_std_dev | %6u | %6u\n", + lchan->meas.ext.toa256_std_dev, mtc->res.toa256_std_dev); + printf("meas.ul_res.full.rx_lev | %6u | %6u\n", + lchan->meas.ul_res.full.rx_lev, mtc->res.rx_lev_full); + printf("meas.ul_res.full.rx_qual | %6u | %6u\n", + lchan->meas.ul_res.full.rx_qual, mtc->res.rx_qual_full); + + if ((lchan->meas.ext.toa256_min != mtc->res.toa256_min) || + (lchan->meas.ext.toa256_max != mtc->res.toa256_max) || + (lchan->meas.ms_toa256 != mtc->res.toa256_mean) || + (lchan->meas.ext.toa256_std_dev != mtc->res.toa256_std_dev) || + (lchan->meas.ul_res.full.rx_lev != mtc->res.rx_lev_full)) { + fprintf(stderr, "%s: Unexpected measurement result!\n", mtc->name); + OSMO_ASSERT(false); + } + } + +} + +static void test_is_meas_complete_single(struct gsm_lchan *lchan, + uint32_t fn_end, uint8_t intv_len) +{ + unsigned int i; + unsigned int k; + int rc; + uint32_t offset; + + /* Walk through multiple measurement intervals and make sure that the + * interval end is detected only in the expected location */ + for (k = 0; k < 100; k++) { + offset = intv_len * k; + for (i = 0; i < intv_len; i++) { + rc = is_meas_complete(lchan, i + offset); + if (rc) + OSMO_ASSERT(i + offset == fn_end + offset); + } + } +} + +static void test_is_meas_complete(void) +{ + struct gsm_lchan *lchan; + printf("\n\n"); + printf("===========================================================\n"); + printf("Testing is_meas_complete()\n"); + + /* Test interval end detection on TCH/F TS0-TS7 */ + lchan = &trx->ts[0].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 12, 104); + + lchan = &trx->ts[1].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 25, 104); + + lchan = &trx->ts[2].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 38, 104); + + lchan = &trx->ts[3].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 51, 104); + + lchan = &trx->ts[4].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 64, 104); + + lchan = &trx->ts[5].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 77, 104); + + lchan = &trx->ts[6].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 90, 104); + + lchan = &trx->ts[7].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + test_is_meas_complete_single(lchan, 103, 104); + + /* Test interval end detection on TCH/H TS0-TS7 */ + lchan = &trx->ts[0].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 12, 104); + + lchan = &trx->ts[1].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 12, 104); + + lchan = &trx->ts[0].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 25, 104); + + lchan = &trx->ts[1].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 25, 104); + + lchan = &trx->ts[2].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 38, 104); + + lchan = &trx->ts[3].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 38, 104); + + lchan = &trx->ts[2].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 51, 104); + + lchan = &trx->ts[3].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 51, 104); + + lchan = &trx->ts[4].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 64, 104); + + lchan = &trx->ts[5].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 64, 104); + + lchan = &trx->ts[4].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 77, 104); + + lchan = &trx->ts[5].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 77, 104); + + lchan = &trx->ts[6].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 90, 104); + + lchan = &trx->ts[7].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 90, 104); + + lchan = &trx->ts[6].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 103, 104); + + lchan = &trx->ts[7].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + test_is_meas_complete_single(lchan, 103, 104); + + /* Test interval end detection on SDCCH/8 SS0-SS7 */ + lchan = &trx->ts[0].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 66, 102); + + lchan = &trx->ts[0].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 70, 102); + + lchan = &trx->ts[0].lchan[2]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 74, 102); + + lchan = &trx->ts[0].lchan[3]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 78, 102); + + lchan = &trx->ts[0].lchan[4]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 98, 102); + + lchan = &trx->ts[0].lchan[5]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 0, 102); + + lchan = &trx->ts[0].lchan[6]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 4, 102); + + lchan = &trx->ts[0].lchan[7]; + lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C; + test_is_meas_complete_single(lchan, 8, 102); + + /* Test interval end detection on SDCCH/4 SS0-SS3 */ + lchan = &trx->ts[0].lchan[0]; + lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4; + test_is_meas_complete_single(lchan, 88, 102); + + lchan = &trx->ts[0].lchan[1]; + lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4; + test_is_meas_complete_single(lchan, 92, 102); + + lchan = &trx->ts[0].lchan[2]; + lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4; + test_is_meas_complete_single(lchan, 6, 102); + + lchan = &trx->ts[0].lchan[3]; + lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4; + test_is_meas_complete_single(lchan, 10, 102); +} + +/* This tests the robustness of lchan_meas_process_measurement(). This is the + * function that is called from l1_sap.c each time a measurement indication is + * received. The process must still go on when measurement indications (blocks) + * are lost or otherwise spaced out. Even the complete absence of the + * measurement indications from the SACCH which are used to detect the interval + * end must not keep the interval from beeing processed. */ +void test_lchan_meas_process_measurement(bool no_sacch, bool dropouts) +{ + struct gsm_lchan *lchan = &trx->ts[2].lchan[0]; + unsigned int i; + unsigned int k = 0; + unsigned int fn = 0; + unsigned int fn104; + struct bts_ul_meas ulm; + int rc; + + printf("\n\n"); + printf("===========================================================\n"); + printf("Testing lchan_meas_process_measurement()\n"); + if (no_sacch) + printf(" * SACCH blocks not generated.\n"); + if (dropouts) + printf + (" * Simulate dropouts by leaving out every 4th measurement\n"); + + ulm.ber10k = 0; + ulm.ta_offs_256bits = 256; + ulm.c_i = 0; + ulm.is_sub = 0; + ulm.inv_rssi = 90; + + lchan->ts->pchan = GSM_PCHAN_TCH_F; + reset_lchan_meas(lchan); + + /* feed uplink measurements into the code */ + for (i = 0; i < 100; i++) { + + fn104 = fn % 104; + ulm.is_sub = 0; + + if (fn104 >= 52 && fn104 <= 59) { + ulm.is_sub = 1; + } + + if (dropouts == false || i % 4) { + if (ulm.is_sub == 1) + printf("(now adding SUB measurement sample %u)\n", fn); + rc = lchan_meas_process_measurement(lchan, &ulm, fn); + OSMO_ASSERT(rc == 0); + } else if (ulm.is_sub == 1) + printf("(leaving out SUB measurement sample for frame number %u)\n", fn); + else + printf("(leaving out measurement sample for frame number %u)\n", fn); + + fn += 4; + if (k == 2) { + fn++; + k = 0; + } else + k++; + + if (fn % 104 == 39 && no_sacch == false) { + printf("(now adding SUB measurement sample for SACCH block at frame number %u)\n", fn); + ulm.is_sub = 1; + rc = lchan_meas_process_measurement(lchan, &ulm, fn - 1); + OSMO_ASSERT(rc); + } else if (fn % 104 == 39 && no_sacch == true) + printf("(leaving out SUB measurement sample for SACCH block at frame number %u)\n", fn); + } +} + +static bool test_ts45008_83_is_sub_is_sacch(uint32_t fn) +{ + if (fn % 104 == 12) + return true; + if (fn % 104 == 25) + return true; + if (fn % 104 == 38) + return true; + if (fn % 104 == 51) + return true; + if (fn % 104 == 64) + return true; + if (fn % 104 == 77) + return true; + if (fn % 104 == 90) + return true; + if (fn % 104 == 103) + return true; + + return false; +} + +static bool test_ts45008_83_is_sub_is_sub(uint32_t fn, uint8_t ss) +{ + fn = fn % 104; + + if (fn >= 52 && fn <= 59) + return true; + + if (ss == 0) { + if (fn == 0) + return true; + if (fn == 2) + return true; + if (fn == 4) + return true; + if (fn == 6) + return true; + if (fn == 52) + return true; + if (fn == 54) + return true; + if (fn == 56) + return true; + if (fn == 58) + return true; + } else if (ss == 1) { + if (fn == 14) + return true; + if (fn == 16) + return true; + if (fn == 18) + return true; + if (fn == 20) + return true; + if (fn == 66) + return true; + if (fn == 68) + return true; + if (fn == 70) + return true; + if (fn == 72) + return true; + } else + OSMO_ASSERT(false); + + return false; +} + +static void test_ts45008_83_is_sub_single(uint8_t ts, uint8_t ss, bool fr) +{ + struct gsm_lchan *lchan; + bool rc; + unsigned int i; + + lchan = &trx->ts[ts].lchan[ss]; + + printf("Checking: "); + + if (fr) { + printf("TCH/F"); + lchan->type = GSM_LCHAN_TCH_F; + lchan->ts->pchan = GSM_PCHAN_TCH_F; + lchan->tch_mode = GSM48_CMODE_SPEECH_V1; + } else { + printf("TCH/H"); + lchan->type = GSM_LCHAN_TCH_H; + lchan->ts->pchan = GSM_PCHAN_TCH_H; + lchan->tch_mode = GSM48_CMODE_SPEECH_V1; + } + + printf(" TS=%u ", ts); + printf("SS=%u", ss); + + /* Walk trough the first 100 intervals and check for unexpected + * results (false positive and false negative) */ + for (i = 0; i < 104 * 100; i++) { + rc = ts45008_83_is_sub(lchan, i, false); + if (rc) { + if (!test_ts45008_83_is_sub_is_sacch(i) + && !test_ts45008_83_is_sub_is_sub(i, ss)) { + printf("==> Unexpected SUB frame at fn=%u", i); + OSMO_ASSERT(false); + } + } else { + if (test_ts45008_83_is_sub_is_sacch(i) + && test_ts45008_83_is_sub_is_sub(i, ss)) { + printf("==> Unexpected non-SUB frame at fn=%u", + i); + OSMO_ASSERT(false); + } + } + } + printf("\n"); +} + +static void test_ts45008_83_is_sub(void) +{ + unsigned int i; + + printf("\n\n"); + printf("===========================================================\n"); + printf("Testing ts45008_83_is_sub()\n"); + + for (i = 0; i < 7; i++) + test_ts45008_83_is_sub_single(i, 0, true); + for (i = 0; i < 7; i++) + test_ts45008_83_is_sub_single(i, 0, false); + for (i = 0; i < 7; i++) + test_ts45008_83_is_sub_single(i, 1, false); +} + +int main(int argc, char **argv) +{ + void *tall_bts_ctx; + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + osmo_stderr_target->categories[DMEAS].loglevel = LOGL_DEBUG; + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to init BTS\n"); + exit(1); + } + + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to alloc TRX structure\n"); + exit(1); + } + if (bts_trx_init(trx) < 0) { + fprintf(stderr, "unable to init TRX\n"); + exit(1); + } + + printf("\n"); + printf("***********************\n"); + printf("*** FULL RATE TESTS ***\n"); + printf("***********************\n"); + + /* Test full rate */ + test_fn_sample(test_fn_tch_f_ts_2_3, ARRAY_SIZE(test_fn_tch_f_ts_2_3), GSM_PCHAN_TCH_F, (1 << 2) | (1 << 3)); + test_fn_sample(test_fn_tch_f_ts_4_5, ARRAY_SIZE(test_fn_tch_f_ts_4_5), GSM_PCHAN_TCH_F, (1 << 4) | (1 << 5)); + test_fn_sample(test_fn_tch_f_ts_6_7, ARRAY_SIZE(test_fn_tch_f_ts_6_7), GSM_PCHAN_TCH_F, (1 << 6) | (1 << 7)); + + printf("\n"); + printf("***********************\n"); + printf("*** HALF RATE TESTS ***\n"); + printf("***********************\n"); + + /* Test half rate */ + test_fn_sample(test_fn_tch_h_ts_2_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_2_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 2)); + test_fn_sample(test_fn_tch_h_ts_3_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_3_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 3)); + test_fn_sample(test_fn_tch_h_ts_4_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_4_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 4)); + test_fn_sample(test_fn_tch_h_ts_5_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_5_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 5)); + test_fn_sample(test_fn_tch_h_ts_6_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_6_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 6)); + test_fn_sample(test_fn_tch_h_ts_7_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_7_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 7)); + + test_meas_compute(&mtc1); + test_meas_compute(&mtc2); + test_meas_compute(&mtc3); + test_meas_compute(&mtc4); + test_meas_compute(&mtc5); + test_meas_compute(&mtc_tch_f_complete); + test_meas_compute(&mtc_tch_f_dtx_with_lost_subs); + test_meas_compute(&mtc_tch_f_dtx); + test_meas_compute(&mtc_tch_h_complete); + test_meas_compute(&mtc_tch_h_dtx_with_lost_subs); + test_meas_compute(&mtc_tch_h_dtx); + test_meas_compute(&mtc_overrun); + test_meas_compute(&mtc_sdcch4_complete); + test_meas_compute(&mtc_sdcch8_complete); + + printf("\n"); + printf("***************************************************\n"); + printf("*** MEASUREMENT INTERVAL ENDING DETECTION TESTS ***\n"); + printf("***************************************************\n"); + + test_is_meas_complete(); + test_lchan_meas_process_measurement(false, false); + test_lchan_meas_process_measurement(true, false); + test_lchan_meas_process_measurement(false, true); + test_lchan_meas_process_measurement(true, true); + test_ts45008_83_is_sub(); + + printf("Success\n"); + + return 0; +} + +/* Stubs */ +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) +{ + 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_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_init(struct gsm_bts_trx *trx) +{ + 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; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + return 0; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + return; +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + return 0; +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + return 0; +} diff --git a/tests/meas/meas_test.ok b/tests/meas/meas_test.ok new file mode 100644 index 00000000..e62bb42f --- /dev/null +++ b/tests/meas/meas_test.ok @@ -0,0 +1,853 @@ + +*********************** +*** FULL RATE TESTS *** +*********************** + + +=========================================================== +Testing: ts[2]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=10958 +Testing: ts[2]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=104 +Testing: ts[3]->lchan[0], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11595=>011595/08/25/18/23, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11686=>011686/08/12/07/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11699=>011699/08/25/20/23, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11790=>011790/08/12/09/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11803=>011803/08/25/22/27, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11894=>011894/08/12/11/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11907=>011907/08/25/24/27, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11998=>011998/09/12/13/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12011=>012011/09/25/26/27, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12102=>012102/09/12/15/18, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12115=>012115/09/25/28/31, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12206=>012206/09/12/17/18, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12219=>012219/09/25/30/31, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12310=>012310/09/12/19/22, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12323=>012323/09/25/32/35, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12414=>012414/09/12/21/22, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12427=>012427/09/25/34/35, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12518=>012518/09/12/23/22, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12531=>012531/09/25/36/35, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12622=>012622/09/12/25/26, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12635=>012635/09/25/38/39, fn%104=51, rc=1, delta=13 + + +=========================================================== +Testing: ts[4]->lchan[0], fn=5888=>005888/04/12/23/00, fn%104=64, rc=1, delta=5888 +Testing: ts[4]->lchan[0], fn=5992=>005992/04/12/25/00, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=6096=>006096/04/12/27/00, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=6200=>006200/04/12/29/04, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=6213=>006213/04/25/42/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6304=>006304/04/12/31/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6317=>006317/04/25/44/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6408=>006408/04/12/33/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6421=>006421/04/25/46/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6512=>006512/04/12/35/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6525=>006525/04/25/48/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6616=>006616/04/12/37/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6629=>006629/04/25/50/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6720=>006720/05/12/39/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6733=>006733/05/25/01/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6824=>006824/05/12/41/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6837=>006837/05/25/03/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6928=>006928/05/12/43/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6941=>006941/05/25/05/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7032=>007032/05/12/45/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7045=>007045/05/25/07/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7136=>007136/05/12/47/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7149=>007149/05/25/09/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7240=>007240/05/12/49/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7253=>007253/05/25/11/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7344=>007344/05/12/00/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7357=>007357/05/25/13/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7448=>007448/05/12/02/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7461=>007461/05/25/15/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7552=>007552/05/12/04/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7565=>007565/05/25/17/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7656=>007656/05/12/06/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7669=>007669/05/25/19/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7773=>007773/05/25/21/41, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7877=>007877/05/25/23/41, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7981=>007981/06/25/25/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8085=>008085/06/25/27/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8592=>008592/06/12/24/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8605=>008605/06/25/37/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8696=>008696/06/12/26/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8709=>008709/06/25/39/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8800=>008800/06/12/28/44, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8813=>008813/06/25/41/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8904=>008904/06/12/30/44, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8917=>008917/06/25/43/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9008=>009008/06/12/32/48, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9021=>009021/06/25/45/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9112=>009112/06/12/34/48, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9125=>009125/06/25/47/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9216=>009216/06/12/36/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9229=>009229/06/25/49/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9320=>009320/07/12/38/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9333=>009333/07/25/00/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9424=>009424/07/12/40/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9437=>009437/07/25/02/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9528=>009528/07/12/42/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9541=>009541/07/25/04/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9632=>009632/07/12/44/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9645=>009645/07/25/06/17, fn%104=77, rc=1, delta=13 + + +=========================================================== +Testing: ts[6]->lchan[0], fn=8618=>008618/06/12/50/14, fn%104=90, rc=1, delta=8618 +Testing: ts[6]->lchan[0], fn=8722=>008722/06/12/01/18, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8826=>008826/06/12/03/18, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8930=>008930/06/12/05/18, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[0], fn=8943=>008943/06/25/18/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9034=>009034/06/12/07/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9047=>009047/06/25/20/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9138=>009138/06/12/09/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9151=>009151/06/25/22/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9242=>009242/06/12/11/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9255=>009255/06/25/24/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9346=>009346/07/12/13/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9359=>009359/07/25/26/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9450=>009450/07/12/15/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9463=>009463/07/25/28/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9554=>009554/07/12/17/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9567=>009567/07/25/30/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9658=>009658/07/12/19/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9671=>009671/07/25/32/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9762=>009762/07/12/21/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9775=>009775/07/25/34/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9866=>009866/07/12/23/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9879=>009879/07/25/36/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9970=>009970/07/12/25/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9983=>009983/07/25/38/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10074=>010074/07/12/27/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10087=>010087/07/25/40/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10178=>010178/07/12/29/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10191=>010191/07/25/42/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10282=>010282/07/12/31/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10295=>010295/07/25/44/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10386=>010386/07/12/33/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10399=>010399/07/25/46/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10490=>010490/07/12/35/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10503=>010503/07/25/48/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10594=>010594/07/12/37/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10607=>010607/07/25/50/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10698=>010698/08/12/39/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10711=>010711/08/25/01/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10802=>010802/08/12/41/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10815=>010815/08/25/03/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10906=>010906/08/12/43/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10919=>010919/08/25/05/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11010=>011010/08/12/45/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11023=>011023/08/25/07/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11114=>011114/08/12/47/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11127=>011127/08/25/09/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11218=>011218/08/12/49/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11231=>011231/08/25/11/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11322=>011322/08/12/00/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11335=>011335/08/25/13/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11426=>011426/08/12/02/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11439=>011439/08/25/15/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11530=>011530/08/12/04/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11543=>011543/08/25/17/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11634=>011634/08/12/06/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11647=>011647/08/25/19/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11738=>011738/08/12/08/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11751=>011751/08/25/21/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11842=>011842/08/12/10/14, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11855=>011855/08/25/23/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11946=>011946/09/12/12/14, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11959=>011959/09/25/25/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=12050=>012050/09/12/14/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=12063=>012063/09/25/27/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=12154=>012154/09/12/16/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=12167=>012167/09/25/29/31, fn%104=103, rc=1, delta=13 + +*********************** +*** HALF RATE TESTS *** +*********************** + + +=========================================================== +Testing: ts[2]->lchan[0], fn=8982=>008982/06/12/06/22, fn%104=38, rc=1, delta=8982 +Testing: ts[2]->lchan[0], fn=9086=>009086/06/12/08/22, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=9190=>009190/06/12/10/22, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=9294=>009294/07/12/12/26, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=9398=>009398/07/12/14/26, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[1], fn=9411=>009411/07/25/27/39, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9502=>009502/07/12/16/30, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9515=>009515/07/25/29/43, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9606=>009606/07/12/18/30, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9619=>009619/07/25/31/43, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9710=>009710/07/12/20/30, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9723=>009723/07/25/33/43, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9814=>009814/07/12/22/34, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9827=>009827/07/25/35/47, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9918=>009918/07/12/24/34, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9931=>009931/07/25/37/47, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10022=>010022/07/12/26/38, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10035=>010035/07/25/39/51, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10126=>010126/07/12/28/38, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10139=>010139/07/25/41/51, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10230=>010230/07/12/30/38, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10243=>010243/07/25/43/03, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10334=>010334/07/12/32/42, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10347=>010347/07/25/45/03, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10438=>010438/07/12/34/42, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10451=>010451/07/25/47/03, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10542=>010542/07/12/36/46, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10555=>010555/07/25/49/07, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10646=>010646/08/12/38/46, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10659=>010659/08/25/00/07, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10750=>010750/08/12/40/46, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10763=>010763/08/25/02/11, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10854=>010854/08/12/42/50, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10867=>010867/08/25/04/11, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10971=>010971/08/25/06/11, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11075=>011075/08/25/08/15, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11179=>011179/08/25/10/15, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11283=>011283/08/25/12/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11387=>011387/08/25/14/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91 + + +=========================================================== +Testing: ts[3]->lchan[0], fn=10022=>010022/07/12/26/38, fn%104=38, rc=1, delta=10022 +Testing: ts[3]->lchan[1], fn=10035=>010035/07/25/39/51, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10126=>010126/07/12/28/38, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10139=>010139/07/25/41/51, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10230=>010230/07/12/30/38, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10243=>010243/07/25/43/03, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10334=>010334/07/12/32/42, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10347=>010347/07/25/45/03, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10438=>010438/07/12/34/42, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10451=>010451/07/25/47/03, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10542=>010542/07/12/36/46, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10555=>010555/07/25/49/07, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10646=>010646/08/12/38/46, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10659=>010659/08/25/00/07, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10750=>010750/08/12/40/46, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10763=>010763/08/25/02/11, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10854=>010854/08/12/42/50, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10867=>010867/08/25/04/11, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10971=>010971/08/25/06/11, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11075=>011075/08/25/08/15, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11179=>011179/08/25/10/15, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11283=>011283/08/25/12/19, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11387=>011387/08/25/14/19, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11595=>011595/08/25/18/23, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11686=>011686/08/12/07/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11699=>011699/08/25/20/23, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11790=>011790/08/12/09/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11803=>011803/08/25/22/27, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11894=>011894/08/12/11/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11907=>011907/08/25/24/27, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11998=>011998/09/12/13/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=12011=>012011/09/25/26/27, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=12102=>012102/09/12/15/18, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=12115=>012115/09/25/28/31, fn%104=51, rc=1, delta=13 + + +=========================================================== +Testing: ts[4]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=7760 +Testing: ts[4]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[1], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8592=>008592/06/12/24/40, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8605=>008605/06/25/37/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8696=>008696/06/12/26/40, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8709=>008709/06/25/39/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8800=>008800/06/12/28/44, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8813=>008813/06/25/41/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8904=>008904/06/12/30/44, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8917=>008917/06/25/43/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9008=>009008/06/12/32/48, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9021=>009021/06/25/45/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9112=>009112/06/12/34/48, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9125=>009125/06/25/47/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9216=>009216/06/12/36/00, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9229=>009229/06/25/49/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9320=>009320/07/12/38/00, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9333=>009333/07/25/00/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9424=>009424/07/12/40/00, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9437=>009437/07/25/02/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9528=>009528/07/12/42/04, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9541=>009541/07/25/04/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9632=>009632/07/12/44/04, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9645=>009645/07/25/06/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9736=>009736/07/12/46/08, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9749=>009749/07/25/08/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9840=>009840/07/12/48/08, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9853=>009853/07/25/10/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9944=>009944/07/12/50/08, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9957=>009957/07/25/12/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10048=>010048/07/12/01/12, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10061=>010061/07/25/14/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10152=>010152/07/12/03/12, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10165=>010165/07/25/16/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10256=>010256/07/12/05/16, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10269=>010269/07/25/18/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10360=>010360/07/12/07/16, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10373=>010373/07/25/20/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10464=>010464/07/12/09/16, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10477=>010477/07/25/22/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10568=>010568/07/12/11/20, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10581=>010581/07/25/24/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10672=>010672/08/12/13/20, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10685=>010685/08/25/26/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10776=>010776/08/12/15/24, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10789=>010789/08/25/28/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10880=>010880/08/12/17/24, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10893=>010893/08/25/30/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10984=>010984/08/12/19/24, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10997=>010997/08/25/32/37, fn%104=77, rc=1, delta=13 + + +=========================================================== +Testing: ts[5]->lchan[0], fn=5264=>005264/03/12/11/40, fn%104=64, rc=1, delta=5264 +Testing: ts[5]->lchan[0], fn=5368=>005368/04/12/13/40, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=5472=>005472/04/12/15/44, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=5576=>005576/04/12/17/44, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=5680=>005680/04/12/19/48, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[1], fn=5693=>005693/04/25/32/09, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=5784=>005784/04/12/21/48, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=5797=>005797/04/25/34/09, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=5888=>005888/04/12/23/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=5901=>005901/04/25/36/13, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=5992=>005992/04/12/25/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6005=>006005/04/25/38/13, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6096=>006096/04/12/27/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6109=>006109/04/25/40/13, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6200=>006200/04/12/29/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6213=>006213/04/25/42/17, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6304=>006304/04/12/31/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6317=>006317/04/25/44/17, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6408=>006408/04/12/33/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6421=>006421/04/25/46/21, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6512=>006512/04/12/35/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6525=>006525/04/25/48/21, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6616=>006616/04/12/37/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6629=>006629/04/25/50/21, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6720=>006720/05/12/39/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6733=>006733/05/25/01/25, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6824=>006824/05/12/41/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6837=>006837/05/25/03/25, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6928=>006928/05/12/43/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6941=>006941/05/25/05/29, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7032=>007032/05/12/45/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7045=>007045/05/25/07/29, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7136=>007136/05/12/47/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7149=>007149/05/25/09/29, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7240=>007240/05/12/49/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7253=>007253/05/25/11/33, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7344=>007344/05/12/00/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7357=>007357/05/25/13/33, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7448=>007448/05/12/02/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7461=>007461/05/25/15/37, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7552=>007552/05/12/04/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7565=>007565/05/25/17/37, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7656=>007656/05/12/06/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7669=>007669/05/25/19/37, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7773=>007773/05/25/21/41, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7877=>007877/05/25/23/41, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7981=>007981/06/25/25/45, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8085=>008085/06/25/27/45, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13 + + +=========================================================== +Testing: ts[6]->lchan[0], fn=8098=>008098/06/12/40/06, fn%104=90, rc=1, delta=8098 +Testing: ts[6]->lchan[0], fn=8202=>008202/06/12/42/10, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8306=>008306/06/12/44/10, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8410=>008410/06/12/46/10, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[1], fn=8423=>008423/06/25/08/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8514=>008514/06/12/48/14, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8527=>008527/06/25/10/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8618=>008618/06/12/50/14, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8631=>008631/06/25/12/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8722=>008722/06/12/01/18, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8735=>008735/06/25/14/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8826=>008826/06/12/03/18, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8839=>008839/06/25/16/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8930=>008930/06/12/05/18, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8943=>008943/06/25/18/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9034=>009034/06/12/07/22, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9047=>009047/06/25/20/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9138=>009138/06/12/09/22, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9151=>009151/06/25/22/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9242=>009242/06/12/11/26, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9255=>009255/06/25/24/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9346=>009346/07/12/13/26, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9359=>009359/07/25/26/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9450=>009450/07/12/15/26, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9463=>009463/07/25/28/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9554=>009554/07/12/17/30, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9567=>009567/07/25/30/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9658=>009658/07/12/19/30, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9671=>009671/07/25/32/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9762=>009762/07/12/21/34, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9775=>009775/07/25/34/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9866=>009866/07/12/23/34, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9879=>009879/07/25/36/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9970=>009970/07/12/25/34, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9983=>009983/07/25/38/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10074=>010074/07/12/27/38, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10087=>010087/07/25/40/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10178=>010178/07/12/29/38, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10191=>010191/07/25/42/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10282=>010282/07/12/31/42, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10295=>010295/07/25/44/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10386=>010386/07/12/33/42, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10399=>010399/07/25/46/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10490=>010490/07/12/35/42, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10503=>010503/07/25/48/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10594=>010594/07/12/37/46, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10607=>010607/07/25/50/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10698=>010698/08/12/39/46, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10711=>010711/08/25/01/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10802=>010802/08/12/41/50, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10815=>010815/08/25/03/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10906=>010906/08/12/43/50, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10919=>010919/08/25/05/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11010=>011010/08/12/45/02, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11023=>011023/08/25/07/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11114=>011114/08/12/47/02, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11127=>011127/08/25/09/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11218=>011218/08/12/49/02, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11231=>011231/08/25/11/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11322=>011322/08/12/00/06, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11335=>011335/08/25/13/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11426=>011426/08/12/02/06, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11439=>011439/08/25/15/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11530=>011530/08/12/04/10, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11543=>011543/08/25/17/23, fn%104=103, rc=1, delta=13 + + +=========================================================== +Testing: ts[7]->lchan[0], fn=11738=>011738/08/12/08/10, fn%104=90, rc=1, delta=11738 +Testing: ts[7]->lchan[0], fn=11842=>011842/08/12/10/14, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[0], fn=11946=>011946/09/12/12/14, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[0], fn=12050=>012050/09/12/14/18, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[1], fn=12063=>012063/09/25/27/31, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12154=>012154/09/12/16/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12167=>012167/09/25/29/31, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12258=>012258/09/12/18/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12271=>012271/09/25/31/31, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12362=>012362/09/12/20/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12375=>012375/09/25/33/35, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12466=>012466/09/12/22/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12479=>012479/09/25/35/35, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12570=>012570/09/12/24/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12583=>012583/09/25/37/39, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12674=>012674/09/12/26/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12687=>012687/09/25/39/39, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12778=>012778/09/12/28/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12791=>012791/09/25/41/39, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12882=>012882/09/12/30/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12895=>012895/09/25/43/43, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12986=>012986/09/12/32/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12999=>012999/09/25/45/43, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13090=>013090/09/12/34/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13103=>013103/09/25/47/47, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13194=>013194/09/12/36/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13207=>013207/09/25/49/47, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13298=>013298/10/12/38/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13311=>013311/10/25/00/47, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13402=>013402/10/12/40/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13415=>013415/10/25/02/51, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13506=>013506/10/12/42/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13519=>013519/10/25/04/51, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13610=>013610/10/12/44/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13623=>013623/10/25/06/03, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13714=>013714/10/12/46/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13727=>013727/10/25/08/03, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13818=>013818/10/12/48/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13831=>013831/10/25/10/07, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13922=>013922/10/12/50/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13935=>013935/10/25/12/07, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14026=>014026/10/12/01/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14039=>014039/10/25/14/07, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14130=>014130/10/12/03/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14143=>014143/10/25/16/11, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14234=>014234/10/12/05/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14247=>014247/10/25/18/11, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14338=>014338/10/12/07/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14351=>014351/10/25/20/15, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14442=>014442/10/12/09/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14455=>014455/10/25/22/15, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14546=>014546/10/12/11/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14559=>014559/10/25/24/15, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14650=>014650/11/12/13/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14663=>014663/11/25/26/19, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14754=>014754/11/12/15/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14767=>014767/11/25/28/19, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14858=>014858/11/12/17/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14871=>014871/11/25/30/23, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14962=>014962/11/12/19/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14975=>014975/11/25/32/23, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=15066=>015066/11/12/21/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=15079=>015079/11/25/34/23, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=15170=>015170/11/12/23/14, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=15183=>015183/11/25/36/27, fn%104=103, rc=1, delta=13 + + +=========================================================== +Measurement Compute Test: TOA256 Min-Max negative/positive +number of measurements: 3 +parameter | actual | expected +meas.ext.toa256_min | -256 | -256 +meas.ext.toa256_max | 256 | 256 +meas.ms_toa256 | 0 | 0 +meas.ext.toa256_std_dev | 209 | 209 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 0 + + +=========================================================== +Measurement Compute Test: TOA256 small jitter around 256 +number of measurements: 25 +parameter | actual | expected +meas.ext.toa256_min | 254 | 254 +meas.ext.toa256_max | 258 | 258 +meas.ms_toa256 | 256 | 256 +meas.ext.toa256_std_dev | 1 | 1 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 7 + + +=========================================================== +Measurement Compute Test: RxLEv averaging +number of measurements: 5 +parameter | actual | expected +meas.ext.toa256_min | 0 | 0 +meas.ext.toa256_max | 0 | 0 +meas.ms_toa256 | 0 | 0 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 0 + + +=========================================================== +Measurement Compute Test: Empty measurements +number of measurements: 0 +parameter | actual | expected +meas.ext.toa256_min | 0 | 0 +meas.ext.toa256_max | 0 | 0 +meas.ms_toa256 | 0 | 0 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 63 | 63 +meas.ul_res.full.rx_qual | 3 | 3 + + +=========================================================== +Measurement Compute Test: TOA256 26 blocks with max TOA256 +number of measurements: 26 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Complete TCH/F measurement period (26 measurements, 3 sub-frames) +number of measurements: 25 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Incomplete TCH/F measurement period (16 measurements, 1 sub-frame) +number of measurements: 16 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: Incomplete but normal TCH/F measurement period (16 measurements, 3 sub-frames) +number of measurements: 16 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: Complete TCH/H measurement period (26 measurements, 5 sub-frames) +number of measurements: 25 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Incomplete TCH/H measurement period (14 measurements, 3 sub-frames) +number of measurements: 14 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: Incomplete but normal TCH/F measurement period (16 measurements, 5 sub-frames) +number of measurements: 16 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: TCH/F measurement period with too much measurement values (overrun) +number of measurements: 59 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Complete SDCCH4 measurement period (3 measurements) +number of measurements: 3 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Complete SDCCH8 measurement period (3 measurements) +number of measurements: 3 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + +*************************************************** +*** MEASUREMENT INTERVAL ENDING DETECTION TESTS *** +*************************************************** + + +=========================================================== +Testing is_meas_complete() + + +=========================================================== +Testing lchan_meas_process_measurement() +(now adding SUB measurement sample for SACCH block at frame number 39) +(now adding SUB measurement sample 52) +(now adding SUB measurement sample 56) +(now adding SUB measurement sample for SACCH block at frame number 143) +(now adding SUB measurement sample 156) +(now adding SUB measurement sample 160) +(now adding SUB measurement sample for SACCH block at frame number 247) +(now adding SUB measurement sample 260) +(now adding SUB measurement sample 264) +(now adding SUB measurement sample for SACCH block at frame number 351) +(now adding SUB measurement sample 364) +(now adding SUB measurement sample 368) + + +=========================================================== +Testing lchan_meas_process_measurement() + * SACCH blocks not generated. +(leaving out SUB measurement sample for SACCH block at frame number 39) +(now adding SUB measurement sample 52) +(now adding SUB measurement sample 56) +(leaving out SUB measurement sample for SACCH block at frame number 143) +(now adding SUB measurement sample 156) +(now adding SUB measurement sample 160) +(leaving out SUB measurement sample for SACCH block at frame number 247) +(now adding SUB measurement sample 260) +(now adding SUB measurement sample 264) +(leaving out SUB measurement sample for SACCH block at frame number 351) +(now adding SUB measurement sample 364) +(now adding SUB measurement sample 368) + + +=========================================================== +Testing lchan_meas_process_measurement() + * Simulate dropouts by leaving out every 4th measurement +(leaving out measurement sample for frame number 0) +(leaving out measurement sample for frame number 17) +(leaving out measurement sample for frame number 34) +(now adding SUB measurement sample for SACCH block at frame number 39) +(leaving out SUB measurement sample for frame number 52) +(now adding SUB measurement sample 56) +(leaving out measurement sample for frame number 69) +(leaving out measurement sample for frame number 86) +(leaving out measurement sample for frame number 104) +(leaving out measurement sample for frame number 121) +(leaving out measurement sample for frame number 138) +(now adding SUB measurement sample for SACCH block at frame number 143) +(leaving out SUB measurement sample for frame number 156) +(now adding SUB measurement sample 160) +(leaving out measurement sample for frame number 173) +(leaving out measurement sample for frame number 190) +(leaving out measurement sample for frame number 208) +(leaving out measurement sample for frame number 225) +(leaving out measurement sample for frame number 242) +(now adding SUB measurement sample for SACCH block at frame number 247) +(leaving out SUB measurement sample for frame number 260) +(now adding SUB measurement sample 264) +(leaving out measurement sample for frame number 277) +(leaving out measurement sample for frame number 294) +(leaving out measurement sample for frame number 312) +(leaving out measurement sample for frame number 329) +(leaving out measurement sample for frame number 346) +(now adding SUB measurement sample for SACCH block at frame number 351) +(leaving out SUB measurement sample for frame number 364) +(now adding SUB measurement sample 368) +(leaving out measurement sample for frame number 381) +(leaving out measurement sample for frame number 398) +(leaving out measurement sample for frame number 416) + + +=========================================================== +Testing lchan_meas_process_measurement() + * SACCH blocks not generated. + * Simulate dropouts by leaving out every 4th measurement +(leaving out measurement sample for frame number 0) +(leaving out measurement sample for frame number 17) +(leaving out measurement sample for frame number 34) +(leaving out SUB measurement sample for SACCH block at frame number 39) +(leaving out SUB measurement sample for frame number 52) +(now adding SUB measurement sample 56) +(leaving out measurement sample for frame number 69) +(leaving out measurement sample for frame number 86) +(leaving out measurement sample for frame number 104) +(leaving out measurement sample for frame number 121) +(leaving out measurement sample for frame number 138) +(leaving out SUB measurement sample for SACCH block at frame number 143) +(leaving out SUB measurement sample for frame number 156) +(now adding SUB measurement sample 160) +(leaving out measurement sample for frame number 173) +(leaving out measurement sample for frame number 190) +(leaving out measurement sample for frame number 208) +(leaving out measurement sample for frame number 225) +(leaving out measurement sample for frame number 242) +(leaving out SUB measurement sample for SACCH block at frame number 247) +(leaving out SUB measurement sample for frame number 260) +(now adding SUB measurement sample 264) +(leaving out measurement sample for frame number 277) +(leaving out measurement sample for frame number 294) +(leaving out measurement sample for frame number 312) +(leaving out measurement sample for frame number 329) +(leaving out measurement sample for frame number 346) +(leaving out SUB measurement sample for SACCH block at frame number 351) +(leaving out SUB measurement sample for frame number 364) +(now adding SUB measurement sample 368) +(leaving out measurement sample for frame number 381) +(leaving out measurement sample for frame number 398) +(leaving out measurement sample for frame number 416) + + +=========================================================== +Testing ts45008_83_is_sub() +Checking: TCH/F TS=0 SS=0 +Checking: TCH/F TS=1 SS=0 +Checking: TCH/F TS=2 SS=0 +Checking: TCH/F TS=3 SS=0 +Checking: TCH/F TS=4 SS=0 +Checking: TCH/F TS=5 SS=0 +Checking: TCH/F TS=6 SS=0 +Checking: TCH/H TS=0 SS=0 +Checking: TCH/H TS=1 SS=0 +Checking: TCH/H TS=2 SS=0 +Checking: TCH/H TS=3 SS=0 +Checking: TCH/H TS=4 SS=0 +Checking: TCH/H TS=5 SS=0 +Checking: TCH/H TS=6 SS=0 +Checking: TCH/H TS=0 SS=1 +Checking: TCH/H TS=1 SS=1 +Checking: TCH/H TS=2 SS=1 +Checking: TCH/H TS=3 SS=1 +Checking: TCH/H TS=4 SS=1 +Checking: TCH/H TS=5 SS=1 +Checking: TCH/H TS=6 SS=1 +Success diff --git a/tests/meas/meas_testcases.h b/tests/meas/meas_testcases.h new file mode 100644 index 00000000..fefa34f7 --- /dev/null +++ b/tests/meas/meas_testcases.h @@ -0,0 +1,578 @@ +#define ULM(ber, ta, sub, neg_rssi) \ + { .ber10k = (ber), .ta_offs_256bits = (ta), .c_i = 1.0, .is_sub = sub, .inv_rssi = (neg_rssi) } + +struct meas_testcase { + const char *name; + /* input data */ + const struct bts_ul_meas *ulm; + unsigned int ulm_count; + uint32_t final_fn; + uint8_t ts; + enum gsm_phys_chan_config pchan; + /* results */ + struct { + int success; + uint8_t rx_lev_full; + uint8_t rx_qual_full; + int16_t toa256_mean; + int16_t toa256_min; + int16_t toa256_max; + uint16_t toa256_std_dev; + } res; +}; + +static struct bts_ul_meas ulm1[] = { + /* Note: The assumptions about the frame number and the subset + * allegiance is random since for the calculation only the amount + * is of relevance. This is true for all following testcases */ + ULM(0, 0, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, -256, 0, 90), +}; + +static const struct meas_testcase mtc1 = { + .name = "TOA256 Min-Max negative/positive", + .ulm = ulm1, + .ulm_count = ARRAY_SIZE(ulm1), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 110-90, + .rx_qual_full = 0, + .toa256_mean = 0, + .toa256_max = 256, + .toa256_min = -256, + .toa256_std_dev = 209, + }, +}; + +static struct bts_ul_meas ulm2[] = { + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 1, 90), + ULM(0, 256, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 1, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 256, 1, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 256, 0, 90), +}; + +static const struct meas_testcase mtc2 = { + .name = "TOA256 small jitter around 256", + .ulm = ulm2, + .ulm_count = ARRAY_SIZE(ulm2), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 110-90, + .rx_qual_full = 7, + .toa256_mean = 256, + .toa256_max = 258, + .toa256_min = 254, + .toa256_std_dev = 1, + }, +}; + +static struct bts_ul_meas ulm3[] = { + ULM(0, 0, 0, 90), + ULM(0, 0, 0, 80), + ULM(0, 0, 0, 80), + ULM(0, 0, 0, 100), + ULM(0, 0, 0, 100), +}; + +static const struct meas_testcase mtc3 = { + .name = "RxLEv averaging", + .ulm = ulm3, + .ulm_count = ARRAY_SIZE(ulm3), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 110-90, + .rx_qual_full = 0, + .toa256_mean = 0, + .toa256_max = 0, + .toa256_min = 0, + .toa256_std_dev = 0, + }, +}; + +static struct bts_ul_meas ulm4[] = {}; + +static const struct meas_testcase mtc4 = { + .name = "Empty measurements", + .ulm = ulm4, + .ulm_count = ARRAY_SIZE(ulm4), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 63, + .rx_qual_full = 3, + .toa256_mean = 0, + .toa256_max = 0, + .toa256_min = 0, + .toa256_std_dev = 0, + }, +}; + +static struct bts_ul_meas ulm5[] = { + /* one 104 multiframe can at max contain 26 blocks (TCH/F), + * each of which can at maximum be 64 bits in advance (TA range) */ + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc5 = { + .name = "TOA256 26 blocks with max TOA256", + .ulm = ulm5, + .ulm_count = ARRAY_SIZE(ulm5), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 110-90, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good case as we can see it when all TCH + * and SACCH blocks are received */ +static struct bts_ul_meas ulm_tch_f_complete[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_tch_f_complete = { + .name = "Complete TCH/F measurement period (26 measurements, 3 sub-frames)", + .ulm = ulm_tch_f_complete, + .ulm_count = ARRAY_SIZE(ulm_tch_f_complete), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models an error case where two of 3 expected sub measurements + * are lost. The calculation logic must detect this and replace those + * measurements. Note that this example also lacks some blocks due to DTX, + * which is normal. */ +static struct bts_ul_meas ulm_tch_f_dtx_with_lost_subs[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_tch_f_dtx_with_lost_subs = { + /* This testcase models a good case as we can see it when all TCH + * and SACCH blocks are received */ + .name = "Incomplete TCH/F measurement period (16 measurements, 1 sub-frame)", + .ulm = ulm_tch_f_dtx_with_lost_subs, + .ulm_count = ARRAY_SIZE(ulm_tch_f_dtx_with_lost_subs), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good-case with DTX. Some measurements are missing + * because no block was transmitted, all sub measurements are there. */ +static struct bts_ul_meas ulm_tch_f_dtx[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_tch_f_dtx = { + .name = "Incomplete but normal TCH/F measurement period (16 measurements, 3 sub-frames)", + .ulm = ulm_tch_f_dtx, + .ulm_count = ARRAY_SIZE(ulm_tch_f_dtx), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good case as we can see it when all TCH + * and SACCH blocks are received */ +static struct bts_ul_meas ulm_tch_h_complete[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_tch_h_complete = { + .name = "Complete TCH/H measurement period (26 measurements, 5 sub-frames)", + .ulm = ulm_tch_h_complete, + .ulm_count = ARRAY_SIZE(ulm_tch_h_complete), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_H, + .res = { + .success = 1, + .rx_lev_full = 110 - 90, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +static struct bts_ul_meas ulm_tch_h_dtx_with_lost_subs[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_tch_h_dtx_with_lost_subs = { + .name = "Incomplete TCH/H measurement period (14 measurements, 3 sub-frames)", + .ulm = ulm_tch_h_dtx_with_lost_subs, + .ulm_count = ARRAY_SIZE(ulm_tch_h_dtx_with_lost_subs), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_H, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good-case with DTX. Some measurements are missing + * because no block was transmitted, all sub measurements are there. */ +static struct bts_ul_meas ulm_tch_h_dtx[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_tch_h_dtx = { + .name = "Incomplete but normal TCH/F measurement period (16 measurements, 5 sub-frames)", + .ulm = ulm_tch_h_dtx, + .ulm_count = ARRAY_SIZE(ulm_tch_h_dtx), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_H, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase assumes that too many measurements were collected. This can + * happen when the measurement calculation for a previous cycle were not + * executed. In this case the older part of the excess data must be discarded. + * the calculation algorithm must make sure that the calculation only takes + * place on the last measurement interval */ +static struct bts_ul_meas ulm_overrun[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + /* All measurements above must be discarded */ + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_overrun = { + .name = "TCH/F measurement period with too much measurement values (overrun)", + .ulm = ulm_overrun, + .ulm_count = ARRAY_SIZE(ulm_overrun), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 110 - 90, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +/* Test SDCCH4 with all frames received */ +static struct bts_ul_meas ulm_sdcch4_complete[] = { + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_sdcch4_complete = { + .name = "Complete SDCCH4 measurement period (3 measurements)", + .ulm = ulm_sdcch4_complete, + .ulm_count = ARRAY_SIZE(ulm_sdcch4_complete), + .final_fn = 88, + .ts = 0, + .pchan = GSM_PCHAN_CCCH_SDCCH4, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 0, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* Test SDCCH8 with all frames received */ +static struct bts_ul_meas ulm_sdcch8_complete[] = { + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_sdcch8_complete = { + .name = "Complete SDCCH8 measurement period (3 measurements)", + .ulm = ulm_sdcch8_complete, + .ulm_count = ARRAY_SIZE(ulm_sdcch8_complete), + .final_fn = 66, + .ts = 0, + .pchan = GSM_PCHAN_SDCCH8_SACCH8C, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 0, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; diff --git a/tests/meas/sysmobts_fr_samples.h b/tests/meas/sysmobts_fr_samples.h new file mode 100644 index 00000000..ee70bd7c --- /dev/null +++ b/tests/meas/sysmobts_fr_samples.h @@ -0,0 +1,2601 @@ +/* The following dataset was generated using a sysmobts in order to have + * some real data from a real phy to test against. */ + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on full rate channels TS2 and TS3 */ +struct fn_sample test_fn_tch_f_ts_2_3[] = { +{10954,2,0,-1},{10959,2,0,-1},{10972,2,0,-1},{10976,2,0,-1},{10980,2,0,-1}, +{10985,2,0,-1},{10989,2,0,-1},{10993,2,0,-1},{10998,2,0,-1},{11002,2,0,-1}, +{11006,2,0,-1},{11011,2,0,-1},{11015,2,0,-1},{11019,2,0,-1},{11024,2,0,-1}, +{11028,2,0,-1},{10958,2,0,-1},{11032,2,0,-1},{11037,2,0,-1},{11041,2,0,-1}, +{11045,2,0,-1},{11050,2,0,-1},{11054,2,0,-1},{11058,2,0,-1},{11063,2,0,-1}, +{11067,2,0,-1},{11071,2,0,-1},{11076,2,0,-1},{11080,2,0,-1},{11084,2,0,-1}, +{11089,2,0,-1},{11093,2,0,-1},{11097,2,0,-1},{11102,2,0,-1},{11106,2,0,-1}, +{11110,2,0,-1},{11115,2,0,-1},{11119,2,0,-1},{11123,2,0,-1},{11128,2,0,-1}, +{11132,2,0,-1},{11062,2,0,-1},{11136,2,0,-1},{11141,2,0,-1},{11145,2,0,-1}, +{11149,2,0,-1},{11154,2,0,-1},{11158,2,0,-1},{11162,2,0,-1},{11167,2,0,-1}, +{11171,2,0,-1},{11175,2,0,-1},{11180,2,0,-1},{11184,2,0,-1},{11188,2,0,-1}, +{11193,2,0,-1},{11197,2,0,-1},{11201,2,0,-1},{11206,2,0,-1},{11210,2,0,-1}, +{11214,2,0,-1},{11219,2,0,-1},{11223,2,0,-1},{11227,2,0,-1},{11232,2,0,-1}, +{11236,2,0,-1},{11166,2,0,-1},{11240,2,0,-1},{11245,2,0,-1},{11249,2,0,-1}, +{11253,2,0,-1},{11258,2,0,-1},{11262,2,0,-1},{11266,2,0,-1},{11271,2,0,-1}, +{11275,2,0,-1},{11279,2,0,-1},{11284,2,0,-1},{11288,2,0,-1},{11292,2,0,-1}, +{11297,2,0,-1},{11301,2,0,-1},{11305,2,0,-1},{11310,2,0,-1},{11314,2,0,-1}, +{11318,2,0,-1},{11323,2,0,-1},{11327,2,0,-1},{11331,2,0,-1},{11336,2,0,-1}, +{11340,2,0,-1},{11270,2,0,-1},{11344,2,0,-1},{11349,2,0,-1},{11353,2,0,-1}, +{11357,2,0,-1},{11362,2,0,-1},{11366,2,0,-1},{11370,2,0,-1},{11375,2,0,-1}, +{11379,2,0,-1},{11383,2,0,-1},{11388,2,0,-1},{11392,2,0,-1},{11396,2,0,-1}, +{11401,2,0,-1},{11405,2,0,-1},{11409,2,0,-1},{11414,2,0,-1},{11418,2,0,-1}, +{11422,2,0,-1},{11427,2,0,-1},{11431,2,0,-1},{11431,3,0,-1},{11435,2,0,-1}, +{11435,3,0,-1},{11440,2,0,-1},{11440,3,0,-1},{11444,2,0,-1},{11444,3,0,-1}, +{11374,2,0,-1},{11448,2,0,-1},{11448,3,0,-1},{11453,2,0,-1},{11453,3,0,-1}, +{11457,2,0,-1},{11457,3,0,-1},{11461,2,0,-1},{11461,3,0,-1},{11466,2,0,-1}, +{11466,3,0,-1},{11470,2,0,-1},{11470,3,0,-1},{11474,2,0,-1},{11474,3,0,-1}, +{11479,2,0,-1},{11479,3,0,-1},{11483,2,0,-1},{11483,3,0,-1},{11487,2,0,-1}, +{11487,3,0,-1},{11492,2,0,-1},{11492,3,0,-1},{11496,2,0,-1},{11496,3,0,-1}, +{11500,2,0,-1},{11500,3,0,-1},{11505,2,0,-1},{11505,3,0,-1},{11509,2,0,-1}, +{11509,3,0,-1},{11513,2,0,-1},{11513,3,0,-1},{11518,2,0,-1},{11518,3,0,-1}, +{11522,2,0,-1},{11522,3,0,-1},{11526,2,0,-1},{11526,3,0,-1},{11531,2,0,-1}, +{11531,3,0,-1},{11535,2,0,-1},{11535,3,0,-1},{11539,2,0,-1},{11539,3,0,-1}, +{11544,2,0,-1},{11544,3,0,-1},{11548,2,0,-1},{11548,3,0,-1},{11478,2,0,-1}, +{11552,2,0,-1},{11552,3,0,-1},{11557,2,0,-1},{11557,3,0,-1},{11561,2,0,-1}, +{11561,3,0,-1},{11491,3,0,-1},{11565,2,0,-1},{11565,3,0,-1},{11570,2,0,-1}, +{11570,3,0,-1},{11574,2,0,-1},{11574,3,0,-1},{11578,2,0,-1},{11578,3,0,-1}, +{11583,2,0,-1},{11583,3,0,-1},{11587,2,0,-1},{11587,3,0,-1},{11591,2,0,-1}, +{11591,3,0,-1},{11596,2,0,-1},{11596,3,0,-1},{11600,2,0,-1},{11600,3,0,-1}, +{11604,2,0,-1},{11604,3,0,-1},{11609,2,0,-1},{11609,3,0,-1},{11613,2,0,-1}, +{11613,3,0,-1},{11617,2,0,-1},{11617,3,0,-1},{11622,2,0,-1},{11622,3,0,-1}, +{11626,2,0,-1},{11626,3,0,-1},{11630,2,0,-1},{11630,3,0,-1},{11635,2,0,-1}, +{11635,3,0,-1},{11639,2,0,-1},{11639,3,0,-1},{11643,2,0,-1},{11643,3,0,-1}, +{11648,2,0,-1},{11648,3,0,-1},{11652,2,0,-1},{11652,3,0,-1},{11582,2,0,-1}, +{11656,2,0,-1},{11656,3,0,-1},{11661,2,0,-1},{11661,3,0,-1},{11665,2,0,-1}, +{11665,3,0,-1},{11595,3,0,-1},{11669,2,0,-1},{11669,3,0,-1},{11674,2,0,-1}, +{11674,3,0,-1},{11678,2,0,-1},{11678,3,0,-1},{11682,2,0,-1},{11682,3,0,-1}, +{11687,2,0,-1},{11687,3,0,-1},{11691,2,0,-1},{11691,3,0,-1},{11695,2,0,-1}, +{11695,3,0,-1},{11700,2,0,-1},{11700,3,0,-1},{11704,2,0,-1},{11704,3,0,-1}, +{11708,2,0,-1},{11708,3,0,-1},{11713,2,0,-1},{11713,3,0,-1},{11717,2,0,-1}, +{11717,3,0,-1},{11721,2,0,-1},{11721,3,0,-1},{11726,2,0,-1},{11726,3,0,-1}, +{11730,2,0,-1},{11730,3,0,-1},{11734,2,0,-1},{11734,3,0,-1},{11739,2,0,-1}, +{11739,3,0,-1},{11743,2,0,-1},{11743,3,0,-1},{11747,2,0,-1},{11747,3,0,-1}, +{11752,2,0,-1},{11752,3,0,-1},{11756,2,0,-1},{11756,3,0,-1},{11686,2,0,-1}, +{11760,2,0,-1},{11760,3,0,-1},{11765,2,0,-1},{11765,3,0,-1},{11769,2,0,-1}, +{11769,3,0,-1},{11699,3,0,-1},{11773,2,0,-1},{11773,3,0,-1},{11778,2,0,-1}, +{11778,3,0,-1},{11782,2,0,-1},{11782,3,0,-1},{11786,2,0,-1},{11786,3,0,-1}, +{11791,2,0,-1},{11791,3,0,-1},{11795,2,0,-1},{11795,3,0,-1},{11799,2,0,-1}, +{11799,3,0,-1},{11804,2,0,-1},{11804,3,0,-1},{11808,2,0,-1},{11808,3,0,-1}, +{11812,2,0,-1},{11812,3,0,-1},{11817,2,0,-1},{11817,3,0,-1},{11821,2,0,-1}, +{11821,3,0,-1},{11825,2,0,-1},{11825,3,0,-1},{11830,2,0,-1},{11830,3,0,-1}, +{11834,2,0,-1},{11834,3,0,-1},{11838,2,0,-1},{11838,3,0,-1},{11843,2,0,-1}, +{11843,3,0,-1},{11847,2,0,-1},{11847,3,0,-1},{11851,2,0,-1},{11851,3,0,-1}, +{11856,2,0,-1},{11856,3,0,-1},{11860,2,0,-1},{11860,3,0,-1},{11790,2,0,-1}, +{11864,2,0,-1},{11864,3,0,-1},{11869,2,0,-1},{11869,3,0,-1},{11873,2,0,-1}, +{11873,3,0,-1},{11803,3,0,-1},{11877,2,0,-1},{11877,3,0,-1},{11882,2,0,-1}, +{11882,3,0,-1},{11886,2,0,-1},{11886,3,0,-1},{11890,2,0,-1},{11890,3,0,-1}, +{11895,2,0,-1},{11895,3,0,-1},{11899,2,0,-1},{11899,3,0,-1},{11903,2,0,-1}, +{11903,3,0,-1},{11908,2,0,-1},{11908,3,0,-1},{11912,2,0,-1},{11912,3,0,-1}, +{11916,2,0,-1},{11916,3,0,-1},{11921,2,0,-1},{11921,3,0,-1},{11925,2,0,-1}, +{11925,3,0,-1},{11929,2,0,-1},{11929,3,0,-1},{11934,2,0,-1},{11934,3,0,-1}, +{11938,2,0,-1},{11938,3,0,-1},{11942,2,0,-1},{11942,3,0,-1},{11947,2,0,-1}, +{11947,3,0,-1},{11951,2,0,-1},{11951,3,0,-1},{11955,2,0,-1},{11955,3,0,-1}, +{11960,2,0,-1},{11960,3,0,-1},{11964,2,0,-1},{11964,3,0,-1},{11894,2,0,-1}, +{11968,2,0,-1},{11968,3,0,-1},{11973,2,0,-1},{11973,3,0,-1},{11977,2,0,-1}, +{11977,3,0,-1},{11907,3,0,-1},{11981,2,0,-1},{11981,3,0,-1},{11986,2,0,-1}, +{11986,3,0,-1},{11990,2,0,-1},{11990,3,0,-1},{11994,2,0,-1},{11994,3,0,-1}, +{11999,2,0,-1},{11999,3,0,-1},{12003,2,0,-1},{12003,3,0,-1},{12007,2,0,-1}, +{12007,3,0,-1},{12012,2,0,-1},{12012,3,0,-1},{12016,2,0,-1},{12016,3,0,-1}, +{12020,2,0,-1},{12020,3,0,-1},{12025,2,0,-1},{12025,3,0,-1},{12029,2,0,-1}, +{12029,3,0,-1},{12033,2,0,-1},{12033,3,0,-1},{12038,2,0,-1},{12038,3,0,-1}, +{12042,2,0,-1},{12042,3,0,-1},{12046,2,0,-1},{12046,3,0,-1},{12051,2,0,-1}, +{12051,3,0,-1},{12055,2,0,-1},{12055,3,0,-1},{12059,2,0,-1},{12059,3,0,-1}, +{12064,2,0,-1},{12064,3,0,-1},{12068,2,0,-1},{12068,3,0,-1},{11998,2,0,-1}, +{12072,2,0,-1},{12072,3,0,-1},{12077,2,0,-1},{12077,3,0,-1},{12081,2,0,-1}, +{12081,3,0,-1},{12011,3,0,-1},{12085,2,0,-1},{12085,3,0,-1},{12090,2,0,-1}, +{12090,3,0,-1},{12094,2,0,-1},{12094,3,0,-1},{12098,2,0,-1},{12098,3,0,-1}, +{12103,2,0,-1},{12103,3,0,-1},{12107,2,0,-1},{12107,3,0,-1},{12111,2,0,-1}, +{12111,3,0,-1},{12116,2,0,-1},{12116,3,0,-1},{12120,2,0,-1},{12120,3,0,-1}, +{12124,2,0,-1},{12124,3,0,-1},{12129,2,0,-1},{12129,3,0,-1},{12133,2,0,-1}, +{12133,3,0,-1},{12137,2,0,-1},{12137,3,0,-1},{12142,2,0,-1},{12142,3,0,-1}, +{12146,2,0,-1},{12146,3,0,-1},{12150,2,0,-1},{12150,3,0,-1},{12155,2,0,-1}, +{12155,3,0,-1},{12159,2,0,-1},{12159,3,0,-1},{12163,2,0,-1},{12163,3,0,-1}, +{12168,2,0,-1},{12168,3,0,-1},{12172,2,0,-1},{12172,3,0,-1},{12102,2,0,-1}, +{12176,2,0,-1},{12176,3,0,-1},{12181,2,0,-1},{12181,3,0,-1},{12185,2,0,-1}, +{12185,3,0,-1},{12115,3,0,-1},{12189,2,0,-1},{12189,3,0,-1},{12194,2,0,-1}, +{12194,3,0,-1},{12198,2,0,-1},{12198,3,0,-1},{12202,2,0,-1},{12202,3,0,-1}, +{12207,2,0,-1},{12207,3,0,-1},{12211,2,0,-1},{12211,3,0,-1},{12215,2,0,-1}, +{12215,3,0,-1},{12220,2,0,-1},{12220,3,0,-1},{12224,2,0,-1},{12224,3,0,-1}, +{12228,2,0,-1},{12228,3,0,-1},{12233,2,0,-1},{12233,3,0,-1},{12237,2,0,-1}, +{12237,3,0,-1},{12241,2,0,-1},{12241,3,0,-1},{12246,2,0,-1},{12246,3,0,-1}, +{12250,2,0,-1},{12250,3,0,-1},{12254,2,0,-1},{12254,3,0,-1},{12259,2,0,-1}, +{12259,3,0,-1},{12263,2,0,-1},{12263,3,0,-1},{12267,2,0,-1},{12267,3,0,-1}, +{12272,2,0,-1},{12272,3,0,-1},{12276,2,0,-1},{12276,3,0,-1},{12206,2,0,-1}, +{12280,2,0,-1},{12280,3,0,-1},{12285,2,0,-1},{12285,3,0,-1},{12289,2,0,-1}, +{12289,3,0,-1},{12219,3,0,-1},{12293,2,0,-1},{12293,3,0,-1},{12298,2,0,-1}, +{12298,3,0,-1},{12302,2,0,-1},{12302,3,0,-1},{12306,2,0,-1},{12306,3,0,-1}, +{12311,2,0,-1},{12311,3,0,-1},{12315,2,0,-1},{12315,3,0,-1},{12319,2,0,-1}, +{12319,3,0,-1},{12324,2,0,-1},{12324,3,0,-1},{12328,2,0,-1},{12328,3,0,-1}, +{12332,2,0,-1},{12332,3,0,-1},{12337,2,0,-1},{12337,3,0,-1},{12341,2,0,-1}, +{12341,3,0,-1},{12345,2,0,-1},{12345,3,0,-1},{12350,2,0,-1},{12350,3,0,-1}, +{12354,2,0,-1},{12354,3,0,-1},{12358,2,0,-1},{12358,3,0,-1},{12363,2,0,-1}, +{12363,3,0,-1},{12367,2,0,-1},{12367,3,0,-1},{12371,2,0,-1},{12371,3,0,-1}, +{12376,2,0,-1},{12376,3,0,-1},{12380,2,0,-1},{12380,3,0,-1},{12310,2,0,-1}, +{12384,2,0,-1},{12384,3,0,-1},{12389,2,0,-1},{12389,3,0,-1},{12393,2,0,-1}, +{12393,3,0,-1},{12323,3,0,-1},{12397,2,0,-1},{12397,3,0,-1},{12402,2,0,-1}, +{12402,3,0,-1},{12406,2,0,-1},{12406,3,0,-1},{12410,2,0,-1},{12410,3,0,-1}, +{12415,2,0,-1},{12415,3,0,-1},{12419,2,0,-1},{12419,3,0,-1},{12423,2,0,-1}, +{12423,3,0,-1},{12428,2,0,-1},{12428,3,0,-1},{12432,2,0,-1},{12432,3,0,-1}, +{12436,2,0,-1},{12436,3,0,-1},{12441,2,0,-1},{12441,3,0,-1},{12445,2,0,-1}, +{12445,3,0,-1},{12449,2,0,-1},{12449,3,0,-1},{12454,2,0,-1},{12454,3,0,-1}, +{12458,2,0,-1},{12458,3,0,-1},{12462,2,0,-1},{12462,3,0,-1},{12467,2,0,-1}, +{12467,3,0,-1},{12471,2,0,-1},{12471,3,0,-1},{12475,2,0,-1},{12475,3,0,-1}, +{12480,2,0,-1},{12480,3,0,-1},{12484,2,0,-1},{12484,3,0,-1},{12414,2,0,-1}, +{12488,2,0,-1},{12488,3,0,-1},{12493,2,0,-1},{12493,3,0,-1},{12497,2,0,-1}, +{12497,3,0,-1},{12427,3,0,-1},{12501,2,0,-1},{12501,3,0,-1},{12506,2,0,-1}, +{12506,3,0,-1},{12510,2,0,-1},{12510,3,0,-1},{12514,2,0,-1},{12514,3,0,-1}, +{12519,2,0,-1},{12519,3,0,-1},{12523,2,0,-1},{12523,3,0,-1},{12527,2,0,-1}, +{12527,3,0,-1},{12532,2,0,-1},{12532,3,0,-1},{12536,2,0,-1},{12536,3,0,-1}, +{12540,2,0,-1},{12540,3,0,-1},{12545,2,0,-1},{12545,3,0,-1},{12549,2,0,-1}, +{12549,3,0,-1},{12553,2,0,-1},{12553,3,0,-1},{12558,2,0,-1},{12558,3,0,-1}, +{12562,2,0,-1},{12562,3,0,-1},{12566,2,0,-1},{12566,3,0,-1},{12571,2,0,-1}, +{12571,3,0,-1},{12575,2,0,-1},{12575,3,0,-1},{12579,2,0,-1},{12579,3,0,-1}, +{12584,2,0,-1},{12584,3,0,-1},{12588,2,0,-1},{12588,3,0,-1},{12518,2,0,-1}, +{12592,2,0,-1},{12592,3,0,-1},{12597,2,0,-1},{12597,3,0,-1},{12601,2,0,-1}, +{12601,3,0,-1},{12531,3,0,-1},{12605,2,0,-1},{12605,3,0,-1},{12610,2,0,-1}, +{12610,3,0,-1},{12614,2,0,-1},{12614,3,0,-1},{12618,2,0,-1},{12618,3,0,-1}, +{12623,2,0,-1},{12623,3,0,-1},{12627,2,0,-1},{12627,3,0,-1},{12631,2,0,-1}, +{12631,3,0,-1},{12636,2,0,-1},{12636,3,0,-1},{12640,2,0,-1},{12640,3,0,-1}, +{12644,2,0,-1},{12644,3,0,-1},{12649,2,0,-1},{12649,3,0,-1},{12653,2,0,-1}, +{12653,3,0,-1},{12657,2,0,-1},{12657,3,0,-1},{12662,2,0,-1},{12662,3,0,-1}, +{12666,2,0,-1},{12666,3,0,-1},{12670,2,0,-1},{12670,3,0,-1},{12675,2,0,-1}, +{12675,3,0,-1},{12679,2,0,-1},{12679,3,0,-1},{12683,2,0,-1},{12683,3,0,-1}, +{12688,2,0,-1},{12688,3,0,-1},{12692,2,0,-1},{12692,3,0,-1},{12622,2,0,-1}, +{12696,2,0,-1},{12696,3,0,-1},{12701,2,0,-1},{12701,3,0,-1},{12705,2,0,-1}, +{12705,3,0,-1},{12635,3,0,-1},{12709,2,0,-1},{12709,3,0,-1},{12714,2,0,-1}, +{12714,3,0,-1},{12718,2,0,-1},{12718,3,0,-1},{12722,2,0,-1},{12722,3,0,-1}, +{12727,2,0,-1},{12727,3,0,-1},{12731,2,0,-1},{12731,3,0,-1},{12735,2,0,-1}, +{12735,3,0,-1},{12740,2,0,-1},{12740,3,0,-1},{12744,2,0,-1},{12744,3,0,-1}, +{12748,2,0,-1},{12748,3,0,-1},{12753,2,0,-1},{12753,3,0,-1},{12757,2,0,-1}, +{12757,3,0,-1},{12761,2,0,-1},{12761,3,0,-1},{12766,2,0,-1},{12766,3,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on full rate channels TS4 and TS5 */ +struct fn_sample test_fn_tch_f_ts_4_5[] = { +{3407,0,1,-1},{3427,0,1,-1},{3458,0,1,-1},{3509,0,1,-1},{3529,0,1,-1}, +{3560,0,1,-1},{3611,0,1,-1},{3662,0,1,-1},{3713,0,1,-1},{3764,0,1,-1}, +{3780,0,2,-1},{3821,0,2,-1},{3872,0,2,-1},{3882,0,2,-1},{3923,0,2,-1}, +{3974,0,2,-1},{3984,0,2,-1},{4025,0,2,-1},{4076,0,2,-1},{4127,0,2,-1}, +{4178,0,2,-1},{5871,4,0,-1},{5876,4,0,-1},{5880,4,0,-1},{5884,4,0,-1}, +{5889,4,0,-1},{5893,4,0,-1},{5897,4,0,-1},{5902,4,0,-1},{5906,4,0,-1}, +{5910,4,0,-1},{5915,4,0,-1},{5919,4,0,-1},{5923,4,0,-1},{5928,4,0,-1}, +{5932,4,0,-1},{5936,4,0,-1},{5941,4,0,-1},{5945,4,0,-1},{5949,4,0,-1}, +{5954,4,0,-1},{5958,4,0,-1},{5888,4,0,-1},{5962,4,0,-1},{5967,4,0,-1}, +{5971,4,0,-1},{5975,4,0,-1},{5980,4,0,-1},{5984,4,0,-1},{5988,4,0,-1}, +{5993,4,0,-1},{5997,4,0,-1},{6001,4,0,-1},{6006,4,0,-1},{6010,4,0,-1}, +{6014,4,0,-1},{6019,4,0,-1},{6023,4,0,-1},{6027,4,0,-1},{6032,4,0,-1}, +{6036,4,0,-1},{6040,4,0,-1},{6045,4,0,-1},{6049,4,0,-1},{6053,4,0,-1}, +{6058,4,0,-1},{6062,4,0,-1},{5992,4,0,-1},{6066,4,0,-1},{6071,4,0,-1}, +{6075,4,0,-1},{6079,4,0,-1},{6084,4,0,-1},{6088,4,0,-1},{6092,4,0,-1}, +{6097,4,0,-1},{6101,4,0,-1},{6105,4,0,-1},{6110,4,0,-1},{6114,4,0,-1}, +{6118,4,0,-1},{6123,4,0,-1},{6127,4,0,-1},{6131,4,0,-1},{6136,4,0,-1}, +{6140,4,0,-1},{6144,4,0,-1},{6149,4,0,-1},{6153,4,0,-1},{6157,4,0,-1}, +{6162,4,0,-1},{6166,4,0,-1},{6096,4,0,-1},{6170,4,0,-1},{6175,4,0,-1}, +{6175,5,0,-1},{6179,4,0,-1},{6179,5,0,-1},{6183,4,0,-1},{6183,5,0,-1}, +{6188,4,0,-1},{6188,5,0,-1},{6192,4,0,-1},{6192,5,0,-1},{6196,4,0,-1}, +{6196,5,0,-1},{6201,4,0,-1},{6201,5,0,-1},{6205,4,0,-1},{6205,5,0,-1}, +{6209,4,0,-1},{6209,5,0,-1},{6214,4,0,-1},{6214,5,0,-1},{6218,4,0,-1}, +{6218,5,0,-1},{6222,4,0,-1},{6222,5,0,-1},{6227,4,0,-1},{6227,5,0,-1}, +{6231,4,0,-1},{6231,5,0,-1},{6235,4,0,-1},{6235,5,0,-1},{6240,4,0,-1}, +{6240,5,0,-1},{6244,4,0,-1},{6244,5,0,-1},{6248,4,0,-1},{6248,5,0,-1}, +{6253,4,0,-1},{6253,5,0,-1},{6257,4,0,-1},{6257,5,0,-1},{6261,4,0,-1}, +{6261,5,0,-1},{6266,4,0,-1},{6266,5,0,-1},{6270,4,0,-1},{6270,5,0,-1}, +{6200,4,0,-1},{6274,4,0,-1},{6274,5,0,-1},{6279,4,0,-1},{6279,5,0,-1}, +{6283,4,0,-1},{6283,5,0,-1},{6213,5,0,-1},{6287,4,0,-1},{6287,5,0,-1}, +{6292,4,0,-1},{6292,5,0,-1},{6296,4,0,-1},{6296,5,0,-1},{6300,4,0,-1}, +{6300,5,0,-1},{6305,4,0,-1},{6305,5,0,-1},{6309,4,0,-1},{6309,5,0,-1}, +{6313,4,0,-1},{6313,5,0,-1},{6318,4,0,-1},{6318,5,0,-1},{6322,4,0,-1}, +{6322,5,0,-1},{6326,4,0,-1},{6326,5,0,-1},{6331,4,0,-1},{6331,5,0,-1}, +{6335,4,0,-1},{6335,5,0,-1},{6339,4,0,-1},{6339,5,0,-1},{6344,4,0,-1}, +{6344,5,0,-1},{6348,4,0,-1},{6348,5,0,-1},{6352,4,0,-1},{6352,5,0,-1}, +{6357,4,0,-1},{6357,5,0,-1},{6361,4,0,-1},{6361,5,0,-1},{6365,4,0,-1}, +{6365,5,0,-1},{6370,4,0,-1},{6370,5,0,-1},{6374,4,0,-1},{6374,5,0,-1}, +{6304,4,0,-1},{6378,4,0,-1},{6378,5,0,-1},{6383,4,0,-1},{6383,5,0,-1}, +{6387,4,0,-1},{6387,5,0,-1},{6317,5,0,-1},{6391,4,0,-1},{6391,5,0,-1}, +{6396,4,0,-1},{6396,5,0,-1},{6400,4,0,-1},{6400,5,0,-1},{6404,4,0,-1}, +{6404,5,0,-1},{6409,4,0,-1},{6409,5,0,-1},{6413,4,0,-1},{6413,5,0,-1}, +{6417,4,0,-1},{6417,5,0,-1},{6422,4,0,-1},{6422,5,0,-1},{6426,4,0,-1}, +{6426,5,0,-1},{6430,4,0,-1},{6430,5,0,-1},{6435,4,0,-1},{6435,5,0,-1}, +{6439,4,0,-1},{6439,5,0,-1},{6443,4,0,-1},{6443,5,0,-1},{6448,4,0,-1}, +{6448,5,0,-1},{6452,4,0,-1},{6452,5,0,-1},{6456,4,0,-1},{6456,5,0,-1}, +{6461,4,0,-1},{6461,5,0,-1},{6465,4,0,-1},{6465,5,0,-1},{6469,4,0,-1}, +{6469,5,0,-1},{6474,4,0,-1},{6474,5,0,-1},{6478,4,0,-1},{6478,5,0,-1}, +{6408,4,0,-1},{6482,4,0,-1},{6482,5,0,-1},{6487,4,0,-1},{6487,5,0,-1}, +{6491,4,0,-1},{6491,5,0,-1},{6421,5,0,-1},{6495,4,0,-1},{6495,5,0,-1}, +{6500,4,0,-1},{6500,5,0,-1},{6504,4,0,-1},{6504,5,0,-1},{6508,4,0,-1}, +{6508,5,0,-1},{6513,4,0,-1},{6513,5,0,-1},{6517,4,0,-1},{6517,5,0,-1}, +{6521,4,0,-1},{6521,5,0,-1},{6526,4,0,-1},{6526,5,0,-1},{6530,4,0,-1}, +{6530,5,0,-1},{6534,4,0,-1},{6534,5,0,-1},{6539,4,0,-1},{6539,5,0,-1}, +{6543,4,0,-1},{6543,5,0,-1},{6547,4,0,-1},{6547,5,0,-1},{6552,4,0,-1}, +{6552,5,0,-1},{6556,4,0,-1},{6556,5,0,-1},{6560,4,0,-1},{6560,5,0,-1}, +{6565,4,0,-1},{6565,5,0,-1},{6569,4,0,-1},{6569,5,0,-1},{6573,4,0,-1}, +{6573,5,0,-1},{6578,4,0,-1},{6578,5,0,-1},{6582,4,0,-1},{6582,5,0,-1}, +{6512,4,0,-1},{6586,4,0,-1},{6586,5,0,-1},{6591,4,0,-1},{6591,5,0,-1}, +{6595,4,0,-1},{6595,5,0,-1},{6525,5,0,-1},{6599,4,0,-1},{6599,5,0,-1}, +{6604,4,0,-1},{6604,5,0,-1},{6608,4,0,-1},{6608,5,0,-1},{6612,4,0,-1}, +{6612,5,0,-1},{6617,4,0,-1},{6617,5,0,-1},{6621,4,0,-1},{6621,5,0,-1}, +{6625,4,0,-1},{6625,5,0,-1},{6630,4,0,-1},{6630,5,0,-1},{6634,4,0,-1}, +{6634,5,0,-1},{6638,4,0,-1},{6638,5,0,-1},{6643,4,0,-1},{6643,5,0,-1}, +{6647,4,0,-1},{6647,5,0,-1},{6651,4,0,-1},{6651,5,0,-1},{6656,4,0,-1}, +{6656,5,0,-1},{6660,4,0,-1},{6660,5,0,-1},{6664,4,0,-1},{6664,5,0,-1}, +{6669,4,0,-1},{6669,5,0,-1},{6673,4,0,-1},{6673,5,0,-1},{6677,4,0,-1}, +{6677,5,0,-1},{6682,4,0,-1},{6682,5,0,-1},{6686,4,0,-1},{6686,5,0,-1}, +{6616,4,0,-1},{6690,4,0,-1},{6690,5,0,-1},{6695,4,0,-1},{6695,5,0,-1}, +{6699,4,0,-1},{6699,5,0,-1},{6629,5,0,-1},{6703,4,0,-1},{6703,5,0,-1}, +{6708,4,0,-1},{6708,5,0,-1},{6712,4,0,-1},{6712,5,0,-1},{6716,4,0,-1}, +{6716,5,0,-1},{6721,4,0,-1},{6721,5,0,-1},{6725,4,0,-1},{6725,5,0,-1}, +{6729,4,0,-1},{6729,5,0,-1},{6734,4,0,-1},{6734,5,0,-1},{6738,4,0,-1}, +{6738,5,0,-1},{6742,4,0,-1},{6742,5,0,-1},{6747,4,0,-1},{6747,5,0,-1}, +{6751,4,0,-1},{6751,5,0,-1},{6755,4,0,-1},{6755,5,0,-1},{6760,4,0,-1}, +{6760,5,0,-1},{6764,4,0,-1},{6764,5,0,-1},{6768,4,0,-1},{6768,5,0,-1}, +{6773,4,0,-1},{6773,5,0,-1},{6777,4,0,-1},{6777,5,0,-1},{6781,4,0,-1}, +{6781,5,0,-1},{6786,4,0,-1},{6786,5,0,-1},{6790,4,0,-1},{6790,5,0,-1}, +{6720,4,0,-1},{6794,4,0,-1},{6794,5,0,-1},{6799,4,0,-1},{6799,5,0,-1}, +{6803,4,0,-1},{6803,5,0,-1},{6733,5,0,-1},{6807,4,0,-1},{6807,5,0,-1}, +{6812,4,0,-1},{6812,5,0,-1},{6816,4,0,-1},{6816,5,0,-1},{6820,4,0,-1}, +{6820,5,0,-1},{6825,4,0,-1},{6825,5,0,-1},{6829,4,0,-1},{6829,5,0,-1}, +{6833,4,0,-1},{6833,5,0,-1},{6838,4,0,-1},{6838,5,0,-1},{6842,4,0,-1}, +{6842,5,0,-1},{6846,4,0,-1},{6846,5,0,-1},{6851,4,0,-1},{6851,5,0,-1}, +{6855,4,0,-1},{6855,5,0,-1},{6859,4,0,-1},{6859,5,0,-1},{6864,4,0,-1}, +{6864,5,0,-1},{6868,4,0,-1},{6868,5,0,-1},{6872,4,0,-1},{6872,5,0,-1}, +{6877,4,0,-1},{6877,5,0,-1},{6881,4,0,-1},{6881,5,0,-1},{6885,4,0,-1}, +{6885,5,0,-1},{6890,4,0,-1},{6890,5,0,-1},{6894,4,0,-1},{6894,5,0,-1}, +{6824,4,0,-1},{6898,4,0,-1},{6898,5,0,-1},{6903,4,0,-1},{6903,5,0,-1}, +{6907,4,0,-1},{6907,5,0,-1},{6837,5,0,-1},{6911,4,0,-1},{6911,5,0,-1}, +{6916,4,0,-1},{6916,5,0,-1},{6920,4,0,-1},{6920,5,0,-1},{6924,4,0,-1}, +{6924,5,0,-1},{6929,4,0,-1},{6929,5,0,-1},{6933,4,0,-1},{6933,5,0,-1}, +{6937,4,0,-1},{6937,5,0,-1},{6942,4,0,-1},{6942,5,0,-1},{6946,4,0,-1}, +{6946,5,0,-1},{6950,4,0,-1},{6950,5,0,-1},{6955,4,0,-1},{6955,5,0,-1}, +{6959,4,0,-1},{6959,5,0,-1},{6963,4,0,-1},{6963,5,0,-1},{6968,4,0,-1}, +{6968,5,0,-1},{6972,4,0,-1},{6972,5,0,-1},{6976,4,0,-1},{6976,5,0,-1}, +{6981,4,0,-1},{6981,5,0,-1},{6985,4,0,-1},{6985,5,0,-1},{6989,4,0,-1}, +{6989,5,0,-1},{6994,4,0,-1},{6994,5,0,-1},{6998,4,0,-1},{6998,5,0,-1}, +{6928,4,0,-1},{7002,4,0,-1},{7002,5,0,-1},{7007,4,0,-1},{7007,5,0,-1}, +{7011,4,0,-1},{7011,5,0,-1},{6941,5,0,-1},{7015,4,0,-1},{7015,5,0,-1}, +{7020,4,0,-1},{7020,5,0,-1},{7024,4,0,-1},{7024,5,0,-1},{7028,4,0,-1}, +{7028,5,0,-1},{7033,4,0,-1},{7033,5,0,-1},{7037,4,0,-1},{7037,5,0,-1}, +{7041,4,0,-1},{7041,5,0,-1},{7046,4,0,-1},{7046,5,0,-1},{7050,4,0,-1}, +{7050,5,0,-1},{7054,4,0,-1},{7054,5,0,-1},{7059,4,0,-1},{7059,5,0,-1}, +{7063,4,0,-1},{7063,5,0,-1},{7067,4,0,-1},{7067,5,0,-1},{7072,4,0,-1}, +{7072,5,0,-1},{7076,4,0,-1},{7076,5,0,-1},{7080,4,0,-1},{7080,5,0,-1}, +{7085,4,0,-1},{7085,5,0,-1},{7089,4,0,-1},{7089,5,0,-1},{7093,4,0,-1}, +{7093,5,0,-1},{7098,4,0,-1},{7098,5,0,-1},{7102,4,0,-1},{7102,5,0,-1}, +{7032,4,0,-1},{7106,4,0,-1},{7106,5,0,-1},{7111,4,0,-1},{7111,5,0,-1}, +{7115,4,0,-1},{7115,5,0,-1},{7045,5,0,-1},{7119,4,0,-1},{7119,5,0,-1}, +{7124,4,0,-1},{7124,5,0,-1},{7128,4,0,-1},{7128,5,0,-1},{7132,4,0,-1}, +{7132,5,0,-1},{7137,4,0,-1},{7137,5,0,-1},{7141,4,0,-1},{7141,5,0,-1}, +{7145,4,0,-1},{7145,5,0,-1},{7150,4,0,-1},{7150,5,0,-1},{7154,4,0,-1}, +{7154,5,0,-1},{7158,4,0,-1},{7158,5,0,-1},{7163,4,0,-1},{7163,5,0,-1}, +{7167,4,0,-1},{7167,5,0,-1},{7171,4,0,-1},{7171,5,0,-1},{7176,4,0,-1}, +{7176,5,0,-1},{7180,4,0,-1},{7180,5,0,-1},{7184,4,0,-1},{7184,5,0,-1}, +{7189,4,0,-1},{7189,5,0,-1},{7193,4,0,-1},{7193,5,0,-1},{7197,4,0,-1}, +{7197,5,0,-1},{7202,4,0,-1},{7202,5,0,-1},{7206,4,0,-1},{7206,5,0,-1}, +{7136,4,0,-1},{7210,4,0,-1},{7210,5,0,-1},{7215,4,0,-1},{7215,5,0,-1}, +{7219,4,0,-1},{7219,5,0,-1},{7149,5,0,-1},{7223,4,0,-1},{7223,5,0,-1}, +{7228,4,0,-1},{7228,5,0,-1},{7232,4,0,-1},{7232,5,0,-1},{7236,4,0,-1}, +{7236,5,0,-1},{7241,4,0,-1},{7241,5,0,-1},{7245,4,0,-1},{7245,5,0,-1}, +{7249,4,0,-1},{7249,5,0,-1},{7254,4,0,-1},{7254,5,0,-1},{7258,4,0,-1}, +{7258,5,0,-1},{7262,4,0,-1},{7262,5,0,-1},{7267,4,0,-1},{7267,5,0,-1}, +{7271,4,0,-1},{7271,5,0,-1},{7275,4,0,-1},{7275,5,0,-1},{7280,4,0,-1}, +{7280,5,0,-1},{7284,4,0,-1},{7284,5,0,-1},{7288,4,0,-1},{7288,5,0,-1}, +{7293,4,0,-1},{7293,5,0,-1},{7297,4,0,-1},{7297,5,0,-1},{7301,4,0,-1}, +{7301,5,0,-1},{7306,4,0,-1},{7306,5,0,-1},{7310,4,0,-1},{7310,5,0,-1}, +{7240,4,0,-1},{7314,4,0,-1},{7314,5,0,-1},{7319,4,0,-1},{7319,5,0,-1}, +{7323,4,0,-1},{7323,5,0,-1},{7253,5,0,-1},{7327,4,0,-1},{7327,5,0,-1}, +{7332,4,0,-1},{7332,5,0,-1},{7336,4,0,-1},{7336,5,0,-1},{7340,4,0,-1}, +{7340,5,0,-1},{7345,4,0,-1},{7345,5,0,-1},{7349,4,0,-1},{7349,5,0,-1}, +{7353,4,0,-1},{7353,5,0,-1},{7358,4,0,-1},{7358,5,0,-1},{7362,4,0,-1}, +{7362,5,0,-1},{7366,4,0,-1},{7366,5,0,-1},{7371,4,0,-1},{7371,5,0,-1}, +{7375,4,0,-1},{7375,5,0,-1},{7379,4,0,-1},{7379,5,0,-1},{7384,4,0,-1}, +{7384,5,0,-1},{7388,4,0,-1},{7388,5,0,-1},{7392,4,0,-1},{7392,5,0,-1}, +{7397,4,0,-1},{7397,5,0,-1},{7401,4,0,-1},{7401,5,0,-1},{7405,4,0,-1}, +{7405,5,0,-1},{7410,4,0,-1},{7410,5,0,-1},{7414,4,0,-1},{7414,5,0,-1}, +{7344,4,0,-1},{7418,4,0,-1},{7418,5,0,-1},{7423,4,0,-1},{7423,5,0,-1}, +{7427,4,0,-1},{7427,5,0,-1},{7357,5,0,-1},{7431,4,0,-1},{7431,5,0,-1}, +{7436,4,0,-1},{7436,5,0,-1},{7440,4,0,-1},{7440,5,0,-1},{7444,4,0,-1}, +{7444,5,0,-1},{7449,4,0,-1},{7449,5,0,-1},{7453,4,0,-1},{7453,5,0,-1}, +{7457,4,0,-1},{7457,5,0,-1},{7462,4,0,-1},{7462,5,0,-1},{7466,4,0,-1}, +{7466,5,0,-1},{7470,4,0,-1},{7470,5,0,-1},{7475,4,0,-1},{7475,5,0,-1}, +{7479,4,0,-1},{7479,5,0,-1},{7483,4,0,-1},{7483,5,0,-1},{7488,4,0,-1}, +{7488,5,0,-1},{7492,4,0,-1},{7492,5,0,-1},{7496,4,0,-1},{7496,5,0,-1}, +{7501,4,0,-1},{7501,5,0,-1},{7505,4,0,-1},{7505,5,0,-1},{7509,4,0,-1}, +{7509,5,0,-1},{7514,4,0,-1},{7514,5,0,-1},{7518,4,0,-1},{7518,5,0,-1}, +{7448,4,0,-1},{7522,4,0,-1},{7522,5,0,-1},{7527,4,0,-1},{7527,5,0,-1}, +{7531,4,0,-1},{7531,5,0,-1},{7461,5,0,-1},{7535,4,0,-1},{7535,5,0,-1}, +{7540,4,0,-1},{7540,5,0,-1},{7544,4,0,-1},{7544,5,0,-1},{7548,4,0,-1}, +{7548,5,0,-1},{7553,4,0,-1},{7553,5,0,-1},{7557,4,0,-1},{7557,5,0,-1}, +{7561,4,0,-1},{7561,5,0,-1},{7566,4,0,-1},{7566,5,0,-1},{7570,4,0,-1}, +{7570,5,0,-1},{7574,4,0,-1},{7574,5,0,-1},{7579,4,0,-1},{7579,5,0,-1}, +{7583,4,0,-1},{7583,5,0,-1},{7587,4,0,-1},{7587,5,0,-1},{7592,4,0,-1}, +{7592,5,0,-1},{7596,4,0,-1},{7596,5,0,-1},{7600,4,0,-1},{7600,5,0,-1}, +{7605,4,0,-1},{7605,5,0,-1},{7609,4,0,-1},{7609,5,0,-1},{7613,4,0,-1}, +{7613,5,0,-1},{7618,4,0,-1},{7618,5,0,-1},{7622,4,0,-1},{7622,5,0,-1}, +{7552,4,0,-1},{7626,4,0,-1},{7626,5,0,-1},{7631,4,0,-1},{7631,5,0,-1}, +{7635,4,0,-1},{7635,5,0,-1},{7565,5,0,-1},{7639,4,0,-1},{7639,5,0,-1}, +{7644,4,0,-1},{7644,5,0,-1},{7648,4,0,-1},{7648,5,0,-1},{7652,4,0,-1}, +{7652,5,0,-1},{7657,4,0,-1},{7657,5,0,-1},{7661,4,0,-1},{7661,5,0,-1}, +{7665,4,0,-1},{7665,5,0,-1},{7670,4,0,-1},{7670,5,0,-1},{7674,4,0,-1}, +{7674,5,0,-1},{7678,4,0,-1},{7678,5,0,-1},{7683,4,0,-1},{7683,5,0,-1}, +{7687,4,0,-1},{7687,5,0,-1},{7691,4,0,-1},{7691,5,0,-1},{7696,4,0,-1}, +{7696,5,0,-1},{7700,4,0,-1},{7700,5,0,-1},{7704,4,0,-1},{7704,5,0,-1}, +{7709,4,0,-1},{7709,5,0,-1},{7713,4,0,-1},{7713,5,0,-1},{7717,4,0,-1}, +{7717,5,0,-1},{7722,4,0,-1},{7722,5,0,-1},{7726,4,0,-1},{7726,5,0,-1}, +{7656,4,0,-1},{7730,4,0,-1},{7730,5,0,-1},{7735,4,0,-1},{7735,5,0,-1}, +{7739,4,0,-1},{7739,5,0,-1},{7669,5,0,-1},{7743,4,0,-1},{7743,5,0,-1}, +{7748,4,0,-1},{7748,5,0,-1},{7752,4,0,-1},{7752,5,0,-1},{7756,4,0,-1}, +{7756,5,0,-1},{7761,4,0,-1},{7761,5,0,-1},{7765,4,0,-1},{7765,5,0,-1}, +{7769,4,0,-1},{7769,5,0,-1},{7774,4,0,-1},{7774,5,0,-1},{7778,4,0,-1}, +{7778,5,0,-1},{7782,4,0,-1},{7782,5,0,-1},{7787,4,0,-1},{7787,5,0,-1}, +{7791,4,0,-1},{7791,5,0,-1},{7795,4,0,-1},{7795,5,0,-1},{7800,4,0,-1}, +{7800,5,0,-1},{7804,4,0,-1},{7804,5,0,-1},{7808,4,0,-1},{7808,5,0,-1}, +{7813,4,0,-1},{7813,5,0,-1},{7817,4,0,-1},{7817,5,0,-1},{7821,4,0,-1}, +{7821,5,0,-1},{7826,4,0,-1},{7826,5,0,-1},{7830,4,0,-1},{7830,5,0,-1}, +{7760,4,0,-1},{7834,4,0,-1},{7834,5,0,-1},{7839,4,0,-1},{7839,5,0,-1}, +{7843,4,0,-1},{7843,5,0,-1},{7773,5,0,-1},{7847,4,0,-1},{7847,5,0,-1}, +{7852,4,0,-1},{7852,5,0,-1},{7856,4,0,-1},{7856,5,0,-1},{7860,4,0,-1}, +{7860,5,0,-1},{7865,4,0,-1},{7865,5,0,-1},{7869,4,0,-1},{7869,5,0,-1}, +{7873,4,0,-1},{7873,5,0,-1},{7878,4,0,-1},{7878,5,0,-1},{7882,4,0,-1}, +{7882,5,0,-1},{7886,4,0,-1},{7886,5,0,-1},{7891,4,0,-1},{7891,5,0,-1}, +{7895,4,0,-1},{7895,5,0,-1},{7899,4,0,-1},{7899,5,0,-1},{7904,4,0,-1}, +{7904,5,0,-1},{7908,4,0,-1},{7908,5,0,-1},{7912,4,0,-1},{7912,5,0,-1}, +{7917,4,0,-1},{7917,5,0,-1},{7921,4,0,-1},{7921,5,0,-1},{7925,4,0,-1}, +{7925,5,0,-1},{7930,4,0,-1},{7930,5,0,-1},{7934,4,0,-1},{7934,5,0,-1}, +{7864,4,0,-1},{7938,4,0,-1},{7938,5,0,-1},{7943,4,0,-1},{7943,5,0,-1}, +{7947,4,0,-1},{7947,5,0,-1},{7877,5,0,-1},{7951,4,0,-1},{7951,5,0,-1}, +{7956,4,0,-1},{7956,5,0,-1},{7960,4,0,-1},{7960,5,0,-1},{7964,4,0,-1}, +{7964,5,0,-1},{7969,4,0,-1},{7969,5,0,-1},{7973,4,0,-1},{7973,5,0,-1}, +{7977,4,0,-1},{7977,5,0,-1},{7982,4,0,-1},{7982,5,0,-1},{7986,4,0,-1}, +{7986,5,0,-1},{7990,4,0,-1},{7990,5,0,-1},{7995,4,0,-1},{7995,5,0,-1}, +{7999,4,0,-1},{7999,5,0,-1},{8003,4,0,-1},{8003,5,0,-1},{8008,4,0,-1}, +{8008,5,0,-1},{8012,4,0,-1},{8012,5,0,-1},{8016,4,0,-1},{8016,5,0,-1}, +{8021,4,0,-1},{8021,5,0,-1},{8025,4,0,-1},{8025,5,0,-1},{8029,4,0,-1}, +{8029,5,0,-1},{8034,4,0,-1},{8034,5,0,-1},{8038,4,0,-1},{8038,5,0,-1}, +{7968,4,0,-1},{8042,4,0,-1},{8042,5,0,-1},{8047,4,0,-1},{8047,5,0,-1}, +{8051,4,0,-1},{8051,5,0,-1},{7981,5,0,-1},{8055,4,0,-1},{8055,5,0,-1}, +{8060,4,0,-1},{8060,5,0,-1},{8064,4,0,-1},{8064,5,0,-1},{8068,4,0,-1}, +{8068,5,0,-1},{8073,4,0,-1},{8073,5,0,-1},{8077,4,0,-1},{8077,5,0,-1}, +{8081,4,0,-1},{8081,5,0,-1},{8086,4,0,-1},{8086,5,0,-1},{8090,4,0,-1}, +{8090,5,0,-1},{8094,4,0,-1},{8094,5,0,-1},{8099,4,0,-1},{8099,5,0,-1}, +{8103,4,0,-1},{8103,5,0,-1},{8107,4,0,-1},{8107,5,0,-1},{8112,4,0,-1}, +{8112,5,0,-1},{8116,4,0,-1},{8116,5,0,-1},{8120,4,0,-1},{8120,5,0,-1}, +{8125,4,0,-1},{8125,5,0,-1},{8129,4,0,-1},{8129,5,0,-1},{8133,4,0,-1}, +{8133,5,0,-1},{8138,4,0,-1},{8138,5,0,-1},{8142,4,0,-1},{8142,5,0,-1}, +{8072,4,0,-1},{8146,4,0,-1},{8146,5,0,-1},{8151,4,0,-1},{8151,5,0,-1}, +{8155,4,0,-1},{8155,5,0,-1},{8085,5,0,-1},{8159,4,0,-1},{8159,5,0,-1}, +{8164,4,0,-1},{8164,5,0,-1},{8168,4,0,-1},{8168,5,0,-1},{8172,4,0,-1}, +{8172,5,0,-1},{8177,4,0,-1},{8177,5,0,-1},{8181,4,0,-1},{8181,5,0,-1}, +{8185,4,0,-1},{8185,5,0,-1},{8190,4,0,-1},{8190,5,0,-1},{8194,4,0,-1}, +{8194,5,0,-1},{8198,4,0,-1},{8198,5,0,-1},{8203,4,0,-1},{8203,5,0,-1}, +{8207,4,0,-1},{8207,5,0,-1},{8211,4,0,-1},{8211,5,0,-1},{8216,4,0,-1}, +{8216,5,0,-1},{8220,4,0,-1},{8220,5,0,-1},{8224,4,0,-1},{8224,5,0,-1}, +{8229,4,0,-1},{8229,5,0,-1},{8233,4,0,-1},{8233,5,0,-1},{8237,4,0,-1}, +{8237,5,0,-1},{8242,4,0,-1},{8242,5,0,-1},{8246,4,0,-1},{8246,5,0,-1}, +{8176,4,0,-1},{8250,4,0,-1},{8250,5,0,-1},{8255,4,0,-1},{8255,5,0,-1}, +{8259,4,0,-1},{8259,5,0,-1},{8189,5,0,-1},{8263,4,0,-1},{8263,5,0,-1}, +{8268,4,0,-1},{8268,5,0,-1},{8272,4,0,-1},{8272,5,0,-1},{8276,4,0,-1}, +{8276,5,0,-1},{8281,4,0,-1},{8281,5,0,-1},{8285,4,0,-1},{8285,5,0,-1}, +{8289,4,0,-1},{8289,5,0,-1},{8294,4,0,-1},{8294,5,0,-1},{8298,4,0,-1}, +{8298,5,0,-1},{8302,4,0,-1},{8302,5,0,-1},{8307,4,0,-1},{8307,5,0,-1}, +{8311,4,0,-1},{8311,5,0,-1},{8315,4,0,-1},{8315,5,0,-1},{8320,4,0,-1}, +{8320,5,0,-1},{8324,4,0,-1},{8324,5,0,-1},{8328,4,0,-1},{8328,5,0,-1}, +{8333,4,0,-1},{8333,5,0,-1},{8337,4,0,-1},{8337,5,0,-1},{8341,4,0,-1}, +{8341,5,0,-1},{8346,4,0,-1},{8346,5,0,-1},{8350,4,0,-1},{8350,5,0,-1}, +{8280,4,0,-1},{8354,4,0,-1},{8354,5,0,-1},{8359,4,0,-1},{8359,5,0,-1}, +{8363,4,0,-1},{8363,5,0,-1},{8293,5,0,-1},{8367,4,0,-1},{8367,5,0,-1}, +{8372,4,0,-1},{8372,5,0,-1},{8376,4,0,-1},{8376,5,0,-1},{8380,4,0,-1}, +{8380,5,0,-1},{8385,4,0,-1},{8385,5,0,-1},{8389,4,0,-1},{8389,5,0,-1}, +{8393,4,0,-1},{8393,5,0,-1},{8398,4,0,-1},{8398,5,0,-1},{8402,4,0,-1}, +{8402,5,0,-1},{8406,4,0,-1},{8406,5,0,-1},{8411,4,0,-1},{8411,5,0,-1}, +{8415,4,0,-1},{8415,5,0,-1},{8419,4,0,-1},{8419,5,0,-1},{8424,4,0,-1}, +{8424,5,0,-1},{8428,4,0,-1},{8428,5,0,-1},{8432,4,0,-1},{8432,5,0,-1}, +{8437,4,0,-1},{8437,5,0,-1},{8441,4,0,-1},{8441,5,0,-1},{8445,4,0,-1}, +{8445,5,0,-1},{8450,4,0,-1},{8450,5,0,-1},{8454,4,0,-1},{8454,5,0,-1}, +{8384,4,0,-1},{8458,4,0,-1},{8458,5,0,-1},{8463,4,0,-1},{8463,5,0,-1}, +{8467,4,0,-1},{8467,5,0,-1},{8397,5,0,-1},{8471,4,0,-1},{8471,5,0,-1}, +{8476,4,0,-1},{8476,5,0,-1},{8480,4,0,-1},{8480,5,0,-1},{8484,4,0,-1}, +{8484,5,0,-1},{8489,4,0,-1},{8489,5,0,-1},{8493,4,0,-1},{8493,5,0,-1}, +{8497,4,0,-1},{8497,5,0,-1},{8502,4,0,-1},{8502,5,0,-1},{8506,4,0,-1}, +{8506,5,0,-1},{8510,4,0,-1},{8510,5,0,-1},{8515,4,0,-1},{8515,5,0,-1}, +{8519,4,0,-1},{8519,5,0,-1},{8523,4,0,-1},{8523,5,0,-1},{8528,4,0,-1}, +{8528,5,0,-1},{8532,4,0,-1},{8532,5,0,-1},{8536,4,0,-1},{8536,5,0,-1}, +{8541,4,0,-1},{8541,5,0,-1},{8545,4,0,-1},{8545,5,0,-1},{8549,4,0,-1}, +{8549,5,0,-1},{8554,4,0,-1},{8554,5,0,-1},{8558,4,0,-1},{8558,5,0,-1}, +{8488,4,0,-1},{8562,4,0,-1},{8562,5,0,-1},{8567,4,0,-1},{8567,5,0,-1}, +{8571,4,0,-1},{8571,5,0,-1},{8501,5,0,-1},{8575,4,0,-1},{8575,5,0,-1}, +{8580,4,0,-1},{8580,5,0,-1},{8584,4,0,-1},{8584,5,0,-1},{8588,4,0,-1}, +{8588,5,0,-1},{8593,4,0,-1},{8593,5,0,-1},{8597,4,0,-1},{8597,5,0,-1}, +{8601,4,0,-1},{8601,5,0,-1},{8606,4,0,-1},{8606,5,0,-1},{8610,4,0,-1}, +{8610,5,0,-1},{8614,4,0,-1},{8614,5,0,-1},{8619,4,0,-1},{8619,5,0,-1}, +{8623,4,0,-1},{8623,5,0,-1},{8627,4,0,-1},{8627,5,0,-1},{8632,4,0,-1}, +{8632,5,0,-1},{8636,4,0,-1},{8636,5,0,-1},{8640,4,0,-1},{8640,5,0,-1}, +{8645,4,0,-1},{8645,5,0,-1},{8649,4,0,-1},{8649,5,0,-1},{8653,4,0,-1}, +{8653,5,0,-1},{8658,4,0,-1},{8658,5,0,-1},{8662,4,0,-1},{8662,5,0,-1}, +{8592,4,0,-1},{8666,4,0,-1},{8666,5,0,-1},{8671,4,0,-1},{8671,5,0,-1}, +{8675,4,0,-1},{8675,5,0,-1},{8605,5,0,-1},{8679,4,0,-1},{8679,5,0,-1}, +{8684,4,0,-1},{8684,5,0,-1},{8688,4,0,-1},{8688,5,0,-1},{8692,4,0,-1}, +{8692,5,0,-1},{8697,4,0,-1},{8697,5,0,-1},{8701,4,0,-1},{8701,5,0,-1}, +{8705,4,0,-1},{8705,5,0,-1},{8710,4,0,-1},{8710,5,0,-1},{8714,4,0,-1}, +{8714,5,0,-1},{8718,4,0,-1},{8718,5,0,-1},{8723,4,0,-1},{8723,5,0,-1}, +{8727,4,0,-1},{8727,5,0,-1},{8731,4,0,-1},{8731,5,0,-1},{8736,4,0,-1}, +{8736,5,0,-1},{8740,4,0,-1},{8740,5,0,-1},{8744,4,0,-1},{8744,5,0,-1}, +{8749,4,0,-1},{8749,5,0,-1},{8753,4,0,-1},{8753,5,0,-1},{8757,4,0,-1}, +{8757,5,0,-1},{8762,4,0,-1},{8762,5,0,-1},{8766,4,0,-1},{8766,5,0,-1}, +{8696,4,0,-1},{8770,4,0,-1},{8770,5,0,-1},{8775,4,0,-1},{8775,5,0,-1}, +{8779,4,0,-1},{8779,5,0,-1},{8709,5,0,-1},{8783,4,0,-1},{8783,5,0,-1}, +{8788,4,0,-1},{8788,5,0,-1},{8792,4,0,-1},{8792,5,0,-1},{8796,4,0,-1}, +{8796,5,0,-1},{8801,4,0,-1},{8801,5,0,-1},{8805,4,0,-1},{8805,5,0,-1}, +{8809,4,0,-1},{8809,5,0,-1},{8814,4,0,-1},{8814,5,0,-1},{8818,4,0,-1}, +{8818,5,0,-1},{8822,4,0,-1},{8822,5,0,-1},{8827,4,0,-1},{8827,5,0,-1}, +{8831,4,0,-1},{8831,5,0,-1},{8835,4,0,-1},{8835,5,0,-1},{8840,4,0,-1}, +{8840,5,0,-1},{8844,4,0,-1},{8844,5,0,-1},{8848,4,0,-1},{8848,5,0,-1}, +{8853,4,0,-1},{8853,5,0,-1},{8857,4,0,-1},{8857,5,0,-1},{8861,4,0,-1}, +{8861,5,0,-1},{8866,4,0,-1},{8866,5,0,-1},{8870,4,0,-1},{8870,5,0,-1}, +{8800,4,0,-1},{8874,4,0,-1},{8874,5,0,-1},{8879,4,0,-1},{8879,5,0,-1}, +{8883,4,0,-1},{8883,5,0,-1},{8813,5,0,-1},{8887,4,0,-1},{8887,5,0,-1}, +{8892,4,0,-1},{8892,5,0,-1},{8896,4,0,-1},{8896,5,0,-1},{8900,4,0,-1}, +{8900,5,0,-1},{8905,4,0,-1},{8905,5,0,-1},{8909,4,0,-1},{8909,5,0,-1}, +{8913,4,0,-1},{8913,5,0,-1},{8918,4,0,-1},{8918,5,0,-1},{8922,4,0,-1}, +{8922,5,0,-1},{8926,4,0,-1},{8926,5,0,-1},{8931,4,0,-1},{8931,5,0,-1}, +{8935,4,0,-1},{8935,5,0,-1},{8939,4,0,-1},{8939,5,0,-1},{8944,4,0,-1}, +{8944,5,0,-1},{8948,4,0,-1},{8948,5,0,-1},{8952,4,0,-1},{8952,5,0,-1}, +{8957,4,0,-1},{8957,5,0,-1},{8961,4,0,-1},{8961,5,0,-1},{8965,4,0,-1}, +{8965,5,0,-1},{8970,4,0,-1},{8970,5,0,-1},{8974,4,0,-1},{8974,5,0,-1}, +{8904,4,0,-1},{8978,4,0,-1},{8978,5,0,-1},{8983,4,0,-1},{8983,5,0,-1}, +{8987,4,0,-1},{8987,5,0,-1},{8917,5,0,-1},{8991,4,0,-1},{8991,5,0,-1}, +{8996,4,0,-1},{8996,5,0,-1},{9000,4,0,-1},{9000,5,0,-1},{9004,4,0,-1}, +{9004,5,0,-1},{9009,4,0,-1},{9009,5,0,-1},{9013,4,0,-1},{9013,5,0,-1}, +{9017,4,0,-1},{9017,5,0,-1},{9022,4,0,-1},{9022,5,0,-1},{9026,4,0,-1}, +{9026,5,0,-1},{9030,4,0,-1},{9030,5,0,-1},{9035,4,0,-1},{9035,5,0,-1}, +{9039,4,0,-1},{9039,5,0,-1},{9043,4,0,-1},{9043,5,0,-1},{9048,4,0,-1}, +{9048,5,0,-1},{9052,4,0,-1},{9052,5,0,-1},{9056,4,0,-1},{9056,5,0,-1}, +{9061,4,0,-1},{9061,5,0,-1},{9065,4,0,-1},{9065,5,0,-1},{9069,4,0,-1}, +{9069,5,0,-1},{9074,4,0,-1},{9074,5,0,-1},{9078,4,0,-1},{9078,5,0,-1}, +{9008,4,0,-1},{9082,4,0,-1},{9082,5,0,-1},{9087,4,0,-1},{9087,5,0,-1}, +{9091,4,0,-1},{9091,5,0,-1},{9021,5,0,-1},{9095,4,0,-1},{9095,5,0,-1}, +{9100,4,0,-1},{9100,5,0,-1},{9104,4,0,-1},{9104,5,0,-1},{9108,4,0,-1}, +{9108,5,0,-1},{9113,4,0,-1},{9113,5,0,-1},{9117,4,0,-1},{9117,5,0,-1}, +{9121,4,0,-1},{9121,5,0,-1},{9126,4,0,-1},{9126,5,0,-1},{9130,4,0,-1}, +{9130,5,0,-1},{9134,4,0,-1},{9134,5,0,-1},{9139,4,0,-1},{9139,5,0,-1}, +{9143,4,0,-1},{9143,5,0,-1},{9147,4,0,-1},{9147,5,0,-1},{9152,4,0,-1}, +{9152,5,0,-1},{9156,4,0,-1},{9156,5,0,-1},{9160,4,0,-1},{9160,5,0,-1}, +{9165,4,0,-1},{9165,5,0,-1},{9169,4,0,-1},{9169,5,0,-1},{9173,4,0,-1}, +{9173,5,0,-1},{9178,4,0,-1},{9178,5,0,-1},{9182,4,0,-1},{9182,5,0,-1}, +{9112,4,0,-1},{9186,4,0,-1},{9186,5,0,-1},{9191,4,0,-1},{9191,5,0,-1}, +{9195,4,0,-1},{9195,5,0,-1},{9125,5,0,-1},{9199,4,0,-1},{9199,5,0,-1}, +{9204,4,0,-1},{9204,5,0,-1},{9208,4,0,-1},{9208,5,0,-1},{9212,4,0,-1}, +{9212,5,0,-1},{9217,4,0,-1},{9217,5,0,-1},{9221,4,0,-1},{9221,5,0,-1}, +{9225,4,0,-1},{9225,5,0,-1},{9230,4,0,-1},{9230,5,0,-1},{9234,4,0,-1}, +{9234,5,0,-1},{9238,4,0,-1},{9238,5,0,-1},{9243,4,0,-1},{9243,5,0,-1}, +{9247,4,0,-1},{9247,5,0,-1},{9251,4,0,-1},{9251,5,0,-1},{9256,4,0,-1}, +{9256,5,0,-1},{9260,4,0,-1},{9260,5,0,-1},{9264,4,0,-1},{9264,5,0,-1}, +{9269,4,0,-1},{9269,5,0,-1},{9273,4,0,-1},{9273,5,0,-1},{9277,4,0,-1}, +{9277,5,0,-1},{9282,4,0,-1},{9282,5,0,-1},{9286,4,0,-1},{9286,5,0,-1}, +{9216,4,0,-1},{9290,4,0,-1},{9290,5,0,-1},{9295,4,0,-1},{9295,5,0,-1}, +{9299,4,0,-1},{9299,5,0,-1},{9229,5,0,-1},{9303,4,0,-1},{9303,5,0,-1}, +{9308,4,0,-1},{9308,5,0,-1},{9312,4,0,-1},{9312,5,0,-1},{9316,4,0,-1}, +{9316,5,0,-1},{9321,4,0,-1},{9321,5,0,-1},{9325,4,0,-1},{9325,5,0,-1}, +{9329,4,0,-1},{9329,5,0,-1},{9334,4,0,-1},{9334,5,0,-1},{9338,4,0,-1}, +{9338,5,0,-1},{9342,4,0,-1},{9342,5,0,-1},{9347,4,0,-1},{9347,5,0,-1}, +{9351,4,0,-1},{9351,5,0,-1},{9355,4,0,-1},{9355,5,0,-1},{9360,4,0,-1}, +{9360,5,0,-1},{9364,4,0,-1},{9364,5,0,-1},{9368,4,0,-1},{9368,5,0,-1}, +{9373,4,0,-1},{9373,5,0,-1},{9377,4,0,-1},{9377,5,0,-1},{9381,4,0,-1}, +{9381,5,0,-1},{9386,4,0,-1},{9386,5,0,-1},{9390,4,0,-1},{9390,5,0,-1}, +{9320,4,0,-1},{9394,4,0,-1},{9394,5,0,-1},{9399,4,0,-1},{9399,5,0,-1}, +{9403,4,0,-1},{9403,5,0,-1},{9333,5,0,-1},{9407,4,0,-1},{9407,5,0,-1}, +{9412,4,0,-1},{9412,5,0,-1},{9416,4,0,-1},{9416,5,0,-1},{9420,4,0,-1}, +{9420,5,0,-1},{9425,4,0,-1},{9425,5,0,-1},{9429,4,0,-1},{9429,5,0,-1}, +{9433,4,0,-1},{9433,5,0,-1},{9438,4,0,-1},{9438,5,0,-1},{9442,4,0,-1}, +{9442,5,0,-1},{9446,4,0,-1},{9446,5,0,-1},{9451,4,0,-1},{9451,5,0,-1}, +{9455,4,0,-1},{9455,5,0,-1},{9459,4,0,-1},{9459,5,0,-1},{9464,4,0,-1}, +{9464,5,0,-1},{9468,4,0,-1},{9468,5,0,-1},{9472,4,0,-1},{9472,5,0,-1}, +{9477,4,0,-1},{9477,5,0,-1},{9481,4,0,-1},{9481,5,0,-1},{9485,4,0,-1}, +{9485,5,0,-1},{9490,4,0,-1},{9490,5,0,-1},{9494,4,0,-1},{9494,5,0,-1}, +{9424,4,0,-1},{9498,4,0,-1},{9498,5,0,-1},{9503,4,0,-1},{9503,5,0,-1}, +{9507,4,0,-1},{9507,5,0,-1},{9437,5,0,-1},{9511,4,0,-1},{9511,5,0,-1}, +{9516,4,0,-1},{9516,5,0,-1},{9520,4,0,-1},{9520,5,0,-1},{9524,4,0,-1}, +{9524,5,0,-1},{9529,4,0,-1},{9529,5,0,-1},{9533,4,0,-1},{9533,5,0,-1}, +{9537,4,0,-1},{9537,5,0,-1},{9542,4,0,-1},{9542,5,0,-1},{9546,4,0,-1}, +{9546,5,0,-1},{9550,4,0,-1},{9550,5,0,-1},{9555,4,0,-1},{9555,5,0,-1}, +{9559,4,0,-1},{9559,5,0,-1},{9563,4,0,-1},{9563,5,0,-1},{9568,4,0,-1}, +{9568,5,0,-1},{9572,4,0,-1},{9572,5,0,-1},{9576,4,0,-1},{9576,5,0,-1}, +{9581,4,0,-1},{9581,5,0,-1},{9585,4,0,-1},{9585,5,0,-1},{9589,4,0,-1}, +{9589,5,0,-1},{9594,4,0,-1},{9594,5,0,-1},{9598,4,0,-1},{9598,5,0,-1}, +{9528,4,0,-1},{9602,4,0,-1},{9602,5,0,-1},{9607,4,0,-1},{9607,5,0,-1}, +{9611,4,0,-1},{9611,5,0,-1},{9541,5,0,-1},{9615,4,0,-1},{9615,5,0,-1}, +{9620,4,0,-1},{9620,5,0,-1},{9624,4,0,-1},{9624,5,0,-1},{9628,4,0,-1}, +{9628,5,0,-1},{9633,4,0,-1},{9633,5,0,-1},{9637,4,0,-1},{9637,5,0,-1}, +{9641,4,0,-1},{9641,5,0,-1},{9646,4,0,-1},{9646,5,0,-1},{9650,4,0,-1}, +{9650,5,0,-1},{9654,4,0,-1},{9654,5,0,-1},{9659,4,0,-1},{9659,5,0,-1}, +{9663,4,0,-1},{9663,5,0,-1},{9667,4,0,-1},{9667,5,0,-1},{9672,4,0,-1}, +{9672,5,0,-1},{9676,4,0,-1},{9676,5,0,-1},{9680,4,0,-1},{9680,5,0,-1}, +{9685,4,0,-1},{9689,4,0,-1},{9689,5,0,-1},{9693,4,0,-1},{9698,4,0,-1}, +{9702,4,0,-1},{9632,4,0,-1},{9706,4,0,-1},{9706,5,0,-1},{9711,4,0,-1}, +{9711,5,0,-1},{9715,4,0,-1},{9715,5,0,-1},{9645,5,0,-1},{9719,4,0,-1}, +{9719,5,0,-1},{9724,4,0,-1},{9724,5,0,-1},{9728,4,0,-1},{9728,5,0,-1}, +{9732,4,0,-1},{9737,4,0,-1},{9741,4,0,-1},{9741,5,0,-1},{9745,4,0,-1}, +{9745,5,0,-1},{9750,4,0,-1},{9754,4,0,-1},{9758,4,0,-1},{9763,4,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on full rate channels TS6 and TS7 */ +struct fn_sample test_fn_tch_f_ts_6_7[] = { +{4753,0,1,-1},{4784,0,1,-1},{4835,0,1,-1},{4855,0,1,-1},{4886,0,1,-1}, +{4937,0,1,-1},{4957,0,1,-1},{4988,0,1,-1},{5039,0,1,-1},{5090,0,1,-1}, +{5141,0,1,-1},{5198,0,2,-1},{5208,0,2,-1},{5249,0,2,-1},{5300,0,2,-1}, +{5310,0,2,-1},{5351,0,2,-1},{5402,0,2,-1},{5453,0,2,-1},{5504,0,2,-1}, +{5555,0,2,-1},{8597,6,0,-1},{8627,6,0,-1},{8632,6,0,-1},{8636,6,0,-1}, +{8640,6,0,-1},{8645,6,0,-1},{8649,6,0,-1},{8653,6,0,-1},{8658,6,0,-1}, +{8662,6,0,-1},{8666,6,0,-1},{8671,6,0,-1},{8675,6,0,-1},{8679,6,0,-1}, +{8684,6,0,-1},{8688,6,0,-1},{8618,6,0,-1},{8692,6,0,-1},{8697,6,0,-1}, +{8701,6,0,-1},{8705,6,0,-1},{8710,6,0,-1},{8714,6,0,-1},{8718,6,0,-1}, +{8723,6,0,-1},{8727,6,0,-1},{8731,6,0,-1},{8736,6,0,-1},{8740,6,0,-1}, +{8744,6,0,-1},{8749,6,0,-1},{8753,6,0,-1},{8757,6,0,-1},{8762,6,0,-1}, +{8766,6,0,-1},{8770,6,0,-1},{8775,6,0,-1},{8779,6,0,-1},{8783,6,0,-1}, +{8788,6,0,-1},{8792,6,0,-1},{8722,6,0,-1},{8796,6,0,-1},{8801,6,0,-1}, +{8805,6,0,-1},{8809,6,0,-1},{8814,6,0,-1},{8818,6,0,-1},{8822,6,0,-1}, +{8827,6,0,-1},{8831,6,0,-1},{8835,6,0,-1},{8840,6,0,-1},{8844,6,0,-1}, +{8848,6,0,-1},{8853,6,0,-1},{8857,6,0,-1},{8861,6,0,-1},{8866,6,0,-1}, +{8870,6,0,-1},{8874,6,0,-1},{8874,7,0,-1},{8879,6,0,-1},{8879,7,0,-1}, +{8883,6,0,-1},{8883,7,0,-1},{8887,6,0,-1},{8887,7,0,-1},{8892,6,0,-1}, +{8892,7,0,-1},{8896,6,0,-1},{8896,7,0,-1},{8826,6,0,-1},{8900,6,0,-1}, +{8900,7,0,-1},{8905,6,0,-1},{8905,7,0,-1},{8909,6,0,-1},{8909,7,0,-1}, +{8913,6,0,-1},{8913,7,0,-1},{8918,6,0,-1},{8918,7,0,-1},{8922,6,0,-1}, +{8922,7,0,-1},{8926,6,0,-1},{8926,7,0,-1},{8931,6,0,-1},{8931,7,0,-1}, +{8935,6,0,-1},{8935,7,0,-1},{8939,6,0,-1},{8939,7,0,-1},{8944,6,0,-1}, +{8944,7,0,-1},{8948,6,0,-1},{8948,7,0,-1},{8952,6,0,-1},{8952,7,0,-1}, +{8957,6,0,-1},{8957,7,0,-1},{8961,6,0,-1},{8961,7,0,-1},{8965,6,0,-1}, +{8965,7,0,-1},{8970,6,0,-1},{8970,7,0,-1},{8974,6,0,-1},{8974,7,0,-1}, +{8978,6,0,-1},{8978,7,0,-1},{8983,6,0,-1},{8983,7,0,-1},{8987,6,0,-1}, +{8987,7,0,-1},{8991,6,0,-1},{8991,7,0,-1},{8996,6,0,-1},{8996,7,0,-1}, +{9000,6,0,-1},{9000,7,0,-1},{8930,6,0,-1},{9004,6,0,-1},{9004,7,0,-1}, +{9009,6,0,-1},{9009,7,0,-1},{9013,6,0,-1},{9013,7,0,-1},{8943,7,0,-1}, +{9017,6,0,-1},{9017,7,0,-1},{9022,6,0,-1},{9022,7,0,-1},{9026,6,0,-1}, +{9026,7,0,-1},{9030,6,0,-1},{9030,7,0,-1},{9035,6,0,-1},{9035,7,0,-1}, +{9039,6,0,-1},{9039,7,0,-1},{9043,6,0,-1},{9043,7,0,-1},{9048,6,0,-1}, +{9048,7,0,-1},{9052,6,0,-1},{9052,7,0,-1},{9056,6,0,-1},{9056,7,0,-1}, +{9061,6,0,-1},{9061,7,0,-1},{9065,6,0,-1},{9065,7,0,-1},{9069,6,0,-1}, +{9069,7,0,-1},{9074,6,0,-1},{9074,7,0,-1},{9078,6,0,-1},{9078,7,0,-1}, +{9082,6,0,-1},{9082,7,0,-1},{9087,6,0,-1},{9087,7,0,-1},{9091,6,0,-1}, +{9091,7,0,-1},{9095,6,0,-1},{9095,7,0,-1},{9100,6,0,-1},{9100,7,0,-1}, +{9104,6,0,-1},{9104,7,0,-1},{9034,6,0,-1},{9108,6,0,-1},{9108,7,0,-1}, +{9113,6,0,-1},{9113,7,0,-1},{9117,6,0,-1},{9117,7,0,-1},{9047,7,0,-1}, +{9121,6,0,-1},{9121,7,0,-1},{9126,6,0,-1},{9126,7,0,-1},{9130,6,0,-1}, +{9130,7,0,-1},{9134,6,0,-1},{9134,7,0,-1},{9139,6,0,-1},{9139,7,0,-1}, +{9143,6,0,-1},{9143,7,0,-1},{9147,6,0,-1},{9147,7,0,-1},{9152,6,0,-1}, +{9152,7,0,-1},{9156,6,0,-1},{9156,7,0,-1},{9160,6,0,-1},{9160,7,0,-1}, +{9165,6,0,-1},{9165,7,0,-1},{9169,6,0,-1},{9169,7,0,-1},{9173,6,0,-1}, +{9173,7,0,-1},{9178,6,0,-1},{9178,7,0,-1},{9182,6,0,-1},{9182,7,0,-1}, +{9186,6,0,-1},{9186,7,0,-1},{9191,6,0,-1},{9191,7,0,-1},{9195,6,0,-1}, +{9195,7,0,-1},{9199,6,0,-1},{9199,7,0,-1},{9204,6,0,-1},{9204,7,0,-1}, +{9208,6,0,-1},{9208,7,0,-1},{9138,6,0,-1},{9212,6,0,-1},{9212,7,0,-1}, +{9217,6,0,-1},{9217,7,0,-1},{9221,6,0,-1},{9221,7,0,-1},{9151,7,0,-1}, +{9225,6,0,-1},{9225,7,0,-1},{9230,6,0,-1},{9230,7,0,-1},{9234,6,0,-1}, +{9234,7,0,-1},{9238,6,0,-1},{9238,7,0,-1},{9243,6,0,-1},{9243,7,0,-1}, +{9247,6,0,-1},{9247,7,0,-1},{9251,6,0,-1},{9251,7,0,-1},{9256,6,0,-1}, +{9256,7,0,-1},{9260,6,0,-1},{9260,7,0,-1},{9264,6,0,-1},{9264,7,0,-1}, +{9269,6,0,-1},{9269,7,0,-1},{9273,6,0,-1},{9273,7,0,-1},{9277,6,0,-1}, +{9277,7,0,-1},{9282,6,0,-1},{9282,7,0,-1},{9286,6,0,-1},{9286,7,0,-1}, +{9290,6,0,-1},{9290,7,0,-1},{9295,6,0,-1},{9295,7,0,-1},{9299,6,0,-1}, +{9299,7,0,-1},{9303,6,0,-1},{9303,7,0,-1},{9308,6,0,-1},{9308,7,0,-1}, +{9312,6,0,-1},{9312,7,0,-1},{9242,6,0,-1},{9316,6,0,-1},{9316,7,0,-1}, +{9321,6,0,-1},{9321,7,0,-1},{9325,6,0,-1},{9325,7,0,-1},{9255,7,0,-1}, +{9329,6,0,-1},{9329,7,0,-1},{9334,6,0,-1},{9334,7,0,-1},{9338,6,0,-1}, +{9338,7,0,-1},{9342,6,0,-1},{9342,7,0,-1},{9347,6,0,-1},{9347,7,0,-1}, +{9351,6,0,-1},{9351,7,0,-1},{9355,6,0,-1},{9355,7,0,-1},{9360,6,0,-1}, +{9360,7,0,-1},{9364,6,0,-1},{9364,7,0,-1},{9368,6,0,-1},{9368,7,0,-1}, +{9373,6,0,-1},{9373,7,0,-1},{9377,6,0,-1},{9377,7,0,-1},{9381,6,0,-1}, +{9381,7,0,-1},{9386,6,0,-1},{9386,7,0,-1},{9390,6,0,-1},{9390,7,0,-1}, +{9394,6,0,-1},{9394,7,0,-1},{9399,6,0,-1},{9399,7,0,-1},{9403,6,0,-1}, +{9403,7,0,-1},{9407,6,0,-1},{9407,7,0,-1},{9412,6,0,-1},{9412,7,0,-1}, +{9416,6,0,-1},{9416,7,0,-1},{9346,6,0,-1},{9420,6,0,-1},{9420,7,0,-1}, +{9425,6,0,-1},{9425,7,0,-1},{9429,6,0,-1},{9429,7,0,-1},{9359,7,0,-1}, +{9433,6,0,-1},{9433,7,0,-1},{9438,6,0,-1},{9438,7,0,-1},{9442,6,0,-1}, +{9442,7,0,-1},{9446,6,0,-1},{9446,7,0,-1},{9451,6,0,-1},{9451,7,0,-1}, +{9455,6,0,-1},{9455,7,0,-1},{9459,6,0,-1},{9459,7,0,-1},{9464,6,0,-1}, +{9464,7,0,-1},{9468,6,0,-1},{9468,7,0,-1},{9472,6,0,-1},{9472,7,0,-1}, +{9477,6,0,-1},{9477,7,0,-1},{9481,6,0,-1},{9481,7,0,-1},{9485,6,0,-1}, +{9485,7,0,-1},{9490,6,0,-1},{9490,7,0,-1},{9494,6,0,-1},{9494,7,0,-1}, +{9498,6,0,-1},{9498,7,0,-1},{9503,6,0,-1},{9503,7,0,-1},{9507,6,0,-1}, +{9507,7,0,-1},{9511,6,0,-1},{9511,7,0,-1},{9516,6,0,-1},{9516,7,0,-1}, +{9520,6,0,-1},{9520,7,0,-1},{9450,6,0,-1},{9524,6,0,-1},{9524,7,0,-1}, +{9529,6,0,-1},{9529,7,0,-1},{9533,6,0,-1},{9533,7,0,-1},{9463,7,0,-1}, +{9537,6,0,-1},{9537,7,0,-1},{9542,6,0,-1},{9542,7,0,-1},{9546,6,0,-1}, +{9546,7,0,-1},{9550,6,0,-1},{9550,7,0,-1},{9555,6,0,-1},{9555,7,0,-1}, +{9559,6,0,-1},{9559,7,0,-1},{9563,6,0,-1},{9563,7,0,-1},{9568,6,0,-1}, +{9568,7,0,-1},{9572,6,0,-1},{9572,7,0,-1},{9576,6,0,-1},{9576,7,0,-1}, +{9581,6,0,-1},{9581,7,0,-1},{9585,6,0,-1},{9585,7,0,-1},{9589,6,0,-1}, +{9589,7,0,-1},{9594,6,0,-1},{9594,7,0,-1},{9598,6,0,-1},{9598,7,0,-1}, +{9602,6,0,-1},{9602,7,0,-1},{9607,6,0,-1},{9607,7,0,-1},{9611,6,0,-1}, +{9611,7,0,-1},{9615,6,0,-1},{9615,7,0,-1},{9620,6,0,-1},{9620,7,0,-1}, +{9624,6,0,-1},{9624,7,0,-1},{9554,6,0,-1},{9628,6,0,-1},{9628,7,0,-1}, +{9633,6,0,-1},{9633,7,0,-1},{9637,6,0,-1},{9637,7,0,-1},{9567,7,0,-1}, +{9641,6,0,-1},{9641,7,0,-1},{9646,6,0,-1},{9646,7,0,-1},{9650,6,0,-1}, +{9650,7,0,-1},{9654,6,0,-1},{9654,7,0,-1},{9659,6,0,-1},{9659,7,0,-1}, +{9663,6,0,-1},{9663,7,0,-1},{9667,6,0,-1},{9667,7,0,-1},{9672,6,0,-1}, +{9672,7,0,-1},{9676,6,0,-1},{9676,7,0,-1},{9680,6,0,-1},{9680,7,0,-1}, +{9685,6,0,-1},{9685,7,0,-1},{9689,6,0,-1},{9689,7,0,-1},{9693,6,0,-1}, +{9693,7,0,-1},{9698,6,0,-1},{9698,7,0,-1},{9702,6,0,-1},{9702,7,0,-1}, +{9706,6,0,-1},{9706,7,0,-1},{9711,6,0,-1},{9711,7,0,-1},{9715,6,0,-1}, +{9715,7,0,-1},{9719,6,0,-1},{9719,7,0,-1},{9724,6,0,-1},{9724,7,0,-1}, +{9728,6,0,-1},{9728,7,0,-1},{9658,6,0,-1},{9732,6,0,-1},{9732,7,0,-1}, +{9737,6,0,-1},{9737,7,0,-1},{9741,6,0,-1},{9741,7,0,-1},{9671,7,0,-1}, +{9745,6,0,-1},{9745,7,0,-1},{9750,6,0,-1},{9750,7,0,-1},{9754,6,0,-1}, +{9754,7,0,-1},{9758,6,0,-1},{9758,7,0,-1},{9763,6,0,-1},{9763,7,0,-1}, +{9767,6,0,-1},{9767,7,0,-1},{9771,6,0,-1},{9771,7,0,-1},{9776,6,0,-1}, +{9776,7,0,-1},{9780,6,0,-1},{9780,7,0,-1},{9784,6,0,-1},{9784,7,0,-1}, +{9789,6,0,-1},{9789,7,0,-1},{9793,6,0,-1},{9793,7,0,-1},{9797,6,0,-1}, +{9797,7,0,-1},{9802,6,0,-1},{9802,7,0,-1},{9806,6,0,-1},{9806,7,0,-1}, +{9810,6,0,-1},{9810,7,0,-1},{9815,6,0,-1},{9815,7,0,-1},{9819,6,0,-1}, +{9819,7,0,-1},{9823,6,0,-1},{9823,7,0,-1},{9828,6,0,-1},{9828,7,0,-1}, +{9832,6,0,-1},{9832,7,0,-1},{9762,6,0,-1},{9836,6,0,-1},{9836,7,0,-1}, +{9841,6,0,-1},{9841,7,0,-1},{9845,6,0,-1},{9845,7,0,-1},{9775,7,0,-1}, +{9849,6,0,-1},{9849,7,0,-1},{9854,6,0,-1},{9854,7,0,-1},{9858,6,0,-1}, +{9858,7,0,-1},{9862,6,0,-1},{9862,7,0,-1},{9867,6,0,-1},{9867,7,0,-1}, +{9871,6,0,-1},{9871,7,0,-1},{9875,6,0,-1},{9875,7,0,-1},{9880,6,0,-1}, +{9880,7,0,-1},{9884,6,0,-1},{9884,7,0,-1},{9888,6,0,-1},{9888,7,0,-1}, +{9893,6,0,-1},{9893,7,0,-1},{9897,6,0,-1},{9897,7,0,-1},{9901,6,0,-1}, +{9901,7,0,-1},{9906,6,0,-1},{9906,7,0,-1},{9910,6,0,-1},{9910,7,0,-1}, +{9914,6,0,-1},{9914,7,0,-1},{9919,6,0,-1},{9919,7,0,-1},{9923,6,0,-1}, +{9923,7,0,-1},{9927,6,0,-1},{9927,7,0,-1},{9932,6,0,-1},{9932,7,0,-1}, +{9936,6,0,-1},{9936,7,0,-1},{9866,6,0,-1},{9940,6,0,-1},{9940,7,0,-1}, +{9945,6,0,-1},{9945,7,0,-1},{9949,6,0,-1},{9949,7,0,-1},{9879,7,0,-1}, +{9953,6,0,-1},{9953,7,0,-1},{9958,6,0,-1},{9958,7,0,-1},{9962,6,0,-1}, +{9962,7,0,-1},{9966,6,0,-1},{9966,7,0,-1},{9971,6,0,-1},{9971,7,0,-1}, +{9975,6,0,-1},{9975,7,0,-1},{9979,6,0,-1},{9979,7,0,-1},{9984,6,0,-1}, +{9984,7,0,-1},{9988,6,0,-1},{9988,7,0,-1},{9992,6,0,-1},{9992,7,0,-1}, +{9997,6,0,-1},{9997,7,0,-1},{10001,6,0,-1},{10001,7,0,-1},{10005,6,0,-1}, +{10005,7,0,-1},{10010,6,0,-1},{10010,7,0,-1},{10014,6,0,-1},{10014,7,0,-1}, +{10018,6,0,-1},{10018,7,0,-1},{10023,6,0,-1},{10023,7,0,-1},{10027,6,0,-1}, +{10027,7,0,-1},{10031,6,0,-1},{10031,7,0,-1},{10036,6,0,-1},{10036,7,0,-1}, +{10040,6,0,-1},{10040,7,0,-1},{9970,6,0,-1},{10044,6,0,-1},{10044,7,0,-1}, +{10049,6,0,-1},{10049,7,0,-1},{10053,6,0,-1},{10053,7,0,-1},{9983,7,0,-1}, +{10057,6,0,-1},{10057,7,0,-1},{10062,6,0,-1},{10062,7,0,-1},{10066,6,0,-1}, +{10066,7,0,-1},{10070,6,0,-1},{10070,7,0,-1},{10075,6,0,-1},{10075,7,0,-1}, +{10079,6,0,-1},{10079,7,0,-1},{10083,6,0,-1},{10083,7,0,-1},{10088,6,0,-1}, +{10088,7,0,-1},{10092,6,0,-1},{10092,7,0,-1},{10096,6,0,-1},{10096,7,0,-1}, +{10101,6,0,-1},{10101,7,0,-1},{10105,6,0,-1},{10105,7,0,-1},{10109,6,0,-1}, +{10109,7,0,-1},{10114,6,0,-1},{10114,7,0,-1},{10118,6,0,-1},{10118,7,0,-1}, +{10122,6,0,-1},{10122,7,0,-1},{10127,6,0,-1},{10127,7,0,-1},{10131,6,0,-1}, +{10131,7,0,-1},{10135,6,0,-1},{10135,7,0,-1},{10140,6,0,-1},{10140,7,0,-1}, +{10144,6,0,-1},{10144,7,0,-1},{10074,6,0,-1},{10148,6,0,-1},{10148,7,0,-1}, +{10153,6,0,-1},{10153,7,0,-1},{10157,6,0,-1},{10157,7,0,-1},{10087,7,0,-1}, +{10161,6,0,-1},{10161,7,0,-1},{10166,6,0,-1},{10166,7,0,-1},{10170,6,0,-1}, +{10170,7,0,-1},{10174,6,0,-1},{10174,7,0,-1},{10179,6,0,-1},{10179,7,0,-1}, +{10183,6,0,-1},{10183,7,0,-1},{10187,6,0,-1},{10187,7,0,-1},{10192,6,0,-1}, +{10192,7,0,-1},{10196,6,0,-1},{10196,7,0,-1},{10200,6,0,-1},{10200,7,0,-1}, +{10205,6,0,-1},{10205,7,0,-1},{10209,6,0,-1},{10209,7,0,-1},{10213,6,0,-1}, +{10213,7,0,-1},{10218,6,0,-1},{10218,7,0,-1},{10222,6,0,-1},{10222,7,0,-1}, +{10226,6,0,-1},{10226,7,0,-1},{10231,6,0,-1},{10231,7,0,-1},{10235,6,0,-1}, +{10235,7,0,-1},{10239,6,0,-1},{10239,7,0,-1},{10244,6,0,-1},{10244,7,0,-1}, +{10248,6,0,-1},{10248,7,0,-1},{10178,6,0,-1},{10252,6,0,-1},{10252,7,0,-1}, +{10257,6,0,-1},{10257,7,0,-1},{10261,6,0,-1},{10261,7,0,-1},{10191,7,0,-1}, +{10265,6,0,-1},{10265,7,0,-1},{10270,6,0,-1},{10270,7,0,-1},{10274,6,0,-1}, +{10274,7,0,-1},{10278,6,0,-1},{10278,7,0,-1},{10283,6,0,-1},{10283,7,0,-1}, +{10287,6,0,-1},{10287,7,0,-1},{10291,6,0,-1},{10291,7,0,-1},{10296,6,0,-1}, +{10296,7,0,-1},{10300,6,0,-1},{10300,7,0,-1},{10304,6,0,-1},{10304,7,0,-1}, +{10309,6,0,-1},{10309,7,0,-1},{10313,6,0,-1},{10313,7,0,-1},{10317,6,0,-1}, +{10317,7,0,-1},{10322,6,0,-1},{10322,7,0,-1},{10326,6,0,-1},{10326,7,0,-1}, +{10330,6,0,-1},{10330,7,0,-1},{10335,6,0,-1},{10335,7,0,-1},{10339,6,0,-1}, +{10339,7,0,-1},{10343,6,0,-1},{10343,7,0,-1},{10348,6,0,-1},{10348,7,0,-1}, +{10352,6,0,-1},{10352,7,0,-1},{10282,6,0,-1},{10356,6,0,-1},{10356,7,0,-1}, +{10361,6,0,-1},{10361,7,0,-1},{10365,6,0,-1},{10365,7,0,-1},{10295,7,0,-1}, +{10369,6,0,-1},{10369,7,0,-1},{10374,6,0,-1},{10374,7,0,-1},{10378,6,0,-1}, +{10378,7,0,-1},{10382,6,0,-1},{10382,7,0,-1},{10387,6,0,-1},{10387,7,0,-1}, +{10391,6,0,-1},{10391,7,0,-1},{10395,6,0,-1},{10395,7,0,-1},{10400,6,0,-1}, +{10400,7,0,-1},{10404,6,0,-1},{10404,7,0,-1},{10408,6,0,-1},{10408,7,0,-1}, +{10413,6,0,-1},{10413,7,0,-1},{10417,6,0,-1},{10417,7,0,-1},{10421,6,0,-1}, +{10421,7,0,-1},{10426,6,0,-1},{10426,7,0,-1},{10430,6,0,-1},{10430,7,0,-1}, +{10434,6,0,-1},{10434,7,0,-1},{10439,6,0,-1},{10439,7,0,-1},{10443,6,0,-1}, +{10443,7,0,-1},{10447,6,0,-1},{10447,7,0,-1},{10452,6,0,-1},{10452,7,0,-1}, +{10456,6,0,-1},{10456,7,0,-1},{10386,6,0,-1},{10460,6,0,-1},{10460,7,0,-1}, +{10465,6,0,-1},{10465,7,0,-1},{10469,6,0,-1},{10469,7,0,-1},{10399,7,0,-1}, +{10473,6,0,-1},{10473,7,0,-1},{10478,6,0,-1},{10478,7,0,-1},{10482,6,0,-1}, +{10482,7,0,-1},{10486,6,0,-1},{10486,7,0,-1},{10491,6,0,-1},{10491,7,0,-1}, +{10495,6,0,-1},{10495,7,0,-1},{10499,6,0,-1},{10499,7,0,-1},{10504,6,0,-1}, +{10504,7,0,-1},{10508,6,0,-1},{10508,7,0,-1},{10512,6,0,-1},{10512,7,0,-1}, +{10517,6,0,-1},{10517,7,0,-1},{10521,6,0,-1},{10521,7,0,-1},{10525,6,0,-1}, +{10525,7,0,-1},{10530,6,0,-1},{10530,7,0,-1},{10534,6,0,-1},{10534,7,0,-1}, +{10538,6,0,-1},{10538,7,0,-1},{10543,6,0,-1},{10543,7,0,-1},{10547,6,0,-1}, +{10547,7,0,-1},{10551,6,0,-1},{10551,7,0,-1},{10556,6,0,-1},{10556,7,0,-1}, +{10560,6,0,-1},{10560,7,0,-1},{10490,6,0,-1},{10564,6,0,-1},{10564,7,0,-1}, +{10569,6,0,-1},{10569,7,0,-1},{10573,6,0,-1},{10573,7,0,-1},{10503,7,0,-1}, +{10577,6,0,-1},{10577,7,0,-1},{10582,6,0,-1},{10582,7,0,-1},{10586,6,0,-1}, +{10586,7,0,-1},{10590,6,0,-1},{10590,7,0,-1},{10595,6,0,-1},{10595,7,0,-1}, +{10599,6,0,-1},{10599,7,0,-1},{10603,6,0,-1},{10603,7,0,-1},{10608,6,0,-1}, +{10608,7,0,-1},{10612,6,0,-1},{10612,7,0,-1},{10616,6,0,-1},{10616,7,0,-1}, +{10621,6,0,-1},{10621,7,0,-1},{10625,6,0,-1},{10625,7,0,-1},{10629,6,0,-1}, +{10629,7,0,-1},{10634,6,0,-1},{10634,7,0,-1},{10638,6,0,-1},{10638,7,0,-1}, +{10642,6,0,-1},{10642,7,0,-1},{10647,6,0,-1},{10647,7,0,-1},{10651,6,0,-1}, +{10651,7,0,-1},{10655,6,0,-1},{10655,7,0,-1},{10660,6,0,-1},{10660,7,0,-1}, +{10664,6,0,-1},{10664,7,0,-1},{10594,6,0,-1},{10668,6,0,-1},{10668,7,0,-1}, +{10673,6,0,-1},{10673,7,0,-1},{10677,6,0,-1},{10677,7,0,-1},{10607,7,0,-1}, +{10681,6,0,-1},{10681,7,0,-1},{10686,6,0,-1},{10686,7,0,-1},{10690,6,0,-1}, +{10690,7,0,-1},{10694,6,0,-1},{10694,7,0,-1},{10699,6,0,-1},{10699,7,0,-1}, +{10703,6,0,-1},{10703,7,0,-1},{10707,6,0,-1},{10707,7,0,-1},{10712,6,0,-1}, +{10712,7,0,-1},{10716,6,0,-1},{10716,7,0,-1},{10720,6,0,-1},{10720,7,0,-1}, +{10725,6,0,-1},{10725,7,0,-1},{10729,6,0,-1},{10729,7,0,-1},{10733,6,0,-1}, +{10733,7,0,-1},{10738,6,0,-1},{10738,7,0,-1},{10742,6,0,-1},{10742,7,0,-1}, +{10746,6,0,-1},{10746,7,0,-1},{10751,6,0,-1},{10751,7,0,-1},{10755,6,0,-1}, +{10755,7,0,-1},{10759,6,0,-1},{10759,7,0,-1},{10764,6,0,-1},{10764,7,0,-1}, +{10768,6,0,-1},{10768,7,0,-1},{10698,6,0,-1},{10772,6,0,-1},{10772,7,0,-1}, +{10777,6,0,-1},{10777,7,0,-1},{10781,6,0,-1},{10781,7,0,-1},{10711,7,0,-1}, +{10785,6,0,-1},{10785,7,0,-1},{10790,6,0,-1},{10790,7,0,-1},{10794,6,0,-1}, +{10794,7,0,-1},{10798,6,0,-1},{10798,7,0,-1},{10803,6,0,-1},{10803,7,0,-1}, +{10807,6,0,-1},{10807,7,0,-1},{10811,6,0,-1},{10811,7,0,-1},{10816,6,0,-1}, +{10816,7,0,-1},{10820,6,0,-1},{10820,7,0,-1},{10824,6,0,-1},{10824,7,0,-1}, +{10829,6,0,-1},{10829,7,0,-1},{10833,6,0,-1},{10833,7,0,-1},{10837,6,0,-1}, +{10837,7,0,-1},{10842,6,0,-1},{10842,7,0,-1},{10846,6,0,-1},{10846,7,0,-1}, +{10850,6,0,-1},{10850,7,0,-1},{10855,6,0,-1},{10855,7,0,-1},{10859,6,0,-1}, +{10859,7,0,-1},{10863,6,0,-1},{10863,7,0,-1},{10868,6,0,-1},{10868,7,0,-1}, +{10872,6,0,-1},{10872,7,0,-1},{10802,6,0,-1},{10876,6,0,-1},{10876,7,0,-1}, +{10881,6,0,-1},{10881,7,0,-1},{10885,6,0,-1},{10885,7,0,-1},{10815,7,0,-1}, +{10889,6,0,-1},{10889,7,0,-1},{10894,6,0,-1},{10894,7,0,-1},{10898,6,0,-1}, +{10898,7,0,-1},{10902,6,0,-1},{10902,7,0,-1},{10907,6,0,-1},{10907,7,0,-1}, +{10911,6,0,-1},{10911,7,0,-1},{10915,6,0,-1},{10915,7,0,-1},{10920,6,0,-1}, +{10920,7,0,-1},{10924,6,0,-1},{10924,7,0,-1},{10928,6,0,-1},{10928,7,0,-1}, +{10933,6,0,-1},{10933,7,0,-1},{10937,6,0,-1},{10937,7,0,-1},{10941,6,0,-1}, +{10941,7,0,-1},{10946,6,0,-1},{10946,7,0,-1},{10950,6,0,-1},{10950,7,0,-1}, +{10954,6,0,-1},{10954,7,0,-1},{10959,6,0,-1},{10959,7,0,-1},{10963,6,0,-1}, +{10963,7,0,-1},{10967,6,0,-1},{10967,7,0,-1},{10972,6,0,-1},{10972,7,0,-1}, +{10976,6,0,-1},{10976,7,0,-1},{10906,6,0,-1},{10980,6,0,-1},{10980,7,0,-1}, +{10985,6,0,-1},{10985,7,0,-1},{10989,6,0,-1},{10989,7,0,-1},{10919,7,0,-1}, +{10993,6,0,-1},{10993,7,0,-1},{10998,6,0,-1},{10998,7,0,-1},{11002,6,0,-1}, +{11002,7,0,-1},{11006,6,0,-1},{11006,7,0,-1},{11011,6,0,-1},{11011,7,0,-1}, +{11015,6,0,-1},{11015,7,0,-1},{11019,6,0,-1},{11019,7,0,-1},{11024,6,0,-1}, +{11024,7,0,-1},{11028,6,0,-1},{11028,7,0,-1},{11032,6,0,-1},{11032,7,0,-1}, +{11037,6,0,-1},{11037,7,0,-1},{11041,6,0,-1},{11041,7,0,-1},{11045,6,0,-1}, +{11045,7,0,-1},{11050,6,0,-1},{11050,7,0,-1},{11054,6,0,-1},{11054,7,0,-1}, +{11058,6,0,-1},{11058,7,0,-1},{11063,6,0,-1},{11063,7,0,-1},{11067,6,0,-1}, +{11067,7,0,-1},{11071,6,0,-1},{11071,7,0,-1},{11076,6,0,-1},{11076,7,0,-1}, +{11080,6,0,-1},{11080,7,0,-1},{11010,6,0,-1},{11084,6,0,-1},{11084,7,0,-1}, +{11089,6,0,-1},{11089,7,0,-1},{11093,6,0,-1},{11093,7,0,-1},{11023,7,0,-1}, +{11097,6,0,-1},{11097,7,0,-1},{11102,6,0,-1},{11102,7,0,-1},{11106,6,0,-1}, +{11106,7,0,-1},{11110,6,0,-1},{11110,7,0,-1},{11115,6,0,-1},{11115,7,0,-1}, +{11119,6,0,-1},{11119,7,0,-1},{11123,6,0,-1},{11123,7,0,-1},{11128,6,0,-1}, +{11128,7,0,-1},{11132,6,0,-1},{11132,7,0,-1},{11136,6,0,-1},{11136,7,0,-1}, +{11141,6,0,-1},{11141,7,0,-1},{11145,6,0,-1},{11145,7,0,-1},{11149,6,0,-1}, +{11149,7,0,-1},{11154,6,0,-1},{11154,7,0,-1},{11158,6,0,-1},{11158,7,0,-1}, +{11162,6,0,-1},{11162,7,0,-1},{11167,6,0,-1},{11167,7,0,-1},{11171,6,0,-1}, +{11171,7,0,-1},{11175,6,0,-1},{11175,7,0,-1},{11180,6,0,-1},{11180,7,0,-1}, +{11184,6,0,-1},{11184,7,0,-1},{11114,6,0,-1},{11188,6,0,-1},{11188,7,0,-1}, +{11193,6,0,-1},{11193,7,0,-1},{11197,6,0,-1},{11197,7,0,-1},{11127,7,0,-1}, +{11201,6,0,-1},{11201,7,0,-1},{11206,6,0,-1},{11206,7,0,-1},{11210,6,0,-1}, +{11210,7,0,-1},{11214,6,0,-1},{11214,7,0,-1},{11219,6,0,-1},{11219,7,0,-1}, +{11223,6,0,-1},{11223,7,0,-1},{11227,6,0,-1},{11227,7,0,-1},{11232,6,0,-1}, +{11232,7,0,-1},{11236,6,0,-1},{11236,7,0,-1},{11240,6,0,-1},{11240,7,0,-1}, +{11245,6,0,-1},{11245,7,0,-1},{11249,6,0,-1},{11249,7,0,-1},{11253,6,0,-1}, +{11253,7,0,-1},{11258,6,0,-1},{11258,7,0,-1},{11262,6,0,-1},{11262,7,0,-1}, +{11266,6,0,-1},{11266,7,0,-1},{11271,6,0,-1},{11271,7,0,-1},{11275,6,0,-1}, +{11275,7,0,-1},{11279,6,0,-1},{11279,7,0,-1},{11284,6,0,-1},{11284,7,0,-1}, +{11288,6,0,-1},{11288,7,0,-1},{11218,6,0,-1},{11292,6,0,-1},{11292,7,0,-1}, +{11297,6,0,-1},{11297,7,0,-1},{11301,6,0,-1},{11301,7,0,-1},{11231,7,0,-1}, +{11305,6,0,-1},{11305,7,0,-1},{11310,6,0,-1},{11310,7,0,-1},{11314,6,0,-1}, +{11314,7,0,-1},{11318,6,0,-1},{11318,7,0,-1},{11323,6,0,-1},{11323,7,0,-1}, +{11327,6,0,-1},{11327,7,0,-1},{11331,6,0,-1},{11331,7,0,-1},{11336,6,0,-1}, +{11336,7,0,-1},{11340,6,0,-1},{11340,7,0,-1},{11344,6,0,-1},{11344,7,0,-1}, +{11349,6,0,-1},{11349,7,0,-1},{11353,6,0,-1},{11353,7,0,-1},{11357,6,0,-1}, +{11357,7,0,-1},{11362,6,0,-1},{11362,7,0,-1},{11366,6,0,-1},{11366,7,0,-1}, +{11370,6,0,-1},{11370,7,0,-1},{11375,6,0,-1},{11375,7,0,-1},{11379,6,0,-1}, +{11379,7,0,-1},{11383,6,0,-1},{11383,7,0,-1},{11388,6,0,-1},{11388,7,0,-1}, +{11392,6,0,-1},{11392,7,0,-1},{11322,6,0,-1},{11396,6,0,-1},{11396,7,0,-1}, +{11401,6,0,-1},{11401,7,0,-1},{11405,6,0,-1},{11405,7,0,-1},{11335,7,0,-1}, +{11409,6,0,-1},{11409,7,0,-1},{11414,6,0,-1},{11414,7,0,-1},{11418,6,0,-1}, +{11418,7,0,-1},{11422,6,0,-1},{11422,7,0,-1},{11427,6,0,-1},{11427,7,0,-1}, +{11431,6,0,-1},{11431,7,0,-1},{11435,6,0,-1},{11435,7,0,-1},{11440,6,0,-1}, +{11440,7,0,-1},{11444,6,0,-1},{11444,7,0,-1},{11448,6,0,-1},{11448,7,0,-1}, +{11453,6,0,-1},{11453,7,0,-1},{11457,6,0,-1},{11457,7,0,-1},{11461,6,0,-1}, +{11461,7,0,-1},{11466,6,0,-1},{11466,7,0,-1},{11470,6,0,-1},{11470,7,0,-1}, +{11474,6,0,-1},{11474,7,0,-1},{11479,6,0,-1},{11479,7,0,-1},{11483,6,0,-1}, +{11483,7,0,-1},{11487,6,0,-1},{11487,7,0,-1},{11492,6,0,-1},{11492,7,0,-1}, +{11496,6,0,-1},{11496,7,0,-1},{11426,6,0,-1},{11500,6,0,-1},{11500,7,0,-1}, +{11505,6,0,-1},{11505,7,0,-1},{11509,6,0,-1},{11509,7,0,-1},{11439,7,0,-1}, +{11513,6,0,-1},{11513,7,0,-1},{11518,6,0,-1},{11518,7,0,-1},{11522,6,0,-1}, +{11522,7,0,-1},{11526,6,0,-1},{11526,7,0,-1},{11531,6,0,-1},{11531,7,0,-1}, +{11535,6,0,-1},{11535,7,0,-1},{11539,6,0,-1},{11539,7,0,-1},{11544,6,0,-1}, +{11544,7,0,-1},{11548,6,0,-1},{11548,7,0,-1},{11552,6,0,-1},{11552,7,0,-1}, +{11557,6,0,-1},{11557,7,0,-1},{11561,6,0,-1},{11561,7,0,-1},{11565,6,0,-1}, +{11565,7,0,-1},{11570,6,0,-1},{11570,7,0,-1},{11574,6,0,-1},{11574,7,0,-1}, +{11578,6,0,-1},{11578,7,0,-1},{11583,6,0,-1},{11583,7,0,-1},{11587,6,0,-1}, +{11587,7,0,-1},{11591,6,0,-1},{11591,7,0,-1},{11596,6,0,-1},{11596,7,0,-1}, +{11600,6,0,-1},{11600,7,0,-1},{11530,6,0,-1},{11604,6,0,-1},{11604,7,0,-1}, +{11609,6,0,-1},{11609,7,0,-1},{11613,6,0,-1},{11613,7,0,-1},{11543,7,0,-1}, +{11617,6,0,-1},{11617,7,0,-1},{11622,6,0,-1},{11622,7,0,-1},{11626,6,0,-1}, +{11626,7,0,-1},{11630,6,0,-1},{11630,7,0,-1},{11635,6,0,-1},{11635,7,0,-1}, +{11639,6,0,-1},{11639,7,0,-1},{11643,6,0,-1},{11643,7,0,-1},{11648,6,0,-1}, +{11648,7,0,-1},{11652,6,0,-1},{11652,7,0,-1},{11656,6,0,-1},{11656,7,0,-1}, +{11661,6,0,-1},{11661,7,0,-1},{11665,6,0,-1},{11665,7,0,-1},{11669,6,0,-1}, +{11669,7,0,-1},{11674,6,0,-1},{11674,7,0,-1},{11678,6,0,-1},{11678,7,0,-1}, +{11682,6,0,-1},{11682,7,0,-1},{11687,6,0,-1},{11687,7,0,-1},{11691,6,0,-1}, +{11691,7,0,-1},{11695,6,0,-1},{11695,7,0,-1},{11700,6,0,-1},{11700,7,0,-1}, +{11704,6,0,-1},{11704,7,0,-1},{11634,6,0,-1},{11708,6,0,-1},{11708,7,0,-1}, +{11713,6,0,-1},{11713,7,0,-1},{11717,6,0,-1},{11717,7,0,-1},{11647,7,0,-1}, +{11721,6,0,-1},{11721,7,0,-1},{11726,6,0,-1},{11726,7,0,-1},{11730,6,0,-1}, +{11730,7,0,-1},{11734,6,0,-1},{11734,7,0,-1},{11739,6,0,-1},{11739,7,0,-1}, +{11743,6,0,-1},{11743,7,0,-1},{11747,6,0,-1},{11747,7,0,-1},{11752,6,0,-1}, +{11752,7,0,-1},{11756,6,0,-1},{11756,7,0,-1},{11760,6,0,-1},{11760,7,0,-1}, +{11765,6,0,-1},{11765,7,0,-1},{11769,6,0,-1},{11769,7,0,-1},{11773,6,0,-1}, +{11773,7,0,-1},{11778,6,0,-1},{11778,7,0,-1},{11782,6,0,-1},{11782,7,0,-1}, +{11786,6,0,-1},{11786,7,0,-1},{11791,6,0,-1},{11791,7,0,-1},{11795,6,0,-1}, +{11795,7,0,-1},{11799,6,0,-1},{11799,7,0,-1},{11804,6,0,-1},{11804,7,0,-1}, +{11808,6,0,-1},{11808,7,0,-1},{11738,6,0,-1},{11812,6,0,-1},{11812,7,0,-1}, +{11817,6,0,-1},{11817,7,0,-1},{11821,6,0,-1},{11821,7,0,-1},{11751,7,0,-1}, +{11825,6,0,-1},{11825,7,0,-1},{11830,6,0,-1},{11830,7,0,-1},{11834,6,0,-1}, +{11834,7,0,-1},{11838,6,0,-1},{11838,7,0,-1},{11843,6,0,-1},{11843,7,0,-1}, +{11847,6,0,-1},{11847,7,0,-1},{11851,6,0,-1},{11851,7,0,-1},{11856,6,0,-1}, +{11856,7,0,-1},{11860,6,0,-1},{11860,7,0,-1},{11864,6,0,-1},{11864,7,0,-1}, +{11869,6,0,-1},{11869,7,0,-1},{11873,6,0,-1},{11873,7,0,-1},{11877,6,0,-1}, +{11877,7,0,-1},{11882,6,0,-1},{11882,7,0,-1},{11886,6,0,-1},{11886,7,0,-1}, +{11890,6,0,-1},{11890,7,0,-1},{11895,6,0,-1},{11895,7,0,-1},{11899,6,0,-1}, +{11899,7,0,-1},{11903,6,0,-1},{11903,7,0,-1},{11908,6,0,-1},{11908,7,0,-1}, +{11912,6,0,-1},{11912,7,0,-1},{11842,6,0,-1},{11916,6,0,-1},{11916,7,0,-1}, +{11921,6,0,-1},{11921,7,0,-1},{11925,6,0,-1},{11925,7,0,-1},{11855,7,0,-1}, +{11929,6,0,-1},{11929,7,0,-1},{11934,6,0,-1},{11934,7,0,-1},{11938,6,0,-1}, +{11938,7,0,-1},{11942,6,0,-1},{11942,7,0,-1},{11947,6,0,-1},{11947,7,0,-1}, +{11951,6,0,-1},{11951,7,0,-1},{11955,6,0,-1},{11955,7,0,-1},{11960,6,0,-1}, +{11960,7,0,-1},{11964,6,0,-1},{11964,7,0,-1},{11968,6,0,-1},{11968,7,0,-1}, +{11973,6,0,-1},{11973,7,0,-1},{11977,6,0,-1},{11977,7,0,-1},{11981,6,0,-1}, +{11981,7,0,-1},{11986,6,0,-1},{11986,7,0,-1},{11990,6,0,-1},{11990,7,0,-1}, +{11994,6,0,-1},{11994,7,0,-1},{11999,6,0,-1},{11999,7,0,-1},{12003,6,0,-1}, +{12003,7,0,-1},{12007,6,0,-1},{12007,7,0,-1},{12012,6,0,-1},{12012,7,0,-1}, +{12016,6,0,-1},{12016,7,0,-1},{11946,6,0,-1},{12020,6,0,-1},{12020,7,0,-1}, +{12025,6,0,-1},{12025,7,0,-1},{12029,6,0,-1},{12029,7,0,-1},{11959,7,0,-1}, +{12033,6,0,-1},{12033,7,0,-1},{12038,6,0,-1},{12038,7,0,-1},{12042,6,0,-1}, +{12042,7,0,-1},{12046,6,0,-1},{12046,7,0,-1},{12051,6,0,-1},{12051,7,0,-1}, +{12055,6,0,-1},{12055,7,0,-1},{12059,6,0,-1},{12059,7,0,-1},{12064,6,0,-1}, +{12064,7,0,-1},{12068,6,0,-1},{12068,7,0,-1},{12072,6,0,-1},{12072,7,0,-1}, +{12077,6,0,-1},{12077,7,0,-1},{12081,6,0,-1},{12081,7,0,-1},{12085,6,0,-1}, +{12085,7,0,-1},{12090,6,0,-1},{12090,7,0,-1},{12094,6,0,-1},{12094,7,0,-1}, +{12098,6,0,-1},{12098,7,0,-1},{12103,6,0,-1},{12103,7,0,-1},{12107,6,0,-1}, +{12107,7,0,-1},{12111,6,0,-1},{12111,7,0,-1},{12116,6,0,-1},{12116,7,0,-1}, +{12120,6,0,-1},{12120,7,0,-1},{12050,6,0,-1},{12124,6,0,-1},{12124,7,0,-1}, +{12129,6,0,-1},{12129,7,0,-1},{12133,6,0,-1},{12133,7,0,-1},{12063,7,0,-1}, +{12137,6,0,-1},{12137,7,0,-1},{12142,6,0,-1},{12142,7,0,-1},{12146,6,0,-1}, +{12146,7,0,-1},{12150,6,0,-1},{12150,7,0,-1},{12155,6,0,-1},{12155,7,0,-1}, +{12159,6,0,-1},{12159,7,0,-1},{12163,6,0,-1},{12163,7,0,-1},{12168,6,0,-1}, +{12168,7,0,-1},{12172,6,0,-1},{12172,7,0,-1},{12176,6,0,-1},{12176,7,0,-1}, +{12181,6,0,-1},{12181,7,0,-1},{12185,6,0,-1},{12185,7,0,-1},{12189,6,0,-1}, +{12189,7,0,-1},{12194,6,0,-1},{12194,7,0,-1},{12198,6,0,-1},{12198,7,0,-1}, +{12202,6,0,-1},{12202,7,0,-1},{12207,6,0,-1},{12207,7,0,-1},{12211,6,0,-1}, +{12211,7,0,-1},{12215,6,0,-1},{12215,7,0,-1},{12220,6,0,-1},{12220,7,0,-1}, +{12224,6,0,-1},{12224,7,0,-1},{12154,6,0,-1},{12228,6,0,-1},{12228,7,0,-1}, +{12233,6,0,-1},{12233,7,0,-1},{12237,6,0,-1},{12237,7,0,-1},{12167,7,0,-1}, +{12241,6,0,-1},{12241,7,0,-1},{12246,6,0,-1},{12246,7,0,-1},{12250,7,0,-1}, +{12254,7,0,-1},{12259,6,0,-1},{12259,7,0,-1},{12263,7,0,-1},{12267,7,0,-1}, +{12272,6,0,-1},{12272,7,0,-1},{12276,6,0,-1},{12276,7,0,-1},{12280,6,0,-1}, +{12280,7,0,-1},{12285,6,0,-1},{12285,7,0,-1},{12289,7,0,-1},{12293,7,0,-1}, +{12298,7,0,-1},{12302,7,0,-1},{12306,6,0,-1},{12306,7,0,-1},{12311,6,0,-1}, +{12311,7,0,-1},{12315,7,0,-1},{12319,7,0,-1},{12324,7,0,-1},{12328,7,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS2, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_2_ss0_ss1[] = { +{8252,0,1,-1},{8987,2,0,-1},{8996,2,0,-1},{9004,2,0,-1},{9013,2,0,-1}, +{9022,2,0,-1},{9030,2,0,-1},{9039,2,0,-1},{9043,2,0,-1},{9048,2,0,-1}, +{8982,2,0,-1},{9056,2,0,-1},{9065,2,0,-1},{9074,2,0,-1},{9082,2,0,-1}, +{9091,2,0,-1},{9100,2,0,-1},{9108,2,0,-1},{9117,2,0,-1},{9126,2,0,-1}, +{9134,2,0,-1},{9143,2,0,-1},{9152,2,0,-1},{9086,2,0,-1},{9160,2,0,-1}, +{9169,2,0,-1},{9173,2,0,-1},{9178,2,0,-1},{9182,2,0,-1},{9186,2,0,-1}, +{9191,2,0,-1},{9195,2,0,-1},{9199,2,0,-1},{9204,2,0,-1},{9208,2,0,-1}, +{9212,2,0,-1},{9217,2,0,-1},{9221,2,0,-1},{9225,2,0,-1},{9230,2,0,-1}, +{9234,2,0,-1},{9238,2,0,-1},{9243,2,0,-1},{9247,2,0,-1},{9251,2,0,-1}, +{9256,2,0,-1},{9190,2,0,-1},{9260,2,0,-1},{9264,2,0,-1},{9269,2,0,-1}, +{9273,2,0,-1},{9277,2,0,-1},{9282,2,0,-1},{9286,2,0,-1},{9290,2,0,-1}, +{9295,2,0,-1},{9299,2,0,-1},{9303,2,0,-1},{9308,2,0,-1},{9312,2,0,-1}, +{9316,2,0,-1},{9321,2,0,-1},{9325,2,0,-1},{9329,2,0,-1},{9334,2,0,-1}, +{9338,2,0,-1},{9342,2,0,-1},{9347,2,0,-1},{9351,2,0,-1},{9355,2,0,-1}, +{9360,2,0,-1},{9294,2,0,-1},{9364,2,0,-1},{9368,2,0,-1},{9369,2,1,-1}, +{9373,2,0,-1},{9377,2,0,-1},{9381,2,0,-1},{9382,2,1,-1},{9386,2,0,-1}, +{9390,2,0,-1},{9391,2,1,-1},{9394,2,0,-1},{9395,2,1,-1},{9399,2,0,-1}, +{9403,2,0,-1},{9404,2,1,-1},{9407,2,0,-1},{9412,2,0,-1},{9413,2,1,-1}, +{9416,2,0,-1},{9420,2,0,-1},{9421,2,1,-1},{9425,2,0,-1},{9429,2,0,-1}, +{9430,2,1,-1},{9433,2,0,-1},{9438,2,0,-1},{9439,2,1,-1},{9442,2,0,-1}, +{9446,2,0,-1},{9447,2,1,-1},{9451,2,0,-1},{9455,2,0,-1},{9456,2,1,-1}, +{9459,2,0,-1},{9464,2,0,-1},{9465,2,1,-1},{9398,2,0,-1},{9468,2,0,-1}, +{9472,2,0,-1},{9473,2,1,-1},{9477,2,0,-1},{9411,2,1,-1},{9481,2,0,-1}, +{9482,2,1,-1},{9485,2,0,-1},{9490,2,0,-1},{9491,2,1,-1},{9494,2,0,-1}, +{9498,2,0,-1},{9499,2,1,-1},{9503,2,0,-1},{9504,2,1,-1},{9507,2,0,-1}, +{9508,2,1,-1},{9511,2,0,-1},{9512,2,1,-1},{9516,2,0,-1},{9517,2,1,-1}, +{9520,2,0,-1},{9521,2,1,-1},{9524,2,0,-1},{9525,2,1,-1},{9529,2,0,-1}, +{9533,2,0,-1},{9534,2,1,-1},{9537,2,0,-1},{9538,2,1,-1},{9542,2,0,-1}, +{9543,2,1,-1},{9546,2,0,-1},{9547,2,1,-1},{9550,2,0,-1},{9551,2,1,-1}, +{9555,2,0,-1},{9556,2,1,-1},{9559,2,0,-1},{9560,2,1,-1},{9563,2,0,-1}, +{9564,2,1,-1},{9568,2,0,-1},{9569,2,1,-1},{9502,2,0,-1},{9573,2,1,-1}, +{9576,2,0,-1},{9577,2,1,-1},{9581,2,0,-1},{9582,2,1,-1},{9515,2,1,-1}, +{9585,2,0,-1},{9586,2,1,-1},{9589,2,0,-1},{9590,2,1,-1},{9594,2,0,-1}, +{9595,2,1,-1},{9598,2,0,-1},{9599,2,1,-1},{9602,2,0,-1},{9603,2,1,-1}, +{9607,2,0,-1},{9608,2,1,-1},{9611,2,0,-1},{9612,2,1,-1},{9615,2,0,-1}, +{9616,2,1,-1},{9620,2,0,-1},{9621,2,1,-1},{9624,2,0,-1},{9625,2,1,-1}, +{9628,2,0,-1},{9629,2,1,-1},{9633,2,0,-1},{9634,2,1,-1},{9637,2,0,-1}, +{9638,2,1,-1},{9641,2,0,-1},{9642,2,1,-1},{9646,2,0,-1},{9647,2,1,-1}, +{9650,2,0,-1},{9651,2,1,-1},{9654,2,0,-1},{9655,2,1,-1},{9659,2,0,-1}, +{9660,2,1,-1},{9663,2,0,-1},{9664,2,1,-1},{9667,2,0,-1},{9668,2,1,-1}, +{9672,2,0,-1},{9673,2,1,-1},{9606,2,0,-1},{9676,2,0,-1},{9677,2,1,-1}, +{9680,2,0,-1},{9681,2,1,-1},{9685,2,0,-1},{9686,2,1,-1},{9619,2,1,-1}, +{9689,2,0,-1},{9690,2,1,-1},{9693,2,0,-1},{9694,2,1,-1},{9698,2,0,-1}, +{9699,2,1,-1},{9702,2,0,-1},{9703,2,1,-1},{9706,2,0,-1},{9707,2,1,-1}, +{9711,2,0,-1},{9712,2,1,-1},{9715,2,0,-1},{9716,2,1,-1},{9719,2,0,-1}, +{9724,2,0,-1},{9725,2,1,-1},{9728,2,0,-1},{9729,2,1,-1},{9732,2,0,-1}, +{9733,2,1,-1},{9737,2,0,-1},{9738,2,1,-1},{9741,2,0,-1},{9742,2,1,-1}, +{9745,2,0,-1},{9746,2,1,-1},{9750,2,0,-1},{9751,2,1,-1},{9754,2,0,-1}, +{9755,2,1,-1},{9758,2,0,-1},{9759,2,1,-1},{9764,2,1,-1},{9767,2,0,-1}, +{9768,2,1,-1},{9771,2,0,-1},{9776,2,0,-1},{9777,2,1,-1},{9710,2,0,-1}, +{9780,2,0,-1},{9781,2,1,-1},{9784,2,0,-1},{9785,2,1,-1},{9789,2,0,-1}, +{9790,2,1,-1},{9723,2,1,-1},{9793,2,0,-1},{9794,2,1,-1},{9797,2,0,-1}, +{9798,2,1,-1},{9802,2,0,-1},{9803,2,1,-1},{9806,2,0,-1},{9807,2,1,-1}, +{9810,2,0,-1},{9811,2,1,-1},{9815,2,0,-1},{9816,2,1,-1},{9819,2,0,-1}, +{9820,2,1,-1},{9823,2,0,-1},{9824,2,1,-1},{9828,2,0,-1},{9829,2,1,-1}, +{9832,2,0,-1},{9833,2,1,-1},{9836,2,0,-1},{9837,2,1,-1},{9841,2,0,-1}, +{9842,2,1,-1},{9845,2,0,-1},{9846,2,1,-1},{9849,2,0,-1},{9850,2,1,-1}, +{9854,2,0,-1},{9855,2,1,-1},{9858,2,0,-1},{9859,2,1,-1},{9862,2,0,-1}, +{9863,2,1,-1},{9867,2,0,-1},{9868,2,1,-1},{9871,2,0,-1},{9872,2,1,-1}, +{9875,2,0,-1},{9876,2,1,-1},{9880,2,0,-1},{9881,2,1,-1},{9814,2,0,-1}, +{9884,2,0,-1},{9885,2,1,-1},{9888,2,0,-1},{9889,2,1,-1},{9893,2,0,-1}, +{9894,2,1,-1},{9827,2,1,-1},{9897,2,0,-1},{9898,2,1,-1},{9901,2,0,-1}, +{9902,2,1,-1},{9906,2,0,-1},{9907,2,1,-1},{9910,2,0,-1},{9911,2,1,-1}, +{9914,2,0,-1},{9915,2,1,-1},{9919,2,0,-1},{9920,2,1,-1},{9923,2,0,-1}, +{9924,2,1,-1},{9927,2,0,-1},{9928,2,1,-1},{9932,2,0,-1},{9933,2,1,-1}, +{9936,2,0,-1},{9937,2,1,-1},{9940,2,0,-1},{9941,2,1,-1},{9945,2,0,-1}, +{9946,2,1,-1},{9949,2,0,-1},{9950,2,1,-1},{9953,2,0,-1},{9954,2,1,-1}, +{9958,2,0,-1},{9959,2,1,-1},{9962,2,0,-1},{9963,2,1,-1},{9966,2,0,-1}, +{9967,2,1,-1},{9971,2,0,-1},{9972,2,1,-1},{9975,2,0,-1},{9976,2,1,-1}, +{9979,2,0,-1},{9980,2,1,-1},{9984,2,0,-1},{9985,2,1,-1},{9918,2,0,-1}, +{9988,2,0,-1},{9989,2,1,-1},{9992,2,0,-1},{9993,2,1,-1},{9997,2,0,-1}, +{9998,2,1,-1},{9931,2,1,-1},{10001,2,0,-1},{10002,2,1,-1},{10005,2,0,-1}, +{10006,2,1,-1},{10010,2,0,-1},{10011,2,1,-1},{10014,2,0,-1},{10015,2,1,-1}, +{10018,2,0,-1},{10019,2,1,-1},{10023,2,0,-1},{10024,2,1,-1},{10027,2,0,-1}, +{10028,2,1,-1},{10031,2,0,-1},{10032,2,1,-1},{10036,2,0,-1},{10037,2,1,-1}, +{10040,2,0,-1},{10041,2,1,-1},{10044,2,0,-1},{10045,2,1,-1},{10049,2,0,-1}, +{10050,2,1,-1},{10053,2,0,-1},{10054,2,1,-1},{10057,2,0,-1},{10058,2,1,-1}, +{10062,2,0,-1},{10063,2,1,-1},{10066,2,0,-1},{10067,2,1,-1},{10070,2,0,-1}, +{10071,2,1,-1},{10075,2,0,-1},{10076,2,1,-1},{10079,2,0,-1},{10080,2,1,-1}, +{10083,2,0,-1},{10084,2,1,-1},{10088,2,0,-1},{10089,2,1,-1},{10022,2,0,-1}, +{10092,2,0,-1},{10093,2,1,-1},{10096,2,0,-1},{10097,2,1,-1},{10101,2,0,-1}, +{10102,2,1,-1},{10035,2,1,-1},{10105,2,0,-1},{10106,2,1,-1},{10109,2,0,-1}, +{10110,2,1,-1},{10114,2,0,-1},{10115,2,1,-1},{10118,2,0,-1},{10119,2,1,-1}, +{10122,2,0,-1},{10123,2,1,-1},{10127,2,0,-1},{10128,2,1,-1},{10131,2,0,-1}, +{10132,2,1,-1},{10135,2,0,-1},{10136,2,1,-1},{10140,2,0,-1},{10141,2,1,-1}, +{10144,2,0,-1},{10145,2,1,-1},{10148,2,0,-1},{10149,2,1,-1},{10153,2,0,-1}, +{10154,2,1,-1},{10157,2,0,-1},{10158,2,1,-1},{10161,2,0,-1},{10162,2,1,-1}, +{10166,2,0,-1},{10167,2,1,-1},{10170,2,0,-1},{10171,2,1,-1},{10174,2,0,-1}, +{10175,2,1,-1},{10179,2,0,-1},{10180,2,1,-1},{10183,2,0,-1},{10184,2,1,-1}, +{10187,2,0,-1},{10188,2,1,-1},{10192,2,0,-1},{10193,2,1,-1},{10126,2,0,-1}, +{10196,2,0,-1},{10197,2,1,-1},{10200,2,0,-1},{10201,2,1,-1},{10205,2,0,-1}, +{10206,2,1,-1},{10139,2,1,-1},{10209,2,0,-1},{10210,2,1,-1},{10213,2,0,-1}, +{10214,2,1,-1},{10218,2,0,-1},{10219,2,1,-1},{10222,2,0,-1},{10223,2,1,-1}, +{10226,2,0,-1},{10227,2,1,-1},{10231,2,0,-1},{10232,2,1,-1},{10235,2,0,-1}, +{10236,2,1,-1},{10239,2,0,-1},{10240,2,1,-1},{10244,2,0,-1},{10245,2,1,-1}, +{10248,2,0,-1},{10249,2,1,-1},{10252,2,0,-1},{10253,2,1,-1},{10257,2,0,-1}, +{10258,2,1,-1},{10261,2,0,-1},{10262,2,1,-1},{10265,2,0,-1},{10266,2,1,-1}, +{10270,2,0,-1},{10271,2,1,-1},{10274,2,0,-1},{10275,2,1,-1},{10278,2,0,-1}, +{10279,2,1,-1},{10283,2,0,-1},{10284,2,1,-1},{10287,2,0,-1},{10288,2,1,-1}, +{10291,2,0,-1},{10292,2,1,-1},{10296,2,0,-1},{10297,2,1,-1},{10230,2,0,-1}, +{10300,2,0,-1},{10301,2,1,-1},{10304,2,0,-1},{10305,2,1,-1},{10309,2,0,-1}, +{10310,2,1,-1},{10243,2,1,-1},{10313,2,0,-1},{10314,2,1,-1},{10317,2,0,-1}, +{10318,2,1,-1},{10322,2,0,-1},{10323,2,1,-1},{10326,2,0,-1},{10327,2,1,-1}, +{10330,2,0,-1},{10331,2,1,-1},{10335,2,0,-1},{10336,2,1,-1},{10339,2,0,-1}, +{10340,2,1,-1},{10343,2,0,-1},{10344,2,1,-1},{10348,2,0,-1},{10349,2,1,-1}, +{10352,2,0,-1},{10353,2,1,-1},{10356,2,0,-1},{10357,2,1,-1},{10361,2,0,-1}, +{10362,2,1,-1},{10365,2,0,-1},{10366,2,1,-1},{10369,2,0,-1},{10370,2,1,-1}, +{10374,2,0,-1},{10375,2,1,-1},{10378,2,0,-1},{10379,2,1,-1},{10382,2,0,-1}, +{10383,2,1,-1},{10387,2,0,-1},{10388,2,1,-1},{10391,2,0,-1},{10392,2,1,-1}, +{10395,2,0,-1},{10396,2,1,-1},{10400,2,0,-1},{10401,2,1,-1},{10334,2,0,-1}, +{10404,2,0,-1},{10405,2,1,-1},{10408,2,0,-1},{10409,2,1,-1},{10413,2,0,-1}, +{10414,2,1,-1},{10347,2,1,-1},{10417,2,0,-1},{10418,2,1,-1},{10421,2,0,-1}, +{10422,2,1,-1},{10426,2,0,-1},{10427,2,1,-1},{10430,2,0,-1},{10431,2,1,-1}, +{10434,2,0,-1},{10435,2,1,-1},{10439,2,0,-1},{10440,2,1,-1},{10443,2,0,-1}, +{10444,2,1,-1},{10447,2,0,-1},{10448,2,1,-1},{10452,2,0,-1},{10453,2,1,-1}, +{10456,2,0,-1},{10457,2,1,-1},{10460,2,0,-1},{10461,2,1,-1},{10465,2,0,-1}, +{10466,2,1,-1},{10469,2,0,-1},{10470,2,1,-1},{10473,2,0,-1},{10474,2,1,-1}, +{10478,2,0,-1},{10479,2,1,-1},{10482,2,0,-1},{10483,2,1,-1},{10486,2,0,-1}, +{10487,2,1,-1},{10491,2,0,-1},{10492,2,1,-1},{10495,2,0,-1},{10496,2,1,-1}, +{10499,2,0,-1},{10500,2,1,-1},{10504,2,0,-1},{10505,2,1,-1},{10438,2,0,-1}, +{10508,2,0,-1},{10509,2,1,-1},{10512,2,0,-1},{10513,2,1,-1},{10517,2,0,-1}, +{10518,2,1,-1},{10451,2,1,-1},{10521,2,0,-1},{10522,2,1,-1},{10525,2,0,-1}, +{10526,2,1,-1},{10530,2,0,-1},{10531,2,1,-1},{10534,2,0,-1},{10535,2,1,-1}, +{10538,2,0,-1},{10539,2,1,-1},{10543,2,0,-1},{10544,2,1,-1},{10547,2,0,-1}, +{10548,2,1,-1},{10551,2,0,-1},{10552,2,1,-1},{10556,2,0,-1},{10557,2,1,-1}, +{10560,2,0,-1},{10561,2,1,-1},{10564,2,0,-1},{10565,2,1,-1},{10569,2,0,-1}, +{10570,2,1,-1},{10573,2,0,-1},{10574,2,1,-1},{10577,2,0,-1},{10578,2,1,-1}, +{10582,2,0,-1},{10583,2,1,-1},{10586,2,0,-1},{10587,2,1,-1},{10590,2,0,-1}, +{10591,2,1,-1},{10595,2,0,-1},{10596,2,1,-1},{10599,2,0,-1},{10600,2,1,-1}, +{10603,2,0,-1},{10604,2,1,-1},{10608,2,0,-1},{10609,2,1,-1},{10542,2,0,-1}, +{10612,2,0,-1},{10613,2,1,-1},{10616,2,0,-1},{10617,2,1,-1},{10621,2,0,-1}, +{10622,2,1,-1},{10555,2,1,-1},{10625,2,0,-1},{10626,2,1,-1},{10629,2,0,-1}, +{10630,2,1,-1},{10634,2,0,-1},{10635,2,1,-1},{10638,2,0,-1},{10639,2,1,-1}, +{10642,2,0,-1},{10643,2,1,-1},{10647,2,0,-1},{10648,2,1,-1},{10651,2,0,-1}, +{10652,2,1,-1},{10655,2,0,-1},{10656,2,1,-1},{10660,2,0,-1},{10661,2,1,-1}, +{10664,2,0,-1},{10665,2,1,-1},{10668,2,0,-1},{10669,2,1,-1},{10673,2,0,-1}, +{10674,2,1,-1},{10677,2,0,-1},{10678,2,1,-1},{10681,2,0,-1},{10682,2,1,-1}, +{10686,2,0,-1},{10687,2,1,-1},{10690,2,0,-1},{10691,2,1,-1},{10694,2,0,-1}, +{10695,2,1,-1},{10699,2,0,-1},{10700,2,1,-1},{10703,2,0,-1},{10704,2,1,-1}, +{10707,2,0,-1},{10708,2,1,-1},{10712,2,0,-1},{10713,2,1,-1},{10646,2,0,-1}, +{10716,2,0,-1},{10717,2,1,-1},{10720,2,0,-1},{10721,2,1,-1},{10725,2,0,-1}, +{10726,2,1,-1},{10659,2,1,-1},{10729,2,0,-1},{10730,2,1,-1},{10733,2,0,-1}, +{10734,2,1,-1},{10738,2,0,-1},{10739,2,1,-1},{10742,2,0,-1},{10743,2,1,-1}, +{10746,2,0,-1},{10747,2,1,-1},{10751,2,0,-1},{10752,2,1,-1},{10755,2,0,-1}, +{10756,2,1,-1},{10759,2,0,-1},{10760,2,1,-1},{10764,2,0,-1},{10765,2,1,-1}, +{10768,2,0,-1},{10769,2,1,-1},{10772,2,0,-1},{10773,2,1,-1},{10777,2,0,-1}, +{10778,2,1,-1},{10781,2,0,-1},{10782,2,1,-1},{10785,2,0,-1},{10786,2,1,-1}, +{10790,2,0,-1},{10791,2,1,-1},{10794,2,0,-1},{10795,2,1,-1},{10798,2,0,-1}, +{10799,2,1,-1},{10803,2,0,-1},{10804,2,1,-1},{10807,2,0,-1},{10808,2,1,-1}, +{10811,2,0,-1},{10812,2,1,-1},{10816,2,0,-1},{10817,2,1,-1},{10750,2,0,-1}, +{10820,2,0,-1},{10821,2,1,-1},{10824,2,0,-1},{10825,2,1,-1},{10829,2,0,-1}, +{10830,2,1,-1},{10763,2,1,-1},{10833,2,0,-1},{10834,2,1,-1},{10837,2,0,-1}, +{10838,2,1,-1},{10842,2,0,-1},{10843,2,1,-1},{10846,2,0,-1},{10847,2,1,-1}, +{10850,2,0,-1},{10851,2,1,-1},{10855,2,0,-1},{10856,2,1,-1},{10859,2,0,-1}, +{10860,2,1,-1},{10863,2,0,-1},{10864,2,1,-1},{10868,2,0,-1},{10869,2,1,-1}, +{10872,2,0,-1},{10873,2,1,-1},{10876,2,0,-1},{10877,2,1,-1},{10881,2,0,-1}, +{10882,2,1,-1},{10885,2,0,-1},{10886,2,1,-1},{10889,2,0,-1},{10890,2,1,-1}, +{10894,2,0,-1},{10895,2,1,-1},{10898,2,0,-1},{10899,2,1,-1},{10902,2,0,-1}, +{10903,2,1,-1},{10907,2,0,-1},{10908,2,1,-1},{10911,2,0,-1},{10912,2,1,-1}, +{10915,2,0,-1},{10916,2,1,-1},{10920,2,0,-1},{10921,2,1,-1},{10854,2,0,-1}, +{10924,2,0,-1},{10925,2,1,-1},{10928,2,0,-1},{10929,2,1,-1},{10933,2,0,-1}, +{10934,2,1,-1},{10867,2,1,-1},{10937,2,0,-1},{10938,2,1,-1},{10941,2,0,-1}, +{10942,2,1,-1},{10946,2,0,-1},{10947,2,1,-1},{10950,2,0,-1},{10951,2,1,-1}, +{10954,2,0,-1},{10955,2,1,-1},{10959,2,0,-1},{10960,2,1,-1},{10963,2,0,-1}, +{10964,2,1,-1},{10967,2,0,-1},{10968,2,1,-1},{10972,2,0,-1},{10973,2,1,-1}, +{10976,2,0,-1},{10977,2,1,-1},{10980,2,0,-1},{10981,2,1,-1},{10985,2,0,-1}, +{10986,2,1,-1},{10989,2,0,-1},{10990,2,1,-1},{10993,2,0,-1},{10994,2,1,-1}, +{10998,2,0,-1},{10999,2,1,-1},{11002,2,0,-1},{11003,2,1,-1},{11006,2,0,-1}, +{11007,2,1,-1},{11011,2,0,-1},{11012,2,1,-1},{11015,2,0,-1},{11016,2,1,-1}, +{11019,2,0,-1},{11020,2,1,-1},{11024,2,0,-1},{11025,2,1,-1},{10958,2,0,-1}, +{11028,2,0,-1},{11029,2,1,-1},{11032,2,0,-1},{11033,2,1,-1},{11037,2,0,-1}, +{11038,2,1,-1},{10971,2,1,-1},{11041,2,0,-1},{11042,2,1,-1},{11045,2,0,-1}, +{11046,2,1,-1},{11050,2,0,-1},{11051,2,1,-1},{11054,2,0,-1},{11055,2,1,-1}, +{11058,2,0,-1},{11059,2,1,-1},{11063,2,0,-1},{11064,2,1,-1},{11067,2,0,-1}, +{11068,2,1,-1},{11071,2,0,-1},{11072,2,1,-1},{11076,2,0,-1},{11077,2,1,-1}, +{11080,2,0,-1},{11081,2,1,-1},{11084,2,0,-1},{11085,2,1,-1},{11089,2,0,-1}, +{11090,2,1,-1},{11093,2,0,-1},{11094,2,1,-1},{11097,2,0,-1},{11098,2,1,-1}, +{11102,2,0,-1},{11103,2,1,-1},{11106,2,0,-1},{11107,2,1,-1},{11110,2,0,-1}, +{11111,2,1,-1},{11115,2,0,-1},{11116,2,1,-1},{11119,2,0,-1},{11120,2,1,-1}, +{11123,2,0,-1},{11124,2,1,-1},{11128,2,0,-1},{11129,2,1,-1},{11062,2,0,-1}, +{11132,2,0,-1},{11133,2,1,-1},{11136,2,0,-1},{11137,2,1,-1},{11141,2,0,-1}, +{11142,2,1,-1},{11075,2,1,-1},{11145,2,0,-1},{11146,2,1,-1},{11149,2,0,-1}, +{11150,2,1,-1},{11154,2,0,-1},{11155,2,1,-1},{11158,2,0,-1},{11159,2,1,-1}, +{11162,2,0,-1},{11163,2,1,-1},{11167,2,0,-1},{11168,2,1,-1},{11171,2,0,-1}, +{11172,2,1,-1},{11175,2,0,-1},{11176,2,1,-1},{11180,2,0,-1},{11181,2,1,-1}, +{11184,2,0,-1},{11185,2,1,-1},{11188,2,0,-1},{11189,2,1,-1},{11193,2,0,-1}, +{11194,2,1,-1},{11197,2,0,-1},{11198,2,1,-1},{11201,2,0,-1},{11202,2,1,-1}, +{11206,2,0,-1},{11207,2,1,-1},{11210,2,0,-1},{11211,2,1,-1},{11214,2,0,-1}, +{11215,2,1,-1},{11219,2,0,-1},{11220,2,1,-1},{11223,2,0,-1},{11224,2,1,-1}, +{11227,2,0,-1},{11228,2,1,-1},{11232,2,0,-1},{11233,2,1,-1},{11166,2,0,-1}, +{11236,2,0,-1},{11237,2,1,-1},{11240,2,0,-1},{11241,2,1,-1},{11245,2,0,-1}, +{11246,2,1,-1},{11179,2,1,-1},{11249,2,0,-1},{11250,2,1,-1},{11253,2,0,-1}, +{11254,2,1,-1},{11258,2,0,-1},{11259,2,1,-1},{11262,2,0,-1},{11263,2,1,-1}, +{11266,2,0,-1},{11267,2,1,-1},{11271,2,0,-1},{11272,2,1,-1},{11275,2,0,-1}, +{11276,2,1,-1},{11279,2,0,-1},{11280,2,1,-1},{11284,2,0,-1},{11285,2,1,-1}, +{11288,2,0,-1},{11289,2,1,-1},{11292,2,0,-1},{11293,2,1,-1},{11297,2,0,-1}, +{11298,2,1,-1},{11301,2,0,-1},{11302,2,1,-1},{11305,2,0,-1},{11306,2,1,-1}, +{11310,2,0,-1},{11311,2,1,-1},{11314,2,0,-1},{11315,2,1,-1},{11318,2,0,-1}, +{11319,2,1,-1},{11323,2,0,-1},{11324,2,1,-1},{11327,2,0,-1},{11328,2,1,-1}, +{11331,2,0,-1},{11332,2,1,-1},{11336,2,0,-1},{11337,2,1,-1},{11270,2,0,-1}, +{11340,2,0,-1},{11341,2,1,-1},{11344,2,0,-1},{11345,2,1,-1},{11349,2,0,-1}, +{11350,2,1,-1},{11283,2,1,-1},{11353,2,0,-1},{11354,2,1,-1},{11357,2,0,-1}, +{11358,2,1,-1},{11362,2,0,-1},{11363,2,1,-1},{11366,2,0,-1},{11367,2,1,-1}, +{11370,2,0,-1},{11371,2,1,-1},{11375,2,0,-1},{11376,2,1,-1},{11379,2,0,-1}, +{11380,2,1,-1},{11383,2,0,-1},{11384,2,1,-1},{11388,2,0,-1},{11389,2,1,-1}, +{11392,2,0,-1},{11393,2,1,-1},{11396,2,0,-1},{11397,2,1,-1},{11401,2,0,-1}, +{11402,2,1,-1},{11405,2,0,-1},{11406,2,1,-1},{11409,2,0,-1},{11410,2,1,-1}, +{11414,2,0,-1},{11415,2,1,-1},{11418,2,0,-1},{11419,2,1,-1},{11422,2,0,-1}, +{11423,2,1,-1},{11427,2,0,-1},{11428,2,1,-1},{11431,2,0,-1},{11432,2,1,-1}, +{11435,2,0,-1},{11436,2,1,-1},{11440,2,0,-1},{11441,2,1,-1},{11374,2,0,-1}, +{11444,2,0,-1},{11445,2,1,-1},{11448,2,0,-1},{11449,2,1,-1},{11453,2,0,-1}, +{11454,2,1,-1},{11387,2,1,-1},{11457,2,0,-1},{11458,2,1,-1},{11461,2,0,-1}, +{11462,2,1,-1},{11466,2,0,-1},{11467,2,1,-1},{11470,2,0,-1},{11471,2,1,-1}, +{11474,2,0,-1},{11475,2,1,-1},{11479,2,0,-1},{11480,2,1,-1},{11483,2,0,-1}, +{11484,2,1,-1},{11487,2,0,-1},{11488,2,1,-1},{11492,2,0,-1},{11493,2,1,-1}, +{11496,2,0,-1},{11497,2,1,-1},{11500,2,0,-1},{11501,2,1,-1},{11505,2,0,-1}, +{11506,2,1,-1},{11509,2,0,-1},{11510,2,1,-1},{11513,2,0,-1},{11514,2,1,-1}, +{11518,2,0,-1},{11519,2,1,-1},{11522,2,0,-1},{11523,2,1,-1},{11526,2,0,-1}, +{11527,2,1,-1},{11531,2,0,-1},{11532,2,1,-1},{11535,2,0,-1},{11536,2,1,-1}, +{11539,2,0,-1},{11540,2,1,-1},{11544,2,0,-1},{11545,2,1,-1},{11478,2,0,-1}, +{11548,2,0,-1},{11549,2,1,-1},{11552,2,0,-1},{11553,2,1,-1},{11557,2,0,-1}, +{11558,2,1,-1},{11491,2,1,-1},{11561,2,0,-1},{11562,2,1,-1},{11565,2,0,-1}, +{11566,2,1,-1},{11570,2,0,-1},{11571,2,1,-1},{11574,2,0,-1},{11575,2,1,-1}, +{11578,2,0,-1},{11579,2,1,-1},{11583,2,0,-1},{11584,2,1,-1},{11587,2,0,-1}, +{11588,2,1,-1},{11591,2,0,-1},{11596,2,0,-1},{11597,2,1,-1},{11600,2,0,-1}, +{11601,2,1,-1},{11604,2,0,-1},{11605,2,1,-1},{11609,2,0,-1},{11610,2,1,-1}, +{11613,2,0,-1},{11614,2,1,-1},{11617,2,0,-1},{11618,2,1,-1},{11622,2,0,-1}, +{11623,2,1,-1},{11626,2,0,-1},{11627,2,1,-1},{11630,2,0,-1},{11631,2,1,-1}, +{11635,2,0,-1},{11636,2,1,-1},{11639,2,0,-1},{11640,2,1,-1},{11648,2,0,-1}, +{11649,2,1,-1},{11582,2,0,-1},{11652,2,0,-1},{11653,2,1,-1},{11656,2,0,-1}, +{11657,2,1,-1},{11661,2,0,-1},{11662,2,1,-1},{11665,2,0,-1},{11666,2,1,-1}, +{11669,2,0,-1},{11670,2,1,-1},{11674,2,0,-1},{11675,2,1,-1},{11678,2,0,-1}, +{11679,2,1,-1},{11682,2,0,-1},{11683,2,1,-1},{11687,2,0,-1},{11688,2,1,-1}, +{11691,2,0,-1},{11692,2,1,-1},{11700,2,0,-1},{11704,2,0,-1},{11708,2,0,-1}, +{11713,2,0,-1},{11717,2,0,-1},{11721,2,0,-1},{11726,2,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS3, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_3_ss0_ss1[] = { +{10001,3,0,-1},{10002,3,1,-1},{10005,3,0,-1},{10006,3,1,-1},{10010,3,0,-1}, +{10011,3,1,-1},{10014,3,0,-1},{10015,3,1,-1},{10018,3,0,-1},{10019,3,1,-1}, +{10023,3,0,-1},{10024,3,1,-1},{10027,3,0,-1},{10028,3,1,-1},{10031,3,0,-1}, +{10032,3,1,-1},{10036,3,0,-1},{10037,3,1,-1},{10040,3,0,-1},{10041,3,1,-1}, +{10044,3,0,-1},{10045,3,1,-1},{10049,3,0,-1},{10050,3,1,-1},{10053,3,0,-1}, +{10054,3,1,-1},{10057,3,0,-1},{10058,3,1,-1},{10062,3,0,-1},{10063,3,1,-1}, +{10066,3,0,-1},{10067,3,1,-1},{10070,3,0,-1},{10071,3,1,-1},{10075,3,0,-1}, +{10076,3,1,-1},{10079,3,0,-1},{10080,3,1,-1},{10083,3,0,-1},{10084,3,1,-1}, +{10088,3,0,-1},{10089,3,1,-1},{10022,3,0,-1},{10092,3,0,-1},{10093,3,1,-1}, +{10096,3,0,-1},{10097,3,1,-1},{10101,3,0,-1},{10102,3,1,-1},{10035,3,1,-1}, +{10105,3,0,-1},{10106,3,1,-1},{10109,3,0,-1},{10110,3,1,-1},{10114,3,0,-1}, +{10115,3,1,-1},{10118,3,0,-1},{10119,3,1,-1},{10122,3,0,-1},{10123,3,1,-1}, +{10127,3,0,-1},{10128,3,1,-1},{10131,3,0,-1},{10132,3,1,-1},{10135,3,0,-1}, +{10136,3,1,-1},{10140,3,0,-1},{10141,3,1,-1},{10144,3,0,-1},{10145,3,1,-1}, +{10148,3,0,-1},{10149,3,1,-1},{10153,3,0,-1},{10154,3,1,-1},{10157,3,0,-1}, +{10158,3,1,-1},{10161,3,0,-1},{10162,3,1,-1},{10166,3,0,-1},{10167,3,1,-1}, +{10170,3,0,-1},{10171,3,1,-1},{10174,3,0,-1},{10175,3,1,-1},{10179,3,0,-1}, +{10180,3,1,-1},{10183,3,0,-1},{10184,3,1,-1},{10187,3,0,-1},{10188,3,1,-1}, +{10192,3,0,-1},{10193,3,1,-1},{10126,3,0,-1},{10196,3,0,-1},{10197,3,1,-1}, +{10200,3,0,-1},{10201,3,1,-1},{10205,3,0,-1},{10206,3,1,-1},{10139,3,1,-1}, +{10209,3,0,-1},{10210,3,1,-1},{10213,3,0,-1},{10214,3,1,-1},{10218,3,0,-1}, +{10219,3,1,-1},{10222,3,0,-1},{10223,3,1,-1},{10226,3,0,-1},{10227,3,1,-1}, +{10231,3,0,-1},{10232,3,1,-1},{10235,3,0,-1},{10236,3,1,-1},{10239,3,0,-1}, +{10240,3,1,-1},{10244,3,0,-1},{10245,3,1,-1},{10248,3,0,-1},{10249,3,1,-1}, +{10252,3,0,-1},{10253,3,1,-1},{10257,3,0,-1},{10258,3,1,-1},{10261,3,0,-1}, +{10262,3,1,-1},{10265,3,0,-1},{10266,3,1,-1},{10270,3,0,-1},{10271,3,1,-1}, +{10274,3,0,-1},{10275,3,1,-1},{10278,3,0,-1},{10279,3,1,-1},{10283,3,0,-1}, +{10284,3,1,-1},{10287,3,0,-1},{10288,3,1,-1},{10291,3,0,-1},{10292,3,1,-1}, +{10296,3,0,-1},{10297,3,1,-1},{10230,3,0,-1},{10300,3,0,-1},{10301,3,1,-1}, +{10304,3,0,-1},{10305,3,1,-1},{10309,3,0,-1},{10310,3,1,-1},{10243,3,1,-1}, +{10313,3,0,-1},{10314,3,1,-1},{10317,3,0,-1},{10318,3,1,-1},{10322,3,0,-1}, +{10323,3,1,-1},{10326,3,0,-1},{10327,3,1,-1},{10330,3,0,-1},{10331,3,1,-1}, +{10335,3,0,-1},{10336,3,1,-1},{10339,3,0,-1},{10340,3,1,-1},{10343,3,0,-1}, +{10344,3,1,-1},{10348,3,0,-1},{10349,3,1,-1},{10352,3,0,-1},{10353,3,1,-1}, +{10356,3,0,-1},{10357,3,1,-1},{10361,3,0,-1},{10362,3,1,-1},{10365,3,0,-1}, +{10366,3,1,-1},{10369,3,0,-1},{10370,3,1,-1},{10374,3,0,-1},{10375,3,1,-1}, +{10378,3,0,-1},{10379,3,1,-1},{10382,3,0,-1},{10383,3,1,-1},{10387,3,0,-1}, +{10388,3,1,-1},{10391,3,0,-1},{10392,3,1,-1},{10395,3,0,-1},{10396,3,1,-1}, +{10400,3,0,-1},{10401,3,1,-1},{10334,3,0,-1},{10404,3,0,-1},{10405,3,1,-1}, +{10408,3,0,-1},{10409,3,1,-1},{10413,3,0,-1},{10414,3,1,-1},{10347,3,1,-1}, +{10417,3,0,-1},{10418,3,1,-1},{10421,3,0,-1},{10422,3,1,-1},{10426,3,0,-1}, +{10427,3,1,-1},{10430,3,0,-1},{10431,3,1,-1},{10434,3,0,-1},{10435,3,1,-1}, +{10439,3,0,-1},{10440,3,1,-1},{10443,3,0,-1},{10444,3,1,-1},{10447,3,0,-1}, +{10448,3,1,-1},{10452,3,0,-1},{10453,3,1,-1},{10456,3,0,-1},{10457,3,1,-1}, +{10460,3,0,-1},{10461,3,1,-1},{10465,3,0,-1},{10466,3,1,-1},{10469,3,0,-1}, +{10470,3,1,-1},{10473,3,0,-1},{10474,3,1,-1},{10478,3,0,-1},{10479,3,1,-1}, +{10482,3,0,-1},{10483,3,1,-1},{10486,3,0,-1},{10487,3,1,-1},{10491,3,0,-1}, +{10492,3,1,-1},{10495,3,0,-1},{10496,3,1,-1},{10499,3,0,-1},{10500,3,1,-1}, +{10504,3,0,-1},{10505,3,1,-1},{10438,3,0,-1},{10508,3,0,-1},{10509,3,1,-1}, +{10512,3,0,-1},{10513,3,1,-1},{10517,3,0,-1},{10518,3,1,-1},{10451,3,1,-1}, +{10521,3,0,-1},{10522,3,1,-1},{10525,3,0,-1},{10526,3,1,-1},{10530,3,0,-1}, +{10531,3,1,-1},{10534,3,0,-1},{10535,3,1,-1},{10538,3,0,-1},{10539,3,1,-1}, +{10543,3,0,-1},{10544,3,1,-1},{10547,3,0,-1},{10548,3,1,-1},{10551,3,0,-1}, +{10552,3,1,-1},{10556,3,0,-1},{10557,3,1,-1},{10560,3,0,-1},{10561,3,1,-1}, +{10564,3,0,-1},{10565,3,1,-1},{10569,3,0,-1},{10570,3,1,-1},{10573,3,0,-1}, +{10574,3,1,-1},{10577,3,0,-1},{10578,3,1,-1},{10582,3,0,-1},{10583,3,1,-1}, +{10586,3,0,-1},{10587,3,1,-1},{10590,3,0,-1},{10591,3,1,-1},{10595,3,0,-1}, +{10596,3,1,-1},{10599,3,0,-1},{10600,3,1,-1},{10603,3,0,-1},{10604,3,1,-1}, +{10608,3,0,-1},{10609,3,1,-1},{10542,3,0,-1},{10612,3,0,-1},{10613,3,1,-1}, +{10616,3,0,-1},{10617,3,1,-1},{10621,3,0,-1},{10622,3,1,-1},{10555,3,1,-1}, +{10625,3,0,-1},{10626,3,1,-1},{10629,3,0,-1},{10630,3,1,-1},{10634,3,0,-1}, +{10635,3,1,-1},{10638,3,0,-1},{10639,3,1,-1},{10642,3,0,-1},{10643,3,1,-1}, +{10647,3,0,-1},{10648,3,1,-1},{10651,3,0,-1},{10652,3,1,-1},{10655,3,0,-1}, +{10656,3,1,-1},{10660,3,0,-1},{10661,3,1,-1},{10664,3,0,-1},{10665,3,1,-1}, +{10668,3,0,-1},{10669,3,1,-1},{10673,3,0,-1},{10674,3,1,-1},{10677,3,0,-1}, +{10678,3,1,-1},{10681,3,0,-1},{10682,3,1,-1},{10686,3,0,-1},{10687,3,1,-1}, +{10690,3,0,-1},{10691,3,1,-1},{10694,3,0,-1},{10695,3,1,-1},{10699,3,0,-1}, +{10700,3,1,-1},{10703,3,0,-1},{10704,3,1,-1},{10707,3,0,-1},{10708,3,1,-1}, +{10712,3,0,-1},{10713,3,1,-1},{10646,3,0,-1},{10716,3,0,-1},{10717,3,1,-1}, +{10720,3,0,-1},{10721,3,1,-1},{10725,3,0,-1},{10726,3,1,-1},{10659,3,1,-1}, +{10729,3,0,-1},{10730,3,1,-1},{10733,3,0,-1},{10734,3,1,-1},{10738,3,0,-1}, +{10739,3,1,-1},{10742,3,0,-1},{10743,3,1,-1},{10746,3,0,-1},{10747,3,1,-1}, +{10751,3,0,-1},{10752,3,1,-1},{10755,3,0,-1},{10756,3,1,-1},{10759,3,0,-1}, +{10760,3,1,-1},{10764,3,0,-1},{10765,3,1,-1},{10768,3,0,-1},{10769,3,1,-1}, +{10772,3,0,-1},{10773,3,1,-1},{10777,3,0,-1},{10778,3,1,-1},{10781,3,0,-1}, +{10782,3,1,-1},{10785,3,0,-1},{10786,3,1,-1},{10790,3,0,-1},{10791,3,1,-1}, +{10794,3,0,-1},{10795,3,1,-1},{10798,3,0,-1},{10799,3,1,-1},{10803,3,0,-1}, +{10804,3,1,-1},{10807,3,0,-1},{10808,3,1,-1},{10811,3,0,-1},{10812,3,1,-1}, +{10816,3,0,-1},{10817,3,1,-1},{10750,3,0,-1},{10820,3,0,-1},{10821,3,1,-1}, +{10824,3,0,-1},{10825,3,1,-1},{10829,3,0,-1},{10830,3,1,-1},{10763,3,1,-1}, +{10833,3,0,-1},{10834,3,1,-1},{10837,3,0,-1},{10838,3,1,-1},{10842,3,0,-1}, +{10843,3,1,-1},{10846,3,0,-1},{10847,3,1,-1},{10850,3,0,-1},{10851,3,1,-1}, +{10855,3,0,-1},{10856,3,1,-1},{10859,3,0,-1},{10860,3,1,-1},{10863,3,0,-1}, +{10864,3,1,-1},{10868,3,0,-1},{10869,3,1,-1},{10872,3,0,-1},{10873,3,1,-1}, +{10876,3,0,-1},{10877,3,1,-1},{10881,3,0,-1},{10882,3,1,-1},{10885,3,0,-1}, +{10886,3,1,-1},{10889,3,0,-1},{10890,3,1,-1},{10894,3,0,-1},{10895,3,1,-1}, +{10898,3,0,-1},{10899,3,1,-1},{10902,3,0,-1},{10903,3,1,-1},{10907,3,0,-1}, +{10908,3,1,-1},{10911,3,0,-1},{10912,3,1,-1},{10915,3,0,-1},{10916,3,1,-1}, +{10920,3,0,-1},{10921,3,1,-1},{10854,3,0,-1},{10924,3,0,-1},{10925,3,1,-1}, +{10928,3,0,-1},{10929,3,1,-1},{10933,3,0,-1},{10934,3,1,-1},{10867,3,1,-1}, +{10937,3,0,-1},{10938,3,1,-1},{10941,3,0,-1},{10942,3,1,-1},{10946,3,0,-1}, +{10947,3,1,-1},{10950,3,0,-1},{10951,3,1,-1},{10954,3,0,-1},{10955,3,1,-1}, +{10959,3,0,-1},{10960,3,1,-1},{10963,3,0,-1},{10964,3,1,-1},{10967,3,0,-1}, +{10968,3,1,-1},{10972,3,0,-1},{10973,3,1,-1},{10976,3,0,-1},{10977,3,1,-1}, +{10980,3,0,-1},{10981,3,1,-1},{10985,3,0,-1},{10986,3,1,-1},{10989,3,0,-1}, +{10990,3,1,-1},{10993,3,0,-1},{10994,3,1,-1},{10998,3,0,-1},{10999,3,1,-1}, +{11002,3,0,-1},{11003,3,1,-1},{11006,3,0,-1},{11007,3,1,-1},{11011,3,0,-1}, +{11012,3,1,-1},{11015,3,0,-1},{11016,3,1,-1},{11019,3,0,-1},{11020,3,1,-1}, +{11024,3,0,-1},{11025,3,1,-1},{10958,3,0,-1},{11028,3,0,-1},{11029,3,1,-1}, +{11032,3,0,-1},{11033,3,1,-1},{11037,3,0,-1},{11038,3,1,-1},{10971,3,1,-1}, +{11041,3,0,-1},{11042,3,1,-1},{11045,3,0,-1},{11046,3,1,-1},{11050,3,0,-1}, +{11051,3,1,-1},{11054,3,0,-1},{11055,3,1,-1},{11058,3,0,-1},{11059,3,1,-1}, +{11063,3,0,-1},{11064,3,1,-1},{11067,3,0,-1},{11068,3,1,-1},{11071,3,0,-1}, +{11072,3,1,-1},{11076,3,0,-1},{11077,3,1,-1},{11080,3,0,-1},{11081,3,1,-1}, +{11084,3,0,-1},{11085,3,1,-1},{11089,3,0,-1},{11090,3,1,-1},{11093,3,0,-1}, +{11094,3,1,-1},{11097,3,0,-1},{11098,3,1,-1},{11102,3,0,-1},{11103,3,1,-1}, +{11106,3,0,-1},{11107,3,1,-1},{11110,3,0,-1},{11111,3,1,-1},{11115,3,0,-1}, +{11116,3,1,-1},{11119,3,0,-1},{11120,3,1,-1},{11123,3,0,-1},{11124,3,1,-1}, +{11128,3,0,-1},{11129,3,1,-1},{11062,3,0,-1},{11132,3,0,-1},{11133,3,1,-1}, +{11136,3,0,-1},{11137,3,1,-1},{11141,3,0,-1},{11142,3,1,-1},{11075,3,1,-1}, +{11145,3,0,-1},{11146,3,1,-1},{11149,3,0,-1},{11150,3,1,-1},{11154,3,0,-1}, +{11155,3,1,-1},{11158,3,0,-1},{11159,3,1,-1},{11162,3,0,-1},{11163,3,1,-1}, +{11167,3,0,-1},{11168,3,1,-1},{11171,3,0,-1},{11172,3,1,-1},{11175,3,0,-1}, +{11176,3,1,-1},{11180,3,0,-1},{11181,3,1,-1},{11184,3,0,-1},{11185,3,1,-1}, +{11188,3,0,-1},{11189,3,1,-1},{11193,3,0,-1},{11194,3,1,-1},{11197,3,0,-1}, +{11198,3,1,-1},{11201,3,0,-1},{11202,3,1,-1},{11206,3,0,-1},{11207,3,1,-1}, +{11210,3,0,-1},{11211,3,1,-1},{11214,3,0,-1},{11215,3,1,-1},{11219,3,0,-1}, +{11220,3,1,-1},{11223,3,0,-1},{11224,3,1,-1},{11227,3,0,-1},{11228,3,1,-1}, +{11232,3,0,-1},{11233,3,1,-1},{11166,3,0,-1},{11236,3,0,-1},{11237,3,1,-1}, +{11240,3,0,-1},{11241,3,1,-1},{11245,3,0,-1},{11246,3,1,-1},{11179,3,1,-1}, +{11249,3,0,-1},{11250,3,1,-1},{11253,3,0,-1},{11254,3,1,-1},{11258,3,0,-1}, +{11259,3,1,-1},{11262,3,0,-1},{11263,3,1,-1},{11266,3,0,-1},{11267,3,1,-1}, +{11271,3,0,-1},{11272,3,1,-1},{11275,3,0,-1},{11276,3,1,-1},{11279,3,0,-1}, +{11280,3,1,-1},{11284,3,0,-1},{11285,3,1,-1},{11288,3,0,-1},{11289,3,1,-1}, +{11292,3,0,-1},{11293,3,1,-1},{11297,3,0,-1},{11298,3,1,-1},{11301,3,0,-1}, +{11302,3,1,-1},{11305,3,0,-1},{11306,3,1,-1},{11310,3,0,-1},{11311,3,1,-1}, +{11314,3,0,-1},{11315,3,1,-1},{11318,3,0,-1},{11319,3,1,-1},{11323,3,0,-1}, +{11324,3,1,-1},{11327,3,0,-1},{11328,3,1,-1},{11331,3,0,-1},{11332,3,1,-1}, +{11336,3,0,-1},{11337,3,1,-1},{11270,3,0,-1},{11340,3,0,-1},{11341,3,1,-1}, +{11344,3,0,-1},{11345,3,1,-1},{11349,3,0,-1},{11350,3,1,-1},{11283,3,1,-1}, +{11353,3,0,-1},{11354,3,1,-1},{11357,3,0,-1},{11358,3,1,-1},{11362,3,0,-1}, +{11363,3,1,-1},{11366,3,0,-1},{11367,3,1,-1},{11370,3,0,-1},{11371,3,1,-1}, +{11375,3,0,-1},{11376,3,1,-1},{11379,3,0,-1},{11380,3,1,-1},{11383,3,0,-1}, +{11384,3,1,-1},{11388,3,0,-1},{11389,3,1,-1},{11392,3,0,-1},{11393,3,1,-1}, +{11396,3,0,-1},{11397,3,1,-1},{11401,3,0,-1},{11402,3,1,-1},{11405,3,0,-1}, +{11406,3,1,-1},{11409,3,0,-1},{11410,3,1,-1},{11414,3,0,-1},{11415,3,1,-1}, +{11418,3,0,-1},{11419,3,1,-1},{11422,3,0,-1},{11423,3,1,-1},{11427,3,0,-1}, +{11428,3,1,-1},{11431,3,0,-1},{11432,3,1,-1},{11435,3,0,-1},{11436,3,1,-1}, +{11440,3,0,-1},{11441,3,1,-1},{11374,3,0,-1},{11444,3,0,-1},{11445,3,1,-1}, +{11448,3,0,-1},{11449,3,1,-1},{11453,3,0,-1},{11454,3,1,-1},{11387,3,1,-1}, +{11457,3,0,-1},{11458,3,1,-1},{11461,3,0,-1},{11462,3,1,-1},{11466,3,0,-1}, +{11467,3,1,-1},{11470,3,0,-1},{11471,3,1,-1},{11474,3,0,-1},{11475,3,1,-1}, +{11479,3,0,-1},{11480,3,1,-1},{11483,3,0,-1},{11484,3,1,-1},{11487,3,0,-1}, +{11488,3,1,-1},{11492,3,0,-1},{11493,3,1,-1},{11496,3,0,-1},{11497,3,1,-1}, +{11500,3,0,-1},{11501,3,1,-1},{11505,3,0,-1},{11506,3,1,-1},{11509,3,0,-1}, +{11510,3,1,-1},{11513,3,0,-1},{11514,3,1,-1},{11518,3,0,-1},{11519,3,1,-1}, +{11522,3,0,-1},{11523,3,1,-1},{11526,3,0,-1},{11527,3,1,-1},{11531,3,0,-1}, +{11532,3,1,-1},{11535,3,0,-1},{11536,3,1,-1},{11539,3,0,-1},{11540,3,1,-1}, +{11544,3,0,-1},{11545,3,1,-1},{11478,3,0,-1},{11548,3,0,-1},{11549,3,1,-1}, +{11552,3,0,-1},{11553,3,1,-1},{11557,3,0,-1},{11558,3,1,-1},{11491,3,1,-1}, +{11561,3,0,-1},{11562,3,1,-1},{11565,3,0,-1},{11566,3,1,-1},{11570,3,0,-1}, +{11571,3,1,-1},{11574,3,0,-1},{11575,3,1,-1},{11578,3,0,-1},{11579,3,1,-1}, +{11583,3,0,-1},{11584,3,1,-1},{11587,3,0,-1},{11588,3,1,-1},{11591,3,0,-1}, +{11592,3,1,-1},{11596,3,0,-1},{11597,3,1,-1},{11600,3,0,-1},{11601,3,1,-1}, +{11604,3,0,-1},{11605,3,1,-1},{11609,3,0,-1},{11610,3,1,-1},{11613,3,0,-1}, +{11614,3,1,-1},{11617,3,0,-1},{11618,3,1,-1},{11622,3,0,-1},{11623,3,1,-1}, +{11626,3,0,-1},{11627,3,1,-1},{11630,3,0,-1},{11631,3,1,-1},{11635,3,0,-1}, +{11636,3,1,-1},{11639,3,0,-1},{11640,3,1,-1},{11643,3,0,-1},{11644,3,1,-1}, +{11648,3,0,-1},{11649,3,1,-1},{11582,3,0,-1},{11652,3,0,-1},{11653,3,1,-1}, +{11656,3,0,-1},{11657,3,1,-1},{11661,3,0,-1},{11662,3,1,-1},{11595,3,1,-1}, +{11665,3,0,-1},{11666,3,1,-1},{11669,3,0,-1},{11670,3,1,-1},{11674,3,0,-1}, +{11675,3,1,-1},{11678,3,0,-1},{11679,3,1,-1},{11682,3,0,-1},{11683,3,1,-1}, +{11687,3,0,-1},{11688,3,1,-1},{11691,3,0,-1},{11692,3,1,-1},{11695,3,0,-1}, +{11696,3,1,-1},{11700,3,0,-1},{11701,3,1,-1},{11704,3,0,-1},{11705,3,1,-1}, +{11708,3,0,-1},{11709,3,1,-1},{11713,3,0,-1},{11714,3,1,-1},{11717,3,0,-1}, +{11718,3,1,-1},{11721,3,0,-1},{11722,3,1,-1},{11726,3,0,-1},{11727,3,1,-1}, +{11730,3,0,-1},{11731,3,1,-1},{11734,3,0,-1},{11735,3,1,-1},{11739,3,0,-1}, +{11740,3,1,-1},{11743,3,0,-1},{11744,3,1,-1},{11747,3,0,-1},{11748,3,1,-1}, +{11752,3,0,-1},{11753,3,1,-1},{11686,3,0,-1},{11756,3,0,-1},{11757,3,1,-1}, +{11760,3,0,-1},{11761,3,1,-1},{11765,3,0,-1},{11766,3,1,-1},{11699,3,1,-1}, +{11769,3,0,-1},{11770,3,1,-1},{11773,3,0,-1},{11774,3,1,-1},{11778,3,0,-1}, +{11779,3,1,-1},{11782,3,0,-1},{11783,3,1,-1},{11786,3,0,-1},{11787,3,1,-1}, +{11791,3,0,-1},{11792,3,1,-1},{11795,3,0,-1},{11796,3,1,-1},{11799,3,0,-1}, +{11800,3,1,-1},{11804,3,0,-1},{11805,3,1,-1},{11808,3,0,-1},{11809,3,1,-1}, +{11812,3,0,-1},{11813,3,1,-1},{11817,3,0,-1},{11818,3,1,-1},{11821,3,0,-1}, +{11822,3,1,-1},{11825,3,0,-1},{11826,3,1,-1},{11830,3,0,-1},{11831,3,1,-1}, +{11834,3,0,-1},{11835,3,1,-1},{11838,3,0,-1},{11839,3,1,-1},{11843,3,0,-1}, +{11844,3,1,-1},{11847,3,0,-1},{11848,3,1,-1},{11851,3,0,-1},{11852,3,1,-1}, +{11856,3,0,-1},{11857,3,1,-1},{11790,3,0,-1},{11860,3,0,-1},{11861,3,1,-1}, +{11864,3,0,-1},{11865,3,1,-1},{11869,3,0,-1},{11870,3,1,-1},{11803,3,1,-1}, +{11873,3,0,-1},{11874,3,1,-1},{11877,3,0,-1},{11878,3,1,-1},{11882,3,0,-1}, +{11883,3,1,-1},{11886,3,0,-1},{11887,3,1,-1},{11890,3,0,-1},{11891,3,1,-1}, +{11895,3,0,-1},{11896,3,1,-1},{11899,3,0,-1},{11900,3,1,-1},{11903,3,0,-1}, +{11904,3,1,-1},{11908,3,0,-1},{11909,3,1,-1},{11912,3,0,-1},{11913,3,1,-1}, +{11916,3,0,-1},{11917,3,1,-1},{11921,3,0,-1},{11922,3,1,-1},{11925,3,0,-1}, +{11926,3,1,-1},{11929,3,0,-1},{11930,3,1,-1},{11934,3,0,-1},{11935,3,1,-1}, +{11938,3,0,-1},{11939,3,1,-1},{11942,3,0,-1},{11943,3,1,-1},{11947,3,0,-1}, +{11948,3,1,-1},{11951,3,0,-1},{11952,3,1,-1},{11955,3,0,-1},{11956,3,1,-1}, +{11960,3,0,-1},{11961,3,1,-1},{11894,3,0,-1},{11964,3,0,-1},{11965,3,1,-1}, +{11968,3,0,-1},{11969,3,1,-1},{11973,3,0,-1},{11974,3,1,-1},{11907,3,1,-1}, +{11977,3,0,-1},{11978,3,1,-1},{11981,3,0,-1},{11982,3,1,-1},{11986,3,0,-1}, +{11987,3,1,-1},{11990,3,0,-1},{11991,3,1,-1},{11994,3,0,-1},{11995,3,1,-1}, +{11999,3,0,-1},{12000,3,1,-1},{12003,3,0,-1},{12004,3,1,-1},{12007,3,0,-1}, +{12008,3,1,-1},{12012,3,0,-1},{12013,3,1,-1},{12016,3,0,-1},{12017,3,1,-1}, +{12020,3,0,-1},{12021,3,1,-1},{12025,3,0,-1},{12026,3,1,-1},{12029,3,0,-1}, +{12030,3,1,-1},{12033,3,0,-1},{12034,3,1,-1},{12038,3,0,-1},{12039,3,1,-1}, +{12042,3,0,-1},{12043,3,1,-1},{12046,3,0,-1},{12047,3,1,-1},{12051,3,0,-1}, +{12052,3,1,-1},{12055,3,0,-1},{12056,3,1,-1},{12059,3,0,-1},{12060,3,1,-1}, +{12064,3,0,-1},{12065,3,1,-1},{11998,3,0,-1},{12068,3,0,-1},{12069,3,1,-1}, +{12072,3,0,-1},{12073,3,1,-1},{12077,3,0,-1},{12078,3,1,-1},{12011,3,1,-1}, +{12081,3,0,-1},{12082,3,1,-1},{12085,3,0,-1},{12086,3,1,-1},{12090,3,0,-1}, +{12091,3,1,-1},{12094,3,0,-1},{12095,3,1,-1},{12098,3,0,-1},{12099,3,1,-1}, +{12103,3,0,-1},{12104,3,1,-1},{12107,3,0,-1},{12108,3,1,-1},{12111,3,0,-1}, +{12112,3,1,-1},{12116,3,0,-1},{12117,3,1,-1},{12120,3,0,-1},{12121,3,1,-1}, +{12124,3,0,-1},{12125,3,1,-1},{12129,3,0,-1},{12130,3,1,-1},{12133,3,0,-1}, +{12134,3,1,-1},{12137,3,0,-1},{12138,3,1,-1},{12142,3,0,-1},{12143,3,1,-1}, +{12146,3,0,-1},{12147,3,1,-1},{12150,3,0,-1},{12151,3,1,-1},{12155,3,0,-1}, +{12156,3,1,-1},{12159,3,0,-1},{12160,3,1,-1},{12164,3,1,-1},{12168,3,0,-1}, +{12169,3,1,-1},{12102,3,0,-1},{12172,3,0,-1},{12173,3,1,-1},{12176,3,0,-1}, +{12177,3,1,-1},{12181,3,0,-1},{12182,3,1,-1},{12115,3,1,-1},{12185,3,0,-1}, +{12186,3,1,-1},{12189,3,0,-1},{12190,3,1,-1},{12194,3,0,-1},{12195,3,1,-1}, +{12198,3,0,-1},{12199,3,1,-1},{12202,3,0,-1},{12203,3,1,-1},{12207,3,0,-1}, +{12208,3,1,-1},{12211,3,0,-1},{12212,3,1,-1},{12216,3,1,-1},{12220,3,0,-1}, +{12221,3,1,-1},{12224,3,0,-1},{12225,3,1,-1},{12228,3,0,-1},{12229,3,1,-1}, +{12233,3,0,-1},{12234,3,1,-1},{12237,3,0,-1},{12238,3,1,-1},{12241,3,0,-1}, +{12242,3,1,-1},{12246,3,0,-1},{12247,3,1,-1},{12250,3,0,-1},{12251,3,1,-1}, +{12254,3,0,-1},{12255,3,1,-1},{12260,3,1,-1},{12264,3,1,-1},{12268,3,1,-1}, +{12273,3,1,-1},{12281,3,1,-1},{12286,3,1,-1},{12290,3,1,-1},{12294,3,1,-1}, +{12299,3,1,-1},{12303,3,1,-1},{12307,3,1,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS4, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_4_ss0_ss1[] = { +{7704,4,0,-1},{7713,4,0,-1},{7722,4,0,-1},{7730,4,0,-1},{7739,4,0,-1}, +{7748,4,0,-1},{7756,4,0,-1},{7765,4,0,-1},{7774,4,0,-1},{7782,4,0,-1}, +{7791,4,0,-1},{7800,4,0,-1},{7808,4,0,-1},{7817,4,0,-1},{7826,4,0,-1}, +{7760,4,0,-1},{7834,4,0,-1},{7843,4,0,-1},{7852,4,0,-1},{7860,4,0,-1}, +{7865,4,0,-1},{7869,4,0,-1},{7873,4,0,-1},{7878,4,0,-1},{7882,4,0,-1}, +{7886,4,0,-1},{7891,4,0,-1},{7895,4,0,-1},{7899,4,0,-1},{7904,4,0,-1}, +{7908,4,0,-1},{7912,4,0,-1},{7917,4,0,-1},{7921,4,0,-1},{7925,4,0,-1}, +{7930,4,0,-1},{7864,4,0,-1},{7934,4,0,-1},{7938,4,0,-1},{7943,4,0,-1}, +{7947,4,0,-1},{7951,4,0,-1},{7956,4,0,-1},{7960,4,0,-1},{7964,4,0,-1}, +{7969,4,0,-1},{7973,4,0,-1},{7977,4,0,-1},{7982,4,0,-1},{7986,4,0,-1}, +{7990,4,0,-1},{7995,4,0,-1},{7999,4,0,-1},{8003,4,0,-1},{8008,4,0,-1}, +{8012,4,0,-1},{8016,4,0,-1},{8021,4,0,-1},{8025,4,0,-1},{8029,4,0,-1}, +{8034,4,0,-1},{7968,4,0,-1},{8038,4,0,-1},{8042,4,0,-1},{8047,4,0,-1}, +{8051,4,0,-1},{8055,4,0,-1},{8060,4,0,-1},{8064,4,0,-1},{8068,4,0,-1}, +{8073,4,0,-1},{8077,4,0,-1},{8081,4,0,-1},{8086,4,0,-1},{8090,4,0,-1}, +{8094,4,0,-1},{8099,4,0,-1},{8103,4,0,-1},{8107,4,0,-1},{8112,4,0,-1}, +{8116,4,0,-1},{8120,4,0,-1},{8121,4,1,-1},{8125,4,0,-1},{8129,4,0,-1}, +{8130,4,1,-1},{8133,4,0,-1},{8138,4,0,-1},{8139,4,1,-1},{8072,4,0,-1}, +{8142,4,0,-1},{8146,4,0,-1},{8147,4,1,-1},{8151,4,0,-1},{8155,4,0,-1}, +{8156,4,1,-1},{8159,4,0,-1},{8164,4,0,-1},{8165,4,1,-1},{8168,4,0,-1}, +{8172,4,0,-1},{8173,4,1,-1},{8177,4,0,-1},{8181,4,0,-1},{8182,4,1,-1}, +{8185,4,0,-1},{8190,4,0,-1},{8191,4,1,-1},{8194,4,0,-1},{8198,4,0,-1}, +{8199,4,1,-1},{8203,4,0,-1},{8207,4,0,-1},{8208,4,1,-1},{8211,4,0,-1}, +{8216,4,0,-1},{8217,4,1,-1},{8220,4,0,-1},{8224,4,0,-1},{8225,4,1,-1}, +{8229,4,0,-1},{8230,4,1,-1},{8233,4,0,-1},{8234,4,1,-1},{8237,4,0,-1}, +{8238,4,1,-1},{8242,4,0,-1},{8243,4,1,-1},{8176,4,0,-1},{8246,4,0,-1}, +{8247,4,1,-1},{8250,4,0,-1},{8251,4,1,-1},{8255,4,0,-1},{8189,4,1,-1}, +{8259,4,0,-1},{8260,4,1,-1},{8263,4,0,-1},{8264,4,1,-1},{8268,4,0,-1}, +{8269,4,1,-1},{8272,4,0,-1},{8273,4,1,-1},{8276,4,0,-1},{8277,4,1,-1}, +{8281,4,0,-1},{8282,4,1,-1},{8285,4,0,-1},{8286,4,1,-1},{8289,4,0,-1}, +{8290,4,1,-1},{8294,4,0,-1},{8295,4,1,-1},{8299,4,1,-1},{8302,4,0,-1}, +{8303,4,1,-1},{8307,4,0,-1},{8308,4,1,-1},{8311,4,0,-1},{8312,4,1,-1}, +{8315,4,0,-1},{8316,4,1,-1},{8320,4,0,-1},{8321,4,1,-1},{8324,4,0,-1}, +{8325,4,1,-1},{8328,4,0,-1},{8329,4,1,-1},{8333,4,0,-1},{8334,4,1,-1}, +{8337,4,0,-1},{8338,4,1,-1},{8341,4,0,-1},{8342,4,1,-1},{8346,4,0,-1}, +{8347,4,1,-1},{8280,4,0,-1},{8350,4,0,-1},{8351,4,1,-1},{8354,4,0,-1}, +{8355,4,1,-1},{8359,4,0,-1},{8360,4,1,-1},{8293,4,1,-1},{8363,4,0,-1}, +{8364,4,1,-1},{8367,4,0,-1},{8368,4,1,-1},{8372,4,0,-1},{8373,4,1,-1}, +{8376,4,0,-1},{8377,4,1,-1},{8380,4,0,-1},{8381,4,1,-1},{8385,4,0,-1}, +{8386,4,1,-1},{8389,4,0,-1},{8390,4,1,-1},{8393,4,0,-1},{8394,4,1,-1}, +{8398,4,0,-1},{8399,4,1,-1},{8402,4,0,-1},{8403,4,1,-1},{8406,4,0,-1}, +{8407,4,1,-1},{8411,4,0,-1},{8412,4,1,-1},{8415,4,0,-1},{8416,4,1,-1}, +{8419,4,0,-1},{8420,4,1,-1},{8424,4,0,-1},{8425,4,1,-1},{8428,4,0,-1}, +{8429,4,1,-1},{8432,4,0,-1},{8433,4,1,-1},{8437,4,0,-1},{8438,4,1,-1}, +{8441,4,0,-1},{8442,4,1,-1},{8445,4,0,-1},{8446,4,1,-1},{8450,4,0,-1}, +{8451,4,1,-1},{8384,4,0,-1},{8454,4,0,-1},{8455,4,1,-1},{8458,4,0,-1}, +{8459,4,1,-1},{8463,4,0,-1},{8397,4,1,-1},{8467,4,0,-1},{8468,4,1,-1}, +{8471,4,0,-1},{8472,4,1,-1},{8476,4,0,-1},{8477,4,1,-1},{8480,4,0,-1}, +{8481,4,1,-1},{8484,4,0,-1},{8485,4,1,-1},{8489,4,0,-1},{8490,4,1,-1}, +{8493,4,0,-1},{8494,4,1,-1},{8497,4,0,-1},{8498,4,1,-1},{8502,4,0,-1}, +{8503,4,1,-1},{8507,4,1,-1},{8510,4,0,-1},{8511,4,1,-1},{8515,4,0,-1}, +{8519,4,0,-1},{8520,4,1,-1},{8523,4,0,-1},{8524,4,1,-1},{8528,4,0,-1}, +{8529,4,1,-1},{8532,4,0,-1},{8533,4,1,-1},{8536,4,0,-1},{8537,4,1,-1}, +{8541,4,0,-1},{8542,4,1,-1},{8545,4,0,-1},{8546,4,1,-1},{8549,4,0,-1}, +{8550,4,1,-1},{8554,4,0,-1},{8555,4,1,-1},{8488,4,0,-1},{8558,4,0,-1}, +{8559,4,1,-1},{8562,4,0,-1},{8563,4,1,-1},{8567,4,0,-1},{8568,4,1,-1}, +{8501,4,1,-1},{8571,4,0,-1},{8572,4,1,-1},{8575,4,0,-1},{8576,4,1,-1}, +{8580,4,0,-1},{8581,4,1,-1},{8584,4,0,-1},{8585,4,1,-1},{8588,4,0,-1}, +{8589,4,1,-1},{8593,4,0,-1},{8594,4,1,-1},{8597,4,0,-1},{8598,4,1,-1}, +{8601,4,0,-1},{8602,4,1,-1},{8606,4,0,-1},{8607,4,1,-1},{8610,4,0,-1}, +{8611,4,1,-1},{8614,4,0,-1},{8615,4,1,-1},{8619,4,0,-1},{8620,4,1,-1}, +{8623,4,0,-1},{8624,4,1,-1},{8627,4,0,-1},{8628,4,1,-1},{8632,4,0,-1}, +{8633,4,1,-1},{8636,4,0,-1},{8637,4,1,-1},{8640,4,0,-1},{8641,4,1,-1}, +{8645,4,0,-1},{8646,4,1,-1},{8649,4,0,-1},{8650,4,1,-1},{8653,4,0,-1}, +{8654,4,1,-1},{8658,4,0,-1},{8659,4,1,-1},{8592,4,0,-1},{8662,4,0,-1}, +{8663,4,1,-1},{8666,4,0,-1},{8667,4,1,-1},{8671,4,0,-1},{8672,4,1,-1}, +{8605,4,1,-1},{8675,4,0,-1},{8676,4,1,-1},{8679,4,0,-1},{8680,4,1,-1}, +{8684,4,0,-1},{8685,4,1,-1},{8688,4,0,-1},{8689,4,1,-1},{8692,4,0,-1}, +{8693,4,1,-1},{8697,4,0,-1},{8698,4,1,-1},{8701,4,0,-1},{8702,4,1,-1}, +{8705,4,0,-1},{8706,4,1,-1},{8710,4,0,-1},{8711,4,1,-1},{8714,4,0,-1}, +{8715,4,1,-1},{8718,4,0,-1},{8719,4,1,-1},{8723,4,0,-1},{8724,4,1,-1}, +{8727,4,0,-1},{8728,4,1,-1},{8731,4,0,-1},{8732,4,1,-1},{8736,4,0,-1}, +{8737,4,1,-1},{8740,4,0,-1},{8741,4,1,-1},{8744,4,0,-1},{8745,4,1,-1}, +{8749,4,0,-1},{8750,4,1,-1},{8753,4,0,-1},{8754,4,1,-1},{8757,4,0,-1}, +{8758,4,1,-1},{8762,4,0,-1},{8763,4,1,-1},{8696,4,0,-1},{8766,4,0,-1}, +{8767,4,1,-1},{8770,4,0,-1},{8771,4,1,-1},{8775,4,0,-1},{8776,4,1,-1}, +{8709,4,1,-1},{8779,4,0,-1},{8780,4,1,-1},{8783,4,0,-1},{8784,4,1,-1}, +{8788,4,0,-1},{8789,4,1,-1},{8792,4,0,-1},{8793,4,1,-1},{8796,4,0,-1}, +{8797,4,1,-1},{8801,4,0,-1},{8802,4,1,-1},{8805,4,0,-1},{8806,4,1,-1}, +{8809,4,0,-1},{8810,4,1,-1},{8814,4,0,-1},{8815,4,1,-1},{8818,4,0,-1}, +{8819,4,1,-1},{8822,4,0,-1},{8823,4,1,-1},{8827,4,0,-1},{8828,4,1,-1}, +{8831,4,0,-1},{8832,4,1,-1},{8835,4,0,-1},{8836,4,1,-1},{8840,4,0,-1}, +{8841,4,1,-1},{8844,4,0,-1},{8845,4,1,-1},{8848,4,0,-1},{8849,4,1,-1}, +{8853,4,0,-1},{8854,4,1,-1},{8857,4,0,-1},{8858,4,1,-1},{8861,4,0,-1}, +{8862,4,1,-1},{8866,4,0,-1},{8867,4,1,-1},{8800,4,0,-1},{8870,4,0,-1}, +{8871,4,1,-1},{8874,4,0,-1},{8875,4,1,-1},{8879,4,0,-1},{8880,4,1,-1}, +{8813,4,1,-1},{8883,4,0,-1},{8884,4,1,-1},{8887,4,0,-1},{8888,4,1,-1}, +{8892,4,0,-1},{8893,4,1,-1},{8896,4,0,-1},{8897,4,1,-1},{8900,4,0,-1}, +{8901,4,1,-1},{8905,4,0,-1},{8906,4,1,-1},{8909,4,0,-1},{8910,4,1,-1}, +{8913,4,0,-1},{8914,4,1,-1},{8918,4,0,-1},{8919,4,1,-1},{8922,4,0,-1}, +{8923,4,1,-1},{8926,4,0,-1},{8927,4,1,-1},{8931,4,0,-1},{8932,4,1,-1}, +{8935,4,0,-1},{8936,4,1,-1},{8939,4,0,-1},{8940,4,1,-1},{8944,4,0,-1}, +{8945,4,1,-1},{8948,4,0,-1},{8949,4,1,-1},{8952,4,0,-1},{8953,4,1,-1}, +{8957,4,0,-1},{8958,4,1,-1},{8961,4,0,-1},{8962,4,1,-1},{8965,4,0,-1}, +{8966,4,1,-1},{8970,4,0,-1},{8971,4,1,-1},{8904,4,0,-1},{8974,4,0,-1}, +{8975,4,1,-1},{8978,4,0,-1},{8979,4,1,-1},{8983,4,0,-1},{8984,4,1,-1}, +{8917,4,1,-1},{8987,4,0,-1},{8988,4,1,-1},{8991,4,0,-1},{8992,4,1,-1}, +{8996,4,0,-1},{8997,4,1,-1},{9000,4,0,-1},{9001,4,1,-1},{9004,4,0,-1}, +{9005,4,1,-1},{9009,4,0,-1},{9010,4,1,-1},{9013,4,0,-1},{9014,4,1,-1}, +{9017,4,0,-1},{9018,4,1,-1},{9022,4,0,-1},{9023,4,1,-1},{9026,4,0,-1}, +{9027,4,1,-1},{9030,4,0,-1},{9031,4,1,-1},{9035,4,0,-1},{9036,4,1,-1}, +{9039,4,0,-1},{9040,4,1,-1},{9043,4,0,-1},{9044,4,1,-1},{9048,4,0,-1}, +{9049,4,1,-1},{9052,4,0,-1},{9053,4,1,-1},{9056,4,0,-1},{9057,4,1,-1}, +{9061,4,0,-1},{9062,4,1,-1},{9065,4,0,-1},{9066,4,1,-1},{9069,4,0,-1}, +{9070,4,1,-1},{9074,4,0,-1},{9075,4,1,-1},{9008,4,0,-1},{9078,4,0,-1}, +{9079,4,1,-1},{9082,4,0,-1},{9083,4,1,-1},{9087,4,0,-1},{9088,4,1,-1}, +{9021,4,1,-1},{9091,4,0,-1},{9092,4,1,-1},{9095,4,0,-1},{9096,4,1,-1}, +{9100,4,0,-1},{9101,4,1,-1},{9104,4,0,-1},{9105,4,1,-1},{9108,4,0,-1}, +{9109,4,1,-1},{9113,4,0,-1},{9114,4,1,-1},{9117,4,0,-1},{9118,4,1,-1}, +{9121,4,0,-1},{9122,4,1,-1},{9126,4,0,-1},{9127,4,1,-1},{9130,4,0,-1}, +{9131,4,1,-1},{9134,4,0,-1},{9135,4,1,-1},{9139,4,0,-1},{9140,4,1,-1}, +{9143,4,0,-1},{9144,4,1,-1},{9147,4,0,-1},{9148,4,1,-1},{9152,4,0,-1}, +{9153,4,1,-1},{9156,4,0,-1},{9157,4,1,-1},{9160,4,0,-1},{9161,4,1,-1}, +{9165,4,0,-1},{9166,4,1,-1},{9169,4,0,-1},{9170,4,1,-1},{9173,4,0,-1}, +{9174,4,1,-1},{9178,4,0,-1},{9179,4,1,-1},{9112,4,0,-1},{9182,4,0,-1}, +{9183,4,1,-1},{9186,4,0,-1},{9187,4,1,-1},{9191,4,0,-1},{9192,4,1,-1}, +{9125,4,1,-1},{9195,4,0,-1},{9196,4,1,-1},{9199,4,0,-1},{9200,4,1,-1}, +{9204,4,0,-1},{9205,4,1,-1},{9208,4,0,-1},{9209,4,1,-1},{9212,4,0,-1}, +{9213,4,1,-1},{9217,4,0,-1},{9218,4,1,-1},{9221,4,0,-1},{9222,4,1,-1}, +{9225,4,0,-1},{9226,4,1,-1},{9230,4,0,-1},{9231,4,1,-1},{9234,4,0,-1}, +{9235,4,1,-1},{9238,4,0,-1},{9239,4,1,-1},{9243,4,0,-1},{9244,4,1,-1}, +{9247,4,0,-1},{9248,4,1,-1},{9251,4,0,-1},{9252,4,1,-1},{9256,4,0,-1}, +{9257,4,1,-1},{9260,4,0,-1},{9261,4,1,-1},{9264,4,0,-1},{9265,4,1,-1}, +{9269,4,0,-1},{9270,4,1,-1},{9273,4,0,-1},{9274,4,1,-1},{9277,4,0,-1}, +{9278,4,1,-1},{9282,4,0,-1},{9283,4,1,-1},{9216,4,0,-1},{9286,4,0,-1}, +{9287,4,1,-1},{9290,4,0,-1},{9291,4,1,-1},{9295,4,0,-1},{9296,4,1,-1}, +{9229,4,1,-1},{9299,4,0,-1},{9300,4,1,-1},{9303,4,0,-1},{9304,4,1,-1}, +{9308,4,0,-1},{9309,4,1,-1},{9312,4,0,-1},{9313,4,1,-1},{9316,4,0,-1}, +{9317,4,1,-1},{9321,4,0,-1},{9322,4,1,-1},{9325,4,0,-1},{9326,4,1,-1}, +{9329,4,0,-1},{9330,4,1,-1},{9334,4,0,-1},{9335,4,1,-1},{9338,4,0,-1}, +{9339,4,1,-1},{9342,4,0,-1},{9343,4,1,-1},{9347,4,0,-1},{9348,4,1,-1}, +{9351,4,0,-1},{9352,4,1,-1},{9355,4,0,-1},{9356,4,1,-1},{9360,4,0,-1}, +{9361,4,1,-1},{9364,4,0,-1},{9365,4,1,-1},{9368,4,0,-1},{9369,4,1,-1}, +{9373,4,0,-1},{9374,4,1,-1},{9377,4,0,-1},{9378,4,1,-1},{9381,4,0,-1}, +{9382,4,1,-1},{9386,4,0,-1},{9387,4,1,-1},{9320,4,0,-1},{9390,4,0,-1}, +{9391,4,1,-1},{9394,4,0,-1},{9395,4,1,-1},{9399,4,0,-1},{9400,4,1,-1}, +{9333,4,1,-1},{9403,4,0,-1},{9404,4,1,-1},{9407,4,0,-1},{9408,4,1,-1}, +{9412,4,0,-1},{9413,4,1,-1},{9416,4,0,-1},{9417,4,1,-1},{9420,4,0,-1}, +{9421,4,1,-1},{9425,4,0,-1},{9426,4,1,-1},{9429,4,0,-1},{9430,4,1,-1}, +{9433,4,0,-1},{9434,4,1,-1},{9438,4,0,-1},{9439,4,1,-1},{9442,4,0,-1}, +{9443,4,1,-1},{9446,4,0,-1},{9447,4,1,-1},{9451,4,0,-1},{9452,4,1,-1}, +{9455,4,0,-1},{9456,4,1,-1},{9459,4,0,-1},{9460,4,1,-1},{9464,4,0,-1}, +{9465,4,1,-1},{9468,4,0,-1},{9469,4,1,-1},{9472,4,0,-1},{9473,4,1,-1}, +{9477,4,0,-1},{9478,4,1,-1},{9481,4,0,-1},{9482,4,1,-1},{9485,4,0,-1}, +{9486,4,1,-1},{9490,4,0,-1},{9491,4,1,-1},{9424,4,0,-1},{9494,4,0,-1}, +{9495,4,1,-1},{9498,4,0,-1},{9499,4,1,-1},{9503,4,0,-1},{9504,4,1,-1}, +{9437,4,1,-1},{9507,4,0,-1},{9508,4,1,-1},{9511,4,0,-1},{9512,4,1,-1}, +{9516,4,0,-1},{9517,4,1,-1},{9520,4,0,-1},{9521,4,1,-1},{9524,4,0,-1}, +{9525,4,1,-1},{9529,4,0,-1},{9530,4,1,-1},{9533,4,0,-1},{9534,4,1,-1}, +{9537,4,0,-1},{9538,4,1,-1},{9542,4,0,-1},{9543,4,1,-1},{9546,4,0,-1}, +{9547,4,1,-1},{9550,4,0,-1},{9551,4,1,-1},{9555,4,0,-1},{9556,4,1,-1}, +{9559,4,0,-1},{9560,4,1,-1},{9563,4,0,-1},{9564,4,1,-1},{9568,4,0,-1}, +{9569,4,1,-1},{9572,4,0,-1},{9573,4,1,-1},{9576,4,0,-1},{9577,4,1,-1}, +{9581,4,0,-1},{9582,4,1,-1},{9585,4,0,-1},{9586,4,1,-1},{9589,4,0,-1}, +{9590,4,1,-1},{9594,4,0,-1},{9595,4,1,-1},{9528,4,0,-1},{9598,4,0,-1}, +{9599,4,1,-1},{9602,4,0,-1},{9603,4,1,-1},{9607,4,0,-1},{9608,4,1,-1}, +{9541,4,1,-1},{9611,4,0,-1},{9612,4,1,-1},{9615,4,0,-1},{9616,4,1,-1}, +{9620,4,0,-1},{9621,4,1,-1},{9624,4,0,-1},{9625,4,1,-1},{9628,4,0,-1}, +{9629,4,1,-1},{9633,4,0,-1},{9634,4,1,-1},{9637,4,0,-1},{9638,4,1,-1}, +{9641,4,0,-1},{9642,4,1,-1},{9646,4,0,-1},{9647,4,1,-1},{9650,4,0,-1}, +{9651,4,1,-1},{9654,4,0,-1},{9655,4,1,-1},{9659,4,0,-1},{9660,4,1,-1}, +{9663,4,0,-1},{9664,4,1,-1},{9667,4,0,-1},{9668,4,1,-1},{9672,4,0,-1}, +{9673,4,1,-1},{9676,4,0,-1},{9677,4,1,-1},{9680,4,0,-1},{9681,4,1,-1}, +{9685,4,0,-1},{9686,4,1,-1},{9689,4,0,-1},{9690,4,1,-1},{9693,4,0,-1}, +{9694,4,1,-1},{9698,4,0,-1},{9699,4,1,-1},{9632,4,0,-1},{9702,4,0,-1}, +{9703,4,1,-1},{9706,4,0,-1},{9707,4,1,-1},{9711,4,0,-1},{9712,4,1,-1}, +{9645,4,1,-1},{9715,4,0,-1},{9716,4,1,-1},{9719,4,0,-1},{9720,4,1,-1}, +{9724,4,0,-1},{9725,4,1,-1},{9728,4,0,-1},{9729,4,1,-1},{9732,4,0,-1}, +{9733,4,1,-1},{9737,4,0,-1},{9738,4,1,-1},{9741,4,0,-1},{9742,4,1,-1}, +{9745,4,0,-1},{9746,4,1,-1},{9750,4,0,-1},{9751,4,1,-1},{9754,4,0,-1}, +{9755,4,1,-1},{9758,4,0,-1},{9759,4,1,-1},{9763,4,0,-1},{9764,4,1,-1}, +{9767,4,0,-1},{9768,4,1,-1},{9771,4,0,-1},{9772,4,1,-1},{9776,4,0,-1}, +{9777,4,1,-1},{9780,4,0,-1},{9781,4,1,-1},{9784,4,0,-1},{9785,4,1,-1}, +{9789,4,0,-1},{9790,4,1,-1},{9793,4,0,-1},{9794,4,1,-1},{9797,4,0,-1}, +{9798,4,1,-1},{9802,4,0,-1},{9803,4,1,-1},{9736,4,0,-1},{9806,4,0,-1}, +{9807,4,1,-1},{9810,4,0,-1},{9811,4,1,-1},{9815,4,0,-1},{9816,4,1,-1}, +{9749,4,1,-1},{9819,4,0,-1},{9820,4,1,-1},{9823,4,0,-1},{9824,4,1,-1}, +{9828,4,0,-1},{9829,4,1,-1},{9832,4,0,-1},{9833,4,1,-1},{9836,4,0,-1}, +{9837,4,1,-1},{9841,4,0,-1},{9842,4,1,-1},{9845,4,0,-1},{9846,4,1,-1}, +{9849,4,0,-1},{9850,4,1,-1},{9854,4,0,-1},{9855,4,1,-1},{9858,4,0,-1}, +{9859,4,1,-1},{9862,4,0,-1},{9863,4,1,-1},{9867,4,0,-1},{9868,4,1,-1}, +{9871,4,0,-1},{9872,4,1,-1},{9875,4,0,-1},{9876,4,1,-1},{9880,4,0,-1}, +{9881,4,1,-1},{9884,4,0,-1},{9885,4,1,-1},{9888,4,0,-1},{9889,4,1,-1}, +{9893,4,0,-1},{9894,4,1,-1},{9897,4,0,-1},{9898,4,1,-1},{9901,4,0,-1}, +{9902,4,1,-1},{9906,4,0,-1},{9907,4,1,-1},{9840,4,0,-1},{9910,4,0,-1}, +{9911,4,1,-1},{9914,4,0,-1},{9915,4,1,-1},{9919,4,0,-1},{9920,4,1,-1}, +{9853,4,1,-1},{9923,4,0,-1},{9924,4,1,-1},{9927,4,0,-1},{9928,4,1,-1}, +{9932,4,0,-1},{9933,4,1,-1},{9936,4,0,-1},{9937,4,1,-1},{9940,4,0,-1}, +{9941,4,1,-1},{9945,4,0,-1},{9946,4,1,-1},{9949,4,0,-1},{9950,4,1,-1}, +{9953,4,0,-1},{9954,4,1,-1},{9958,4,0,-1},{9959,4,1,-1},{9962,4,0,-1}, +{9963,4,1,-1},{9966,4,0,-1},{9967,4,1,-1},{9971,4,0,-1},{9972,4,1,-1}, +{9975,4,0,-1},{9976,4,1,-1},{9979,4,0,-1},{9980,4,1,-1},{9984,4,0,-1}, +{9985,4,1,-1},{9988,4,0,-1},{9989,4,1,-1},{9992,4,0,-1},{9993,4,1,-1}, +{9997,4,0,-1},{9998,4,1,-1},{10001,4,0,-1},{10002,4,1,-1},{10005,4,0,-1}, +{10006,4,1,-1},{10010,4,0,-1},{10011,4,1,-1},{9944,4,0,-1},{10014,4,0,-1}, +{10015,4,1,-1},{10018,4,0,-1},{10019,4,1,-1},{10023,4,0,-1},{10024,4,1,-1}, +{9957,4,1,-1},{10027,4,0,-1},{10028,4,1,-1},{10031,4,0,-1},{10032,4,1,-1}, +{10036,4,0,-1},{10037,4,1,-1},{10040,4,0,-1},{10041,4,1,-1},{10044,4,0,-1}, +{10045,4,1,-1},{10049,4,0,-1},{10050,4,1,-1},{10053,4,0,-1},{10054,4,1,-1}, +{10057,4,0,-1},{10058,4,1,-1},{10062,4,0,-1},{10063,4,1,-1},{10066,4,0,-1}, +{10067,4,1,-1},{10070,4,0,-1},{10071,4,1,-1},{10075,4,0,-1},{10076,4,1,-1}, +{10079,4,0,-1},{10080,4,1,-1},{10083,4,0,-1},{10084,4,1,-1},{10088,4,0,-1}, +{10089,4,1,-1},{10092,4,0,-1},{10093,4,1,-1},{10096,4,0,-1},{10097,4,1,-1}, +{10101,4,0,-1},{10102,4,1,-1},{10105,4,0,-1},{10106,4,1,-1},{10109,4,0,-1}, +{10110,4,1,-1},{10114,4,0,-1},{10115,4,1,-1},{10048,4,0,-1},{10118,4,0,-1}, +{10119,4,1,-1},{10122,4,0,-1},{10123,4,1,-1},{10127,4,0,-1},{10128,4,1,-1}, +{10061,4,1,-1},{10131,4,0,-1},{10132,4,1,-1},{10135,4,0,-1},{10136,4,1,-1}, +{10140,4,0,-1},{10141,4,1,-1},{10144,4,0,-1},{10145,4,1,-1},{10148,4,0,-1}, +{10149,4,1,-1},{10153,4,0,-1},{10154,4,1,-1},{10157,4,0,-1},{10158,4,1,-1}, +{10161,4,0,-1},{10162,4,1,-1},{10166,4,0,-1},{10167,4,1,-1},{10170,4,0,-1}, +{10171,4,1,-1},{10174,4,0,-1},{10175,4,1,-1},{10179,4,0,-1},{10180,4,1,-1}, +{10183,4,0,-1},{10184,4,1,-1},{10187,4,0,-1},{10188,4,1,-1},{10192,4,0,-1}, +{10193,4,1,-1},{10196,4,0,-1},{10197,4,1,-1},{10200,4,0,-1},{10201,4,1,-1}, +{10205,4,0,-1},{10206,4,1,-1},{10209,4,0,-1},{10210,4,1,-1},{10213,4,0,-1}, +{10214,4,1,-1},{10218,4,0,-1},{10219,4,1,-1},{10152,4,0,-1},{10222,4,0,-1}, +{10223,4,1,-1},{10226,4,0,-1},{10227,4,1,-1},{10231,4,0,-1},{10232,4,1,-1}, +{10165,4,1,-1},{10235,4,0,-1},{10236,4,1,-1},{10239,4,0,-1},{10240,4,1,-1}, +{10244,4,0,-1},{10245,4,1,-1},{10248,4,0,-1},{10249,4,1,-1},{10252,4,0,-1}, +{10253,4,1,-1},{10257,4,0,-1},{10258,4,1,-1},{10261,4,0,-1},{10262,4,1,-1}, +{10265,4,0,-1},{10266,4,1,-1},{10270,4,0,-1},{10271,4,1,-1},{10274,4,0,-1}, +{10275,4,1,-1},{10278,4,0,-1},{10279,4,1,-1},{10283,4,0,-1},{10284,4,1,-1}, +{10287,4,0,-1},{10288,4,1,-1},{10291,4,0,-1},{10292,4,1,-1},{10296,4,0,-1}, +{10297,4,1,-1},{10300,4,0,-1},{10301,4,1,-1},{10304,4,0,-1},{10305,4,1,-1}, +{10309,4,0,-1},{10310,4,1,-1},{10313,4,0,-1},{10314,4,1,-1},{10317,4,0,-1}, +{10318,4,1,-1},{10322,4,0,-1},{10323,4,1,-1},{10256,4,0,-1},{10326,4,0,-1}, +{10327,4,1,-1},{10330,4,0,-1},{10331,4,1,-1},{10335,4,0,-1},{10336,4,1,-1}, +{10269,4,1,-1},{10339,4,0,-1},{10340,4,1,-1},{10343,4,0,-1},{10344,4,1,-1}, +{10348,4,0,-1},{10349,4,1,-1},{10352,4,0,-1},{10353,4,1,-1},{10356,4,0,-1}, +{10357,4,1,-1},{10361,4,0,-1},{10362,4,1,-1},{10365,4,0,-1},{10366,4,1,-1}, +{10369,4,0,-1},{10370,4,1,-1},{10374,4,0,-1},{10375,4,1,-1},{10378,4,0,-1}, +{10379,4,1,-1},{10382,4,0,-1},{10383,4,1,-1},{10387,4,0,-1},{10388,4,1,-1}, +{10391,4,0,-1},{10392,4,1,-1},{10395,4,0,-1},{10396,4,1,-1},{10400,4,0,-1}, +{10401,4,1,-1},{10404,4,0,-1},{10405,4,1,-1},{10408,4,0,-1},{10409,4,1,-1}, +{10413,4,0,-1},{10414,4,1,-1},{10417,4,0,-1},{10418,4,1,-1},{10421,4,0,-1}, +{10422,4,1,-1},{10426,4,0,-1},{10427,4,1,-1},{10360,4,0,-1},{10430,4,0,-1}, +{10431,4,1,-1},{10434,4,0,-1},{10435,4,1,-1},{10439,4,0,-1},{10440,4,1,-1}, +{10373,4,1,-1},{10443,4,0,-1},{10444,4,1,-1},{10447,4,0,-1},{10448,4,1,-1}, +{10452,4,0,-1},{10453,4,1,-1},{10456,4,0,-1},{10457,4,1,-1},{10460,4,0,-1}, +{10461,4,1,-1},{10465,4,0,-1},{10466,4,1,-1},{10469,4,0,-1},{10470,4,1,-1}, +{10473,4,0,-1},{10474,4,1,-1},{10478,4,0,-1},{10479,4,1,-1},{10482,4,0,-1}, +{10483,4,1,-1},{10486,4,0,-1},{10487,4,1,-1},{10491,4,0,-1},{10492,4,1,-1}, +{10495,4,0,-1},{10496,4,1,-1},{10499,4,0,-1},{10500,4,1,-1},{10504,4,0,-1}, +{10505,4,1,-1},{10508,4,0,-1},{10509,4,1,-1},{10512,4,0,-1},{10513,4,1,-1}, +{10517,4,0,-1},{10518,4,1,-1},{10521,4,0,-1},{10522,4,1,-1},{10525,4,0,-1}, +{10526,4,1,-1},{10530,4,0,-1},{10531,4,1,-1},{10464,4,0,-1},{10534,4,0,-1}, +{10535,4,1,-1},{10538,4,0,-1},{10539,4,1,-1},{10543,4,0,-1},{10544,4,1,-1}, +{10477,4,1,-1},{10547,4,0,-1},{10548,4,1,-1},{10551,4,0,-1},{10552,4,1,-1}, +{10556,4,0,-1},{10557,4,1,-1},{10560,4,0,-1},{10561,4,1,-1},{10564,4,0,-1}, +{10565,4,1,-1},{10569,4,0,-1},{10570,4,1,-1},{10573,4,0,-1},{10574,4,1,-1}, +{10577,4,0,-1},{10578,4,1,-1},{10582,4,0,-1},{10583,4,1,-1},{10586,4,0,-1}, +{10587,4,1,-1},{10590,4,0,-1},{10591,4,1,-1},{10595,4,0,-1},{10596,4,1,-1}, +{10599,4,0,-1},{10600,4,1,-1},{10603,4,0,-1},{10604,4,1,-1},{10608,4,0,-1}, +{10609,4,1,-1},{10612,4,0,-1},{10613,4,1,-1},{10616,4,0,-1},{10617,4,1,-1}, +{10621,4,0,-1},{10622,4,1,-1},{10625,4,0,-1},{10626,4,1,-1},{10629,4,0,-1}, +{10630,4,1,-1},{10634,4,0,-1},{10635,4,1,-1},{10568,4,0,-1},{10638,4,0,-1}, +{10639,4,1,-1},{10642,4,0,-1},{10643,4,1,-1},{10647,4,0,-1},{10648,4,1,-1}, +{10581,4,1,-1},{10651,4,0,-1},{10652,4,1,-1},{10655,4,0,-1},{10656,4,1,-1}, +{10660,4,0,-1},{10661,4,1,-1},{10664,4,0,-1},{10665,4,1,-1},{10668,4,0,-1}, +{10669,4,1,-1},{10673,4,0,-1},{10674,4,1,-1},{10677,4,0,-1},{10678,4,1,-1}, +{10681,4,0,-1},{10682,4,1,-1},{10686,4,0,-1},{10687,4,1,-1},{10690,4,0,-1}, +{10691,4,1,-1},{10694,4,0,-1},{10695,4,1,-1},{10699,4,0,-1},{10700,4,1,-1}, +{10703,4,0,-1},{10704,4,1,-1},{10707,4,0,-1},{10708,4,1,-1},{10712,4,0,-1}, +{10713,4,1,-1},{10716,4,0,-1},{10717,4,1,-1},{10720,4,0,-1},{10721,4,1,-1}, +{10725,4,0,-1},{10726,4,1,-1},{10729,4,0,-1},{10730,4,1,-1},{10733,4,0,-1}, +{10734,4,1,-1},{10738,4,0,-1},{10739,4,1,-1},{10672,4,0,-1},{10742,4,0,-1}, +{10743,4,1,-1},{10746,4,0,-1},{10747,4,1,-1},{10751,4,0,-1},{10752,4,1,-1}, +{10685,4,1,-1},{10755,4,0,-1},{10756,4,1,-1},{10759,4,0,-1},{10760,4,1,-1}, +{10764,4,0,-1},{10765,4,1,-1},{10768,4,0,-1},{10769,4,1,-1},{10772,4,0,-1}, +{10773,4,1,-1},{10777,4,0,-1},{10778,4,1,-1},{10781,4,0,-1},{10782,4,1,-1}, +{10785,4,0,-1},{10786,4,1,-1},{10790,4,0,-1},{10791,4,1,-1},{10794,4,0,-1}, +{10795,4,1,-1},{10798,4,0,-1},{10799,4,1,-1},{10803,4,0,-1},{10804,4,1,-1}, +{10807,4,0,-1},{10808,4,1,-1},{10811,4,0,-1},{10812,4,1,-1},{10816,4,0,-1}, +{10817,4,1,-1},{10820,4,0,-1},{10821,4,1,-1},{10824,4,0,-1},{10825,4,1,-1}, +{10829,4,0,-1},{10830,4,1,-1},{10833,4,0,-1},{10834,4,1,-1},{10837,4,0,-1}, +{10838,4,1,-1},{10842,4,0,-1},{10843,4,1,-1},{10776,4,0,-1},{10846,4,0,-1}, +{10847,4,1,-1},{10850,4,0,-1},{10851,4,1,-1},{10855,4,0,-1},{10856,4,1,-1}, +{10789,4,1,-1},{10859,4,0,-1},{10860,4,1,-1},{10863,4,0,-1},{10864,4,1,-1}, +{10868,4,0,-1},{10869,4,1,-1},{10872,4,0,-1},{10873,4,1,-1},{10876,4,0,-1}, +{10877,4,1,-1},{10881,4,0,-1},{10882,4,1,-1},{10885,4,0,-1},{10886,4,1,-1}, +{10889,4,0,-1},{10890,4,1,-1},{10894,4,0,-1},{10895,4,1,-1},{10898,4,0,-1}, +{10899,4,1,-1},{10902,4,0,-1},{10903,4,1,-1},{10907,4,0,-1},{10908,4,1,-1}, +{10911,4,0,-1},{10912,4,1,-1},{10915,4,0,-1},{10916,4,1,-1},{10920,4,0,-1}, +{10921,4,1,-1},{10924,4,0,-1},{10925,4,1,-1},{10928,4,0,-1},{10929,4,1,-1}, +{10933,4,0,-1},{10934,4,1,-1},{10937,4,0,-1},{10938,4,1,-1},{10941,4,0,-1}, +{10942,4,1,-1},{10946,4,0,-1},{10947,4,1,-1},{10880,4,0,-1},{10950,4,0,-1}, +{10951,4,1,-1},{10954,4,0,-1},{10955,4,1,-1},{10959,4,0,-1},{10960,4,1,-1}, +{10893,4,1,-1},{10963,4,0,-1},{10964,4,1,-1},{10967,4,0,-1},{10968,4,1,-1}, +{10972,4,0,-1},{10973,4,1,-1},{10976,4,0,-1},{10977,4,1,-1},{10980,4,0,-1}, +{10981,4,1,-1},{10985,4,0,-1},{10986,4,1,-1},{10989,4,0,-1},{10990,4,1,-1}, +{10993,4,0,-1},{10994,4,1,-1},{10998,4,0,-1},{10999,4,1,-1},{11002,4,0,-1}, +{11003,4,1,-1},{11006,4,0,-1},{11007,4,1,-1},{11012,4,1,-1},{11016,4,1,-1}, +{11020,4,1,-1},{11025,4,1,-1},{11029,4,1,-1},{11033,4,1,-1},{11038,4,1,-1}, +{11042,4,1,-1},{11046,4,1,-1},{11051,4,1,-1},{10984,4,0,-1},{11055,4,1,-1}, +{11058,4,0,-1},{11059,4,1,-1},{10997,4,1,-1},{11067,4,0,-1},{11068,4,1,-1}, +{11072,4,1,-1},{11077,4,1,-1},{11081,4,1,-1},{11085,4,1,-1},{11090,4,1,-1}, +{11094,4,1,-1},{11098,4,1,-1},{11102,4,0,-1},{11103,4,1,-1},{11107,4,1,-1}, +{11111,4,1,-1},{11116,4,1,-1},{11120,4,1,-1},{11129,4,1,-1},{11133,4,1,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS4, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_5_ss0_ss1[] = { +{5269,5,0,-1},{5278,5,0,-1},{5286,5,0,-1},{5295,5,0,-1},{5304,5,0,-1}, +{5312,5,0,-1},{5321,5,0,-1},{5330,5,0,-1},{5264,5,0,-1},{5338,5,0,-1}, +{5347,5,0,-1},{5351,5,0,-1},{5356,5,0,-1},{5360,5,0,-1},{5364,5,0,-1}, +{5369,5,0,-1},{5373,5,0,-1},{5377,5,0,-1},{5382,5,0,-1},{5386,5,0,-1}, +{5390,5,0,-1},{5395,5,0,-1},{5399,5,0,-1},{5403,5,0,-1},{5408,5,0,-1}, +{5412,5,0,-1},{5416,5,0,-1},{5421,5,0,-1},{5425,5,0,-1},{5429,5,0,-1}, +{5434,5,0,-1},{5368,5,0,-1},{5438,5,0,-1},{5442,5,0,-1},{5447,5,0,-1}, +{5451,5,0,-1},{5455,5,0,-1},{5460,5,0,-1},{5464,5,0,-1},{5468,5,0,-1}, +{5473,5,0,-1},{5477,5,0,-1},{5481,5,0,-1},{5486,5,0,-1},{5490,5,0,-1}, +{5494,5,0,-1},{5499,5,0,-1},{5503,5,0,-1},{5507,5,0,-1},{5512,5,0,-1}, +{5516,5,0,-1},{5520,5,0,-1},{5525,5,0,-1},{5529,5,0,-1},{5533,5,0,-1}, +{5538,5,0,-1},{5472,5,0,-1},{5542,5,0,-1},{5546,5,0,-1},{5551,5,0,-1}, +{5555,5,0,-1},{5559,5,0,-1},{5564,5,0,-1},{5568,5,0,-1},{5572,5,0,-1}, +{5577,5,0,-1},{5581,5,0,-1},{5585,5,0,-1},{5590,5,0,-1},{5594,5,0,-1}, +{5598,5,0,-1},{5603,5,0,-1},{5607,5,0,-1},{5611,5,0,-1},{5616,5,0,-1}, +{5620,5,0,-1},{5624,5,0,-1},{5629,5,0,-1},{5633,5,0,-1},{5637,5,0,-1}, +{5638,5,1,-1},{5642,5,0,-1},{5576,5,0,-1},{5646,5,0,-1},{5647,5,1,-1}, +{5650,5,0,-1},{5655,5,0,-1},{5656,5,1,-1},{5659,5,0,-1},{5663,5,0,-1}, +{5668,5,0,-1},{5669,5,1,-1},{5672,5,0,-1},{5676,5,0,-1},{5677,5,1,-1}, +{5681,5,0,-1},{5685,5,0,-1},{5686,5,1,-1},{5689,5,0,-1},{5694,5,0,-1}, +{5695,5,1,-1},{5698,5,0,-1},{5702,5,0,-1},{5703,5,1,-1},{5707,5,0,-1}, +{5711,5,0,-1},{5712,5,1,-1},{5715,5,0,-1},{5720,5,0,-1},{5721,5,1,-1}, +{5724,5,0,-1},{5728,5,0,-1},{5729,5,1,-1},{5733,5,0,-1},{5737,5,0,-1}, +{5738,5,1,-1},{5741,5,0,-1},{5746,5,0,-1},{5747,5,1,-1},{5680,5,0,-1}, +{5750,5,0,-1},{5754,5,0,-1},{5755,5,1,-1},{5759,5,0,-1},{5693,5,1,-1}, +{5763,5,0,-1},{5764,5,1,-1},{5767,5,0,-1},{5772,5,0,-1},{5773,5,1,-1}, +{5776,5,0,-1},{5780,5,0,-1},{5781,5,1,-1},{5785,5,0,-1},{5789,5,0,-1}, +{5790,5,1,-1},{5793,5,0,-1},{5798,5,0,-1},{5799,5,1,-1},{5802,5,0,-1}, +{5806,5,0,-1},{5807,5,1,-1},{5811,5,0,-1},{5815,5,0,-1},{5816,5,1,-1}, +{5819,5,0,-1},{5820,5,1,-1},{5824,5,0,-1},{5825,5,1,-1},{5828,5,0,-1}, +{5829,5,1,-1},{5832,5,0,-1},{5833,5,1,-1},{5837,5,0,-1},{5838,5,1,-1}, +{5841,5,0,-1},{5842,5,1,-1},{5845,5,0,-1},{5850,5,0,-1},{5851,5,1,-1}, +{5784,5,0,-1},{5854,5,0,-1},{5855,5,1,-1},{5858,5,0,-1},{5859,5,1,-1}, +{5863,5,0,-1},{5864,5,1,-1},{5797,5,1,-1},{5867,5,0,-1},{5868,5,1,-1}, +{5871,5,0,-1},{5872,5,1,-1},{5876,5,0,-1},{5877,5,1,-1},{5880,5,0,-1}, +{5881,5,1,-1},{5884,5,0,-1},{5885,5,1,-1},{5890,5,1,-1},{5893,5,0,-1}, +{5894,5,1,-1},{5897,5,0,-1},{5898,5,1,-1},{5902,5,0,-1},{5903,5,1,-1}, +{5906,5,0,-1},{5907,5,1,-1},{5910,5,0,-1},{5911,5,1,-1},{5915,5,0,-1}, +{5916,5,1,-1},{5919,5,0,-1},{5920,5,1,-1},{5923,5,0,-1},{5924,5,1,-1}, +{5928,5,0,-1},{5929,5,1,-1},{5932,5,0,-1},{5933,5,1,-1},{5936,5,0,-1}, +{5937,5,1,-1},{5941,5,0,-1},{5942,5,1,-1},{5945,5,0,-1},{5946,5,1,-1}, +{5949,5,0,-1},{5950,5,1,-1},{5954,5,0,-1},{5955,5,1,-1},{5888,5,0,-1}, +{5958,5,0,-1},{5959,5,1,-1},{5962,5,0,-1},{5963,5,1,-1},{5967,5,0,-1}, +{5968,5,1,-1},{5901,5,1,-1},{5971,5,0,-1},{5972,5,1,-1},{5975,5,0,-1}, +{5976,5,1,-1},{5980,5,0,-1},{5981,5,1,-1},{5984,5,0,-1},{5988,5,0,-1}, +{5989,5,1,-1},{5993,5,0,-1},{5994,5,1,-1},{5997,5,0,-1},{5998,5,1,-1}, +{6001,5,0,-1},{6002,5,1,-1},{6006,5,0,-1},{6007,5,1,-1},{6010,5,0,-1}, +{6011,5,1,-1},{6014,5,0,-1},{6015,5,1,-1},{6019,5,0,-1},{6020,5,1,-1}, +{6023,5,0,-1},{6024,5,1,-1},{6028,5,1,-1},{6032,5,0,-1},{6033,5,1,-1}, +{6036,5,0,-1},{6040,5,0,-1},{6041,5,1,-1},{6045,5,0,-1},{6046,5,1,-1}, +{6049,5,0,-1},{6050,5,1,-1},{6053,5,0,-1},{6054,5,1,-1},{6058,5,0,-1}, +{6059,5,1,-1},{5992,5,0,-1},{6062,5,0,-1},{6063,5,1,-1},{6066,5,0,-1}, +{6067,5,1,-1},{6071,5,0,-1},{6072,5,1,-1},{6005,5,1,-1},{6075,5,0,-1}, +{6076,5,1,-1},{6079,5,0,-1},{6080,5,1,-1},{6084,5,0,-1},{6085,5,1,-1}, +{6088,5,0,-1},{6089,5,1,-1},{6092,5,0,-1},{6093,5,1,-1},{6097,5,0,-1}, +{6098,5,1,-1},{6101,5,0,-1},{6102,5,1,-1},{6105,5,0,-1},{6106,5,1,-1}, +{6110,5,0,-1},{6111,5,1,-1},{6114,5,0,-1},{6115,5,1,-1},{6118,5,0,-1}, +{6119,5,1,-1},{6123,5,0,-1},{6124,5,1,-1},{6127,5,0,-1},{6128,5,1,-1}, +{6131,5,0,-1},{6132,5,1,-1},{6136,5,0,-1},{6137,5,1,-1},{6140,5,0,-1}, +{6141,5,1,-1},{6144,5,0,-1},{6145,5,1,-1},{6149,5,0,-1},{6150,5,1,-1}, +{6153,5,0,-1},{6154,5,1,-1},{6157,5,0,-1},{6158,5,1,-1},{6162,5,0,-1}, +{6163,5,1,-1},{6096,5,0,-1},{6166,5,0,-1},{6167,5,1,-1},{6170,5,0,-1}, +{6171,5,1,-1},{6175,5,0,-1},{6176,5,1,-1},{6109,5,1,-1},{6179,5,0,-1}, +{6180,5,1,-1},{6183,5,0,-1},{6184,5,1,-1},{6188,5,0,-1},{6189,5,1,-1}, +{6192,5,0,-1},{6193,5,1,-1},{6196,5,0,-1},{6197,5,1,-1},{6201,5,0,-1}, +{6202,5,1,-1},{6205,5,0,-1},{6206,5,1,-1},{6209,5,0,-1},{6210,5,1,-1}, +{6214,5,0,-1},{6215,5,1,-1},{6218,5,0,-1},{6219,5,1,-1},{6222,5,0,-1}, +{6223,5,1,-1},{6227,5,0,-1},{6228,5,1,-1},{6231,5,0,-1},{6232,5,1,-1}, +{6235,5,0,-1},{6236,5,1,-1},{6240,5,0,-1},{6241,5,1,-1},{6244,5,0,-1}, +{6245,5,1,-1},{6248,5,0,-1},{6249,5,1,-1},{6253,5,0,-1},{6254,5,1,-1}, +{6257,5,0,-1},{6258,5,1,-1},{6261,5,0,-1},{6262,5,1,-1},{6266,5,0,-1}, +{6267,5,1,-1},{6200,5,0,-1},{6270,5,0,-1},{6271,5,1,-1},{6274,5,0,-1}, +{6275,5,1,-1},{6279,5,0,-1},{6280,5,1,-1},{6213,5,1,-1},{6283,5,0,-1}, +{6284,5,1,-1},{6287,5,0,-1},{6288,5,1,-1},{6292,5,0,-1},{6293,5,1,-1}, +{6296,5,0,-1},{6297,5,1,-1},{6300,5,0,-1},{6301,5,1,-1},{6305,5,0,-1}, +{6306,5,1,-1},{6309,5,0,-1},{6310,5,1,-1},{6313,5,0,-1},{6314,5,1,-1}, +{6318,5,0,-1},{6319,5,1,-1},{6322,5,0,-1},{6323,5,1,-1},{6326,5,0,-1}, +{6327,5,1,-1},{6331,5,0,-1},{6332,5,1,-1},{6335,5,0,-1},{6336,5,1,-1}, +{6339,5,0,-1},{6340,5,1,-1},{6344,5,0,-1},{6345,5,1,-1},{6348,5,0,-1}, +{6349,5,1,-1},{6352,5,0,-1},{6353,5,1,-1},{6357,5,0,-1},{6358,5,1,-1}, +{6361,5,0,-1},{6362,5,1,-1},{6365,5,0,-1},{6366,5,1,-1},{6370,5,0,-1}, +{6371,5,1,-1},{6304,5,0,-1},{6374,5,0,-1},{6375,5,1,-1},{6378,5,0,-1}, +{6379,5,1,-1},{6383,5,0,-1},{6384,5,1,-1},{6317,5,1,-1},{6387,5,0,-1}, +{6388,5,1,-1},{6391,5,0,-1},{6392,5,1,-1},{6396,5,0,-1},{6397,5,1,-1}, +{6400,5,0,-1},{6401,5,1,-1},{6404,5,0,-1},{6405,5,1,-1},{6409,5,0,-1}, +{6410,5,1,-1},{6413,5,0,-1},{6414,5,1,-1},{6417,5,0,-1},{6418,5,1,-1}, +{6422,5,0,-1},{6423,5,1,-1},{6426,5,0,-1},{6427,5,1,-1},{6430,5,0,-1}, +{6431,5,1,-1},{6435,5,0,-1},{6436,5,1,-1},{6439,5,0,-1},{6440,5,1,-1}, +{6443,5,0,-1},{6444,5,1,-1},{6448,5,0,-1},{6449,5,1,-1},{6452,5,0,-1}, +{6453,5,1,-1},{6456,5,0,-1},{6457,5,1,-1},{6461,5,0,-1},{6462,5,1,-1}, +{6465,5,0,-1},{6466,5,1,-1},{6469,5,0,-1},{6470,5,1,-1},{6474,5,0,-1}, +{6475,5,1,-1},{6408,5,0,-1},{6478,5,0,-1},{6479,5,1,-1},{6482,5,0,-1}, +{6483,5,1,-1},{6487,5,0,-1},{6488,5,1,-1},{6421,5,1,-1},{6491,5,0,-1}, +{6492,5,1,-1},{6495,5,0,-1},{6496,5,1,-1},{6500,5,0,-1},{6501,5,1,-1}, +{6504,5,0,-1},{6505,5,1,-1},{6508,5,0,-1},{6509,5,1,-1},{6513,5,0,-1}, +{6514,5,1,-1},{6517,5,0,-1},{6518,5,1,-1},{6521,5,0,-1},{6522,5,1,-1}, +{6526,5,0,-1},{6527,5,1,-1},{6530,5,0,-1},{6531,5,1,-1},{6534,5,0,-1}, +{6535,5,1,-1},{6539,5,0,-1},{6540,5,1,-1},{6543,5,0,-1},{6544,5,1,-1}, +{6547,5,0,-1},{6548,5,1,-1},{6552,5,0,-1},{6553,5,1,-1},{6556,5,0,-1}, +{6557,5,1,-1},{6560,5,0,-1},{6561,5,1,-1},{6565,5,0,-1},{6566,5,1,-1}, +{6569,5,0,-1},{6570,5,1,-1},{6573,5,0,-1},{6574,5,1,-1},{6578,5,0,-1}, +{6579,5,1,-1},{6512,5,0,-1},{6582,5,0,-1},{6583,5,1,-1},{6586,5,0,-1}, +{6587,5,1,-1},{6591,5,0,-1},{6592,5,1,-1},{6525,5,1,-1},{6595,5,0,-1}, +{6596,5,1,-1},{6599,5,0,-1},{6600,5,1,-1},{6604,5,0,-1},{6605,5,1,-1}, +{6608,5,0,-1},{6609,5,1,-1},{6612,5,0,-1},{6613,5,1,-1},{6617,5,0,-1}, +{6618,5,1,-1},{6621,5,0,-1},{6622,5,1,-1},{6625,5,0,-1},{6626,5,1,-1}, +{6630,5,0,-1},{6631,5,1,-1},{6634,5,0,-1},{6635,5,1,-1},{6638,5,0,-1}, +{6639,5,1,-1},{6643,5,0,-1},{6644,5,1,-1},{6647,5,0,-1},{6648,5,1,-1}, +{6651,5,0,-1},{6652,5,1,-1},{6656,5,0,-1},{6657,5,1,-1},{6660,5,0,-1}, +{6661,5,1,-1},{6664,5,0,-1},{6665,5,1,-1},{6669,5,0,-1},{6670,5,1,-1}, +{6673,5,0,-1},{6674,5,1,-1},{6677,5,0,-1},{6678,5,1,-1},{6682,5,0,-1}, +{6683,5,1,-1},{6616,5,0,-1},{6686,5,0,-1},{6687,5,1,-1},{6690,5,0,-1}, +{6691,5,1,-1},{6695,5,0,-1},{6696,5,1,-1},{6629,5,1,-1},{6699,5,0,-1}, +{6700,5,1,-1},{6703,5,0,-1},{6704,5,1,-1},{6708,5,0,-1},{6709,5,1,-1}, +{6712,5,0,-1},{6713,5,1,-1},{6716,5,0,-1},{6717,5,1,-1},{6721,5,0,-1}, +{6722,5,1,-1},{6725,5,0,-1},{6726,5,1,-1},{6729,5,0,-1},{6730,5,1,-1}, +{6734,5,0,-1},{6735,5,1,-1},{6738,5,0,-1},{6739,5,1,-1},{6742,5,0,-1}, +{6743,5,1,-1},{6747,5,0,-1},{6748,5,1,-1},{6751,5,0,-1},{6752,5,1,-1}, +{6755,5,0,-1},{6756,5,1,-1},{6760,5,0,-1},{6761,5,1,-1},{6764,5,0,-1}, +{6765,5,1,-1},{6768,5,0,-1},{6769,5,1,-1},{6773,5,0,-1},{6774,5,1,-1}, +{6777,5,0,-1},{6778,5,1,-1},{6781,5,0,-1},{6782,5,1,-1},{6786,5,0,-1}, +{6787,5,1,-1},{6720,5,0,-1},{6790,5,0,-1},{6791,5,1,-1},{6794,5,0,-1}, +{6795,5,1,-1},{6799,5,0,-1},{6800,5,1,-1},{6733,5,1,-1},{6803,5,0,-1}, +{6804,5,1,-1},{6807,5,0,-1},{6808,5,1,-1},{6812,5,0,-1},{6813,5,1,-1}, +{6816,5,0,-1},{6817,5,1,-1},{6820,5,0,-1},{6821,5,1,-1},{6825,5,0,-1}, +{6826,5,1,-1},{6829,5,0,-1},{6830,5,1,-1},{6833,5,0,-1},{6834,5,1,-1}, +{6838,5,0,-1},{6839,5,1,-1},{6842,5,0,-1},{6843,5,1,-1},{6846,5,0,-1}, +{6847,5,1,-1},{6851,5,0,-1},{6852,5,1,-1},{6855,5,0,-1},{6856,5,1,-1}, +{6859,5,0,-1},{6860,5,1,-1},{6864,5,0,-1},{6865,5,1,-1},{6868,5,0,-1}, +{6869,5,1,-1},{6872,5,0,-1},{6873,5,1,-1},{6877,5,0,-1},{6878,5,1,-1}, +{6881,5,0,-1},{6882,5,1,-1},{6885,5,0,-1},{6886,5,1,-1},{6890,5,0,-1}, +{6891,5,1,-1},{6824,5,0,-1},{6894,5,0,-1},{6895,5,1,-1},{6898,5,0,-1}, +{6899,5,1,-1},{6903,5,0,-1},{6904,5,1,-1},{6837,5,1,-1},{6907,5,0,-1}, +{6908,5,1,-1},{6911,5,0,-1},{6912,5,1,-1},{6916,5,0,-1},{6917,5,1,-1}, +{6920,5,0,-1},{6921,5,1,-1},{6924,5,0,-1},{6925,5,1,-1},{6929,5,0,-1}, +{6930,5,1,-1},{6933,5,0,-1},{6934,5,1,-1},{6937,5,0,-1},{6938,5,1,-1}, +{6942,5,0,-1},{6943,5,1,-1},{6946,5,0,-1},{6947,5,1,-1},{6950,5,0,-1}, +{6951,5,1,-1},{6955,5,0,-1},{6956,5,1,-1},{6959,5,0,-1},{6960,5,1,-1}, +{6963,5,0,-1},{6964,5,1,-1},{6968,5,0,-1},{6969,5,1,-1},{6972,5,0,-1}, +{6973,5,1,-1},{6976,5,0,-1},{6977,5,1,-1},{6981,5,0,-1},{6982,5,1,-1}, +{6985,5,0,-1},{6986,5,1,-1},{6989,5,0,-1},{6990,5,1,-1},{6994,5,0,-1}, +{6995,5,1,-1},{6928,5,0,-1},{6998,5,0,-1},{6999,5,1,-1},{7002,5,0,-1}, +{7003,5,1,-1},{7007,5,0,-1},{7008,5,1,-1},{6941,5,1,-1},{7011,5,0,-1}, +{7012,5,1,-1},{7015,5,0,-1},{7016,5,1,-1},{7020,5,0,-1},{7021,5,1,-1}, +{7024,5,0,-1},{7025,5,1,-1},{7028,5,0,-1},{7029,5,1,-1},{7033,5,0,-1}, +{7034,5,1,-1},{7037,5,0,-1},{7038,5,1,-1},{7041,5,0,-1},{7042,5,1,-1}, +{7046,5,0,-1},{7047,5,1,-1},{7050,5,0,-1},{7051,5,1,-1},{7054,5,0,-1}, +{7055,5,1,-1},{7059,5,0,-1},{7060,5,1,-1},{7063,5,0,-1},{7064,5,1,-1}, +{7067,5,0,-1},{7068,5,1,-1},{7072,5,0,-1},{7073,5,1,-1},{7076,5,0,-1}, +{7077,5,1,-1},{7080,5,0,-1},{7081,5,1,-1},{7085,5,0,-1},{7086,5,1,-1}, +{7089,5,0,-1},{7090,5,1,-1},{7093,5,0,-1},{7094,5,1,-1},{7098,5,0,-1}, +{7099,5,1,-1},{7032,5,0,-1},{7102,5,0,-1},{7103,5,1,-1},{7106,5,0,-1}, +{7107,5,1,-1},{7111,5,0,-1},{7112,5,1,-1},{7045,5,1,-1},{7115,5,0,-1}, +{7116,5,1,-1},{7119,5,0,-1},{7120,5,1,-1},{7124,5,0,-1},{7125,5,1,-1}, +{7128,5,0,-1},{7129,5,1,-1},{7132,5,0,-1},{7133,5,1,-1},{7137,5,0,-1}, +{7138,5,1,-1},{7141,5,0,-1},{7142,5,1,-1},{7145,5,0,-1},{7146,5,1,-1}, +{7150,5,0,-1},{7151,5,1,-1},{7154,5,0,-1},{7155,5,1,-1},{7158,5,0,-1}, +{7159,5,1,-1},{7163,5,0,-1},{7164,5,1,-1},{7167,5,0,-1},{7168,5,1,-1}, +{7171,5,0,-1},{7172,5,1,-1},{7176,5,0,-1},{7177,5,1,-1},{7180,5,0,-1}, +{7181,5,1,-1},{7184,5,0,-1},{7185,5,1,-1},{7189,5,0,-1},{7190,5,1,-1}, +{7193,5,0,-1},{7194,5,1,-1},{7197,5,0,-1},{7198,5,1,-1},{7202,5,0,-1}, +{7203,5,1,-1},{7136,5,0,-1},{7206,5,0,-1},{7207,5,1,-1},{7210,5,0,-1}, +{7211,5,1,-1},{7215,5,0,-1},{7216,5,1,-1},{7149,5,1,-1},{7219,5,0,-1}, +{7220,5,1,-1},{7223,5,0,-1},{7224,5,1,-1},{7228,5,0,-1},{7229,5,1,-1}, +{7232,5,0,-1},{7233,5,1,-1},{7236,5,0,-1},{7237,5,1,-1},{7241,5,0,-1}, +{7242,5,1,-1},{7245,5,0,-1},{7246,5,1,-1},{7249,5,0,-1},{7250,5,1,-1}, +{7254,5,0,-1},{7255,5,1,-1},{7258,5,0,-1},{7259,5,1,-1},{7262,5,0,-1}, +{7263,5,1,-1},{7267,5,0,-1},{7268,5,1,-1},{7271,5,0,-1},{7272,5,1,-1}, +{7275,5,0,-1},{7276,5,1,-1},{7280,5,0,-1},{7281,5,1,-1},{7284,5,0,-1}, +{7285,5,1,-1},{7288,5,0,-1},{7289,5,1,-1},{7293,5,0,-1},{7294,5,1,-1}, +{7297,5,0,-1},{7298,5,1,-1},{7301,5,0,-1},{7302,5,1,-1},{7306,5,0,-1}, +{7307,5,1,-1},{7240,5,0,-1},{7310,5,0,-1},{7311,5,1,-1},{7314,5,0,-1}, +{7315,5,1,-1},{7319,5,0,-1},{7320,5,1,-1},{7253,5,1,-1},{7323,5,0,-1}, +{7324,5,1,-1},{7327,5,0,-1},{7328,5,1,-1},{7332,5,0,-1},{7333,5,1,-1}, +{7336,5,0,-1},{7337,5,1,-1},{7340,5,0,-1},{7341,5,1,-1},{7345,5,0,-1}, +{7346,5,1,-1},{7349,5,0,-1},{7350,5,1,-1},{7353,5,0,-1},{7354,5,1,-1}, +{7358,5,0,-1},{7359,5,1,-1},{7362,5,0,-1},{7363,5,1,-1},{7366,5,0,-1}, +{7367,5,1,-1},{7371,5,0,-1},{7372,5,1,-1},{7375,5,0,-1},{7376,5,1,-1}, +{7379,5,0,-1},{7380,5,1,-1},{7384,5,0,-1},{7385,5,1,-1},{7388,5,0,-1}, +{7389,5,1,-1},{7392,5,0,-1},{7393,5,1,-1},{7397,5,0,-1},{7398,5,1,-1}, +{7401,5,0,-1},{7402,5,1,-1},{7405,5,0,-1},{7406,5,1,-1},{7410,5,0,-1}, +{7411,5,1,-1},{7344,5,0,-1},{7414,5,0,-1},{7415,5,1,-1},{7418,5,0,-1}, +{7419,5,1,-1},{7423,5,0,-1},{7424,5,1,-1},{7357,5,1,-1},{7427,5,0,-1}, +{7428,5,1,-1},{7431,5,0,-1},{7432,5,1,-1},{7436,5,0,-1},{7437,5,1,-1}, +{7440,5,0,-1},{7441,5,1,-1},{7444,5,0,-1},{7445,5,1,-1},{7449,5,0,-1}, +{7450,5,1,-1},{7453,5,0,-1},{7454,5,1,-1},{7457,5,0,-1},{7458,5,1,-1}, +{7462,5,0,-1},{7463,5,1,-1},{7466,5,0,-1},{7467,5,1,-1},{7470,5,0,-1}, +{7471,5,1,-1},{7475,5,0,-1},{7476,5,1,-1},{7479,5,0,-1},{7480,5,1,-1}, +{7483,5,0,-1},{7484,5,1,-1},{7488,5,0,-1},{7489,5,1,-1},{7492,5,0,-1}, +{7493,5,1,-1},{7496,5,0,-1},{7497,5,1,-1},{7501,5,0,-1},{7502,5,1,-1}, +{7505,5,0,-1},{7506,5,1,-1},{7509,5,0,-1},{7510,5,1,-1},{7514,5,0,-1}, +{7515,5,1,-1},{7448,5,0,-1},{7518,5,0,-1},{7519,5,1,-1},{7522,5,0,-1}, +{7523,5,1,-1},{7527,5,0,-1},{7528,5,1,-1},{7461,5,1,-1},{7531,5,0,-1}, +{7532,5,1,-1},{7535,5,0,-1},{7536,5,1,-1},{7540,5,0,-1},{7541,5,1,-1}, +{7544,5,0,-1},{7545,5,1,-1},{7548,5,0,-1},{7549,5,1,-1},{7553,5,0,-1}, +{7554,5,1,-1},{7557,5,0,-1},{7558,5,1,-1},{7561,5,0,-1},{7562,5,1,-1}, +{7566,5,0,-1},{7567,5,1,-1},{7570,5,0,-1},{7571,5,1,-1},{7574,5,0,-1}, +{7575,5,1,-1},{7579,5,0,-1},{7580,5,1,-1},{7583,5,0,-1},{7584,5,1,-1}, +{7587,5,0,-1},{7588,5,1,-1},{7592,5,0,-1},{7593,5,1,-1},{7596,5,0,-1}, +{7597,5,1,-1},{7600,5,0,-1},{7601,5,1,-1},{7605,5,0,-1},{7606,5,1,-1}, +{7609,5,0,-1},{7610,5,1,-1},{7613,5,0,-1},{7614,5,1,-1},{7618,5,0,-1}, +{7619,5,1,-1},{7552,5,0,-1},{7622,5,0,-1},{7623,5,1,-1},{7626,5,0,-1}, +{7627,5,1,-1},{7631,5,0,-1},{7632,5,1,-1},{7565,5,1,-1},{7635,5,0,-1}, +{7636,5,1,-1},{7639,5,0,-1},{7640,5,1,-1},{7644,5,0,-1},{7645,5,1,-1}, +{7648,5,0,-1},{7649,5,1,-1},{7652,5,0,-1},{7653,5,1,-1},{7657,5,0,-1}, +{7658,5,1,-1},{7661,5,0,-1},{7662,5,1,-1},{7665,5,0,-1},{7666,5,1,-1}, +{7670,5,0,-1},{7671,5,1,-1},{7674,5,0,-1},{7675,5,1,-1},{7678,5,0,-1}, +{7679,5,1,-1},{7683,5,0,-1},{7684,5,1,-1},{7687,5,0,-1},{7688,5,1,-1}, +{7691,5,0,-1},{7692,5,1,-1},{7696,5,0,-1},{7697,5,1,-1},{7700,5,0,-1}, +{7701,5,1,-1},{7704,5,0,-1},{7705,5,1,-1},{7709,5,0,-1},{7710,5,1,-1}, +{7713,5,0,-1},{7714,5,1,-1},{7717,5,0,-1},{7718,5,1,-1},{7722,5,0,-1}, +{7723,5,1,-1},{7656,5,0,-1},{7726,5,0,-1},{7727,5,1,-1},{7730,5,0,-1}, +{7731,5,1,-1},{7735,5,0,-1},{7736,5,1,-1},{7669,5,1,-1},{7739,5,0,-1}, +{7740,5,1,-1},{7743,5,0,-1},{7744,5,1,-1},{7748,5,0,-1},{7749,5,1,-1}, +{7752,5,0,-1},{7753,5,1,-1},{7756,5,0,-1},{7757,5,1,-1},{7761,5,0,-1}, +{7762,5,1,-1},{7765,5,0,-1},{7766,5,1,-1},{7769,5,0,-1},{7770,5,1,-1}, +{7774,5,0,-1},{7775,5,1,-1},{7778,5,0,-1},{7779,5,1,-1},{7782,5,0,-1}, +{7783,5,1,-1},{7787,5,0,-1},{7788,5,1,-1},{7791,5,0,-1},{7792,5,1,-1}, +{7795,5,0,-1},{7796,5,1,-1},{7800,5,0,-1},{7801,5,1,-1},{7804,5,0,-1}, +{7805,5,1,-1},{7808,5,0,-1},{7809,5,1,-1},{7813,5,0,-1},{7814,5,1,-1}, +{7817,5,0,-1},{7818,5,1,-1},{7821,5,0,-1},{7822,5,1,-1},{7826,5,0,-1}, +{7827,5,1,-1},{7760,5,0,-1},{7830,5,0,-1},{7831,5,1,-1},{7834,5,0,-1}, +{7835,5,1,-1},{7839,5,0,-1},{7840,5,1,-1},{7773,5,1,-1},{7843,5,0,-1}, +{7844,5,1,-1},{7847,5,0,-1},{7848,5,1,-1},{7852,5,0,-1},{7853,5,1,-1}, +{7856,5,0,-1},{7857,5,1,-1},{7860,5,0,-1},{7861,5,1,-1},{7865,5,0,-1}, +{7866,5,1,-1},{7869,5,0,-1},{7870,5,1,-1},{7873,5,0,-1},{7874,5,1,-1}, +{7878,5,0,-1},{7879,5,1,-1},{7882,5,0,-1},{7883,5,1,-1},{7886,5,0,-1}, +{7887,5,1,-1},{7891,5,0,-1},{7892,5,1,-1},{7895,5,0,-1},{7896,5,1,-1}, +{7899,5,0,-1},{7900,5,1,-1},{7904,5,0,-1},{7905,5,1,-1},{7908,5,0,-1}, +{7909,5,1,-1},{7912,5,0,-1},{7913,5,1,-1},{7917,5,0,-1},{7918,5,1,-1}, +{7921,5,0,-1},{7922,5,1,-1},{7925,5,0,-1},{7926,5,1,-1},{7930,5,0,-1}, +{7931,5,1,-1},{7864,5,0,-1},{7934,5,0,-1},{7935,5,1,-1},{7938,5,0,-1}, +{7939,5,1,-1},{7943,5,0,-1},{7944,5,1,-1},{7877,5,1,-1},{7947,5,0,-1}, +{7948,5,1,-1},{7951,5,0,-1},{7952,5,1,-1},{7956,5,0,-1},{7957,5,1,-1}, +{7960,5,0,-1},{7961,5,1,-1},{7964,5,0,-1},{7965,5,1,-1},{7969,5,0,-1}, +{7970,5,1,-1},{7973,5,0,-1},{7974,5,1,-1},{7977,5,0,-1},{7978,5,1,-1}, +{7982,5,0,-1},{7983,5,1,-1},{7986,5,0,-1},{7987,5,1,-1},{7990,5,0,-1}, +{7991,5,1,-1},{7995,5,0,-1},{7996,5,1,-1},{7999,5,0,-1},{8000,5,1,-1}, +{8003,5,0,-1},{8004,5,1,-1},{8008,5,0,-1},{8009,5,1,-1},{8012,5,0,-1}, +{8013,5,1,-1},{8016,5,0,-1},{8017,5,1,-1},{8021,5,0,-1},{8022,5,1,-1}, +{8025,5,0,-1},{8026,5,1,-1},{8029,5,0,-1},{8030,5,1,-1},{8034,5,0,-1}, +{8035,5,1,-1},{7968,5,0,-1},{8038,5,0,-1},{8039,5,1,-1},{8042,5,0,-1}, +{8043,5,1,-1},{8047,5,0,-1},{8048,5,1,-1},{7981,5,1,-1},{8051,5,0,-1}, +{8052,5,1,-1},{8055,5,0,-1},{8056,5,1,-1},{8060,5,0,-1},{8061,5,1,-1}, +{8064,5,0,-1},{8065,5,1,-1},{8068,5,0,-1},{8069,5,1,-1},{8073,5,0,-1}, +{8074,5,1,-1},{8077,5,0,-1},{8078,5,1,-1},{8081,5,0,-1},{8082,5,1,-1}, +{8086,5,0,-1},{8087,5,1,-1},{8090,5,0,-1},{8091,5,1,-1},{8094,5,0,-1}, +{8095,5,1,-1},{8099,5,0,-1},{8100,5,1,-1},{8103,5,0,-1},{8104,5,1,-1}, +{8107,5,0,-1},{8108,5,1,-1},{8112,5,0,-1},{8113,5,1,-1},{8116,5,0,-1}, +{8117,5,1,-1},{8120,5,0,-1},{8121,5,1,-1},{8125,5,0,-1},{8126,5,1,-1}, +{8129,5,0,-1},{8130,5,1,-1},{8133,5,0,-1},{8134,5,1,-1},{8138,5,0,-1}, +{8139,5,1,-1},{8072,5,0,-1},{8142,5,0,-1},{8143,5,1,-1},{8146,5,0,-1}, +{8147,5,1,-1},{8151,5,0,-1},{8152,5,1,-1},{8085,5,1,-1},{8155,5,0,-1}, +{8156,5,1,-1},{8159,5,0,-1},{8160,5,1,-1},{8164,5,0,-1},{8165,5,1,-1}, +{8168,5,0,-1},{8169,5,1,-1},{8172,5,0,-1},{8173,5,1,-1},{8177,5,0,-1}, +{8178,5,1,-1},{8181,5,0,-1},{8182,5,1,-1},{8185,5,0,-1},{8186,5,1,-1}, +{8190,5,0,-1},{8191,5,1,-1},{8194,5,0,-1},{8195,5,1,-1},{8198,5,0,-1}, +{8199,5,1,-1},{8203,5,0,-1},{8204,5,1,-1},{8207,5,0,-1},{8208,5,1,-1}, +{8211,5,0,-1},{8212,5,1,-1},{8216,5,0,-1},{8217,5,1,-1},{8220,5,0,-1}, +{8221,5,1,-1},{8224,5,0,-1},{8225,5,1,-1},{8229,5,0,-1},{8230,5,1,-1}, +{8233,5,0,-1},{8234,5,1,-1},{8237,5,0,-1},{8238,5,1,-1},{8242,5,0,-1}, +{8243,5,1,-1},{8176,5,0,-1},{8246,5,0,-1},{8247,5,1,-1},{8250,5,0,-1}, +{8251,5,1,-1},{8255,5,0,-1},{8256,5,1,-1},{8189,5,1,-1},{8259,5,0,-1}, +{8260,5,1,-1},{8263,5,0,-1},{8264,5,1,-1},{8268,5,0,-1},{8269,5,1,-1}, +{8272,5,0,-1},{8273,5,1,-1},{8276,5,0,-1},{8277,5,1,-1},{8281,5,0,-1}, +{8282,5,1,-1},{8285,5,0,-1},{8286,5,1,-1},{8289,5,0,-1},{8290,5,1,-1}, +{8294,5,0,-1},{8295,5,1,-1},{8298,5,0,-1},{8299,5,1,-1},{8302,5,0,-1}, +{8303,5,1,-1},{8307,5,0,-1},{8308,5,1,-1},{8311,5,0,-1},{8312,5,1,-1}, +{8315,5,0,-1},{8316,5,1,-1},{8320,5,0,-1},{8321,5,1,-1},{8324,5,0,-1}, +{8325,5,1,-1},{8328,5,0,-1},{8329,5,1,-1},{8333,5,0,-1},{8334,5,1,-1}, +{8337,5,0,-1},{8338,5,1,-1},{8341,5,0,-1},{8342,5,1,-1},{8346,5,0,-1}, +{8347,5,1,-1},{8280,5,0,-1},{8350,5,0,-1},{8351,5,1,-1},{8354,5,0,-1}, +{8355,5,1,-1},{8359,5,0,-1},{8360,5,1,-1},{8293,5,1,-1},{8363,5,0,-1}, +{8364,5,1,-1},{8367,5,0,-1},{8368,5,1,-1},{8372,5,0,-1},{8373,5,1,-1}, +{8376,5,0,-1},{8377,5,1,-1},{8380,5,0,-1},{8381,5,1,-1},{8385,5,0,-1}, +{8386,5,1,-1},{8389,5,0,-1},{8390,5,1,-1},{8393,5,0,-1},{8394,5,1,-1}, +{8398,5,0,-1},{8399,5,1,-1},{8402,5,0,-1},{8403,5,1,-1},{8406,5,0,-1}, +{8407,5,1,-1},{8411,5,0,-1},{8412,5,1,-1},{8415,5,0,-1},{8416,5,1,-1}, +{8419,5,0,-1},{8420,5,1,-1},{8424,5,0,-1},{8425,5,1,-1},{8428,5,0,-1}, +{8429,5,1,-1},{8432,5,0,-1},{8433,5,1,-1},{8437,5,0,-1},{8438,5,1,-1}, +{8441,5,0,-1},{8442,5,1,-1},{8445,5,0,-1},{8446,5,1,-1},{8450,5,0,-1}, +{8451,5,1,-1},{8384,5,0,-1},{8454,5,0,-1},{8455,5,1,-1},{8458,5,0,-1}, +{8459,5,1,-1},{8463,5,0,-1},{8464,5,1,-1},{8397,5,1,-1},{8467,5,0,-1}, +{8468,5,1,-1},{8471,5,0,-1},{8472,5,1,-1},{8476,5,0,-1},{8477,5,1,-1}, +{8480,5,0,-1},{8481,5,1,-1},{8484,5,0,-1},{8485,5,1,-1},{8489,5,0,-1}, +{8490,5,1,-1},{8493,5,0,-1},{8494,5,1,-1},{8497,5,0,-1},{8498,5,1,-1}, +{8502,5,0,-1},{8503,5,1,-1},{8506,5,0,-1},{8507,5,1,-1},{8510,5,0,-1}, +{8511,5,1,-1},{8515,5,0,-1},{8516,5,1,-1},{8519,5,0,-1},{8520,5,1,-1}, +{8523,5,0,-1},{8524,5,1,-1},{8528,5,0,-1},{8529,5,1,-1},{8532,5,0,-1}, +{8533,5,1,-1},{8536,5,0,-1},{8537,5,1,-1},{8541,5,0,-1},{8545,5,0,-1}, +{8549,5,0,-1},{8554,5,0,-1},{8488,5,0,-1},{8558,5,0,-1},{8562,5,0,-1}, +{8567,5,0,-1},{8501,5,1,-1},{8571,5,0,-1},{8575,5,0,-1},{8580,5,0,-1}, +{8584,5,0,-1},{8585,5,1,-1},{8588,5,0,-1},{8589,5,1,-1},{8597,5,0,-1}, +{8601,5,0,-1},{8606,5,0,-1},{8610,5,0,-1},{8614,5,0,-1},{8619,5,0,-1}, +{8623,5,0,-1},{8627,5,0,-1},{8632,5,0,-1},{8636,5,0,-1},{8640,5,0,-1}, +{8641,5,1,-1},{8649,5,0,-1},{8653,5,0,-1},{8658,5,0,-1},{8662,5,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS6, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_6_ss0_ss1[] = { +{8112,6,0,-1},{8120,6,0,-1},{8129,6,0,-1},{8138,6,0,-1},{8146,6,0,-1}, +{8155,6,0,-1},{8164,6,0,-1},{8098,6,0,-1},{8172,6,0,-1},{8181,6,0,-1}, +{8190,6,0,-1},{8198,6,0,-1},{8207,6,0,-1},{8211,6,0,-1},{8216,6,0,-1}, +{8220,6,0,-1},{8224,6,0,-1},{8229,6,0,-1},{8233,6,0,-1},{8237,6,0,-1}, +{8242,6,0,-1},{8246,6,0,-1},{8250,6,0,-1},{8255,6,0,-1},{8259,6,0,-1}, +{8263,6,0,-1},{8268,6,0,-1},{8202,6,0,-1},{8272,6,0,-1},{8276,6,0,-1}, +{8281,6,0,-1},{8285,6,0,-1},{8289,6,0,-1},{8294,6,0,-1},{8298,6,0,-1}, +{8302,6,0,-1},{8307,6,0,-1},{8311,6,0,-1},{8315,6,0,-1},{8320,6,0,-1}, +{8324,6,0,-1},{8328,6,0,-1},{8333,6,0,-1},{8337,6,0,-1},{8341,6,0,-1}, +{8346,6,0,-1},{8350,6,0,-1},{8354,6,0,-1},{8355,6,1,-1},{8359,6,0,-1}, +{8363,6,0,-1},{8364,6,1,-1},{8367,6,0,-1},{8372,6,0,-1},{8373,6,1,-1}, +{8306,6,0,-1},{8376,6,0,-1},{8380,6,0,-1},{8381,6,1,-1},{8385,6,0,-1}, +{8389,6,0,-1},{8390,6,1,-1},{8393,6,0,-1},{8398,6,0,-1},{8399,6,1,-1}, +{8402,6,0,-1},{8406,6,0,-1},{8407,6,1,-1},{8411,6,0,-1},{8412,6,1,-1}, +{8415,6,0,-1},{8416,6,1,-1},{8419,6,0,-1},{8420,6,1,-1},{8424,6,0,-1}, +{8425,6,1,-1},{8428,6,0,-1},{8432,6,0,-1},{8433,6,1,-1},{8437,6,0,-1}, +{8441,6,0,-1},{8442,6,1,-1},{8445,6,0,-1},{8450,6,0,-1},{8451,6,1,-1}, +{8454,6,0,-1},{8458,6,0,-1},{8459,6,1,-1},{8463,6,0,-1},{8467,6,0,-1}, +{8468,6,1,-1},{8471,6,0,-1},{8476,6,0,-1},{8477,6,1,-1},{8410,6,0,-1}, +{8480,6,0,-1},{8484,6,0,-1},{8485,6,1,-1},{8489,6,0,-1},{8490,6,1,-1}, +{8423,6,1,-1},{8493,6,0,-1},{8494,6,1,-1},{8497,6,0,-1},{8498,6,1,-1}, +{8502,6,0,-1},{8503,6,1,-1},{8506,6,0,-1},{8507,6,1,-1},{8510,6,0,-1}, +{8511,6,1,-1},{8515,6,0,-1},{8519,6,0,-1},{8520,6,1,-1},{8523,6,0,-1}, +{8524,6,1,-1},{8528,6,0,-1},{8529,6,1,-1},{8532,6,0,-1},{8533,6,1,-1}, +{8536,6,0,-1},{8537,6,1,-1},{8541,6,0,-1},{8542,6,1,-1},{8545,6,0,-1}, +{8546,6,1,-1},{8549,6,0,-1},{8550,6,1,-1},{8554,6,0,-1},{8555,6,1,-1}, +{8558,6,0,-1},{8559,6,1,-1},{8562,6,0,-1},{8563,6,1,-1},{8567,6,0,-1}, +{8568,6,1,-1},{8571,6,0,-1},{8572,6,1,-1},{8575,6,0,-1},{8576,6,1,-1}, +{8580,6,0,-1},{8581,6,1,-1},{8514,6,0,-1},{8584,6,0,-1},{8585,6,1,-1}, +{8588,6,0,-1},{8589,6,1,-1},{8593,6,0,-1},{8594,6,1,-1},{8527,6,1,-1}, +{8597,6,0,-1},{8598,6,1,-1},{8601,6,0,-1},{8602,6,1,-1},{8606,6,0,-1}, +{8607,6,1,-1},{8610,6,0,-1},{8611,6,1,-1},{8614,6,0,-1},{8615,6,1,-1}, +{8619,6,0,-1},{8620,6,1,-1},{8623,6,0,-1},{8624,6,1,-1},{8627,6,0,-1}, +{8628,6,1,-1},{8632,6,0,-1},{8633,6,1,-1},{8636,6,0,-1},{8637,6,1,-1}, +{8640,6,0,-1},{8641,6,1,-1},{8645,6,0,-1},{8646,6,1,-1},{8649,6,0,-1}, +{8650,6,1,-1},{8653,6,0,-1},{8654,6,1,-1},{8658,6,0,-1},{8659,6,1,-1}, +{8662,6,0,-1},{8663,6,1,-1},{8666,6,0,-1},{8667,6,1,-1},{8671,6,0,-1}, +{8672,6,1,-1},{8675,6,0,-1},{8676,6,1,-1},{8679,6,0,-1},{8680,6,1,-1}, +{8684,6,0,-1},{8685,6,1,-1},{8618,6,0,-1},{8688,6,0,-1},{8689,6,1,-1}, +{8692,6,0,-1},{8693,6,1,-1},{8697,6,0,-1},{8698,6,1,-1},{8631,6,1,-1}, +{8701,6,0,-1},{8702,6,1,-1},{8705,6,0,-1},{8706,6,1,-1},{8710,6,0,-1}, +{8711,6,1,-1},{8714,6,0,-1},{8715,6,1,-1},{8718,6,0,-1},{8719,6,1,-1}, +{8723,6,0,-1},{8724,6,1,-1},{8727,6,0,-1},{8728,6,1,-1},{8731,6,0,-1}, +{8732,6,1,-1},{8736,6,0,-1},{8737,6,1,-1},{8740,6,0,-1},{8741,6,1,-1}, +{8744,6,0,-1},{8745,6,1,-1},{8749,6,0,-1},{8750,6,1,-1},{8753,6,0,-1}, +{8754,6,1,-1},{8757,6,0,-1},{8758,6,1,-1},{8762,6,0,-1},{8763,6,1,-1}, +{8766,6,0,-1},{8770,6,0,-1},{8771,6,1,-1},{8775,6,0,-1},{8776,6,1,-1}, +{8779,6,0,-1},{8780,6,1,-1},{8783,6,0,-1},{8784,6,1,-1},{8788,6,0,-1}, +{8789,6,1,-1},{8722,6,0,-1},{8792,6,0,-1},{8793,6,1,-1},{8796,6,0,-1}, +{8797,6,1,-1},{8801,6,0,-1},{8802,6,1,-1},{8735,6,1,-1},{8805,6,0,-1}, +{8806,6,1,-1},{8810,6,1,-1},{8814,6,0,-1},{8815,6,1,-1},{8818,6,0,-1}, +{8822,6,0,-1},{8823,6,1,-1},{8827,6,0,-1},{8828,6,1,-1},{8831,6,0,-1}, +{8832,6,1,-1},{8835,6,0,-1},{8836,6,1,-1},{8840,6,0,-1},{8841,6,1,-1}, +{8844,6,0,-1},{8845,6,1,-1},{8848,6,0,-1},{8849,6,1,-1},{8853,6,0,-1}, +{8854,6,1,-1},{8857,6,0,-1},{8858,6,1,-1},{8861,6,0,-1},{8862,6,1,-1}, +{8866,6,0,-1},{8867,6,1,-1},{8870,6,0,-1},{8871,6,1,-1},{8874,6,0,-1}, +{8875,6,1,-1},{8879,6,0,-1},{8880,6,1,-1},{8883,6,0,-1},{8884,6,1,-1}, +{8887,6,0,-1},{8888,6,1,-1},{8892,6,0,-1},{8893,6,1,-1},{8826,6,0,-1}, +{8896,6,0,-1},{8897,6,1,-1},{8900,6,0,-1},{8901,6,1,-1},{8905,6,0,-1}, +{8906,6,1,-1},{8839,6,1,-1},{8909,6,0,-1},{8910,6,1,-1},{8913,6,0,-1}, +{8914,6,1,-1},{8918,6,0,-1},{8919,6,1,-1},{8922,6,0,-1},{8923,6,1,-1}, +{8926,6,0,-1},{8927,6,1,-1},{8931,6,0,-1},{8932,6,1,-1},{8935,6,0,-1}, +{8936,6,1,-1},{8939,6,0,-1},{8940,6,1,-1},{8944,6,0,-1},{8945,6,1,-1}, +{8948,6,0,-1},{8949,6,1,-1},{8952,6,0,-1},{8953,6,1,-1},{8957,6,0,-1}, +{8958,6,1,-1},{8961,6,0,-1},{8962,6,1,-1},{8965,6,0,-1},{8966,6,1,-1}, +{8970,6,0,-1},{8971,6,1,-1},{8974,6,0,-1},{8975,6,1,-1},{8978,6,0,-1}, +{8979,6,1,-1},{8983,6,0,-1},{8984,6,1,-1},{8987,6,0,-1},{8988,6,1,-1}, +{8991,6,0,-1},{8992,6,1,-1},{8996,6,0,-1},{8997,6,1,-1},{8930,6,0,-1}, +{9000,6,0,-1},{9001,6,1,-1},{9004,6,0,-1},{9005,6,1,-1},{9009,6,0,-1}, +{9010,6,1,-1},{8943,6,1,-1},{9013,6,0,-1},{9014,6,1,-1},{9017,6,0,-1}, +{9018,6,1,-1},{9022,6,0,-1},{9023,6,1,-1},{9026,6,0,-1},{9027,6,1,-1}, +{9030,6,0,-1},{9031,6,1,-1},{9035,6,0,-1},{9036,6,1,-1},{9039,6,0,-1}, +{9040,6,1,-1},{9043,6,0,-1},{9044,6,1,-1},{9048,6,0,-1},{9049,6,1,-1}, +{9052,6,0,-1},{9053,6,1,-1},{9056,6,0,-1},{9057,6,1,-1},{9061,6,0,-1}, +{9062,6,1,-1},{9065,6,0,-1},{9066,6,1,-1},{9069,6,0,-1},{9070,6,1,-1}, +{9074,6,0,-1},{9075,6,1,-1},{9078,6,0,-1},{9079,6,1,-1},{9082,6,0,-1}, +{9083,6,1,-1},{9087,6,0,-1},{9088,6,1,-1},{9091,6,0,-1},{9092,6,1,-1}, +{9095,6,0,-1},{9096,6,1,-1},{9100,6,0,-1},{9101,6,1,-1},{9034,6,0,-1}, +{9104,6,0,-1},{9105,6,1,-1},{9108,6,0,-1},{9109,6,1,-1},{9113,6,0,-1}, +{9114,6,1,-1},{9047,6,1,-1},{9117,6,0,-1},{9118,6,1,-1},{9121,6,0,-1}, +{9122,6,1,-1},{9126,6,0,-1},{9127,6,1,-1},{9130,6,0,-1},{9131,6,1,-1}, +{9134,6,0,-1},{9135,6,1,-1},{9139,6,0,-1},{9140,6,1,-1},{9143,6,0,-1}, +{9144,6,1,-1},{9147,6,0,-1},{9148,6,1,-1},{9152,6,0,-1},{9153,6,1,-1}, +{9156,6,0,-1},{9157,6,1,-1},{9160,6,0,-1},{9161,6,1,-1},{9165,6,0,-1}, +{9166,6,1,-1},{9169,6,0,-1},{9170,6,1,-1},{9173,6,0,-1},{9174,6,1,-1}, +{9178,6,0,-1},{9179,6,1,-1},{9182,6,0,-1},{9183,6,1,-1},{9186,6,0,-1}, +{9187,6,1,-1},{9191,6,0,-1},{9192,6,1,-1},{9195,6,0,-1},{9196,6,1,-1}, +{9199,6,0,-1},{9200,6,1,-1},{9204,6,0,-1},{9205,6,1,-1},{9138,6,0,-1}, +{9208,6,0,-1},{9209,6,1,-1},{9212,6,0,-1},{9213,6,1,-1},{9217,6,0,-1}, +{9218,6,1,-1},{9151,6,1,-1},{9221,6,0,-1},{9222,6,1,-1},{9225,6,0,-1}, +{9226,6,1,-1},{9230,6,0,-1},{9231,6,1,-1},{9234,6,0,-1},{9235,6,1,-1}, +{9238,6,0,-1},{9239,6,1,-1},{9243,6,0,-1},{9244,6,1,-1},{9247,6,0,-1}, +{9248,6,1,-1},{9251,6,0,-1},{9252,6,1,-1},{9256,6,0,-1},{9257,6,1,-1}, +{9260,6,0,-1},{9261,6,1,-1},{9264,6,0,-1},{9265,6,1,-1},{9269,6,0,-1}, +{9270,6,1,-1},{9273,6,0,-1},{9274,6,1,-1},{9277,6,0,-1},{9278,6,1,-1}, +{9282,6,0,-1},{9283,6,1,-1},{9286,6,0,-1},{9287,6,1,-1},{9290,6,0,-1}, +{9291,6,1,-1},{9295,6,0,-1},{9296,6,1,-1},{9299,6,0,-1},{9300,6,1,-1}, +{9303,6,0,-1},{9304,6,1,-1},{9308,6,0,-1},{9309,6,1,-1},{9242,6,0,-1}, +{9312,6,0,-1},{9313,6,1,-1},{9316,6,0,-1},{9317,6,1,-1},{9321,6,0,-1}, +{9322,6,1,-1},{9255,6,1,-1},{9325,6,0,-1},{9326,6,1,-1},{9329,6,0,-1}, +{9330,6,1,-1},{9334,6,0,-1},{9335,6,1,-1},{9338,6,0,-1},{9339,6,1,-1}, +{9342,6,0,-1},{9343,6,1,-1},{9347,6,0,-1},{9348,6,1,-1},{9351,6,0,-1}, +{9352,6,1,-1},{9355,6,0,-1},{9356,6,1,-1},{9360,6,0,-1},{9361,6,1,-1}, +{9364,6,0,-1},{9365,6,1,-1},{9368,6,0,-1},{9369,6,1,-1},{9373,6,0,-1}, +{9374,6,1,-1},{9377,6,0,-1},{9378,6,1,-1},{9381,6,0,-1},{9382,6,1,-1}, +{9386,6,0,-1},{9387,6,1,-1},{9390,6,0,-1},{9391,6,1,-1},{9394,6,0,-1}, +{9395,6,1,-1},{9399,6,0,-1},{9400,6,1,-1},{9403,6,0,-1},{9404,6,1,-1}, +{9407,6,0,-1},{9408,6,1,-1},{9412,6,0,-1},{9413,6,1,-1},{9346,6,0,-1}, +{9416,6,0,-1},{9417,6,1,-1},{9420,6,0,-1},{9421,6,1,-1},{9425,6,0,-1}, +{9426,6,1,-1},{9359,6,1,-1},{9429,6,0,-1},{9430,6,1,-1},{9433,6,0,-1}, +{9434,6,1,-1},{9438,6,0,-1},{9439,6,1,-1},{9442,6,0,-1},{9443,6,1,-1}, +{9446,6,0,-1},{9447,6,1,-1},{9451,6,0,-1},{9452,6,1,-1},{9455,6,0,-1}, +{9456,6,1,-1},{9459,6,0,-1},{9460,6,1,-1},{9464,6,0,-1},{9465,6,1,-1}, +{9468,6,0,-1},{9469,6,1,-1},{9472,6,0,-1},{9473,6,1,-1},{9477,6,0,-1}, +{9478,6,1,-1},{9481,6,0,-1},{9482,6,1,-1},{9485,6,0,-1},{9486,6,1,-1}, +{9490,6,0,-1},{9491,6,1,-1},{9494,6,0,-1},{9495,6,1,-1},{9498,6,0,-1}, +{9499,6,1,-1},{9503,6,0,-1},{9504,6,1,-1},{9507,6,0,-1},{9508,6,1,-1}, +{9511,6,0,-1},{9512,6,1,-1},{9516,6,0,-1},{9517,6,1,-1},{9450,6,0,-1}, +{9520,6,0,-1},{9521,6,1,-1},{9524,6,0,-1},{9525,6,1,-1},{9529,6,0,-1}, +{9530,6,1,-1},{9463,6,1,-1},{9533,6,0,-1},{9534,6,1,-1},{9537,6,0,-1}, +{9538,6,1,-1},{9542,6,0,-1},{9543,6,1,-1},{9546,6,0,-1},{9547,6,1,-1}, +{9550,6,0,-1},{9551,6,1,-1},{9555,6,0,-1},{9556,6,1,-1},{9559,6,0,-1}, +{9560,6,1,-1},{9563,6,0,-1},{9564,6,1,-1},{9568,6,0,-1},{9569,6,1,-1}, +{9572,6,0,-1},{9573,6,1,-1},{9576,6,0,-1},{9577,6,1,-1},{9581,6,0,-1}, +{9582,6,1,-1},{9585,6,0,-1},{9586,6,1,-1},{9589,6,0,-1},{9590,6,1,-1}, +{9594,6,0,-1},{9595,6,1,-1},{9598,6,0,-1},{9599,6,1,-1},{9602,6,0,-1}, +{9603,6,1,-1},{9607,6,0,-1},{9608,6,1,-1},{9611,6,0,-1},{9612,6,1,-1}, +{9615,6,0,-1},{9616,6,1,-1},{9620,6,0,-1},{9621,6,1,-1},{9554,6,0,-1}, +{9624,6,0,-1},{9625,6,1,-1},{9628,6,0,-1},{9629,6,1,-1},{9633,6,0,-1}, +{9634,6,1,-1},{9567,6,1,-1},{9637,6,0,-1},{9638,6,1,-1},{9641,6,0,-1}, +{9642,6,1,-1},{9646,6,0,-1},{9647,6,1,-1},{9650,6,0,-1},{9651,6,1,-1}, +{9654,6,0,-1},{9655,6,1,-1},{9659,6,0,-1},{9660,6,1,-1},{9663,6,0,-1}, +{9664,6,1,-1},{9667,6,0,-1},{9668,6,1,-1},{9672,6,0,-1},{9673,6,1,-1}, +{9676,6,0,-1},{9677,6,1,-1},{9680,6,0,-1},{9681,6,1,-1},{9685,6,0,-1}, +{9686,6,1,-1},{9689,6,0,-1},{9690,6,1,-1},{9693,6,0,-1},{9694,6,1,-1}, +{9698,6,0,-1},{9699,6,1,-1},{9702,6,0,-1},{9703,6,1,-1},{9706,6,0,-1}, +{9707,6,1,-1},{9711,6,0,-1},{9712,6,1,-1},{9715,6,0,-1},{9716,6,1,-1}, +{9719,6,0,-1},{9720,6,1,-1},{9724,6,0,-1},{9725,6,1,-1},{9658,6,0,-1}, +{9728,6,0,-1},{9729,6,1,-1},{9732,6,0,-1},{9733,6,1,-1},{9737,6,0,-1}, +{9738,6,1,-1},{9671,6,1,-1},{9741,6,0,-1},{9742,6,1,-1},{9745,6,0,-1}, +{9746,6,1,-1},{9750,6,0,-1},{9751,6,1,-1},{9754,6,0,-1},{9755,6,1,-1}, +{9758,6,0,-1},{9759,6,1,-1},{9763,6,0,-1},{9764,6,1,-1},{9767,6,0,-1}, +{9768,6,1,-1},{9771,6,0,-1},{9772,6,1,-1},{9776,6,0,-1},{9777,6,1,-1}, +{9780,6,0,-1},{9781,6,1,-1},{9784,6,0,-1},{9785,6,1,-1},{9789,6,0,-1}, +{9790,6,1,-1},{9793,6,0,-1},{9794,6,1,-1},{9797,6,0,-1},{9798,6,1,-1}, +{9802,6,0,-1},{9803,6,1,-1},{9806,6,0,-1},{9807,6,1,-1},{9810,6,0,-1}, +{9811,6,1,-1},{9815,6,0,-1},{9816,6,1,-1},{9819,6,0,-1},{9820,6,1,-1}, +{9823,6,0,-1},{9824,6,1,-1},{9828,6,0,-1},{9829,6,1,-1},{9762,6,0,-1}, +{9832,6,0,-1},{9833,6,1,-1},{9836,6,0,-1},{9837,6,1,-1},{9841,6,0,-1}, +{9842,6,1,-1},{9775,6,1,-1},{9845,6,0,-1},{9846,6,1,-1},{9849,6,0,-1}, +{9850,6,1,-1},{9854,6,0,-1},{9855,6,1,-1},{9858,6,0,-1},{9859,6,1,-1}, +{9862,6,0,-1},{9863,6,1,-1},{9867,6,0,-1},{9868,6,1,-1},{9871,6,0,-1}, +{9872,6,1,-1},{9875,6,0,-1},{9876,6,1,-1},{9880,6,0,-1},{9881,6,1,-1}, +{9884,6,0,-1},{9885,6,1,-1},{9888,6,0,-1},{9889,6,1,-1},{9893,6,0,-1}, +{9894,6,1,-1},{9897,6,0,-1},{9898,6,1,-1},{9901,6,0,-1},{9902,6,1,-1}, +{9906,6,0,-1},{9907,6,1,-1},{9910,6,0,-1},{9911,6,1,-1},{9914,6,0,-1}, +{9915,6,1,-1},{9919,6,0,-1},{9920,6,1,-1},{9923,6,0,-1},{9924,6,1,-1}, +{9927,6,0,-1},{9928,6,1,-1},{9932,6,0,-1},{9933,6,1,-1},{9866,6,0,-1}, +{9936,6,0,-1},{9937,6,1,-1},{9940,6,0,-1},{9941,6,1,-1},{9945,6,0,-1}, +{9946,6,1,-1},{9879,6,1,-1},{9949,6,0,-1},{9950,6,1,-1},{9953,6,0,-1}, +{9954,6,1,-1},{9958,6,0,-1},{9959,6,1,-1},{9962,6,0,-1},{9963,6,1,-1}, +{9966,6,0,-1},{9967,6,1,-1},{9971,6,0,-1},{9972,6,1,-1},{9975,6,0,-1}, +{9976,6,1,-1},{9979,6,0,-1},{9980,6,1,-1},{9984,6,0,-1},{9985,6,1,-1}, +{9988,6,0,-1},{9989,6,1,-1},{9992,6,0,-1},{9993,6,1,-1},{9997,6,0,-1}, +{9998,6,1,-1},{10001,6,0,-1},{10002,6,1,-1},{10005,6,0,-1},{10006,6,1,-1}, +{10010,6,0,-1},{10011,6,1,-1},{10014,6,0,-1},{10015,6,1,-1},{10018,6,0,-1}, +{10019,6,1,-1},{10023,6,0,-1},{10024,6,1,-1},{10027,6,0,-1},{10028,6,1,-1}, +{10031,6,0,-1},{10032,6,1,-1},{10036,6,0,-1},{10037,6,1,-1},{9970,6,0,-1}, +{10040,6,0,-1},{10041,6,1,-1},{10044,6,0,-1},{10045,6,1,-1},{10049,6,0,-1}, +{10050,6,1,-1},{9983,6,1,-1},{10053,6,0,-1},{10054,6,1,-1},{10057,6,0,-1}, +{10058,6,1,-1},{10062,6,0,-1},{10063,6,1,-1},{10066,6,0,-1},{10067,6,1,-1}, +{10070,6,0,-1},{10071,6,1,-1},{10075,6,0,-1},{10076,6,1,-1},{10079,6,0,-1}, +{10080,6,1,-1},{10083,6,0,-1},{10084,6,1,-1},{10088,6,0,-1},{10089,6,1,-1}, +{10092,6,0,-1},{10093,6,1,-1},{10096,6,0,-1},{10097,6,1,-1},{10101,6,0,-1}, +{10102,6,1,-1},{10105,6,0,-1},{10106,6,1,-1},{10109,6,0,-1},{10110,6,1,-1}, +{10114,6,0,-1},{10115,6,1,-1},{10118,6,0,-1},{10119,6,1,-1},{10122,6,0,-1}, +{10123,6,1,-1},{10127,6,0,-1},{10128,6,1,-1},{10131,6,0,-1},{10132,6,1,-1}, +{10135,6,0,-1},{10136,6,1,-1},{10140,6,0,-1},{10141,6,1,-1},{10074,6,0,-1}, +{10144,6,0,-1},{10145,6,1,-1},{10148,6,0,-1},{10149,6,1,-1},{10153,6,0,-1}, +{10154,6,1,-1},{10087,6,1,-1},{10157,6,0,-1},{10158,6,1,-1},{10161,6,0,-1}, +{10162,6,1,-1},{10166,6,0,-1},{10167,6,1,-1},{10170,6,0,-1},{10171,6,1,-1}, +{10174,6,0,-1},{10175,6,1,-1},{10179,6,0,-1},{10180,6,1,-1},{10183,6,0,-1}, +{10184,6,1,-1},{10187,6,0,-1},{10188,6,1,-1},{10192,6,0,-1},{10193,6,1,-1}, +{10196,6,0,-1},{10197,6,1,-1},{10200,6,0,-1},{10201,6,1,-1},{10205,6,0,-1}, +{10206,6,1,-1},{10209,6,0,-1},{10210,6,1,-1},{10213,6,0,-1},{10214,6,1,-1}, +{10218,6,0,-1},{10219,6,1,-1},{10222,6,0,-1},{10223,6,1,-1},{10226,6,0,-1}, +{10227,6,1,-1},{10231,6,0,-1},{10232,6,1,-1},{10235,6,0,-1},{10236,6,1,-1}, +{10239,6,0,-1},{10240,6,1,-1},{10244,6,0,-1},{10245,6,1,-1},{10178,6,0,-1}, +{10248,6,0,-1},{10249,6,1,-1},{10252,6,0,-1},{10253,6,1,-1},{10257,6,0,-1}, +{10258,6,1,-1},{10191,6,1,-1},{10261,6,0,-1},{10262,6,1,-1},{10265,6,0,-1}, +{10266,6,1,-1},{10270,6,0,-1},{10271,6,1,-1},{10274,6,0,-1},{10275,6,1,-1}, +{10278,6,0,-1},{10279,6,1,-1},{10283,6,0,-1},{10284,6,1,-1},{10287,6,0,-1}, +{10288,6,1,-1},{10291,6,0,-1},{10292,6,1,-1},{10296,6,0,-1},{10297,6,1,-1}, +{10300,6,0,-1},{10301,6,1,-1},{10304,6,0,-1},{10305,6,1,-1},{10309,6,0,-1}, +{10310,6,1,-1},{10313,6,0,-1},{10314,6,1,-1},{10317,6,0,-1},{10318,6,1,-1}, +{10322,6,0,-1},{10323,6,1,-1},{10326,6,0,-1},{10327,6,1,-1},{10330,6,0,-1}, +{10331,6,1,-1},{10335,6,0,-1},{10336,6,1,-1},{10339,6,0,-1},{10340,6,1,-1}, +{10343,6,0,-1},{10344,6,1,-1},{10348,6,0,-1},{10349,6,1,-1},{10282,6,0,-1}, +{10352,6,0,-1},{10353,6,1,-1},{10356,6,0,-1},{10357,6,1,-1},{10361,6,0,-1}, +{10362,6,1,-1},{10295,6,1,-1},{10365,6,0,-1},{10366,6,1,-1},{10369,6,0,-1}, +{10370,6,1,-1},{10374,6,0,-1},{10375,6,1,-1},{10378,6,0,-1},{10379,6,1,-1}, +{10382,6,0,-1},{10383,6,1,-1},{10387,6,0,-1},{10388,6,1,-1},{10391,6,0,-1}, +{10392,6,1,-1},{10395,6,0,-1},{10396,6,1,-1},{10400,6,0,-1},{10401,6,1,-1}, +{10404,6,0,-1},{10405,6,1,-1},{10408,6,0,-1},{10409,6,1,-1},{10413,6,0,-1}, +{10414,6,1,-1},{10417,6,0,-1},{10418,6,1,-1},{10421,6,0,-1},{10422,6,1,-1}, +{10426,6,0,-1},{10427,6,1,-1},{10430,6,0,-1},{10431,6,1,-1},{10434,6,0,-1}, +{10435,6,1,-1},{10439,6,0,-1},{10440,6,1,-1},{10443,6,0,-1},{10444,6,1,-1}, +{10447,6,0,-1},{10448,6,1,-1},{10452,6,0,-1},{10453,6,1,-1},{10386,6,0,-1}, +{10456,6,0,-1},{10457,6,1,-1},{10460,6,0,-1},{10461,6,1,-1},{10465,6,0,-1}, +{10466,6,1,-1},{10399,6,1,-1},{10469,6,0,-1},{10470,6,1,-1},{10473,6,0,-1}, +{10474,6,1,-1},{10478,6,0,-1},{10479,6,1,-1},{10482,6,0,-1},{10483,6,1,-1}, +{10486,6,0,-1},{10487,6,1,-1},{10491,6,0,-1},{10492,6,1,-1},{10495,6,0,-1}, +{10496,6,1,-1},{10499,6,0,-1},{10500,6,1,-1},{10504,6,0,-1},{10505,6,1,-1}, +{10508,6,0,-1},{10509,6,1,-1},{10512,6,0,-1},{10513,6,1,-1},{10517,6,0,-1}, +{10518,6,1,-1},{10521,6,0,-1},{10522,6,1,-1},{10525,6,0,-1},{10526,6,1,-1}, +{10530,6,0,-1},{10531,6,1,-1},{10534,6,0,-1},{10535,6,1,-1},{10538,6,0,-1}, +{10539,6,1,-1},{10543,6,0,-1},{10544,6,1,-1},{10547,6,0,-1},{10548,6,1,-1}, +{10551,6,0,-1},{10552,6,1,-1},{10556,6,0,-1},{10557,6,1,-1},{10490,6,0,-1}, +{10560,6,0,-1},{10561,6,1,-1},{10564,6,0,-1},{10565,6,1,-1},{10569,6,0,-1}, +{10570,6,1,-1},{10503,6,1,-1},{10573,6,0,-1},{10574,6,1,-1},{10577,6,0,-1}, +{10578,6,1,-1},{10582,6,0,-1},{10583,6,1,-1},{10586,6,0,-1},{10587,6,1,-1}, +{10590,6,0,-1},{10591,6,1,-1},{10595,6,0,-1},{10596,6,1,-1},{10599,6,0,-1}, +{10600,6,1,-1},{10603,6,0,-1},{10604,6,1,-1},{10608,6,0,-1},{10609,6,1,-1}, +{10612,6,0,-1},{10613,6,1,-1},{10616,6,0,-1},{10617,6,1,-1},{10621,6,0,-1}, +{10622,6,1,-1},{10625,6,0,-1},{10626,6,1,-1},{10629,6,0,-1},{10630,6,1,-1}, +{10634,6,0,-1},{10635,6,1,-1},{10638,6,0,-1},{10639,6,1,-1},{10642,6,0,-1}, +{10643,6,1,-1},{10647,6,0,-1},{10648,6,1,-1},{10651,6,0,-1},{10652,6,1,-1}, +{10655,6,0,-1},{10656,6,1,-1},{10660,6,0,-1},{10661,6,1,-1},{10594,6,0,-1}, +{10664,6,0,-1},{10665,6,1,-1},{10668,6,0,-1},{10669,6,1,-1},{10673,6,0,-1}, +{10674,6,1,-1},{10607,6,1,-1},{10677,6,0,-1},{10678,6,1,-1},{10681,6,0,-1}, +{10682,6,1,-1},{10686,6,0,-1},{10687,6,1,-1},{10690,6,0,-1},{10691,6,1,-1}, +{10694,6,0,-1},{10695,6,1,-1},{10699,6,0,-1},{10700,6,1,-1},{10703,6,0,-1}, +{10704,6,1,-1},{10707,6,0,-1},{10708,6,1,-1},{10712,6,0,-1},{10713,6,1,-1}, +{10716,6,0,-1},{10717,6,1,-1},{10720,6,0,-1},{10721,6,1,-1},{10725,6,0,-1}, +{10726,6,1,-1},{10729,6,0,-1},{10730,6,1,-1},{10733,6,0,-1},{10734,6,1,-1}, +{10738,6,0,-1},{10739,6,1,-1},{10742,6,0,-1},{10743,6,1,-1},{10746,6,0,-1}, +{10747,6,1,-1},{10751,6,0,-1},{10752,6,1,-1},{10755,6,0,-1},{10756,6,1,-1}, +{10759,6,0,-1},{10760,6,1,-1},{10764,6,0,-1},{10765,6,1,-1},{10698,6,0,-1}, +{10768,6,0,-1},{10769,6,1,-1},{10772,6,0,-1},{10773,6,1,-1},{10777,6,0,-1}, +{10778,6,1,-1},{10711,6,1,-1},{10781,6,0,-1},{10782,6,1,-1},{10785,6,0,-1}, +{10786,6,1,-1},{10790,6,0,-1},{10791,6,1,-1},{10794,6,0,-1},{10795,6,1,-1}, +{10798,6,0,-1},{10799,6,1,-1},{10803,6,0,-1},{10804,6,1,-1},{10807,6,0,-1}, +{10808,6,1,-1},{10811,6,0,-1},{10812,6,1,-1},{10816,6,0,-1},{10817,6,1,-1}, +{10820,6,0,-1},{10821,6,1,-1},{10824,6,0,-1},{10825,6,1,-1},{10829,6,0,-1}, +{10830,6,1,-1},{10833,6,0,-1},{10834,6,1,-1},{10837,6,0,-1},{10838,6,1,-1}, +{10842,6,0,-1},{10843,6,1,-1},{10846,6,0,-1},{10847,6,1,-1},{10850,6,0,-1}, +{10851,6,1,-1},{10855,6,0,-1},{10856,6,1,-1},{10859,6,0,-1},{10860,6,1,-1}, +{10863,6,0,-1},{10864,6,1,-1},{10868,6,0,-1},{10869,6,1,-1},{10802,6,0,-1}, +{10872,6,0,-1},{10873,6,1,-1},{10876,6,0,-1},{10877,6,1,-1},{10881,6,0,-1}, +{10882,6,1,-1},{10815,6,1,-1},{10885,6,0,-1},{10886,6,1,-1},{10889,6,0,-1}, +{10890,6,1,-1},{10894,6,0,-1},{10895,6,1,-1},{10898,6,0,-1},{10899,6,1,-1}, +{10902,6,0,-1},{10903,6,1,-1},{10907,6,0,-1},{10908,6,1,-1},{10911,6,0,-1}, +{10912,6,1,-1},{10915,6,0,-1},{10916,6,1,-1},{10920,6,0,-1},{10921,6,1,-1}, +{10924,6,0,-1},{10925,6,1,-1},{10928,6,0,-1},{10929,6,1,-1},{10933,6,0,-1}, +{10934,6,1,-1},{10937,6,0,-1},{10938,6,1,-1},{10941,6,0,-1},{10942,6,1,-1}, +{10946,6,0,-1},{10947,6,1,-1},{10950,6,0,-1},{10951,6,1,-1},{10954,6,0,-1}, +{10955,6,1,-1},{10959,6,0,-1},{10960,6,1,-1},{10963,6,0,-1},{10964,6,1,-1}, +{10967,6,0,-1},{10968,6,1,-1},{10972,6,0,-1},{10973,6,1,-1},{10906,6,0,-1}, +{10976,6,0,-1},{10977,6,1,-1},{10980,6,0,-1},{10981,6,1,-1},{10985,6,0,-1}, +{10986,6,1,-1},{10919,6,1,-1},{10989,6,0,-1},{10990,6,1,-1},{10993,6,0,-1}, +{10994,6,1,-1},{10998,6,0,-1},{10999,6,1,-1},{11002,6,0,-1},{11003,6,1,-1}, +{11006,6,0,-1},{11007,6,1,-1},{11011,6,0,-1},{11012,6,1,-1},{11015,6,0,-1}, +{11016,6,1,-1},{11019,6,0,-1},{11020,6,1,-1},{11024,6,0,-1},{11025,6,1,-1}, +{11028,6,0,-1},{11029,6,1,-1},{11032,6,0,-1},{11033,6,1,-1},{11037,6,0,-1}, +{11038,6,1,-1},{11041,6,0,-1},{11042,6,1,-1},{11045,6,0,-1},{11046,6,1,-1}, +{11050,6,0,-1},{11051,6,1,-1},{11054,6,0,-1},{11055,6,1,-1},{11058,6,0,-1}, +{11059,6,1,-1},{11063,6,0,-1},{11064,6,1,-1},{11067,6,0,-1},{11068,6,1,-1}, +{11071,6,0,-1},{11072,6,1,-1},{11076,6,0,-1},{11077,6,1,-1},{11010,6,0,-1}, +{11080,6,0,-1},{11081,6,1,-1},{11084,6,0,-1},{11085,6,1,-1},{11089,6,0,-1}, +{11090,6,1,-1},{11023,6,1,-1},{11093,6,0,-1},{11094,6,1,-1},{11097,6,0,-1}, +{11098,6,1,-1},{11102,6,0,-1},{11103,6,1,-1},{11106,6,0,-1},{11107,6,1,-1}, +{11110,6,0,-1},{11111,6,1,-1},{11115,6,0,-1},{11116,6,1,-1},{11119,6,0,-1}, +{11120,6,1,-1},{11123,6,0,-1},{11124,6,1,-1},{11128,6,0,-1},{11129,6,1,-1}, +{11132,6,0,-1},{11133,6,1,-1},{11136,6,0,-1},{11137,6,1,-1},{11141,6,0,-1}, +{11142,6,1,-1},{11145,6,0,-1},{11146,6,1,-1},{11149,6,0,-1},{11150,6,1,-1}, +{11154,6,0,-1},{11155,6,1,-1},{11158,6,0,-1},{11159,6,1,-1},{11162,6,0,-1}, +{11163,6,1,-1},{11167,6,0,-1},{11168,6,1,-1},{11171,6,0,-1},{11172,6,1,-1}, +{11175,6,0,-1},{11176,6,1,-1},{11180,6,0,-1},{11181,6,1,-1},{11114,6,0,-1}, +{11184,6,0,-1},{11185,6,1,-1},{11188,6,0,-1},{11189,6,1,-1},{11193,6,0,-1}, +{11194,6,1,-1},{11127,6,1,-1},{11197,6,0,-1},{11198,6,1,-1},{11201,6,0,-1}, +{11202,6,1,-1},{11206,6,0,-1},{11207,6,1,-1},{11210,6,0,-1},{11211,6,1,-1}, +{11214,6,0,-1},{11215,6,1,-1},{11219,6,0,-1},{11220,6,1,-1},{11223,6,0,-1}, +{11224,6,1,-1},{11227,6,0,-1},{11228,6,1,-1},{11232,6,0,-1},{11233,6,1,-1}, +{11236,6,0,-1},{11237,6,1,-1},{11240,6,0,-1},{11241,6,1,-1},{11245,6,0,-1}, +{11246,6,1,-1},{11249,6,0,-1},{11250,6,1,-1},{11253,6,0,-1},{11254,6,1,-1}, +{11258,6,0,-1},{11259,6,1,-1},{11262,6,0,-1},{11263,6,1,-1},{11266,6,0,-1}, +{11267,6,1,-1},{11271,6,0,-1},{11272,6,1,-1},{11275,6,0,-1},{11276,6,1,-1}, +{11279,6,0,-1},{11280,6,1,-1},{11284,6,0,-1},{11285,6,1,-1},{11218,6,0,-1}, +{11288,6,0,-1},{11289,6,1,-1},{11292,6,0,-1},{11293,6,1,-1},{11297,6,0,-1}, +{11298,6,1,-1},{11231,6,1,-1},{11301,6,0,-1},{11302,6,1,-1},{11305,6,0,-1}, +{11306,6,1,-1},{11310,6,0,-1},{11311,6,1,-1},{11314,6,0,-1},{11315,6,1,-1}, +{11318,6,0,-1},{11319,6,1,-1},{11323,6,0,-1},{11324,6,1,-1},{11327,6,0,-1}, +{11328,6,1,-1},{11331,6,0,-1},{11332,6,1,-1},{11336,6,0,-1},{11337,6,1,-1}, +{11340,6,0,-1},{11341,6,1,-1},{11344,6,0,-1},{11345,6,1,-1},{11349,6,0,-1}, +{11350,6,1,-1},{11353,6,0,-1},{11354,6,1,-1},{11357,6,0,-1},{11358,6,1,-1}, +{11362,6,0,-1},{11363,6,1,-1},{11366,6,0,-1},{11367,6,1,-1},{11370,6,0,-1}, +{11371,6,1,-1},{11375,6,0,-1},{11376,6,1,-1},{11379,6,0,-1},{11380,6,1,-1}, +{11383,6,0,-1},{11384,6,1,-1},{11388,6,0,-1},{11389,6,1,-1},{11322,6,0,-1}, +{11392,6,0,-1},{11393,6,1,-1},{11396,6,0,-1},{11397,6,1,-1},{11401,6,0,-1}, +{11402,6,1,-1},{11335,6,1,-1},{11405,6,0,-1},{11406,6,1,-1},{11409,6,0,-1}, +{11410,6,1,-1},{11414,6,0,-1},{11415,6,1,-1},{11418,6,0,-1},{11419,6,1,-1}, +{11422,6,0,-1},{11423,6,1,-1},{11427,6,0,-1},{11428,6,1,-1},{11431,6,0,-1}, +{11432,6,1,-1},{11435,6,0,-1},{11436,6,1,-1},{11440,6,0,-1},{11441,6,1,-1}, +{11444,6,0,-1},{11445,6,1,-1},{11448,6,0,-1},{11449,6,1,-1},{11453,6,0,-1}, +{11454,6,1,-1},{11457,6,0,-1},{11458,6,1,-1},{11461,6,0,-1},{11462,6,1,-1}, +{11466,6,0,-1},{11467,6,1,-1},{11470,6,0,-1},{11471,6,1,-1},{11474,6,0,-1}, +{11475,6,1,-1},{11479,6,0,-1},{11480,6,1,-1},{11483,6,0,-1},{11484,6,1,-1}, +{11487,6,0,-1},{11488,6,1,-1},{11492,6,0,-1},{11493,6,1,-1},{11426,6,0,-1}, +{11496,6,0,-1},{11497,6,1,-1},{11500,6,0,-1},{11501,6,1,-1},{11505,6,0,-1}, +{11506,6,1,-1},{11439,6,1,-1},{11509,6,0,-1},{11510,6,1,-1},{11513,6,0,-1}, +{11514,6,1,-1},{11518,6,0,-1},{11519,6,1,-1},{11522,6,0,-1},{11523,6,1,-1}, +{11526,6,0,-1},{11527,6,1,-1},{11531,6,0,-1},{11532,6,1,-1},{11535,6,0,-1}, +{11536,6,1,-1},{11539,6,0,-1},{11540,6,1,-1},{11544,6,0,-1},{11545,6,1,-1}, +{11548,6,0,-1},{11549,6,1,-1},{11552,6,0,-1},{11553,6,1,-1},{11557,6,0,-1}, +{11558,6,1,-1},{11561,6,0,-1},{11562,6,1,-1},{11565,6,0,-1},{11566,6,1,-1}, +{11570,6,0,-1},{11571,6,1,-1},{11574,6,0,-1},{11575,6,1,-1},{11578,6,0,-1}, +{11579,6,1,-1},{11583,6,0,-1},{11584,6,1,-1},{11587,6,0,-1},{11588,6,1,-1}, +{11591,6,0,-1},{11592,6,1,-1},{11596,6,0,-1},{11597,6,1,-1},{11530,6,0,-1}, +{11600,6,0,-1},{11601,6,1,-1},{11604,6,0,-1},{11605,6,1,-1},{11609,6,0,-1}, +{11610,6,1,-1},{11543,6,1,-1},{11613,6,0,-1},{11614,6,1,-1},{11617,6,0,-1}, +{11622,6,0,-1},{11623,6,1,-1},{11626,6,0,-1},{11627,6,1,-1},{11630,6,0,-1}, +{11631,6,1,-1},{11635,6,0,-1},{11636,6,1,-1},{11639,6,0,-1},{11640,6,1,-1}, +{11643,6,0,-1},{11644,6,1,-1},{11648,6,0,-1},{11649,6,1,-1},{11652,6,0,-1}, +{11653,6,1,-1},{11656,6,0,-1},{11657,6,1,-1},{11661,6,0,-1},{11662,6,1,-1}, +{11665,6,0,-1},{11666,6,1,-1},{11674,6,0,-1},{11675,6,1,-1},{11678,6,0,-1}, +{11679,6,1,-1},{11682,6,0,-1},{11683,6,1,-1},{11687,6,0,-1},{11688,6,1,-1}, +{11691,6,0,-1},{11692,6,1,-1},{11695,6,0,-1},{11696,6,1,-1},{11700,6,0,-1}, +{11701,6,1,-1},{11704,6,0,-1},{11705,6,1,-1},{11708,6,0,-1},{11709,6,1,-1}, +{11713,6,0,-1},{11717,6,0,-1},{11726,6,0,-1},{11730,6,0,-1},{11734,6,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS7, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_7_ss0_ss1[] = { +{11752,7,0,-1},{11760,7,0,-1},{11769,7,0,-1},{11778,7,0,-1},{11786,7,0,-1}, +{11795,7,0,-1},{11799,7,0,-1},{11804,7,0,-1},{11738,7,0,-1},{11808,7,0,-1}, +{11812,7,0,-1},{11817,7,0,-1},{11821,7,0,-1},{11825,7,0,-1},{11830,7,0,-1}, +{11834,7,0,-1},{11838,7,0,-1},{11843,7,0,-1},{11847,7,0,-1},{11851,7,0,-1}, +{11856,7,0,-1},{11860,7,0,-1},{11864,7,0,-1},{11869,7,0,-1},{11873,7,0,-1}, +{11877,7,0,-1},{11882,7,0,-1},{11886,7,0,-1},{11890,7,0,-1},{11895,7,0,-1}, +{11899,7,0,-1},{11903,7,0,-1},{11908,7,0,-1},{11842,7,0,-1},{11912,7,0,-1}, +{11916,7,0,-1},{11921,7,0,-1},{11925,7,0,-1},{11929,7,0,-1},{11934,7,0,-1}, +{11938,7,0,-1},{11942,7,0,-1},{11947,7,0,-1},{11951,7,0,-1},{11955,7,0,-1}, +{11960,7,0,-1},{11964,7,0,-1},{11968,7,0,-1},{11973,7,0,-1},{11977,7,0,-1}, +{11981,7,0,-1},{11986,7,0,-1},{11990,7,0,-1},{11994,7,0,-1},{11999,7,0,-1}, +{12003,7,0,-1},{12007,7,0,-1},{12012,7,0,-1},{11946,7,0,-1},{12016,7,0,-1}, +{12020,7,0,-1},{12025,7,0,-1},{12026,7,1,-1},{12029,7,0,-1},{12033,7,0,-1}, +{12034,7,1,-1},{12038,7,0,-1},{12042,7,0,-1},{12046,7,0,-1},{12047,7,1,-1}, +{12051,7,0,-1},{12055,7,0,-1},{12056,7,1,-1},{12059,7,0,-1},{12064,7,0,-1}, +{12065,7,1,-1},{12068,7,0,-1},{12072,7,0,-1},{12073,7,1,-1},{12077,7,0,-1}, +{12081,7,0,-1},{12082,7,1,-1},{12085,7,0,-1},{12090,7,0,-1},{12091,7,1,-1}, +{12094,7,0,-1},{12098,7,0,-1},{12099,7,1,-1},{12103,7,0,-1},{12107,7,0,-1}, +{12108,7,1,-1},{12111,7,0,-1},{12116,7,0,-1},{12117,7,1,-1},{12050,7,0,-1}, +{12120,7,0,-1},{12124,7,0,-1},{12125,7,1,-1},{12129,7,0,-1},{12063,7,1,-1}, +{12133,7,0,-1},{12134,7,1,-1},{12137,7,0,-1},{12142,7,0,-1},{12143,7,1,-1}, +{12146,7,0,-1},{12150,7,0,-1},{12151,7,1,-1},{12155,7,0,-1},{12159,7,0,-1}, +{12160,7,1,-1},{12163,7,0,-1},{12168,7,0,-1},{12169,7,1,-1},{12172,7,0,-1}, +{12176,7,0,-1},{12177,7,1,-1},{12181,7,0,-1},{12185,7,0,-1},{12186,7,1,-1}, +{12189,7,0,-1},{12190,7,1,-1},{12194,7,0,-1},{12195,7,1,-1},{12198,7,0,-1}, +{12199,7,1,-1},{12202,7,0,-1},{12203,7,1,-1},{12207,7,0,-1},{12208,7,1,-1}, +{12211,7,0,-1},{12212,7,1,-1},{12215,7,0,-1},{12220,7,0,-1},{12221,7,1,-1}, +{12154,7,0,-1},{12224,7,0,-1},{12225,7,1,-1},{12228,7,0,-1},{12229,7,1,-1}, +{12233,7,0,-1},{12234,7,1,-1},{12167,7,1,-1},{12237,7,0,-1},{12238,7,1,-1}, +{12241,7,0,-1},{12242,7,1,-1},{12246,7,0,-1},{12247,7,1,-1},{12250,7,0,-1}, +{12251,7,1,-1},{12254,7,0,-1},{12255,7,1,-1},{12260,7,1,-1},{12263,7,0,-1}, +{12264,7,1,-1},{12267,7,0,-1},{12268,7,1,-1},{12272,7,0,-1},{12273,7,1,-1}, +{12276,7,0,-1},{12277,7,1,-1},{12280,7,0,-1},{12281,7,1,-1},{12285,7,0,-1}, +{12286,7,1,-1},{12289,7,0,-1},{12290,7,1,-1},{12293,7,0,-1},{12294,7,1,-1}, +{12298,7,0,-1},{12299,7,1,-1},{12302,7,0,-1},{12303,7,1,-1},{12306,7,0,-1}, +{12307,7,1,-1},{12311,7,0,-1},{12312,7,1,-1},{12315,7,0,-1},{12316,7,1,-1}, +{12319,7,0,-1},{12320,7,1,-1},{12324,7,0,-1},{12325,7,1,-1},{12258,7,0,-1}, +{12328,7,0,-1},{12329,7,1,-1},{12332,7,0,-1},{12333,7,1,-1},{12337,7,0,-1}, +{12338,7,1,-1},{12271,7,1,-1},{12341,7,0,-1},{12342,7,1,-1},{12345,7,0,-1}, +{12346,7,1,-1},{12350,7,0,-1},{12351,7,1,-1},{12354,7,0,-1},{12355,7,1,-1}, +{12358,7,0,-1},{12359,7,1,-1},{12363,7,0,-1},{12367,7,0,-1},{12368,7,1,-1}, +{12371,7,0,-1},{12372,7,1,-1},{12376,7,0,-1},{12377,7,1,-1},{12380,7,0,-1}, +{12381,7,1,-1},{12384,7,0,-1},{12385,7,1,-1},{12389,7,0,-1},{12390,7,1,-1}, +{12393,7,0,-1},{12394,7,1,-1},{12397,7,0,-1},{12398,7,1,-1},{12402,7,0,-1}, +{12403,7,1,-1},{12407,7,1,-1},{12410,7,0,-1},{12411,7,1,-1},{12415,7,0,-1}, +{12419,7,0,-1},{12420,7,1,-1},{12423,7,0,-1},{12424,7,1,-1},{12428,7,0,-1}, +{12429,7,1,-1},{12362,7,0,-1},{12432,7,0,-1},{12433,7,1,-1},{12436,7,0,-1}, +{12437,7,1,-1},{12441,7,0,-1},{12442,7,1,-1},{12375,7,1,-1},{12445,7,0,-1}, +{12446,7,1,-1},{12449,7,0,-1},{12450,7,1,-1},{12454,7,0,-1},{12455,7,1,-1}, +{12458,7,0,-1},{12459,7,1,-1},{12462,7,0,-1},{12463,7,1,-1},{12467,7,0,-1}, +{12468,7,1,-1},{12471,7,0,-1},{12472,7,1,-1},{12475,7,0,-1},{12476,7,1,-1}, +{12480,7,0,-1},{12481,7,1,-1},{12484,7,0,-1},{12485,7,1,-1},{12488,7,0,-1}, +{12489,7,1,-1},{12493,7,0,-1},{12494,7,1,-1},{12497,7,0,-1},{12498,7,1,-1}, +{12501,7,0,-1},{12502,7,1,-1},{12506,7,0,-1},{12507,7,1,-1},{12510,7,0,-1}, +{12511,7,1,-1},{12514,7,0,-1},{12515,7,1,-1},{12519,7,0,-1},{12520,7,1,-1}, +{12523,7,0,-1},{12524,7,1,-1},{12527,7,0,-1},{12528,7,1,-1},{12532,7,0,-1}, +{12533,7,1,-1},{12466,7,0,-1},{12536,7,0,-1},{12537,7,1,-1},{12540,7,0,-1}, +{12541,7,1,-1},{12545,7,0,-1},{12546,7,1,-1},{12479,7,1,-1},{12549,7,0,-1}, +{12550,7,1,-1},{12553,7,0,-1},{12554,7,1,-1},{12558,7,0,-1},{12559,7,1,-1}, +{12562,7,0,-1},{12563,7,1,-1},{12566,7,0,-1},{12567,7,1,-1},{12571,7,0,-1}, +{12572,7,1,-1},{12575,7,0,-1},{12576,7,1,-1},{12579,7,0,-1},{12580,7,1,-1}, +{12584,7,0,-1},{12585,7,1,-1},{12588,7,0,-1},{12589,7,1,-1},{12592,7,0,-1}, +{12593,7,1,-1},{12597,7,0,-1},{12598,7,1,-1},{12601,7,0,-1},{12602,7,1,-1}, +{12605,7,0,-1},{12606,7,1,-1},{12610,7,0,-1},{12611,7,1,-1},{12614,7,0,-1}, +{12615,7,1,-1},{12618,7,0,-1},{12619,7,1,-1},{12623,7,0,-1},{12624,7,1,-1}, +{12627,7,0,-1},{12628,7,1,-1},{12631,7,0,-1},{12632,7,1,-1},{12636,7,0,-1}, +{12637,7,1,-1},{12570,7,0,-1},{12640,7,0,-1},{12641,7,1,-1},{12644,7,0,-1}, +{12645,7,1,-1},{12649,7,0,-1},{12650,7,1,-1},{12583,7,1,-1},{12653,7,0,-1}, +{12654,7,1,-1},{12657,7,0,-1},{12658,7,1,-1},{12662,7,0,-1},{12663,7,1,-1}, +{12666,7,0,-1},{12667,7,1,-1},{12670,7,0,-1},{12671,7,1,-1},{12675,7,0,-1}, +{12676,7,1,-1},{12679,7,0,-1},{12680,7,1,-1},{12683,7,0,-1},{12684,7,1,-1}, +{12688,7,0,-1},{12689,7,1,-1},{12692,7,0,-1},{12693,7,1,-1},{12696,7,0,-1}, +{12697,7,1,-1},{12701,7,0,-1},{12702,7,1,-1},{12705,7,0,-1},{12706,7,1,-1}, +{12709,7,0,-1},{12710,7,1,-1},{12714,7,0,-1},{12715,7,1,-1},{12718,7,0,-1}, +{12719,7,1,-1},{12722,7,0,-1},{12723,7,1,-1},{12727,7,0,-1},{12728,7,1,-1}, +{12731,7,0,-1},{12732,7,1,-1},{12735,7,0,-1},{12736,7,1,-1},{12740,7,0,-1}, +{12741,7,1,-1},{12674,7,0,-1},{12744,7,0,-1},{12745,7,1,-1},{12748,7,0,-1}, +{12749,7,1,-1},{12753,7,0,-1},{12754,7,1,-1},{12687,7,1,-1},{12757,7,0,-1}, +{12758,7,1,-1},{12761,7,0,-1},{12762,7,1,-1},{12766,7,0,-1},{12767,7,1,-1}, +{12770,7,0,-1},{12771,7,1,-1},{12774,7,0,-1},{12775,7,1,-1},{12779,7,0,-1}, +{12780,7,1,-1},{12783,7,0,-1},{12784,7,1,-1},{12787,7,0,-1},{12788,7,1,-1}, +{12792,7,0,-1},{12793,7,1,-1},{12796,7,0,-1},{12797,7,1,-1},{12800,7,0,-1}, +{12801,7,1,-1},{12805,7,0,-1},{12806,7,1,-1},{12809,7,0,-1},{12810,7,1,-1}, +{12813,7,0,-1},{12814,7,1,-1},{12818,7,0,-1},{12819,7,1,-1},{12822,7,0,-1}, +{12823,7,1,-1},{12826,7,0,-1},{12827,7,1,-1},{12831,7,0,-1},{12832,7,1,-1}, +{12835,7,0,-1},{12836,7,1,-1},{12839,7,0,-1},{12840,7,1,-1},{12844,7,0,-1}, +{12845,7,1,-1},{12778,7,0,-1},{12848,7,0,-1},{12849,7,1,-1},{12852,7,0,-1}, +{12853,7,1,-1},{12857,7,0,-1},{12858,7,1,-1},{12791,7,1,-1},{12861,7,0,-1}, +{12862,7,1,-1},{12865,7,0,-1},{12866,7,1,-1},{12870,7,0,-1},{12871,7,1,-1}, +{12874,7,0,-1},{12875,7,1,-1},{12878,7,0,-1},{12879,7,1,-1},{12883,7,0,-1}, +{12884,7,1,-1},{12887,7,0,-1},{12888,7,1,-1},{12891,7,0,-1},{12892,7,1,-1}, +{12896,7,0,-1},{12897,7,1,-1},{12900,7,0,-1},{12901,7,1,-1},{12904,7,0,-1}, +{12905,7,1,-1},{12909,7,0,-1},{12910,7,1,-1},{12913,7,0,-1},{12914,7,1,-1}, +{12917,7,0,-1},{12918,7,1,-1},{12922,7,0,-1},{12923,7,1,-1},{12926,7,0,-1}, +{12927,7,1,-1},{12930,7,0,-1},{12931,7,1,-1},{12935,7,0,-1},{12936,7,1,-1}, +{12939,7,0,-1},{12940,7,1,-1},{12943,7,0,-1},{12944,7,1,-1},{12948,7,0,-1}, +{12949,7,1,-1},{12882,7,0,-1},{12952,7,0,-1},{12953,7,1,-1},{12956,7,0,-1}, +{12957,7,1,-1},{12961,7,0,-1},{12962,7,1,-1},{12895,7,1,-1},{12965,7,0,-1}, +{12966,7,1,-1},{12969,7,0,-1},{12970,7,1,-1},{12974,7,0,-1},{12975,7,1,-1}, +{12978,7,0,-1},{12979,7,1,-1},{12982,7,0,-1},{12983,7,1,-1},{12987,7,0,-1}, +{12988,7,1,-1},{12991,7,0,-1},{12992,7,1,-1},{12995,7,0,-1},{12996,7,1,-1}, +{13000,7,0,-1},{13001,7,1,-1},{13004,7,0,-1},{13005,7,1,-1},{13008,7,0,-1}, +{13009,7,1,-1},{13013,7,0,-1},{13014,7,1,-1},{13017,7,0,-1},{13018,7,1,-1}, +{13021,7,0,-1},{13022,7,1,-1},{13026,7,0,-1},{13027,7,1,-1},{13030,7,0,-1}, +{13031,7,1,-1},{13034,7,0,-1},{13035,7,1,-1},{13039,7,0,-1},{13040,7,1,-1}, +{13043,7,0,-1},{13044,7,1,-1},{13047,7,0,-1},{13048,7,1,-1},{13052,7,0,-1}, +{13053,7,1,-1},{12986,7,0,-1},{13056,7,0,-1},{13057,7,1,-1},{13060,7,0,-1}, +{13061,7,1,-1},{13065,7,0,-1},{13066,7,1,-1},{12999,7,1,-1},{13069,7,0,-1}, +{13070,7,1,-1},{13073,7,0,-1},{13074,7,1,-1},{13078,7,0,-1},{13079,7,1,-1}, +{13082,7,0,-1},{13083,7,1,-1},{13086,7,0,-1},{13087,7,1,-1},{13091,7,0,-1}, +{13092,7,1,-1},{13095,7,0,-1},{13096,7,1,-1},{13099,7,0,-1},{13100,7,1,-1}, +{13104,7,0,-1},{13105,7,1,-1},{13108,7,0,-1},{13109,7,1,-1},{13112,7,0,-1}, +{13113,7,1,-1},{13117,7,0,-1},{13118,7,1,-1},{13121,7,0,-1},{13122,7,1,-1}, +{13125,7,0,-1},{13126,7,1,-1},{13130,7,0,-1},{13131,7,1,-1},{13134,7,0,-1}, +{13135,7,1,-1},{13138,7,0,-1},{13139,7,1,-1},{13143,7,0,-1},{13144,7,1,-1}, +{13147,7,0,-1},{13148,7,1,-1},{13151,7,0,-1},{13152,7,1,-1},{13156,7,0,-1}, +{13157,7,1,-1},{13090,7,0,-1},{13160,7,0,-1},{13161,7,1,-1},{13164,7,0,-1}, +{13165,7,1,-1},{13169,7,0,-1},{13170,7,1,-1},{13103,7,1,-1},{13173,7,0,-1}, +{13174,7,1,-1},{13177,7,0,-1},{13178,7,1,-1},{13182,7,0,-1},{13183,7,1,-1}, +{13186,7,0,-1},{13187,7,1,-1},{13190,7,0,-1},{13191,7,1,-1},{13195,7,0,-1}, +{13196,7,1,-1},{13199,7,0,-1},{13200,7,1,-1},{13203,7,0,-1},{13204,7,1,-1}, +{13208,7,0,-1},{13209,7,1,-1},{13212,7,0,-1},{13213,7,1,-1},{13216,7,0,-1}, +{13217,7,1,-1},{13221,7,0,-1},{13222,7,1,-1},{13225,7,0,-1},{13226,7,1,-1}, +{13229,7,0,-1},{13230,7,1,-1},{13234,7,0,-1},{13235,7,1,-1},{13238,7,0,-1}, +{13239,7,1,-1},{13242,7,0,-1},{13243,7,1,-1},{13247,7,0,-1},{13248,7,1,-1}, +{13251,7,0,-1},{13252,7,1,-1},{13255,7,0,-1},{13256,7,1,-1},{13260,7,0,-1}, +{13261,7,1,-1},{13194,7,0,-1},{13264,7,0,-1},{13265,7,1,-1},{13268,7,0,-1}, +{13269,7,1,-1},{13273,7,0,-1},{13274,7,1,-1},{13207,7,1,-1},{13277,7,0,-1}, +{13278,7,1,-1},{13281,7,0,-1},{13282,7,1,-1},{13286,7,0,-1},{13287,7,1,-1}, +{13290,7,0,-1},{13291,7,1,-1},{13294,7,0,-1},{13295,7,1,-1},{13299,7,0,-1}, +{13300,7,1,-1},{13303,7,0,-1},{13304,7,1,-1},{13307,7,0,-1},{13308,7,1,-1}, +{13312,7,0,-1},{13313,7,1,-1},{13316,7,0,-1},{13317,7,1,-1},{13320,7,0,-1}, +{13321,7,1,-1},{13325,7,0,-1},{13326,7,1,-1},{13329,7,0,-1},{13330,7,1,-1}, +{13333,7,0,-1},{13334,7,1,-1},{13338,7,0,-1},{13339,7,1,-1},{13342,7,0,-1}, +{13343,7,1,-1},{13346,7,0,-1},{13347,7,1,-1},{13351,7,0,-1},{13352,7,1,-1}, +{13355,7,0,-1},{13356,7,1,-1},{13359,7,0,-1},{13360,7,1,-1},{13364,7,0,-1}, +{13365,7,1,-1},{13298,7,0,-1},{13368,7,0,-1},{13369,7,1,-1},{13372,7,0,-1}, +{13373,7,1,-1},{13377,7,0,-1},{13378,7,1,-1},{13311,7,1,-1},{13381,7,0,-1}, +{13382,7,1,-1},{13385,7,0,-1},{13386,7,1,-1},{13390,7,0,-1},{13391,7,1,-1}, +{13394,7,0,-1},{13395,7,1,-1},{13398,7,0,-1},{13399,7,1,-1},{13403,7,0,-1}, +{13404,7,1,-1},{13407,7,0,-1},{13408,7,1,-1},{13411,7,0,-1},{13412,7,1,-1}, +{13416,7,0,-1},{13417,7,1,-1},{13420,7,0,-1},{13421,7,1,-1},{13424,7,0,-1}, +{13425,7,1,-1},{13429,7,0,-1},{13430,7,1,-1},{13433,7,0,-1},{13434,7,1,-1}, +{13437,7,0,-1},{13438,7,1,-1},{13442,7,0,-1},{13443,7,1,-1},{13446,7,0,-1}, +{13447,7,1,-1},{13450,7,0,-1},{13451,7,1,-1},{13455,7,0,-1},{13456,7,1,-1}, +{13459,7,0,-1},{13460,7,1,-1},{13463,7,0,-1},{13464,7,1,-1},{13468,7,0,-1}, +{13469,7,1,-1},{13402,7,0,-1},{13472,7,0,-1},{13473,7,1,-1},{13476,7,0,-1}, +{13477,7,1,-1},{13481,7,0,-1},{13482,7,1,-1},{13415,7,1,-1},{13485,7,0,-1}, +{13486,7,1,-1},{13489,7,0,-1},{13490,7,1,-1},{13494,7,0,-1},{13495,7,1,-1}, +{13498,7,0,-1},{13499,7,1,-1},{13502,7,0,-1},{13503,7,1,-1},{13507,7,0,-1}, +{13508,7,1,-1},{13511,7,0,-1},{13512,7,1,-1},{13515,7,0,-1},{13516,7,1,-1}, +{13520,7,0,-1},{13521,7,1,-1},{13524,7,0,-1},{13525,7,1,-1},{13528,7,0,-1}, +{13529,7,1,-1},{13533,7,0,-1},{13534,7,1,-1},{13537,7,0,-1},{13538,7,1,-1}, +{13541,7,0,-1},{13542,7,1,-1},{13546,7,0,-1},{13547,7,1,-1},{13550,7,0,-1}, +{13551,7,1,-1},{13554,7,0,-1},{13555,7,1,-1},{13559,7,0,-1},{13560,7,1,-1}, +{13563,7,0,-1},{13564,7,1,-1},{13567,7,0,-1},{13568,7,1,-1},{13572,7,0,-1}, +{13573,7,1,-1},{13506,7,0,-1},{13576,7,0,-1},{13577,7,1,-1},{13580,7,0,-1}, +{13581,7,1,-1},{13585,7,0,-1},{13586,7,1,-1},{13519,7,1,-1},{13589,7,0,-1}, +{13590,7,1,-1},{13593,7,0,-1},{13594,7,1,-1},{13598,7,0,-1},{13599,7,1,-1}, +{13602,7,0,-1},{13603,7,1,-1},{13606,7,0,-1},{13607,7,1,-1},{13611,7,0,-1}, +{13612,7,1,-1},{13615,7,0,-1},{13616,7,1,-1},{13619,7,0,-1},{13620,7,1,-1}, +{13624,7,0,-1},{13625,7,1,-1},{13628,7,0,-1},{13629,7,1,-1},{13632,7,0,-1}, +{13633,7,1,-1},{13637,7,0,-1},{13638,7,1,-1},{13641,7,0,-1},{13642,7,1,-1}, +{13645,7,0,-1},{13646,7,1,-1},{13650,7,0,-1},{13651,7,1,-1},{13654,7,0,-1}, +{13655,7,1,-1},{13658,7,0,-1},{13659,7,1,-1},{13663,7,0,-1},{13664,7,1,-1}, +{13667,7,0,-1},{13668,7,1,-1},{13671,7,0,-1},{13672,7,1,-1},{13676,7,0,-1}, +{13677,7,1,-1},{13610,7,0,-1},{13680,7,0,-1},{13681,7,1,-1},{13684,7,0,-1}, +{13685,7,1,-1},{13689,7,0,-1},{13690,7,1,-1},{13623,7,1,-1},{13693,7,0,-1}, +{13694,7,1,-1},{13697,7,0,-1},{13698,7,1,-1},{13702,7,0,-1},{13703,7,1,-1}, +{13706,7,0,-1},{13707,7,1,-1},{13710,7,0,-1},{13711,7,1,-1},{13715,7,0,-1}, +{13716,7,1,-1},{13719,7,0,-1},{13720,7,1,-1},{13723,7,0,-1},{13724,7,1,-1}, +{13728,7,0,-1},{13729,7,1,-1},{13732,7,0,-1},{13733,7,1,-1},{13736,7,0,-1}, +{13737,7,1,-1},{13741,7,0,-1},{13742,7,1,-1},{13745,7,0,-1},{13746,7,1,-1}, +{13749,7,0,-1},{13750,7,1,-1},{13754,7,0,-1},{13755,7,1,-1},{13758,7,0,-1}, +{13759,7,1,-1},{13762,7,0,-1},{13763,7,1,-1},{13767,7,0,-1},{13768,7,1,-1}, +{13771,7,0,-1},{13772,7,1,-1},{13775,7,0,-1},{13776,7,1,-1},{13780,7,0,-1}, +{13781,7,1,-1},{13714,7,0,-1},{13784,7,0,-1},{13785,7,1,-1},{13788,7,0,-1}, +{13789,7,1,-1},{13793,7,0,-1},{13794,7,1,-1},{13727,7,1,-1},{13797,7,0,-1}, +{13798,7,1,-1},{13801,7,0,-1},{13802,7,1,-1},{13806,7,0,-1},{13807,7,1,-1}, +{13810,7,0,-1},{13811,7,1,-1},{13814,7,0,-1},{13815,7,1,-1},{13819,7,0,-1}, +{13820,7,1,-1},{13823,7,0,-1},{13824,7,1,-1},{13827,7,0,-1},{13828,7,1,-1}, +{13832,7,0,-1},{13833,7,1,-1},{13836,7,0,-1},{13837,7,1,-1},{13840,7,0,-1}, +{13841,7,1,-1},{13845,7,0,-1},{13846,7,1,-1},{13849,7,0,-1},{13850,7,1,-1}, +{13853,7,0,-1},{13854,7,1,-1},{13858,7,0,-1},{13859,7,1,-1},{13862,7,0,-1}, +{13863,7,1,-1},{13866,7,0,-1},{13867,7,1,-1},{13871,7,0,-1},{13872,7,1,-1}, +{13875,7,0,-1},{13876,7,1,-1},{13879,7,0,-1},{13880,7,1,-1},{13884,7,0,-1}, +{13885,7,1,-1},{13818,7,0,-1},{13888,7,0,-1},{13889,7,1,-1},{13892,7,0,-1}, +{13893,7,1,-1},{13897,7,0,-1},{13898,7,1,-1},{13831,7,1,-1},{13901,7,0,-1}, +{13902,7,1,-1},{13905,7,0,-1},{13906,7,1,-1},{13910,7,0,-1},{13911,7,1,-1}, +{13914,7,0,-1},{13915,7,1,-1},{13918,7,0,-1},{13919,7,1,-1},{13923,7,0,-1}, +{13924,7,1,-1},{13927,7,0,-1},{13928,7,1,-1},{13931,7,0,-1},{13932,7,1,-1}, +{13936,7,0,-1},{13937,7,1,-1},{13940,7,0,-1},{13941,7,1,-1},{13944,7,0,-1}, +{13945,7,1,-1},{13949,7,0,-1},{13950,7,1,-1},{13953,7,0,-1},{13954,7,1,-1}, +{13957,7,0,-1},{13958,7,1,-1},{13962,7,0,-1},{13963,7,1,-1},{13966,7,0,-1}, +{13967,7,1,-1},{13970,7,0,-1},{13971,7,1,-1},{13975,7,0,-1},{13976,7,1,-1}, +{13979,7,0,-1},{13980,7,1,-1},{13983,7,0,-1},{13984,7,1,-1},{13988,7,0,-1}, +{13989,7,1,-1},{13922,7,0,-1},{13992,7,0,-1},{13993,7,1,-1},{13996,7,0,-1}, +{13997,7,1,-1},{14001,7,0,-1},{14002,7,1,-1},{13935,7,1,-1},{14005,7,0,-1}, +{14006,7,1,-1},{14009,7,0,-1},{14010,7,1,-1},{14014,7,0,-1},{14015,7,1,-1}, +{14018,7,0,-1},{14019,7,1,-1},{14022,7,0,-1},{14023,7,1,-1},{14027,7,0,-1}, +{14028,7,1,-1},{14031,7,0,-1},{14032,7,1,-1},{14035,7,0,-1},{14036,7,1,-1}, +{14040,7,0,-1},{14041,7,1,-1},{14044,7,0,-1},{14045,7,1,-1},{14048,7,0,-1}, +{14049,7,1,-1},{14053,7,0,-1},{14054,7,1,-1},{14057,7,0,-1},{14058,7,1,-1}, +{14061,7,0,-1},{14062,7,1,-1},{14066,7,0,-1},{14067,7,1,-1},{14070,7,0,-1}, +{14071,7,1,-1},{14074,7,0,-1},{14075,7,1,-1},{14079,7,0,-1},{14080,7,1,-1}, +{14083,7,0,-1},{14084,7,1,-1},{14087,7,0,-1},{14088,7,1,-1},{14092,7,0,-1}, +{14093,7,1,-1},{14026,7,0,-1},{14096,7,0,-1},{14097,7,1,-1},{14100,7,0,-1}, +{14101,7,1,-1},{14105,7,0,-1},{14106,7,1,-1},{14039,7,1,-1},{14109,7,0,-1}, +{14110,7,1,-1},{14113,7,0,-1},{14114,7,1,-1},{14118,7,0,-1},{14119,7,1,-1}, +{14122,7,0,-1},{14123,7,1,-1},{14126,7,0,-1},{14127,7,1,-1},{14131,7,0,-1}, +{14132,7,1,-1},{14135,7,0,-1},{14136,7,1,-1},{14139,7,0,-1},{14140,7,1,-1}, +{14144,7,0,-1},{14145,7,1,-1},{14148,7,0,-1},{14149,7,1,-1},{14152,7,0,-1}, +{14153,7,1,-1},{14157,7,0,-1},{14158,7,1,-1},{14161,7,0,-1},{14162,7,1,-1}, +{14165,7,0,-1},{14166,7,1,-1},{14170,7,0,-1},{14171,7,1,-1},{14174,7,0,-1}, +{14175,7,1,-1},{14178,7,0,-1},{14179,7,1,-1},{14183,7,0,-1},{14184,7,1,-1}, +{14187,7,0,-1},{14188,7,1,-1},{14191,7,0,-1},{14192,7,1,-1},{14196,7,0,-1}, +{14197,7,1,-1},{14130,7,0,-1},{14200,7,0,-1},{14201,7,1,-1},{14204,7,0,-1}, +{14205,7,1,-1},{14209,7,0,-1},{14210,7,1,-1},{14143,7,1,-1},{14213,7,0,-1}, +{14214,7,1,-1},{14217,7,0,-1},{14218,7,1,-1},{14222,7,0,-1},{14223,7,1,-1}, +{14226,7,0,-1},{14227,7,1,-1},{14230,7,0,-1},{14231,7,1,-1},{14235,7,0,-1}, +{14236,7,1,-1},{14239,7,0,-1},{14240,7,1,-1},{14243,7,0,-1},{14244,7,1,-1}, +{14248,7,0,-1},{14249,7,1,-1},{14252,7,0,-1},{14253,7,1,-1},{14256,7,0,-1}, +{14257,7,1,-1},{14261,7,0,-1},{14262,7,1,-1},{14265,7,0,-1},{14266,7,1,-1}, +{14269,7,0,-1},{14270,7,1,-1},{14274,7,0,-1},{14275,7,1,-1},{14278,7,0,-1}, +{14279,7,1,-1},{14282,7,0,-1},{14283,7,1,-1},{14287,7,0,-1},{14288,7,1,-1}, +{14291,7,0,-1},{14292,7,1,-1},{14295,7,0,-1},{14296,7,1,-1},{14300,7,0,-1}, +{14301,7,1,-1},{14234,7,0,-1},{14304,7,0,-1},{14305,7,1,-1},{14308,7,0,-1}, +{14309,7,1,-1},{14313,7,0,-1},{14314,7,1,-1},{14247,7,1,-1},{14317,7,0,-1}, +{14318,7,1,-1},{14321,7,0,-1},{14322,7,1,-1},{14326,7,0,-1},{14327,7,1,-1}, +{14330,7,0,-1},{14331,7,1,-1},{14334,7,0,-1},{14335,7,1,-1},{14339,7,0,-1}, +{14340,7,1,-1},{14343,7,0,-1},{14344,7,1,-1},{14347,7,0,-1},{14348,7,1,-1}, +{14352,7,0,-1},{14353,7,1,-1},{14356,7,0,-1},{14357,7,1,-1},{14360,7,0,-1}, +{14361,7,1,-1},{14365,7,0,-1},{14366,7,1,-1},{14369,7,0,-1},{14370,7,1,-1}, +{14373,7,0,-1},{14374,7,1,-1},{14378,7,0,-1},{14379,7,1,-1},{14382,7,0,-1}, +{14383,7,1,-1},{14386,7,0,-1},{14387,7,1,-1},{14391,7,0,-1},{14392,7,1,-1}, +{14395,7,0,-1},{14396,7,1,-1},{14399,7,0,-1},{14400,7,1,-1},{14404,7,0,-1}, +{14405,7,1,-1},{14338,7,0,-1},{14408,7,0,-1},{14409,7,1,-1},{14412,7,0,-1}, +{14413,7,1,-1},{14417,7,0,-1},{14418,7,1,-1},{14351,7,1,-1},{14421,7,0,-1}, +{14422,7,1,-1},{14425,7,0,-1},{14426,7,1,-1},{14430,7,0,-1},{14431,7,1,-1}, +{14434,7,0,-1},{14435,7,1,-1},{14438,7,0,-1},{14439,7,1,-1},{14443,7,0,-1}, +{14444,7,1,-1},{14447,7,0,-1},{14448,7,1,-1},{14451,7,0,-1},{14452,7,1,-1}, +{14456,7,0,-1},{14457,7,1,-1},{14460,7,0,-1},{14461,7,1,-1},{14464,7,0,-1}, +{14465,7,1,-1},{14469,7,0,-1},{14470,7,1,-1},{14473,7,0,-1},{14474,7,1,-1}, +{14477,7,0,-1},{14478,7,1,-1},{14482,7,0,-1},{14483,7,1,-1},{14486,7,0,-1}, +{14487,7,1,-1},{14490,7,0,-1},{14491,7,1,-1},{14495,7,0,-1},{14496,7,1,-1}, +{14499,7,0,-1},{14500,7,1,-1},{14503,7,0,-1},{14504,7,1,-1},{14508,7,0,-1}, +{14509,7,1,-1},{14442,7,0,-1},{14512,7,0,-1},{14513,7,1,-1},{14516,7,0,-1}, +{14517,7,1,-1},{14521,7,0,-1},{14522,7,1,-1},{14455,7,1,-1},{14525,7,0,-1}, +{14526,7,1,-1},{14529,7,0,-1},{14530,7,1,-1},{14534,7,0,-1},{14535,7,1,-1}, +{14538,7,0,-1},{14539,7,1,-1},{14542,7,0,-1},{14543,7,1,-1},{14547,7,0,-1}, +{14548,7,1,-1},{14551,7,0,-1},{14552,7,1,-1},{14555,7,0,-1},{14556,7,1,-1}, +{14560,7,0,-1},{14561,7,1,-1},{14564,7,0,-1},{14565,7,1,-1},{14568,7,0,-1}, +{14569,7,1,-1},{14573,7,0,-1},{14574,7,1,-1},{14577,7,0,-1},{14578,7,1,-1}, +{14581,7,0,-1},{14582,7,1,-1},{14586,7,0,-1},{14587,7,1,-1},{14590,7,0,-1}, +{14591,7,1,-1},{14594,7,0,-1},{14595,7,1,-1},{14599,7,0,-1},{14600,7,1,-1}, +{14603,7,0,-1},{14604,7,1,-1},{14607,7,0,-1},{14608,7,1,-1},{14612,7,0,-1}, +{14613,7,1,-1},{14546,7,0,-1},{14616,7,0,-1},{14617,7,1,-1},{14620,7,0,-1}, +{14621,7,1,-1},{14625,7,0,-1},{14626,7,1,-1},{14559,7,1,-1},{14629,7,0,-1}, +{14630,7,1,-1},{14633,7,0,-1},{14634,7,1,-1},{14638,7,0,-1},{14639,7,1,-1}, +{14642,7,0,-1},{14643,7,1,-1},{14646,7,0,-1},{14647,7,1,-1},{14651,7,0,-1}, +{14652,7,1,-1},{14655,7,0,-1},{14656,7,1,-1},{14659,7,0,-1},{14660,7,1,-1}, +{14664,7,0,-1},{14665,7,1,-1},{14668,7,0,-1},{14669,7,1,-1},{14672,7,0,-1}, +{14673,7,1,-1},{14677,7,0,-1},{14678,7,1,-1},{14681,7,0,-1},{14682,7,1,-1}, +{14685,7,0,-1},{14686,7,1,-1},{14690,7,0,-1},{14691,7,1,-1},{14694,7,0,-1}, +{14695,7,1,-1},{14698,7,0,-1},{14699,7,1,-1},{14703,7,0,-1},{14704,7,1,-1}, +{14707,7,0,-1},{14708,7,1,-1},{14711,7,0,-1},{14712,7,1,-1},{14716,7,0,-1}, +{14717,7,1,-1},{14650,7,0,-1},{14720,7,0,-1},{14721,7,1,-1},{14724,7,0,-1}, +{14725,7,1,-1},{14729,7,0,-1},{14730,7,1,-1},{14663,7,1,-1},{14733,7,0,-1}, +{14734,7,1,-1},{14737,7,0,-1},{14738,7,1,-1},{14742,7,0,-1},{14743,7,1,-1}, +{14746,7,0,-1},{14747,7,1,-1},{14750,7,0,-1},{14751,7,1,-1},{14755,7,0,-1}, +{14756,7,1,-1},{14759,7,0,-1},{14760,7,1,-1},{14763,7,0,-1},{14764,7,1,-1}, +{14768,7,0,-1},{14769,7,1,-1},{14772,7,0,-1},{14773,7,1,-1},{14776,7,0,-1}, +{14777,7,1,-1},{14781,7,0,-1},{14782,7,1,-1},{14785,7,0,-1},{14786,7,1,-1}, +{14789,7,0,-1},{14790,7,1,-1},{14794,7,0,-1},{14795,7,1,-1},{14798,7,0,-1}, +{14799,7,1,-1},{14802,7,0,-1},{14803,7,1,-1},{14807,7,0,-1},{14808,7,1,-1}, +{14811,7,0,-1},{14812,7,1,-1},{14815,7,0,-1},{14816,7,1,-1},{14820,7,0,-1}, +{14821,7,1,-1},{14754,7,0,-1},{14824,7,0,-1},{14825,7,1,-1},{14828,7,0,-1}, +{14829,7,1,-1},{14833,7,0,-1},{14834,7,1,-1},{14767,7,1,-1},{14837,7,0,-1}, +{14838,7,1,-1},{14841,7,0,-1},{14842,7,1,-1},{14846,7,0,-1},{14847,7,1,-1}, +{14850,7,0,-1},{14851,7,1,-1},{14854,7,0,-1},{14855,7,1,-1},{14859,7,0,-1}, +{14860,7,1,-1},{14863,7,0,-1},{14864,7,1,-1},{14867,7,0,-1},{14868,7,1,-1}, +{14872,7,0,-1},{14873,7,1,-1},{14876,7,0,-1},{14877,7,1,-1},{14880,7,0,-1}, +{14881,7,1,-1},{14885,7,0,-1},{14886,7,1,-1},{14889,7,0,-1},{14890,7,1,-1}, +{14893,7,0,-1},{14894,7,1,-1},{14898,7,0,-1},{14899,7,1,-1},{14902,7,0,-1}, +{14903,7,1,-1},{14906,7,0,-1},{14907,7,1,-1},{14911,7,0,-1},{14912,7,1,-1}, +{14915,7,0,-1},{14916,7,1,-1},{14919,7,0,-1},{14920,7,1,-1},{14924,7,0,-1}, +{14925,7,1,-1},{14858,7,0,-1},{14928,7,0,-1},{14929,7,1,-1},{14932,7,0,-1}, +{14933,7,1,-1},{14937,7,0,-1},{14938,7,1,-1},{14871,7,1,-1},{14941,7,0,-1}, +{14942,7,1,-1},{14945,7,0,-1},{14946,7,1,-1},{14950,7,0,-1},{14951,7,1,-1}, +{14954,7,0,-1},{14955,7,1,-1},{14958,7,0,-1},{14959,7,1,-1},{14963,7,0,-1}, +{14964,7,1,-1},{14967,7,0,-1},{14968,7,1,-1},{14971,7,0,-1},{14972,7,1,-1}, +{14976,7,0,-1},{14977,7,1,-1},{14980,7,0,-1},{14981,7,1,-1},{14984,7,0,-1}, +{14985,7,1,-1},{14989,7,0,-1},{14990,7,1,-1},{14993,7,0,-1},{14994,7,1,-1}, +{14997,7,0,-1},{14998,7,1,-1},{15002,7,0,-1},{15003,7,1,-1},{15006,7,0,-1}, +{15007,7,1,-1},{15010,7,0,-1},{15011,7,1,-1},{15015,7,0,-1},{15016,7,1,-1}, +{15019,7,0,-1},{15020,7,1,-1},{15023,7,0,-1},{15024,7,1,-1},{15028,7,0,-1}, +{15029,7,1,-1},{14962,7,0,-1},{15032,7,0,-1},{15033,7,1,-1},{15036,7,0,-1}, +{15037,7,1,-1},{15041,7,0,-1},{15042,7,1,-1},{14975,7,1,-1},{15045,7,0,-1}, +{15046,7,1,-1},{15049,7,0,-1},{15050,7,1,-1},{15054,7,0,-1},{15055,7,1,-1}, +{15058,7,0,-1},{15059,7,1,-1},{15062,7,0,-1},{15063,7,1,-1},{15067,7,0,-1}, +{15068,7,1,-1},{15071,7,0,-1},{15072,7,1,-1},{15075,7,0,-1},{15076,7,1,-1}, +{15080,7,0,-1},{15081,7,1,-1},{15084,7,0,-1},{15085,7,1,-1},{15088,7,0,-1}, +{15089,7,1,-1},{15093,7,0,-1},{15094,7,1,-1},{15097,7,0,-1},{15098,7,1,-1}, +{15101,7,0,-1},{15102,7,1,-1},{15106,7,0,-1},{15107,7,1,-1},{15110,7,0,-1}, +{15111,7,1,-1},{15114,7,0,-1},{15115,7,1,-1},{15119,7,0,-1},{15120,7,1,-1}, +{15123,7,0,-1},{15124,7,1,-1},{15127,7,0,-1},{15128,7,1,-1},{15132,7,0,-1}, +{15133,7,1,-1},{15066,7,0,-1},{15136,7,0,-1},{15137,7,1,-1},{15140,7,0,-1}, +{15141,7,1,-1},{15145,7,0,-1},{15146,7,1,-1},{15079,7,1,-1},{15149,7,0,-1}, +{15150,7,1,-1},{15153,7,0,-1},{15154,7,1,-1},{15158,7,0,-1},{15159,7,1,-1}, +{15162,7,0,-1},{15163,7,1,-1},{15166,7,0,-1},{15167,7,1,-1},{15171,7,0,-1}, +{15172,7,1,-1},{15175,7,0,-1},{15176,7,1,-1},{15179,7,0,-1},{15180,7,1,-1}, +{15184,7,0,-1},{15185,7,1,-1},{15188,7,0,-1},{15189,7,1,-1},{15192,7,0,-1}, +{15193,7,1,-1},{15197,7,0,-1},{15198,7,1,-1},{15201,7,0,-1},{15202,7,1,-1}, +{15205,7,0,-1},{15206,7,1,-1},{15210,7,0,-1},{15211,7,1,-1},{15214,7,0,-1}, +{15215,7,1,-1},{15218,7,0,-1},{15219,7,1,-1},{15223,7,0,-1},{15224,7,1,-1}, +{15227,7,0,-1},{15228,7,1,-1},{15231,7,0,-1},{15232,7,1,-1},{15236,7,0,-1}, +{15237,7,1,-1},{15170,7,0,-1},{15240,7,0,-1},{15241,7,1,-1},{15244,7,0,-1}, +{15245,7,1,-1},{15249,7,0,-1},{15250,7,1,-1},{15183,7,1,-1},{15253,7,0,-1}, +{15254,7,1,-1},{15257,7,0,-1},{15258,7,1,-1},{15262,7,0,-1},{15263,7,1,-1}, +{15266,7,0,-1},{15267,7,1,-1},{15270,7,0,-1},{15271,7,1,-1},{15275,7,0,-1}, +{15276,7,1,-1},{15279,7,0,-1},{15280,7,1,-1},{15284,7,1,-1},{15288,7,0,-1}, +{15289,7,1,-1},{15292,7,0,-1},{15293,7,1,-1},{15296,7,0,-1},{15297,7,1,-1}, +{15301,7,0,-1},{15302,7,1,-1},{15305,7,0,-1},{15306,7,1,-1},{15309,7,0,-1}, +{15310,7,1,-1},{15314,7,0,-1},{15315,7,1,-1},{15318,7,0,-1},{15319,7,1,-1}, +{15322,7,0,-1},{15323,7,1,-1},{15327,7,0,-1},{15328,7,1,-1},{15331,7,0,-1}, +{15332,7,1,-1},{15336,7,1,-1},{15340,7,0,-1},{15341,7,1,-1},{15344,7,0,-1}, +{15345,7,1,-1},{15348,7,0,-1},{15349,7,1,-1},{15353,7,0,-1},{15354,7,1,-1}, +{15357,7,0,-1},{15361,7,0,-1},{15366,7,0,-1},{15370,7,0,-1},{15374,7,0,-1}}; diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am new file mode 100644 index 00000000..f232c493 --- /dev/null +++ b/tests/misc/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) $(LIBOSMOTRAU_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOABIS_LIBS) $(LIBOSMOTRAU_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 \ + $(LDADD) diff --git a/tests/misc/misc_test.c b/tests/misc/misc_test.c new file mode 100644 index 00000000..c4d3a595 --- /dev/null +++ b/tests/misc/misc_test.c @@ -0,0 +1,199 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmo-bts/bts.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/logging.h> + +#include <osmocom/core/application.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <stdlib.h> +#include <stdio.h> + +void *ctx = NULL; + +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(GSM_LCHAN_SI(&lchan, i), i, GSM_MACBLOCK_LEN); + } + + /* 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); + } +} + +static void test_bts_supports_cm(void) +{ + struct gsm_bts *bts; + + bts = gsm_bts_alloc(ctx, 0); + + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_V1) == 1); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_H, GSM48_CMODE_SPEECH_V1) == 1); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_EFR) == 0); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_AMR) == 1); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_H, GSM48_CMODE_SPEECH_AMR) == 1); + + talloc_free(bts); +} + +int main(int argc, char **argv) +{ + ctx = talloc_named_const(NULL, 0, "misc_test"); + + osmo_init_logging2(ctx, &bts_log_info); + + test_sacch_get(); + test_msg_utils_ipa(); + test_msg_utils_oml(); + test_bts_supports_cm(); + return EXIT_SUCCESS; +} diff --git a/tests/misc/misc_test.ok b/tests/misc/misc_test.ok new file mode 100644 index 00000000..a52ce5dd --- /dev/null +++ b/tests/misc/misc_test.ok @@ -0,0 +1,6 @@ +Testing lchan_sacch_get +Testing IPA structure +Testing OML structure + Testing IPA messages. + Testing Osmo messages. + Testing ETSI messages. diff --git a/tests/paging/Makefile.am b/tests/paging/Makefile.am new file mode 100644 index 00000000..74d98265 --- /dev/null +++ b/tests/paging/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS) +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) diff --git a/tests/paging/paging_test.c b/tests/paging/paging_test.c new file mode 100644 index 00000000..f112404a --- /dev/null +++ b/tests/paging/paging_test.c @@ -0,0 +1,187 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/l1sap.h> + +#include <unistd.h> + +static struct gsm_bts *bts; + +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(bts->paging_state, 0, static_ilv, 0); + ASSERT_TRUE(rc == 0); + ASSERT_TRUE(paging_queue_length(bts->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(bts->paging_state, out_buf, &g_time, &is_empty); + ASSERT_TRUE(rc == 13); + ASSERT_TRUE(is_empty == 0); + + ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0)); + ASSERT_TRUE(paging_queue_length(bts->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(bts->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(bts->paging_state, 0, static_ilv, 0); + ASSERT_TRUE(rc == 0); + ASSERT_TRUE(paging_queue_length(bts->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(bts->paging_state, out_buf, &g_time, &is_empty); + ASSERT_TRUE(rc == 13); + ASSERT_TRUE(is_empty == 0); + + ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0)); + ASSERT_TRUE(paging_queue_length(bts->paging_state) == 0); +} + +/* Set up a dummy trx with a valid setting for bs_ag_blks_res in SI3 */ +static struct gsm_bts_trx *test_is_ccch_for_agch_setup(uint8_t bs_ag_blks_res) +{ + static struct gsm_bts_trx trx; + static struct gsm_bts bts; + struct gsm48_system_information_type_3 si3 = { 0 }; + si3.control_channel_desc.bs_ag_blks_res = bs_ag_blks_res; + trx.bts = &bts; + bts.si_valid |= 0x8; + memcpy(&bts.si_buf[SYSINFO_TYPE_3][0], &si3, sizeof(si3)); + return &trx; +} + +/* Walk through all possible settings for bs_ag_blks_res for two + * multiframe 51. The patterns shown in 3GPP TS 05.02 Clause 7 + * Table 5 of 9 must occur. */ +static void test_is_ccch_for_agch(void) +{ + int is_ag_res; + int fn; + uint8_t bs_ag_blks_res; + struct gsm_bts_trx *trx; + + printf("Fn: AGCH: (bs_ag_blks_res=[0:7]\n"); + for (fn = 0; fn < 102; fn++) { + uint8_t fn51 = fn % 51; + /* Note: the formula that computes the CCCH block number for a + * given frame number is optimized to work on block boarders, + * for frame numbers that do not fall at the beginning of the + * related block this formula would produce wrong results, so + * we only check with frame numbers that mark the beginning + * of a new block. See also L1SAP_FN2CCCHBLOCK() in l1sap.h */ + + if (fn51 % 10 != 2 && fn51 % 10 != 6) + continue; + + printf("%03u: ", fn); + + if (fn51 == 2) { + printf(" . . . . . . . . (BCCH)\n"); + continue; + } + + /* Try allo possible settings for bs_ag_blks_res */ + for (bs_ag_blks_res = 0; bs_ag_blks_res <= 7; bs_ag_blks_res++) { + trx = test_is_ccch_for_agch_setup(bs_ag_blks_res); + is_ag_res = is_ccch_for_agch(trx, fn); + printf(" %u", is_ag_res); + } + printf("\n"); + } +} + +int main(int argc, char **argv) +{ + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + test_paging_smoke(); + test_paging_sleep(); + test_is_ccch_for_agch(); + printf("Success\n"); + + return 0; +} + diff --git a/tests/paging/paging_test.ok b/tests/paging/paging_test.ok new file mode 100644 index 00000000..927fdf92 --- /dev/null +++ b/tests/paging/paging_test.ok @@ -0,0 +1,24 @@ +Testing that paging messages expire. +Testing that paging messages expire with sleep. +Fn: AGCH: (bs_ag_blks_res=[0:7] +002: . . . . . . . . (BCCH) +006: 0 1 1 1 1 1 1 1 +012: 0 0 1 1 1 1 1 1 +016: 0 0 0 1 1 1 1 1 +022: 0 0 0 0 1 1 1 1 +026: 0 0 0 0 0 1 1 1 +032: 0 0 0 0 0 0 1 1 +036: 0 0 0 0 0 0 0 1 +042: 0 0 0 0 0 0 0 0 +046: 0 0 0 0 0 0 0 0 +053: . . . . . . . . (BCCH) +057: 0 1 1 1 1 1 1 1 +063: 0 0 1 1 1 1 1 1 +067: 0 0 0 1 1 1 1 1 +073: 0 0 0 0 1 1 1 1 +077: 0 0 0 0 0 1 1 1 +083: 0 0 0 0 0 0 1 1 +087: 0 0 0 0 0 0 0 1 +093: 0 0 0 0 0 0 0 0 +097: 0 0 0 0 0 0 0 0 +Success diff --git a/tests/power/Makefile.am b/tests/power/Makefile.am new file mode 100644 index 00000000..ac45f238 --- /dev/null +++ b/tests/power/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) + +noinst_PROGRAMS = power_test +EXTRA_DIST = power_test.ok + +power_test_SOURCES = power_test.c $(srcdir)/../stubs.c +power_test_LDADD = $(top_builddir)/src/common/libbts.a $(LIBOSMOABIS_LIBS) $(LDADD) diff --git a/tests/power/power_test.c b/tests/power/power_test.c new file mode 100644 index 00000000..a46a430c --- /dev/null +++ b/tests/power/power_test.c @@ -0,0 +1,88 @@ +/* + * (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 <http://www.gnu.org/licenses/>. + */ + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/power_control.h> + +#include <stdio.h> + +static inline void apply_power_test(struct gsm_lchan *lchan, int rxlev, int exp_ret, uint8_t exp_current) +{ + int ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, rxlev); + + printf("power control [%d]: MS current power %u\n", ret, lchan->ms_power_ctrl.current); + OSMO_ASSERT(ret == exp_ret); + OSMO_ASSERT(lchan->ms_power_ctrl.current == exp_current); +} + +static void test_power_loop(void) +{ + struct gsm_bts bts; + struct gsm_bts_trx trx; + struct gsm_bts_trx_ts ts; + struct gsm_lchan *lchan; + + memset(&bts, 0, sizeof(bts)); + memset(&trx, 0, sizeof(trx)); + memset(&ts, 0, sizeof(ts)); + + lchan = &ts.lchan[0]; + lchan->ts = &ts; + ts.trx = &trx; + trx.bts = &bts; + bts.band = GSM_BAND_1800; + trx.ms_power_control = 1; + bts.ul_power_target = -75; + + 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); + + /* Simply clamping */ + apply_power_test(lchan, -60, 0, 15); + + /* + * Now 15 dB too little and we should power it up. Could be a + * power level of 7 or 8 for 15 dBm + */ + apply_power_test(lchan, -90, 1, 7); + + /* It should be clamped to level 0 and 30 dBm */ + apply_power_test(lchan, -100, 1, 0); + + /* Fix it and jump down */ + lchan->ms_power_ctrl.fixed = 1; + apply_power_test(lchan, -60, 0, 0); + + /* And leave it again */ + lchan->ms_power_ctrl.fixed = 0; + apply_power_test(lchan, -40, 1, 15); +} + +int main(int argc, char **argv) +{ + printf("Testing power loop...\n"); + + test_power_loop(); + + printf("Power loop test OK\n"); + + return 0; +} diff --git a/tests/power/power_test.ok b/tests/power/power_test.ok new file mode 100644 index 00000000..cf0a38b4 --- /dev/null +++ b/tests/power/power_test.ok @@ -0,0 +1,7 @@ +Testing power loop... +power control [0]: MS current power 15 +power control [1]: MS current power 7 +power control [1]: MS current power 0 +power control [0]: MS current power 0 +power control [1]: MS current power 15 +Power loop test OK diff --git a/tests/stubs.c b/tests/stubs.c new file mode 100644 index 00000000..7c64034b --- /dev/null +++ b/tests/stubs.c @@ -0,0 +1,59 @@ +#include <osmo-bts/bts.h> + +struct femtol1_hdl; +struct bts_model_set_dyn_pdch_data; + +/* + * 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 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_init(struct gsm_bts_trx *trx) +{ 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_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_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ 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_lchan_deactivate(struct gsm_lchan *lchan) { return 0; } +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return 0; } + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ return 0; } + +void bts_model_abis_close(struct gsm_bts *bts) +{ } + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ return 0; } + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ return; } diff --git a/tests/sysmobts/Makefile.am b/tests/sysmobts/Makefile.am new file mode 100644 index 00000000..0829ca52 --- /dev/null +++ b/tests/sysmobts/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_srcdir)/src/osmo-bts-sysmo $(SYSMOBTS_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) + +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) diff --git a/tests/sysmobts/sysmobts_test.c b/tests/sysmobts/sysmobts_test.c new file mode 100644 index 00000000..4b01ed7a --- /dev/null +++ b/tests/sysmobts/sysmobts_test.c @@ -0,0 +1,210 @@ +/* + * (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 <http://www.gnu.org/licenses/>. + */ + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/power_control.h> + +#include "femtobts.h" +#include "l1_if.h" +#include "utils.h" + +#include <sysmocom/femtobts/gsml1prim.h> + +#include <stdio.h> + +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_trx trx; + struct femtol1_hdl hdl; + int i; + + memset(&bts, 0, sizeof(bts)); + memset(&trx, 0, sizeof(trx)); + memset(&hdl, 0, sizeof(hdl)); + 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; + + bts.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; + + bts.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; + + bts.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; + + bts.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 */ +} + +int main(int argc, char **argv) +{ + printf("Testing sysmobts routines\n"); + test_sysmobts_auto_band(); + test_sysmobts_cipher(); + + 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_trx_init(struct gsm_bts_trx *trx) +{ return 0; } +int bts_model_oml_estab(struct gsm_bts *bts) +{ return 0; } +void bts_model_abis_close(struct gsm_bts *bts) +{ } +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ } +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ } diff --git a/tests/sysmobts/sysmobts_test.ok b/tests/sysmobts/sysmobts_test.ok new file mode 100644 index 00000000..1f534172 --- /dev/null +++ b/tests/sysmobts/sysmobts_test.ok @@ -0,0 +1,19 @@ +Testing 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) diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 00000000..2d1cefd3 --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,51 @@ +AT_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([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 + +AT_SETUP([power]) +AT_KEYWORDS([power]) +cat $abs_srcdir/power/power_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/power/power_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([tx_power]) +AT_KEYWORDS([tx_power]) +cat $abs_srcdir/tx_power/tx_power_test.ok > expout +cat $abs_srcdir/tx_power/tx_power_test.err > experr +AT_CHECK([$abs_top_builddir/tests/tx_power/tx_power_test], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([meas]) +AT_KEYWORDS([meas]) +cat $abs_srcdir/meas/meas_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/meas/meas_test], [], [expout], [ignore]) +AT_CLEANUP diff --git a/tests/tx_power/Makefile.am b/tests/tx_power/Makefile.am new file mode 100644 index 00000000..cd7ccc2f --- /dev/null +++ b/tests/tx_power/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) +noinst_PROGRAMS = tx_power_test +EXTRA_DIST = tx_power_test.ok tx_power_test.err + +tx_power_test_SOURCES = tx_power_test.c $(srcdir)/../stubs.c +tx_power_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/tx_power/tx_power_test.c b/tests/tx_power/tx_power_test.c new file mode 100644 index 00000000..ad3f68ce --- /dev/null +++ b/tests/tx_power/tx_power_test.c @@ -0,0 +1,247 @@ +/* Test cases for tx_power.c Transmit Power Computation */ + +/* (C) 2017 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/tx_power.h> + + +static const struct trx_power_params tpp_1002 = { + .trx_p_max_out_mdBm = to_mdB(23), + .p_total_tgt_mdBm = to_mdB(23), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = 0, + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(23), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static const struct trx_power_params tpp_1020 = { + .trx_p_max_out_mdBm = to_mdB(23), + .p_total_tgt_mdBm = to_mdB(33), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = to_mdB(10), + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(0), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static const struct trx_power_params tpp_1100 = { + .trx_p_max_out_mdBm = to_mdB(23), + .p_total_tgt_mdBm = to_mdB(40), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = to_mdB(17), + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(0), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static const struct trx_power_params tpp_2050 = { + .trx_p_max_out_mdBm = to_mdB(37), + .p_total_tgt_mdBm = to_mdB(37), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = 0, + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(0), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static void test_sbts1002(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 1002\n"); + trx->power_params = tpp_1002; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(23)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(23)); + /* at max_power_red = 0, we expect full 23dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(23)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 21dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(21)); + /* at 1 step (of 2dB), we expect full 23-2-2=19 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(19)); + /* at 2 steps (= 4dB), we expect 23-2-4=17*/ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17)); +} + +static void test_sbts1020(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 1020\n"); + trx->power_params = tpp_1020; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(-10)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(33)); + /* at max_power_red = 0, we expect full 33dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(33)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 31dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(31)); + /* at 1 step (of 2dB), we expect full 33-2-2=29 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(29)); + /* at 2 steps (= 4dB), we expect 33-2-4-10=17*/ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17)); +} + + +static void test_sbts1100(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 1100\n"); + trx->power_params = tpp_1100; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(-17)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(40)); + /* at max_power_red = 0, we expect full 33dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(40)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 38dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(38)); + /* at 1 step (of 2dB), we expect full 40-2-2=36 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(36)); + /* at 2 steps (= 4dB), we expect 40-2-4-17=17*/ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17)); +} + +static void test_sbts2050(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 2050\n"); + trx->power_params = tpp_2050; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(0)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(37)); + /* at max_power_red = 0, we expect full 37dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(37)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 35dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(35)); + /* at 1 step (of 2dB), we expect full 37-2-2=33 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(33)); + /* at 2 steps (= 4dB), we expect 37-2-4=31dBm */ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(31)); +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + + printf("CHANGE_POWER(%d)\n", p_trxout_mdBm); + + if (tpp->ramp.attenuation_mdB == 0) + exit(0); + + power_trx_change_compl(trx, p_trxout_mdBm); + return 0; +} + +static void test_power_ramp(struct gsm_bts_trx *trx, int dBm) +{ + printf("Testing tx_power ramping for sysmoBTS 1020\n"); + trx->power_params = tpp_1020; + trx->max_power_red = 0; + + power_ramp_start(trx, to_mdB(dBm), 0); +} + +int main(int argc, char **argv) +{ + static struct gsm_bts *bts; + struct gsm_bts_trx *trx; + void *tall_bts_ctx; + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + osmo_stderr_target->categories[DL1C].loglevel = LOGL_DEBUG; + log_set_print_filename(osmo_stderr_target, 0); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + 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); + } + + test_sbts1002(trx); + test_sbts1020(trx); + test_sbts1100(trx); + test_sbts2050(trx); + + /* test error case / excess power (40 dBm is too much) */ + test_power_ramp(trx, 40); + /* test actaul ramping to full 33 dBm */ + test_power_ramp(trx, 33); + + while (1) { + osmo_select_main(0); + } +} diff --git a/tests/tx_power/tx_power_test.err b/tests/tx_power/tx_power_test.err new file mode 100644 index 00000000..bf33b424 --- /dev/null +++ b/tests/tx_power/tx_power_test.err @@ -0,0 +1,38 @@ +power_ramp_start(cur=0, tgt=40000) +[0;mAsked to ramp power up to 40000 mdBm, which exceeds P_max_out (33000) +[0;mpower_ramp_start(cur=0, tgt=33000) +[0;mramp_timer_cb(cur_pout=2000, tgt_pout=33000, ramp_att=31000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to -8000 mdBm. +[0;mramp_timer_cb(cur_pout=4000, tgt_pout=33000, ramp_att=29000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to -6000 mdBm. +[0;mramp_timer_cb(cur_pout=6000, tgt_pout=33000, ramp_att=27000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to -4000 mdBm. +[0;mramp_timer_cb(cur_pout=8000, tgt_pout=33000, ramp_att=25000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to -2000 mdBm. +[0;mramp_timer_cb(cur_pout=10000, tgt_pout=33000, ramp_att=23000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 0 mdBm. +[0;mramp_timer_cb(cur_pout=12000, tgt_pout=33000, ramp_att=21000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 2000 mdBm. +[0;mramp_timer_cb(cur_pout=14000, tgt_pout=33000, ramp_att=19000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 4000 mdBm. +[0;mramp_timer_cb(cur_pout=16000, tgt_pout=33000, ramp_att=17000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 6000 mdBm. +[0;mramp_timer_cb(cur_pout=18000, tgt_pout=33000, ramp_att=15000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 8000 mdBm. +[0;mramp_timer_cb(cur_pout=20000, tgt_pout=33000, ramp_att=13000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 10000 mdBm. +[0;mramp_timer_cb(cur_pout=22000, tgt_pout=33000, ramp_att=11000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 12000 mdBm. +[0;mramp_timer_cb(cur_pout=24000, tgt_pout=33000, ramp_att=9000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 14000 mdBm. +[0;mramp_timer_cb(cur_pout=26000, tgt_pout=33000, ramp_att=7000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 16000 mdBm. +[0;mramp_timer_cb(cur_pout=28000, tgt_pout=33000, ramp_att=5000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 18000 mdBm. +[0;mramp_timer_cb(cur_pout=30000, tgt_pout=33000, ramp_att=3000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 20000 mdBm. +[0;mramp_timer_cb(cur_pout=32000, tgt_pout=33000, ramp_att=1000, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 22000 mdBm. +[0;mramp_timer_cb(cur_pout=33000, tgt_pout=33000, ramp_att=0, therm_att=0, user_gain=0) +[0;mramping TRX board output power to 23000 mdBm. +[0;m
\ No newline at end of file diff --git a/tests/tx_power/tx_power_test.ok b/tests/tx_power/tx_power_test.ok new file mode 100644 index 00000000..ceb88ab4 --- /dev/null +++ b/tests/tx_power/tx_power_test.ok @@ -0,0 +1,23 @@ +Testing tx_power calculation for sysmoBTS 1002 +Testing tx_power calculation for sysmoBTS 1020 +Testing tx_power calculation for sysmoBTS 1100 +Testing tx_power calculation for sysmoBTS 2050 +Testing tx_power ramping for sysmoBTS 1020 +Testing tx_power ramping for sysmoBTS 1020 +CHANGE_POWER(-8000) +CHANGE_POWER(-6000) +CHANGE_POWER(-4000) +CHANGE_POWER(-2000) +CHANGE_POWER(0) +CHANGE_POWER(2000) +CHANGE_POWER(4000) +CHANGE_POWER(6000) +CHANGE_POWER(8000) +CHANGE_POWER(10000) +CHANGE_POWER(12000) +CHANGE_POWER(14000) +CHANGE_POWER(16000) +CHANGE_POWER(18000) +CHANGE_POWER(20000) +CHANGE_POWER(22000) +CHANGE_POWER(23000) |