From a288295e43b918b10d8b66565ca582141b265d06 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 21:28:21 +0100 Subject: [PATCH 001/560] created new repo for VILLASfpga --- fpga/.gitmodules | 6 + fpga/CHANGELOG.md | 26 ++ fpga/COPYING.md | 675 ++++++++++++++++++++++++++++++++++++++ fpga/README.md | 45 +++ fpga/thirdparty/criterion | 1 + fpga/thirdparty/libxil | 1 + 6 files changed, 754 insertions(+) create mode 100644 fpga/.gitmodules create mode 100644 fpga/CHANGELOG.md create mode 100644 fpga/COPYING.md create mode 100644 fpga/README.md create mode 160000 fpga/thirdparty/criterion create mode 160000 fpga/thirdparty/libxil diff --git a/fpga/.gitmodules b/fpga/.gitmodules new file mode 100644 index 000000000..a451f96b4 --- /dev/null +++ b/fpga/.gitmodules @@ -0,0 +1,6 @@ +[submodule "thirdparty/criterion"] + path = thirdparty/criterion + url = https://github.com/Snaipe/Criterion.git +[submodule "thirdparty/libxil"] + path = thirdparty/libxil + url = git@git.rwth-aachen.de:VILLASframework/libxil.git diff --git a/fpga/CHANGELOG.md b/fpga/CHANGELOG.md new file mode 100644 index 000000000..609cbc988 --- /dev/null +++ b/fpga/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [0.1.0] - 2017-11-21 + +### Changed + +- Switched to CMake + +### Added + +- Sourcecode import from VILLASnode project: + http://git.rwth-aachen.de/VILLASframework/VILLASnode + diff --git a/fpga/COPYING.md b/fpga/COPYING.md new file mode 100644 index 000000000..2a99aeee3 --- /dev/null +++ b/fpga/COPYING.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +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. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +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 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. Use with the GNU Affero General Public License. + +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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU 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 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 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 General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that 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 . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +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 GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . \ No newline at end of file diff --git a/fpga/README.md b/fpga/README.md new file mode 100644 index 000000000..55eb6130c --- /dev/null +++ b/fpga/README.md @@ -0,0 +1,45 @@ +# VILLASfpga + +[![build status](https://git.rwth-aachen.de/VILLASframework/VILLASfpga/badges/develop/build.svg)](https://git.rwth-aachen.de/acs/VILLASfpga/commits/develop) +[![coverage report](https://git.rwth-aachen.de/VILLASframework/VILLASfpga/badges/develop/coverage.svg)](https://git.rwth-aachen.de/acs/VILLASfpga/commits/develop) + +**TODO:** Write project description + +## Documentation + +User documentation is available here: + +## Copyright + +2017, Institute for Automation of Complex Power Systems, EONERC + +## License + +This project is released under the terms of the [GPL version 3](COPYING.md). + +``` +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 +any later version. + +This program is distributed in the hope that 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 . +``` + +For other licensing options please consult [Prof. Antonello Monti](mailto:amonti@eonerc.rwth-aachen.de). + +## Contact + +[![EONERC ACS Logo](doc/pictures/eonerc_logo.png)](http://www.acs.eonerc.rwth-aachen.de) + +- Steffen Vogel + +[Institute for Automation of Complex Power Systems (ACS)](http://www.acs.eonerc.rwth-aachen.de) +[EON Energy Research Center (EONERC)](http://www.eonerc.rwth-aachen.de) +[RWTH University Aachen, Germany](http://www.rwth-aachen.de) diff --git a/fpga/thirdparty/criterion b/fpga/thirdparty/criterion new file mode 160000 index 000000000..1613f3e93 --- /dev/null +++ b/fpga/thirdparty/criterion @@ -0,0 +1 @@ +Subproject commit 1613f3e93cfc3e771a6c22d6d56018ec722ced48 diff --git a/fpga/thirdparty/libxil b/fpga/thirdparty/libxil new file mode 160000 index 000000000..89eb3ead0 --- /dev/null +++ b/fpga/thirdparty/libxil @@ -0,0 +1 @@ +Subproject commit 89eb3ead0c210318144238f2b5b6a96ce3feec73 From 01130e6fa48d956fdffb481d4d0a4d198e8293f8 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 21:29:34 +0100 Subject: [PATCH 002/560] added simple Dockerfile for development --- fpga/.dockerignore | 2 ++ fpga/Dockerfile | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 fpga/.dockerignore create mode 100644 fpga/Dockerfile diff --git a/fpga/.dockerignore b/fpga/.dockerignore new file mode 100644 index 000000000..d4d699950 --- /dev/null +++ b/fpga/.dockerignore @@ -0,0 +1,2 @@ +* +!thirdparty/criterion/ diff --git a/fpga/Dockerfile b/fpga/Dockerfile new file mode 100644 index 000000000..a4ddf76a4 --- /dev/null +++ b/fpga/Dockerfile @@ -0,0 +1,71 @@ +# Dockerfile for VILLASfpga development. +# +# This Dockerfile builds an image which contains all library dependencies +# and tools to build VILLASfpga. +# However, VILLASfpga itself it not part of the image. +# +# This image can be used for developing VILLASfpga +# by running: +# make docker +# +# @author Steffen Vogel +# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC +# @license GNU General Public License (version 3) +# +# VILLASfpga +# +# 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 +# any later version. +# +# This program is distributed in the hope that 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 . +################################################################################### + +FROM registry.fedoraproject.org/fedora:27 + +LABEL \ + org.label-schema.schema-version="1.0" \ + org.label-schema.name="VILLASfpga" \ + org.label-schema.license="GPL-3.0" \ + org.label-schema.vendor="Institute for Automation of Complex Power Systems, RWTH Aachen University" \ + org.label-schema.author.name="Steffen Vogel" \ + org.label-schema.author.email="stvogel@eonerc.rwth-aachen.de" \ + org.label-schema.description="A image containing all build-time dependencies for VILLASfpga based on Fedora" \ + org.label-schema.url="http://fein-aachen.org/projects/villas-framework/" \ + org.label-schema.vcs-url="https://git.rwth-aachen.de/VILLASframework/VILLASfpga" \ + org.label-schema.usage="https://villas.fein-aachen.org/doc/fpga.html" + +# Toolchain +RUN dnf -y install \ + gcc gcc-c++ \ + pkgconfig make cmake \ + autoconf automake autogen libtool \ + texinfo git curl tar + +# Several tools only needed for developement and testing +RUN dnf -y install \ + rpmdevtools rpm-build + +# Some of the dependencies are only available in our own repo +ADD https://villas.fein-aachen.org/packages/villas.repo /etc/yum.repos.d/ + +# Dependencies +RUN dnf -y install \ + jansson-devel \ + libxil-devel + +# Build & Install Criterion +COPY thirdparty/criterion /tmp/criterion +RUN mkdir -p /tmp/criterion/build && cd /tmp/criterion/build && cmake .. && make install && rm -rf /tmp/* + +ENV LD_LIBRARY_PATH /usr/local/lib:/usr/local/lib64 + +WORKDIR /villas +ENTRYPOINT bash From c3164e93ef27edc2396dc2600019fb512c6574e3 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 21:31:08 +0100 Subject: [PATCH 003/560] imported source code from VILLASfpga repo and made it compile --- fpga/.editorconfig | 17 + fpga/.gitlab-ci.yml | 94 + fpga/CMakeLists.txt | 8 + fpga/cmake/FindCriterion.cmake | 27 + fpga/doc/pictures/eonerc_logo.png | Bin 0 -> 9348 bytes fpga/doc/pictures/villas_fpga.png | Bin 0 -> 14274 bytes fpga/doc/pictures/villas_fpga.svg | 113 + fpga/etc/fpga-simple.conf | 73 + fpga/etc/fpga.conf | 168 + fpga/include/villas/common.h | 36 + fpga/include/villas/config.h | 71 + fpga/include/villas/fpga/card.h | 95 + fpga/include/villas/fpga/ip.h | 119 + fpga/include/villas/fpga/ips/dft.h | 52 + fpga/include/villas/fpga/ips/dma.h | 88 + fpga/include/villas/fpga/ips/fifo.h | 52 + fpga/include/villas/fpga/ips/intc.h | 56 + fpga/include/villas/fpga/ips/model.h | 147 + fpga/include/villas/fpga/ips/rtds_axis.h | 62 + fpga/include/villas/fpga/ips/switch.h | 67 + fpga/include/villas/fpga/ips/timer.h | 43 + fpga/include/villas/fpga/vlnv.h | 53 + fpga/include/villas/kernel/kernel.h | 90 + fpga/include/villas/kernel/pci.h | 62 + fpga/include/villas/kernel/vfio.h | 112 + fpga/include/villas/list.h | 112 + fpga/include/villas/log.h | 194 + fpga/include/villas/log_config.h | 37 + fpga/include/villas/plugin.h | 82 + fpga/include/villas/utils.h | 276 + fpga/lib/CMakeLists.txt | 53 + fpga/lib/card.c | 313 + fpga/lib/ip.c | 167 + fpga/lib/ips/dft.c | 140 + fpga/lib/ips/dma.c | 657 ++ fpga/lib/ips/fifo.c | 153 + fpga/lib/ips/intc.c | 180 + fpga/lib/ips/model.c | 427 ++ fpga/lib/ips/rtds_axis.c | 80 + fpga/lib/ips/switch.c | 221 + fpga/lib/ips/timer.c | 61 + fpga/lib/kernel/kernel.c | 282 + fpga/lib/kernel/pci.c | 294 + fpga/lib/kernel/vfio.c | 641 ++ fpga/lib/list.c | 203 + fpga/lib/log.c | 309 + fpga/lib/log_config.c | 85 + fpga/lib/log_helper.c | 138 + fpga/lib/plugin.c | 111 + fpga/lib/utils.c | 401 ++ fpga/lib/vlnv.c | 64 + fpga/scripts/hwdef-parse.py | 200 + fpga/scripts/hwdef-villas.xml | 8119 ++++++++++++++++++++++ fpga/scripts/rebind_device.sh | 47 + fpga/scripts/reset_pci_device.sh | 35 + fpga/tests/CMakeLists.txt | 24 + fpga/tests/dma.c | 94 + fpga/tests/fifo.c | 74 + fpga/tests/hls.c | 80 + fpga/tests/intc.c | 57 + fpga/tests/main.c | 91 + fpga/tests/rtds_rtt.c | 81 + fpga/tests/tmrctr.c | 70 + fpga/tests/xsg.c | 97 + 64 files changed, 16455 insertions(+) create mode 100644 fpga/.editorconfig create mode 100644 fpga/.gitlab-ci.yml create mode 100644 fpga/CMakeLists.txt create mode 100644 fpga/cmake/FindCriterion.cmake create mode 100644 fpga/doc/pictures/eonerc_logo.png create mode 100644 fpga/doc/pictures/villas_fpga.png create mode 100644 fpga/doc/pictures/villas_fpga.svg create mode 100644 fpga/etc/fpga-simple.conf create mode 100644 fpga/etc/fpga.conf create mode 100644 fpga/include/villas/common.h create mode 100644 fpga/include/villas/config.h create mode 100644 fpga/include/villas/fpga/card.h create mode 100644 fpga/include/villas/fpga/ip.h create mode 100644 fpga/include/villas/fpga/ips/dft.h create mode 100644 fpga/include/villas/fpga/ips/dma.h create mode 100644 fpga/include/villas/fpga/ips/fifo.h create mode 100644 fpga/include/villas/fpga/ips/intc.h create mode 100644 fpga/include/villas/fpga/ips/model.h create mode 100644 fpga/include/villas/fpga/ips/rtds_axis.h create mode 100644 fpga/include/villas/fpga/ips/switch.h create mode 100644 fpga/include/villas/fpga/ips/timer.h create mode 100644 fpga/include/villas/fpga/vlnv.h create mode 100644 fpga/include/villas/kernel/kernel.h create mode 100644 fpga/include/villas/kernel/pci.h create mode 100644 fpga/include/villas/kernel/vfio.h create mode 100644 fpga/include/villas/list.h create mode 100644 fpga/include/villas/log.h create mode 100644 fpga/include/villas/log_config.h create mode 100644 fpga/include/villas/plugin.h create mode 100644 fpga/include/villas/utils.h create mode 100644 fpga/lib/CMakeLists.txt create mode 100644 fpga/lib/card.c create mode 100644 fpga/lib/ip.c create mode 100644 fpga/lib/ips/dft.c create mode 100644 fpga/lib/ips/dma.c create mode 100644 fpga/lib/ips/fifo.c create mode 100644 fpga/lib/ips/intc.c create mode 100644 fpga/lib/ips/model.c create mode 100644 fpga/lib/ips/rtds_axis.c create mode 100644 fpga/lib/ips/switch.c create mode 100644 fpga/lib/ips/timer.c create mode 100644 fpga/lib/kernel/kernel.c create mode 100644 fpga/lib/kernel/pci.c create mode 100644 fpga/lib/kernel/vfio.c create mode 100644 fpga/lib/list.c create mode 100644 fpga/lib/log.c create mode 100644 fpga/lib/log_config.c create mode 100644 fpga/lib/log_helper.c create mode 100644 fpga/lib/plugin.c create mode 100644 fpga/lib/utils.c create mode 100644 fpga/lib/vlnv.c create mode 100755 fpga/scripts/hwdef-parse.py create mode 100644 fpga/scripts/hwdef-villas.xml create mode 100755 fpga/scripts/rebind_device.sh create mode 100755 fpga/scripts/reset_pci_device.sh create mode 100644 fpga/tests/CMakeLists.txt create mode 100644 fpga/tests/dma.c create mode 100644 fpga/tests/fifo.c create mode 100644 fpga/tests/hls.c create mode 100644 fpga/tests/intc.c create mode 100644 fpga/tests/main.c create mode 100644 fpga/tests/rtds_rtt.c create mode 100644 fpga/tests/tmrctr.c create mode 100644 fpga/tests/xsg.c diff --git a/fpga/.editorconfig b/fpga/.editorconfig new file mode 100644 index 000000000..a31bb2637 --- /dev/null +++ b/fpga/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[{etc,include,lib,plugins,src,tests,tools}/**.{c,h}] +charset = utf-8 +indent_style = tab +indent_size = 8 +trim_trailing_whitespace=true diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml new file mode 100644 index 000000000..8c92f218a --- /dev/null +++ b/fpga/.gitlab-ci.yml @@ -0,0 +1,94 @@ +variables: + GIT_STRATEGY: fetch + GIT_SUBMODULE_STRATEGY: recursive + PREFIX: /usr/ + DOCKER_TAG_DEV: ${CI_COMMIT_REF_NAME} + DOCKER_IMAGE_DEV: villas/fpga-dev + +stages: + - prepare + - build + - test + - deploy + - docker + +# For some reason, GitLab CI prunes the contents of the submodules so we need to restore them. +before_script: + - git submodule foreach git checkout . + +# Stage: prepare +############################################################################## + +# Build docker image which is used to build & test VILLASnode +docker-dev: + stage: prepare + script: + - docker build -t ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} . + tags: + - shell + - linux + +# Stage: build +############################################################################## + +build:source: + stage: build + script: + - mkdir build && cd build && cmake .. && make + artifacts: + expire_in: 1 week + name: ${CI_PROJECT_NAME}-${CI_BUILD_REF} + paths: + - build/ + image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} + tags: + - docker + +#build:packages: +# stage: build +# script: +# - mkdir build && cd build && cmake .. && make package +# artifacts: +# expire_in: 1 week +# name: ${CI_PROJECT_NAME}-${CI_BUILD_REF} +# paths: +# - build/ +# image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} +# tags: +# - docker + +# Stage: test +############################################################################## + +#test:unit: +# stage: test +# dependencies: +# - build:source +# script: +# - make test +# image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} +# tags: +# - docker +# - fpga + +# Stage: deploy +############################################################################## + +#deploy:packages: +# stage: deploy +# script: +# - ssh ${DEPLOY_USER}@${DEPLOY_HOST} mkdir -p ${DEPLOY_PATH}/{dist,../packages} +# - rsync ${RSYNC_OPTS} build/*.rpm ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/../packages/ +# - rsync ${RSYNC_OPTS} build//*.tar.gz ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/dist/ +# - ssh ${DEPLOY_USER}@${DEPLOY_HOST} createrepo ${DEPLOY_PATH}/../packages +# dependencies: +# - build:packages +# tags: +# - villas-deploy +# only: +# - tags +# +#deploy:git-mirror: +# stage: deploy +# script: +# - git push --force --mirror --prune https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com:VILLASframework/VILLASnode.git diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt new file mode 100644 index 000000000..3bb0552ad --- /dev/null +++ b/fpga/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.5) + +project(VILLASfpga C) + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) + +add_subdirectory(lib) +add_subdirectory(tests) diff --git a/fpga/cmake/FindCriterion.cmake b/fpga/cmake/FindCriterion.cmake new file mode 100644 index 000000000..0e4a2c069 --- /dev/null +++ b/fpga/cmake/FindCriterion.cmake @@ -0,0 +1,27 @@ +# This file is licensed under the WTFPL version 2 -- you can see the full +# license over at http://www.wtfpl.net/txt/copying/ +# +# - Try to find Criterion +# +# Once done this will define +# CRITERION_FOUND - System has Criterion +# CRITERION_INCLUDE_DIRS - The Criterion include directories +# CRITERION_LIBRARIES - The libraries needed to use Criterion + +find_package(PkgConfig) + +find_path(CRITERION_INCLUDE_DIR criterion/criterion.h + PATH_SUFFIXES criterion) + +find_library(CRITERION_LIBRARY NAMES criterion libcriterion) + +set(CRITERION_LIBRARIES ${CRITERION_LIBRARY}) +set(CRITERION_INCLUDE_DIRS ${CRITERION_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIET and REQUIRED arguments and set CRITERION_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(Criterion DEFAULT_MSG + CRITERION_LIBRARY CRITERION_INCLUDE_DIR) + +mark_as_advanced(CRITERION_INCLUDE_DIR CRITERION_LIBRARY) diff --git a/fpga/doc/pictures/eonerc_logo.png b/fpga/doc/pictures/eonerc_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..81c3ad0caac94c8c04735b3033d67082d95028ea GIT binary patch literal 9348 zcmV-~BzxP5P)T{00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3=seT3=sjpjCYy<000McNliru-Ub;B8Yf#$=BWSxAOJ~3 zK~#9!?VWji6!rc8pYPcOxx@?Q@;1w*;*kYJ$R-gjWHwmZD*g8Nt@^E6@fHP-R&A-( zsvLr1ThVH34{fDdTe7pDgd`gj4I33Gh)nQ80|75ot|U93-ygFZb|q=RmX#Wg3BXz-r*-gnK*&xDrq)%Dn;1cBI`5 zoS&wxSAb_6Y1aY;?KI|x#m4{IAQYkgn^`rYmb0Mo-`dAOQ3Mdd045qlhfFEbF&{K)`gA`IqCJ{z%vLJ6-CY z0{9Y=I_tVV-Y|@(fo^RW?=0XKfbOUdph4I5%Us4}l!zqWi)a>bQ#(B{S-=Y}=>@=b z?bN4cgpE?&5xJ(F`m-816bO57pbv1#9@a4p_!&?EEJ%~@hc1MDek?r|zUM;N!&@k` zuW`K#VV?|Knx@R#PQo6q6cP%3FLsU1FmK+xTQ9xz(uQ;$5s@2WA4(@q2cAts^b>(6 zTuvPe9IKQ%Q$%ht4C7&4*Y8T~wj=*?;7*r%@_>t7(tiLP(Z=z9JXL*$Va%xNGHChK z!c!8*#Cb2GTIM~^AcH2{0el4P0RGuR-brn&yA$xkv+h`M&<)zlBE+OyS9I&vt$0RR zZA;aRNs)fW=bwKrDJ1L5MC6H9cJofdFn-oi$52EjCX#i^X?Nxw%^-s$91m=WO<&XD z=etq&&zDadd$5jMx(h3wQrI`0I^>*m6<1YN1s-0q*-6&fz@J^R{}Xr^sCGH`n^?K_ zLQ1|>N?qMrwM0k5XTS&T&~6X+A=Vm!I$+y=zqyJn8UAb+^alLEyTG6nd5#4}_qwU9 zv~EseYwH~X^gnV^+1jsW7alZ&Wf;cUj-*wtZ+Znp(nE!H)a5UrG2DI&L|IG=48#s%+oJ8SXe z{C)=pVXql^4&HF&7p;(P_6bbCPrTts(n8Z|;mC&A3!K4$jV-_z3D5lrE#&__wod%T zGn^RcY#(v!W)$GD6(wyG_eCZUxuywhqP!FtiNN@^UAKh&~jBT;w_n)v;MqOis zdu%8a>RD7&^jQk&GO4^}Sr3TFZxfQn(R^L&a%Vk>WIZ>cP<6(=BH|?M!hJ;8jk>O{ zZpFtf-RX&w^ZPA6U{oRlUwgY<+jo+61_u=$2TqM0Cj!y}84C~r(k+%1Vp}A%1((O_ zPK95@$T{&iqlG#@0M2wNv%eOslL%Kts$AN>`1Y4SO;vta;*UTe@H@x(euiN<7xh_U*Cq>`3 zJAU`-V9#Z(Lju3_u9DgeVP}v*BI;%|86}jY$+iu6u!X8_-i_w(=^(>!9JV=R;&Eha z3r{Um1cO1RGQJ@Y2yAi^wu{e=~DB5m~dNtk6x}@_*Pi z^;ZH{Dy8mf;~4GhS$Cy~Bu+i9d8oK=>!gnM#`5nB;jC9yv|IZ%1w+XxUzTCBLklOS zIrlN}Ew)olI;Wm1+t}ujZUnZUq0&gDtt0G0AruNFEf`|ku%qkx5{JGv+c1m)x~{+H zl$@P}{fFB3;hi;M$4q2g0txpJX{J@$-np=>Ssw#|zzeOsoRbX0_?u%^V$s~mB^5lu zV9-nIMaLb1K;S)>^zKTjxxjs~-xrpamS&9}J-V^O6ZToJtia6oU4`PJc4)q9PWiHC z2JJN?&pt+FW&HuwE}o80>%Z?p-|}LqN1V? zMWmU8ZCTciJsgiCf&aHBbyaGQT&DK~Pb0F6oVF-m!Cm zPY<-glTKYx^;-e>_rL$04fJ=YCc+MB`*H_i&jgsgs1B*GZf5SM>-x%=Wc;OJ7+XoO za#2c+1LmYqiEQuSLzhzE8#{LFCc`jHz#CVKq$2E$4m*QG4MzjNZGo(7X|88}poMzf z;)sM>D09Gh(P*6y2Vh27Em>Jv&D1(8SFQ{LOt07o%`HU=;{Qy#uopDz1 zNqImTGW@zvFcUj&NGg1H<81#sDbyrwi6PPjmA7)BxKotEPGTG#c9clSmF0)cM~!>A%=ldhDy7MH+$9g48uEjSNv z`5TjHdtjZt0y95XycN|BPsi#{*zEDb>?)`bxtvTC$ia2JZ2AJZ|plp%y#=5s_l0)Jsk) z5v5djd&06y#<_R=Sr~>){X^FDy8C1L?>Xcb97vl$e{8! z+O>TrS!ZxCLck6w8cLJtaGErvkne(+83l=OGMRWV>)?%2h>ziHDhos2wT)g|DqmJ$6mtSrH z2~q+*GlZSNA&-r-MghEryD45A=K&`kf9vZ_37hBSgKfF6p^Z)ci{>uU2PC?7(nupA zTi7}$=aHPrvK~mmJuQ|62nK`0T<$@t@P{Wme%E%?v7K4A5fOPRU7il5!;a+U;;XK1 zxAxyHIIrE+G8ybwJb)dQFQt_I$*l7yrYQG#8{3)&{PI8_u*0*W{K+u-)7-*kmaglr zF;Wb}XmD_i-zD4mx~?yEDSINe15Oh}vp#9xJsmTIedytF+=JoAaXs)mm-OG^ zvM7`W+kk&#Z#I0uqK9nViOYhcQmQ-!S%a-cN=MkAj2SZ~orKzC?iPbr*Yyt!!zgj6 zW=>K{mB)zLt)9}NqM|kJID9Rd>CUY96t*sZDivX8bl4djy0|71S8t?4v+$`oHyHr; zx236Gw^o8};&(u!bCyJDt}ose2n0S)Q>>zeYRl6KaU#u}PC}v350p~hr}ev2ZL%!u zqIihbj*7|o{m^y2G?gTtA?yqe7Q`jA`V>cV@5U#602{GQ^-`7VhaDHY2{m(cuot~b z)09hUvu;*iTvDt_lfRMlUYP0HN+r>y%db+5qm`megV*cLbLmT3Q%7Cbm!#=`6j(g1 zw9d8c)G~!qsw~#mrgD~LjSU0>4Z5zM3Oqz=$F?Z&sIKe1)^_u$gb=OH{-lzA8&iyP z{caqMF7>5qcdkqMonrcZ?PYuXj5i#)%9&{TeNWT&aFu%n-f$%2Ak#khNlaC+PlIgr zykiNUO-o)L>2*sbb&uw?xvt}Gu3*z+`7P$_JGqS7nYKWB+@x|g%`RwjTfHZ~%Eq}Z z2Cz&Dqu1+}3hExs-)}c?M%gB&6dvb#&M*wob$ze%%p;|Bd|i1QQ>WUm?Scgh1~xV} zp5XC#`YNS92iEJlzC2F+Qwt?7ol8+sQM3NcEZ@ZBf?d}zbMYo7U(o!To?P$+*Ni=S z?_EcE9k&(i+E-!j{8}cC>)Y|((siSTrTx9XVfuX;!frppe)&)z1j8^~pTAHjl$1WR zY-5}24hDm*dbXWy?ak}x2$+_z3t%u99BdfIrNLlud@vY1WB&a4hqXta9y82V=gkmQ z28WD>34szu=UyR0%2X( zXH0+NYi>WUhx5aAk%-Iz2BfIxUEnrd*B2-JTG`f6oHhn+zN9Sd4akcuB#EAtVJM#Yg9KaMsGW7uOx^17f> zN>a4tth+O?*x8;^>bw+Wos%FC#*Z@WU|7=DBP%Nlm-?KC2YZ8nalmxLFt!c;yt4oA ztG-F>*L@=LN*b~T7=#_uWM+b;i8$IvcAbY8-da@C!qZ0nV|rK<^rLjUWMX9je{c-R?akiouy7>1#*?lR1nQrL5M>sXH+ z_A`#&De{eQA^g=TT&|DNb^Xh9jmGwVbYg|B>$f{pF@FV|WH-E|q~r*iZ?-&7*Y$hj zAF6=g*;cpRU;Ee55_Qz>s;FVjZ!LOhn+zN zhmu-k56Bv9yK^U*4t2@r+%@3MGFET=N_HEYWn@t!a;~gId+_wIkDr3 zXf)cmH~Tp^qOdH>%}e&mDTRH(JuJulaSzRmN+g2}4sKKc`3Vy7Wg9!^{N<|A1KM0s zG#V|{v}Q8i@ZbHY-{Sy}ELe|bt!#__dSBp3$L|kuMEi^N>4u5<10S2ccr%kOu=U49 zMMYKE-l$GB$6o8)MBCM)&M=HJlFctKkEuqS@o7HPb-ihZl98}8$l&0{N?>h*WI1tn z$Qq!yxcF0=yHYoKyWKYS1aMh!6p?g9Nn-TR%XV2kDNb$ ze%Ep1#%J3fPhClS`7lcBg{%-kaFRI%Ym(?2x0Am3s zS-${`&~<$-z~ubn2m}J3VsHDFE++K}z_W&7e0=h@H6!=+ebIG2?IswOWj!!;s%n|A z*A*lOVNy31rk;xcra!k?9UqN4bJzL2p{^Y={l3iGw3lG!os;}~!PDkN{w(Kozh7c_S)aqnh#ta3Wd7NEZg<5*kGOeridH` z{Jn+pCy2-r!!W+LANrbV$D>ejaq*Y&NQ6x)5!3H00}{<%-Z`zjDkO#V3U$)8c8~%4L{--&5Fz=<$z)@`fYr z&e&#tu8xv>v4h=w1sL9NWJX7L!)o$;ekI)K4M!%V%eU&hA%gOBGyT5bV{fmvQj^AO z%%O!Fz01nl)aL3@LuBxh70S%>iMKMMGCX&1z~k{e6OBgaCP>BCDy4RJW8k-eLg?0P)nAx?-=n7A_rj^AZ%#1teBFV3)9<_8 z8;(r3X;^vyZiecGN@=6|S5`IM!1qQCkpW9qq$P#2f&vQ5%lGB8+z(jj4M(QF>mPb# z7S?~vyxc9TM*E){KXq2xje!-(*7Nb-&h*`eC=W>5mnxE*%i&$5^Uf&WfcinU#Wf9@ zmTM``!`@|OSH$zQc5~ywOI9e;@2m5MBfXsMwC4uN8SELxj2Y8t7{(&t!kAQiEe;EW zHej)Xu;Wu~ry5~VOBE@l3|-gP??xY%>AIdISjMhzbmF^h7{>5Kv$&Y8Gh@b#smInK z-yaMHFA$Nx0DWE5JyU^I`{JJ5ta|}!4Z~OmBr41;UDscCE0g7-;h<0DN&wDX_8z9+ z_q-Ac6np{T4M%P<{l1+w`M%!DUi*C!xc8$x-{YvA+w0TuPqvk0gg#)&3T66pFY<<~ zUtR0Z%@<^#G|FGHj?Uip&6nRw7p?1I%G!Fp>GyT@D4A2=Is5DEo!h%wc+OGc`B0QK zF1w-e$*+&^uDaB1zXCW8coD_12o1m~-f(1If@j};B8(V6 zbrt|ebdxJhzwa~={*qG>Sre;gFk=0%2Kkt9D# zCV$e^pmp_zmp%4DK>?@4K4QM=YD~XxCNRYtu6_xiCV%LXoQlX2(?3+a;brQGu3T&S zef^a1bpMLThwqTM@~f~ zVipYcU}-m){@kNA&GUQdSyk8Y<*>uFMr)`RwN^z%8{fY&^2ZDMAOPPVQsCtNz=5|j(h=JJT0eg zUd_~F2iwyH&$)~58QZ6yVHiuX_1uXV6`N7)OE|4)QabBsfcD;~b5CBImwWo!ymL=C z^M^UNQaHYUMPyx0MUr$ikND2!LNY01j;or*HRZYTHjwRyR*sfxT{`h9;B zq!{R;veY*xm%L6l>C{z8<3_OFLDZ{JQR~_EZ6^zDFz}qH=pR7kSXx6?m%8m+fWxr^ z!Wam*q45Q9vFRUL^j<-3ocjLNSijY6@40$g-==baUl9wYZ~A>7fkXv)PATTE_4|Bl z^L;spssQaZgifa4cb~Vc3e)d<-X0Tq7P!^)`>ykbmtp#S&nhKnDAXDhx0!jq>tf?J zyGvu^LM03l=x6$KFG8e3;6s!;&-4%db?jq0uqOZ3w*|=pYh;bzH|Lb*GY%bPHGP{EM#vH>iRwW!C#mO4r zp0RzHRZ)v&SwBuO1CMr;ha(Hf)->&I`+w0;)?bP3(Y7`}w=asXT*|Fiihh8J0&is` zWcq#6%{JG|lQ#j*5R z9#5wmK~(@7z2V4MJ3aR@;1O>)@>tENb5Zavn)WMidF1`I`MD2^;*kO6)uDC%q1`Ri zw}W0^<2DKSKhy8K2Pgrq_J$*6HTk|VO7XTgyzE&3v%q(?So~5f9#+D-UMG4N_4w;^ zuK=tY={wQ_-5ZV^0q}wUtVVp47)aJV-d8MKDG$nuviQmAKK`hODAsW$25z4TIQ>OQy}c*$ECc`+u{ z->5WxhRAimkv*&mPYS)q~(U`KfVA#VsOBKd15CefA)v6ZfHgUlktH?$+`rd-ymr0m_kC+A>-!e!D3Auz z?^}={B2~r+ZZ@{t=?X0C7=#r~l9Q()aY3d}PWjtgTsl_;Fp!6+Q9cXz_%G{``hFz1 z;L5yt)cAb^6%1>-2Cz^>EDx3k*2T#>QDRfsoitR^3~c~#hUxc>jMWhpD7BPWBC2V} z>I_-$HEeK@i!5BY@R+4bzw^1nVqkb{r_pgtuhk4wqPjz%a_G}t~2w8o(Zhf zvMo+qR1NS0wY}jd(j~iTaj}2pTc4QuzJDv*(B19Rt!Sm)w=_mQz$c>A6FHUDNp4Q2 zKR3y;r2mr0d#2yFRV*uL`hDGjm%QP~g9%w4G5x+Dp}3|IPl{bhRkk-={kZA(O)&kw zN#1Z|w!nu9UiF3}H#>9ZgsW!*v&}r8PZRAGOL2ylAocIknGfp;)&y@jax{QB%6A&V zv=o=U?aib6XvYodWqsS@7|#4pn`V>$L3yqOe(4zSkAN4w;YgAj?0ch!bxWxuYiLkb zmM77@_hSXG^$$mqB>S~_L;GjAbss2ZMHVWhMkg_GW8<``Q>AHv%`l8hTqNw0mg>Ge zzc;`uhGAIjfe*&4MRibLz1Tt6ZiL>N>(vdz_$*D9ddsqmgxz0SLexR9M@6I2H7+qw zrrmN;-=;0krWE$YFpS^2)VF}-?b6y^CE~GF8OnpmPC;$i-mtmW^!r}YqWr`XeuPqC zK`vAcd~W)EPXc%Mk1XG5=J}GQ;4*LCb*A4J1y@PdBC3YgDjHt{yjM6}29&+28azCf9kr#@ zLE$&QPB$1iQSxg%G?;n5S92U@dZ>lI2;^8bT)u@miB62E9y{ts}1o?D8 zW#miK@7rPehyFrP&swNHn#kbI+Bbjd8SU1mreNrCma>jQ@hq?|Mf3L^eMa9`8lvjz z&R>1i-}L*2feN>D1Ir2ukZAoKO6s?oeqTfEML7?l$XgNlj_LO`n*QAXiX{=Hso|j2 zS!otgPJ8|BZ%n_h&-;E~PE_dR$?h~gT3`RI>CauSP-`@$6)My_%AP7_u$N(3)*nS= znuFA?Gz{Y`Y^#gYfnpaO@?&ji<{9AX-R6P}eIZ(^1T9vQt)khP@FbGOuaPYpk+2k96W` zlr~-o*G4TSiKshc=}$WHJ*##`8*(Zl8-!?orExlSdWir41O-V%K~$9mb5Y83%$M>p zLLDLoK+X9>Aj>L6FcfkYVhvQ5y3F+Z)PS-#6~N%K$hRPifj_5sWLAN^4)Aejr4;I1 zG=5~yF&mFYFWT0V4LPIHLxF7#67hjT!5;*%7$#W$PfD zP*`5h_QoySqDXh_*uuX6x_K)i-+6cRd7`k60U4__{wT;9R;TDPwKLiP{LL{C30_xf zToh)a@SeKp^=-zX+MjKiasLs4l9UAX-j0?O0+e+sdF=gxT@wiE_wUJK3uoEku@-R+_-U{ zb)@Gb%Wca4{srf7>f*?C?_FTq!5`L;z4nBqORoZ0Gjf;trFTi>Q{bufBSvt-(xq|Y zc@>iFc2|uUfj7MLVP`w*3Wjl7dDUGt{-NZAm&Nnl7eC*So&CX}(yA+(>1*McxM`c| z_dSYYp0_d*+@&`0IaaLI@J6`DZ2-I#Z@nJZ*GOqK*gHh8c>jWP_+pE;(^tJ~eB$y% ze|tf0I)LNaoBCdxn;TCvfW*)H<7UASyyZ!18#`}h=|BoL80sNGs7^BpyfCl059vh z{=J7^+032aIkv^jv8|}8VgZ+W>;*mcMQ(cEv17+Jw9^o-0*+786_u2nr0e?Kp-`xs zQtFo0RHBrc7zhNO>}b!2Yw)L27uz%D0gG)SiW3sRz}K43rW4P{k?3S`y32Wh)5@!2 zI^kum`399%rR?}A<>f5@ZXta6%>%$=rr&pMY$|jVu{g74p6?oj>wpD=!jZ4i>e5n- zLApGtt{tFn8YdHP`HB{AOro>U>8q0M_S-^`srqM+;X6s!I=C{q&*%U3KX!lLOub=9 zhOGB4CQJ~fmVJO;c*^y`U~rCz+>E`0OTU;zxCuMP^Lt9ECjx=Mvd0&F!i^U;k@fQ= z$8?UQXzEJ98NVRD#rSjwH++_Y)MkjtTP~;mo1)EEfUny)BwJ&DX9GLawK1!_4sFNV zKeH@rd4hbUG)?<1@K{k%(WH`+k{Oz&-G=Stb4rXaK8ua%hd@;{8vWh4apQI-TxXSt yBnq4!5&1Ype`ke(LX$31Boq;)N|$0ly7Vg21q?{< zy-5qb-^1^D-sisWUH9Mn-_2TCERvi#GiT3y_ILK)^G;1gfsB-q6aWCSXHVf8002n> z-=9f{z)xmOUipE42wa~$(;@+1J|q@l;Q!a0p6a^-0O`-GH{_N5n?K-(3@C&iO4IQb z%HySrCE(%VAzuUbe$x^`4#VUCd#s~oHz%%$`Ezi#zlinzU{S<+-vDhZtt|?6&j0)Cmde!Yb>azFYRD5mqsqdr9`l=vx4KaPXXOo1@u53ROdfiP zzg%=FV3yAjU8Oa|<6}BVZmqSol6N=&9YQ*VHE>M%jI=W}&u_@Ym;{H_Zk=BEu?2Ke z>c=LDu6_!9x{zPUn${uIxC+2kuxcHQVe(9(vHI6dPyEjVYYaqh-FxvgTmCX%Ud!Xl zkJ%KP@g7UX_=Y7LT26dVc{xI!(cez?!nCJZ;jmy$MIu1Uw=kq+qw(l7j}t!My6RV_ zD!Ji;uP8B!m~K5$Ew!7Klj6KoBz9UcR*o5o81IwRcse8e&CN+xQFH-K`WH3%Ti%kV8k1U(F->Cfx`)){{!^dS-~wjY z_qjam?_|>j5|uWo22X*AN8^np5ZylJFLgczX=S{2Gsn$NN2?ZhTuNdS!zO+vLpsV% z9IF>G7Iz0AGiA$r6h_9-SgkJN_0Qy9S=lGq%}*_St*uvsa2t+ksQXMYOyFo!2c~~- zH(Rh5Kejuipp>lq0GViHV6Uy6=*pq(?0#S0kolOVi#ytxKPqldc5Waq9#>@VL`?{y_GNQNXk1 zeKQA{;>2XL(=Xv??n2B{o;dGmSGTw1*yA}d#xC2S$|>k@uLUQmk7TIe`eqM+p}ljD z*94DUZSK(DzWAHHk@d4q8*3ZJU#-_~NxriZ{TLtRyPK?eGf-CHZmbtqI~RV{c|&D^ zA>FuV*hKvEToV>M+x+ew+gppweMs)2mG8qRx5{4m%4vKhhA6qN2svCfxvY5(QjR8( zhWckGDizy}mg=%V$+2Fu3}lOu7VJ*vO`+0z3J~$8>JJblz7)x)fr8D;-q!5QQ%xHr z+-%?ws{%tp*5K^L+JPot*DRyhes3g0TpCgCP(KK5E?>SYJF~-(T39~KM+rSqiwGjT z`BNq)xr&Z_z_50RT_6ox1>=7m4s3jV7HiBv66)$}kLKp(V{}Qu`(TGeD&b*cN*-7i z5llo~Q{|++ zA8H2UR!Z#|bIq9??GX`?qhv8+z}=)?S5n>SL+i@QDCt_&5za=|2zbTaM^I@C=nWca zyCDHPwZ-0n&_0QB!kQhppRGrZBjmU`nZ9HwqJdB%k~a6qYz6tsiw3a(2*4- zA+DlKfX9+~!U@NHx0q;{r2o*l(B40~sx7N| z*`%EDBfPJ?EXiBRHnMlhKT?q&`s)WDjn{n|@@Y&Q4c~Pl6LFR>QsO@3N*#!vDWUgD z4Foan+C7&a7<4))>{96n3xn(%Md`^MVI}SxDDbfeZAXNZ3jZF_GtaLW#YiOeejxN{ zJhC5;&$7pxFoTeMJUN#julO!y_<9Zwf0FTWRc*m2^*9@GJiGF(Qbk2nys$*uglMMq z1H@CLX(Z1Wh!MN8JuA}oG<@~pf)Ok$T1Rd#`Y>MR>k_?!sOL`goYR%?OBv(bWmYNBiOA)!Nn%1bG_VlyGZP_lE3iqPFg3WNU3s~a#^95p)#(33RZ-fvnCqmg__C6jXgb|J3Gon9 zeWQ-pk_XjV<+qV8wis~2;uBv8U7-p^-{r{1cp^f0hl=D6S8V67eA{7sa__?XC!<}G zY=+kOeH0-rs`qZkpp`>2&Ok_!0Kfga`E|KM+uMA4QM7bbm&}os4vxKTk#TGLK`|Y- zv$L})x+)ue`%&y&Y{6%W=(hC6jM<=0S<|4AgX`Q;;G-K()V!bm51;P+!zaS&LQ!$P zB+hI!UHlzJ#w3nX-N06-%h8*$Rj8$c#QQj{`!|n`F!kq*11~%3%uWGHu$T8@z1jN( zn(hpE-Iy`5+N_edKdlU!-ENEB*hp>UPc3b?6-8L2XJqIEhT-|!Lec6DG77inf*2=$ zjq+WWP5)VOd1`84VDN7!iL!)GXcsh=hj%@anD%+hNx4#tFxHNl6JP+GP9>A@yi9jt z$^jj|Ur{HYnky=xU`Ie>^O(Ng__;tGS_6i&Onb-Ijd!@~Pujr=yY&85y;ft>L$F@Uh3U z#7#jbjy0>9S8j-J{m``2qUHSC=T*Z`QFaQ&5;X7lK!i?fv$kFC!r<#`&u@{D^=N1a zL&ZZbFD*koQoMrK5SSVcG3U9h(6KRTb11oGC>s#HLP4-}uRU`7dW<`8OL=+cp+}#P zXXMh^J41W)*=wPjqe)2HWJ@S{3X^eGr)yAPQ})632>n!K?4wP=6q*Zhhy!Kb&@sG0 zcx0%eaPQ|kv74l13FHW?c9R!gYalwQT#}58Rkau^=VA6;<+bPE zzl$Z2%UQLHB*C(-rsRjt@jIVhlK^Q6?p@76(m(}odnsRjSf~xuIxZaci8RzvyPGtQ zFi0?&9G-jCzD0V;PDr2yE*s=OAH%&*pLQa0WVxMK>`t?rojkJ&P5PV_y~5Z6U_ws2 zJ#1GtwjaFAYJv@Th5yKq+pItI@-AcI0|K?~4oHICe@BK_KVWF*J< zvNs_!)4aGul9jOViGYPw@gSIsdy7j+oME)#{@YeZxYMdrGAu_Jgi)$9AM2wDw^HD zI_oSx5!K{&20T=v6D)ixDq0X=xdEXBn(iIq0*-tN>ksXz{7M!>DKKjxe**FS>0eZC zy5_V{-=d8S`bT(JxJbZY{ZYhj_LWn_4=?DyW%f+7&*-}q^~^j;Xm=E%|9_XAv3Jk96-z}bIu zaQXv^cZ;g4QACxYp8eH%;M(R%=lusqhGXsk`YthQXYK=BPR{ijCH`*$zmf$hiTK{C zoy2>;v|@6JZpT<@#()G41B zIZ_$@l^Ffp*S8Io;M1NdNk&G{b`Ku+ZjBU}S6a>}7y&9qR`*J;$sBjS9C)c>#eTE; zvDPqt)gKgX*O+3QC86S*=4EB#PAk47O>TD3p+ExM08o=qSZ)vkJv7PU^`_8{(UlM~ zuAuXC50Y&FP@y|0WO2Vcpw(bEDn0NRM}>X`_h;X}27v-TZC9;A)Q!X(c9)d#JZ!3fg3P^?{1c#qCyHAi!ly_KvU*rPs^!5eac-0R*S zfxas@)rz8(pti%_q2oWf$(L^w7@Y_Xb6qRl?JP(6(hT~v0sGX#g;ej_AbWuDfEPEf zHDN0E?&(%sPEOUTkjnk4OopoCrRWjqX*pm`$tt6OC3vl6F(`;?w1VFfswB;{vT_t= zA^Z{mPzoks$f!v~wfMu0ay) z!1-t&nlsSs+#TNz{+vdYPC2t=Cl-LFpYU8Y)^BueAmF7>0)*L|sPyU=+Ly;+SX@nvkRusG12>iEqn z-^cHj@H<*Ae@7^|s7sYYJL7U2zI-n%gz4)H5yaF&l!Q`3OEZBAbVolflF3AZC5QgT z+m>AgqBdhQ=yl6y7V(6l@#W=bZK#iSo;gT)@Hr4Wm0UG@8DQhuy42Gs#`0wZ`Oq*` z!~RXjeW!{vT^@MFc;c^8T(+W=*tp;pItLVY&#A15x4pozDRL5P?_Y#Vy25-R$jzP$%< zRDN}T{LGrJ>*G`lM%b0PR<2I?9C=B>srL7Na5jyR^Tt}U9?r8JCXheL&BX{)lM`z# zyad>;279~7RgEl8Kp*F?Y$X|6cRwi$+?H1J#d?u9nOsdPZ+-ZiMJ5SpTPR6nWTA-+ zkC8__8ukjD6Jfd!5qC$&K zPqe5Qi=aWNMKInjyM~r#@bxzxkEyD&7Dh_$DJg)rg=R@=UuGh7(U#6+l9$-NI(|yj0o>KP=LP; z?&Maa;C`Rf$y6rFSG5P!xhqAfaTMA*el0q_Y5~@ur!g6MX(g<@*d4ba!eYUPt3k@A z2n)Fq1~`KrSt`aMuMjg*{;r#kP(`<>!8EUc?@JR4aF(-6XPuNhEQ>w`xSW8Jjs%~>R*cv zCuo;a&q`Im-HX&0(I1)9nbBG1>??%wI@j(2G(`|Pc$wZ&NJ zwR?B=+Y*ZLJf?%#7sCN*2!?rU{g@|pz!wD{O7Gh78i8&nAwVwsI zTxTz(Z3+b?XaWbJpVq7Ke2dloH$JrAX2OX)hxe1e13AgP^ALaPLDS=4|>%8@?p3pbXm1?xyI;*%8-4n>?LNYz^uVQQ?7<<4$G=)p`2IQ!@ zsi~=6v%-G&FMsUE%MURf-*3;`J>;eX;88y+*aW1UIXN@bx=_rO)Cg<@Bhs$!vGzDk zf)y0aL;81b+}(9Fj=u<87_E=xIBbz~KZ(K$2Iq{rDj;^xEQJ;C2^b97LL3r@ z`BXy@EYq^el2^#X#vIX6VUKXds2tm-fBPm6{{b}EK+!FkYW4YcogXsOuIZH{L~OQ) z43@`MQF;hc`;b_W%S3um=YgEeN7}h(5|r;I*fA`Cv<*}-Y9Dl3|JuXNDajTiFA8KR z69ilOrm272$X&J@oO#=myz6eO%Q<5-Z?{Q2v;S?}9xw>uvmJeF`d$R0+MFpF{9oaQ z?iv&A`lB3wIjt>$={m2hRd)FD6E zI@gsrV@&pqJ(rc!)MCEV#C?}f^>JxJ(P!FaUvU}%B{%|gsX0nkOZQWGg~Ls%UIG~Y zaJW1~F+`PLD(i}#5KhV~mRqI&H&BOg>k|E#J;wVNkFDMZtc~^Gx;ONc`zM=+)$9xG z-k+h1n@uu(n}7EvSmRhl3a852HOqWKmGUp&p$*(+jKIa=Wq>gE zL_4PNUB$UA90-|j!z$FV9o&R+RtP-?i1BYqsP-3ba(Tzd0~C2 zEMa8v>ElH1=2mkI-nL$~{q${v3TBU;D!3O14Y|KYs4lJaA}(W^da%}$<*s*5_^Uru z5Z0=yvR!ouOzcmI4CT?=8;Ge~cf`_x)C8SBKnGDZ31UX`_^=@wZO?`t^rw81@4r@z)*Z-d${fEwORdU%sQGo9yF2-)v@VLxd5bKn`O)+g zf5BTPKD!`Z-k93>`t9v4aJ%%@GNq4|*&34i^z__A^$Z(5CmE%nRs6x;-lR0ZJFc->q&!Q&8b-0EM!a)-moqg6X?J!f0)iR4<)LvRIhF8y_@!r78TpM%M`lcij6pt$P@3|T*LjD z5Fi9YBQN>r__Kgb&_Q&%_R&!--X;spy#YB~9lck!r53Z~%+Fuf+4j`sm-uJ zi-aVBXi8MuQZJdCuI{4;sOYe;wsbZ0t3RIV^?l2`_5GR#$;lbsa5zeZv>qw%OVBiw zTJ3tNdZ-`+?L*OXW}0d6Aw-n+iA?V9YN*=VE7%?UeLmnE78d^48Js>h0c8!1o8!6$ z;{^eID0a@wCpr-z7%_MoQs2M(hYkJ#<8NRiPqcp_@Bzx1=Tu$|xfSlVecAEQ5*86E zb#>m8Oa=~M&2+q|73||i?a`dG)j1s;E_#W=J%doJzHwf3wCCr+jw~bBel!<}&yHz1L z{5T2`%OAsJZ|YG{;ZEjpc!v`z0)kO{@SAyco?!q@cGIRWI8ZC-4JvhM5La;c1T+p( zMop4L{uur-x(X!^hew%VDXC(z^h7~hanq7fQAvg)7wQQX^6t2e-T z9ENbch$1cS@3n9cW_cUN&33Fzyj=|X!(x#{Z;W|~@zol@(V*`#aXzw-iCdHMHWp(E z4&$CKLH70-f8WSj)bx_avJ^@zqPNL9(aQ#O>d#GPgdTfc|b68_UCU?cKE`5AQgq+nPM> z!?SR?`HSnatmgGAq9cJT?{lOb?-yBVcuP{iWj0tM&n6U1>kR&mXlU>+JRCU;9rnsb z-Xw(y6Tn0(%XZ-^L6U!0(E^yKT3Qo4O_PAiIbuHw&-O0fhRHj5V&%o#_q8^(wZB(9 z)e9)9TaLcSlPwlwUDjb2=se6k-ks?lWo7dD8emscXq?{k0$}q?YaHX^R9vg6^~z;d zCqL7)TqF$2&PHqH-2ebOk*it&UiN;x!G4i*DMI(KJ`bzz47pRI2;GmkN+jI4IB;mK z-rIlOluyAfP3rVkh2?krqrVB4F>5M{cdFPKCpQd*Gk?(F6?6{dO60A*00!ER`aS?d zxms*5u*1t=2X6{?wr6_bV?4Z_w-;4yYz1+n2fk|Ow57FJv!`1A>6pWz!#(D1H|fkL zgF4!7DO~;JGdMnzIc2Kq{OZbK*Xc*b?^O}=qs{RKR=s?~&htqGdW5EC9?*+jJnn$1t@2{uQkdDJ05O+G-&S)Cet<2 zc4^+3_mJI(0I@gZy}(WE3rh5kvMORW27^KO$GJ&Mj4p(%U7y<(Bc@>UYt|!*2ECn` zm>9p#{rxP(gDwB|Z{|x~9c~FEz5b<-H^b^k2A58ddmcGil#(0-A61gXH`oY;&gVC^ z#I-7KyyG)rjlPQh@fz|n3XU0TiVRn5kOT-O_qq^~nu_$VF7BTm?kcMi-Zs=0GYN6j zO|^%1GwjJ(q&W6}8C3rK*I9=zX^Apy$@%< z1I&RP_yezg9bLHfNyUq^gmA;`Uwpr@)uRROmRoAzZqJH~idOEG(HUW3-eB7Ah{@3h zpOXSF;T1F*eemRawlwW&_A0y9=OJUySwn<;8o*HSZbQJtCc~SKXkougOg1sa zK_dBlMJ9LBH`gUCxi9o8gYEaZFwtb;69bfytP(6Cq`IDKe^g(@4Q4duDp_@2=Ty+z z51ijQI+T;e@>$ZMuy!dDj&I1x1!&T%z>A;n^8*>fW+iMPA-Tv~i*WE_2F_AOPBQN8 ztwcMLV%@-@IL|Y>oU*D6_Gy5lDM`XgB`Dm7qA56M*Rx4L+uoQSecpzm#8qtnN?aV8 zF^$FZzz1_Rs#BVi*9LRqDyOH+q2aNlg~$qRU6cG4*G_2%FzCV^uu;5`y)Pv-5fs!r z+A)%_4+vZ(7(=L!9^X%C@L0rV5muKuG*kDeE`jYb`K_RH*Ou>PG>yTb^-JQL&5zB0 zi`$RU3~ev@r%9x=&QIygJQA7Tc{8p*ylc|IB-rTVqt98Q6hPB)uHxPLmrT9BR)BUK z2_;#~Rj~S%kn}uzlcs5-K9MvD5QLBrfme>|50@db{!L^kpt4o2`zy7KpVw1Uclc6_ z(9?oYFK2XwT>V7Dl_%M$~PohdPgZ0qa03t1$oN2PuzndtjBX=pr1 z9TR`i8Btn)+uFRSUS(e>=ve5CRo5uJ$;q$AzW`oW(vA0~XXmI$nOKs}%S2Qrzc5u0 zv(DiH26*>aF^@R<4cQ_aUO`D0bx4Zus-{h)V{9Qx5-Fim2#8(Z(Rp`V!~PJc1w?4Q z3`7lID&?^{$4t8-GZp`UUTR{)#Ub0Mj6a9c_4?f!bp0 z-d6aMzwzr;Vv$u831%A46x>0WABe*nf;KnbcoU`T6{HDUzG^FN=q`DXx|1I^?q$bR-N#ze*4Fc-}r?;fnKH$ZBM?P z`gy3S$@HZyv}1Ka8w)fIdF?)rd47IdiwzL!yowq}MNIoIPj5}uPNOKHQiNa3$I1yY zHME>;peic8gCjQh;?+6Z(kQ{QsDvv-$~#~C#(sxTugKDpVrXkBW!Sc(H75(RcHiI& zev_3lm}OWz|1g?Y$DgD=Zq;}3)?R;OhWJPK6d2E-{b}ZP8OV(S=}z&Z^a#Iq9CWze zvv&j3K_dX|J=E`IMr3!DvB;^^IXVoOdvJO-)Ex^Rnr>(T)1yH#JLgn@E12i_>*@PDCl}mU^BCX}T;)CX|AF)gFLnhC z+Xf>i|9o}zjiVvjuDnY9keaOns& zn}9Sqg4v7gbaA;|6c*STt4Pceu2Wjjt*|F{3~vQhYis_KfW}hH8WW`#Ws@kFj8Viq z8r-6&V1<^Cqdpq$_<%tM6FL)^^Z% zrFg<_;*q+XM!w-qQkTj9TK(9r79Ws6;tHim%lRVYF9%WYeaa4gTyHFIvUPzt)ekT-+|qCoL1CjYs>L%%cfJeO{L$MM;+f~6u4xBUciv!wq)U~b#K zQO;(r`g%kO;`L)NynN}nIq@|yBg0#JVI1(eN*ZWnp6Gp@_CK6R)2pZ}PPyCY(e)qxVK%Ntd-1%|BU09atq( z&aLoLBw)G@@)GJ`7z_NvVG2ud5r^6fe%o60nnWcSlIm*rggy-g;uF9m$T(rlQry(k zG&tNBSa42!6B83L9nXh2>Hnxbq0x(fnmatPv}5_M!g`>vI3(A|r}-*ZYM@5OvcArn zv^ZHdOSc{dUX=E8d*gSSa7u9LK}g<|5gpnd%$yiXLCxF#Xn`S9pzdXBElBpje<3es zKBr~j7HtpDlS{GQC2v4L6kK=*1)n+z=>Co$maei}xpp1-O}*KAN&0siO_2QB+dQXi z;WllLwO!pA4(!fPVTcYMw+MKZ8UDzeuF=VNJw8FU5VWD^ELn%e7>95l*v%-wn+9qB zuHFA7R8iU-TNk}iX6660q9C+sht=3!D=;*>fKQ{*+rv2G5Sfp`P{n|SBBJ03z^_iD z+vle#Al3sfaV+RpH!6Bk&$(Dw^^P7~WwtOWn6aAT++`CbM3_=4jDd@b7edf1Vp;TD zZsA&*<>Vx4MQs8_Uu24nz?KaguSYlTv`Gln&AtH3c|LLtrchcJM$4PHuD=I;Da`kE z5C%7v7XJ>G#o0X65^C~*rPWqXba|y+Oia5$Y3VW5F|DV^dtI_06}A(v4?w=|t5O~- zNJJr`!ot@+T!BuU9IX={fVzpx2^ zyR##3C%GItUiWG>XLC~u49=CUtsM)W1x(rvBqIOvrS*L$UWLE)Uh_mhaoc_0fm^Jv z4^;}~A%JB`s$S2+=Bn$s^T6K#;VzhBx?>HV!|)K*Q14DO<*^;Oc;|7RpSfah^CdCo zP1irto}QeH**RW%vf#Xp=DIxgDNL~(@saUtPUdt`6k=`6$*w%~04c^ut4w|lO7iBD z&CPNC@dhH@BZ1J6dP)$rck+-)5?Evco2e7!5m~UnEn1WEcvcBeZAl^FQ z(}BACT4H)8-5X&(UP0=({Y!%MFLy~n&Lax8DJ!UA#ymr$9Kr7zBx+n1(W>C)8TBbQ zmz0jVak{?j29-d{A{E5=<_!AV&XG@C#3MaXa$BF{#fxApk~`}KzG>_#O!U{*VeP?U z5fI;tYZ)gc5DafGB5#}TOD5M;(i0UpKGpjhd4eog*DeZuI;Bb@fD#s(S&EnGL;hma zFg-VgO67{NZsl3+)J7rZOF`q56}fqsa9}Edj_$8NTGOY&I?nA^fUA}Vrq2Um3!AFL ztzgY_=BasUBOx43h3Hms@@x%PLQi^GV3O3yAf@^Mw!sY?4qqA0}7Tg zd#F;0IhMYBWq&w&2$97*yK6*FWv&^F?k_Pngz_qZyzJN2_wp((atvJ+5{;;oSPnet z?NtFx&h?dh$b{=s_7s$@?xSwCMb7u@zbwh9LGan?7grt%&3&FpQ}vXORiJfu+km~$ zKi-LP7B^IN|IPI2lS8WIc0`;0CD9BfuA!mL)NusiNLE?UG)Vh-mf@SST=NFQh zHLP`VZB&jMW39``x~(Qq>#e_-+mT5G3#-~PJ2g$lYY#VS^a@6aq^}M7OQhufemf!6 zZHisc!Q(u%)z>F$Z}Y@2!Y))NXJTdWj(+rE@|V$Tufq)O9%^tn?W5DcN=7hb@!xcc zoO!zlW3(Q9q_k10Qm>{dS3oSNRX^t=vg#Pv>D=|3=)n8Gj*B=fM#3yEnuFd2V^>^^ZZx7@FPF!g3_Lu78pHfesxB$-@p^d;8w_iz>#e;v(eg4P!o4AtZc!|KNL&KmBOlKA zinjS4WrZkaP+#@3q1)!4fN4549UZAsmge+TCDxfuIvIuE5!&|xiptyOcmIK3i`r%} zk`#WvIeCvxK8vHfLJ+^W2bBtIPln*S)d}CU&+SBi|2f|%#1i5XT`57AKqw_O39Ko! zjh5qs89q%`mwmKJ70UfCxGg+7yWmm|C3k+M1es@04rFAoawXJqB>rs$RZVq1MW-Ql zS@B@JoVw+E`(mXQq-^M%fXN2iVKraAgNI#RHj=$dMpdX+y-c^w%?l|G4qWh?IGZzh z@b+sx*uJ7$lk8)HUzIP%ExT+}lz@-pX_Y!&|u~{UEvs{SLm(kMFRiWbc!ZoXxS+Ur#1$HTNecSs2nB1>md-&gW}O_{qIclELxl*7-D>&5iSKt3np6 z=$wxKaK?W8f;wP2v3LHaZsXB6Gs!HfXFSEy+0aUGLG7sR(D0=|im;TxOipSl9nI0- zgS9n6(sD?GB{Vk3@O-Y)emo9u`mQXf{yCLB4h}X&Qi5?5VC`|=e@K%J+LTDi%7w&{ zs3S#+6Sp|z>2r}1N>OI9Qm!YuN5^R5SMC2&l<|%Iu$kwhH&0r!%XYb3;DIv{o@b?| zj)IPfxreQp86p_aY1d0L+P__kXV-X2Fq;03pRf^3{E8$=4n;)=FE&T zOmu5%qPTgRHh62&mxf{aCTH#$d?{Zy%lX#3Kp#II&kE}(kr#c%$H*AFp6V@N#DEFN$@-2wG-ebh)-go8EB6m1HmiF^HnavC@*I#3G6s{mKPsZA1DF9{|QI ztG&y}wxv7p%|mS+W7>%J>U{#s$Vv%KowY}g9zlF8Ej}v~G%*h(_CoIA*U5mY>t|^k zp|>|hR^>2?756X|``b$&T%Eq`G%jZKN?5=rM%cPVIKC47z72%;p%` zo~+9%_4=0{`F$JkoPiNZ@w!<_B-iSmA%vAkXvGGIkMFe;@kntSYtjX=bWyX~5i@*) zIc=p6JiqYEf4cE91@wy9V8J{7$n01kc*5kCM_)P^yvFIl{Ek0(o|zcJ1h5P{2d{)T z!DS!-?E33yx(1E7KML?7XX~F7VeIbGTRW1X1d;Mzk(gifUA$o3dtFI!zU?x$tBY5@ zZS=Dpf$o1Rnk3Nev@th|tM~3O zk}ZBG#I7g;4G&9M9uo&Bx@?yk8$mUJ@o%Wt*1OoOqU9B_cg&z~z`X^DKR{2PklXYe zxYxV<-LT<0pDtpT?Z3!!!;1_Z0?WE+3S9Vs+IQ;S-hefp=qYS>`LTDiK%2m zw-;H1aIx=wLRB4xkW1B^9^8NBD>{Z8KM(`_;#(D~YEa@x{bgf2P98!q&bnN&w=cyi zEYlXkZCeK(*m2WT?L(9LxSxKOgwS}=HxaktwhT?6Ow8r8yOGCRyv1_xBm-}6Z{`TZ z0%Iz8i0E3nTggaUV0idi%mxWj+qR=2e^P&mV9Ewnj9?|`Dh$ZkUUjN9n_|@-BmXU9 zNHFn`k%I<*{lUTPvV~CxB1%bN;f6~PF6>wRoV;rKZum z7N+d=xz8iVOi^xOCt}*9d*@uM13V%1e$BkZDyQeO7Z>O+DE)`oYG7IexZ{h=5v_gQ zI7=gJ|G4*Qe-QEM#4ou8aauB(g8zn@!KjF`XZQi&(G4rnq0ZP`)*^#|lc9zNrlc#* z^f1G7ijjFU{IAdVv#zXdsUHtH3La;Q$qtS0J>hU^EGos7n!+wFhTn&Ub&OXG{rTmz s>&ekHE$w>}?7$iu_J6!O9$d;iQ81kIll9&K&)Wgd5GwG(C#G-y7unN$s{jB1 literal 0 HcmV?d00001 diff --git a/fpga/doc/pictures/villas_fpga.svg b/fpga/doc/pictures/villas_fpga.svg new file mode 100644 index 000000000..509e098b1 --- /dev/null +++ b/fpga/doc/pictures/villas_fpga.svg @@ -0,0 +1,113 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/fpga/etc/fpga-simple.conf b/fpga/etc/fpga-simple.conf new file mode 100644 index 000000000..a0625dde0 --- /dev/null +++ b/fpga/etc/fpga-simple.conf @@ -0,0 +1,73 @@ +/** Example configuration file for VILLASfpga. + * + * The syntax of this file is similar to JSON. + * A detailed description of the format can be found here: + * http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +# Some global settings are used by multiple configuration files +# and therefore defined in separate files +@include "global.conf" +@include "plugins.conf" + +fpgas = { + vc707 = { + /* Card identification */ + id = "10ee:7022"; + slot = "01:00.0"; + + intc = 0x5000; + reset = 0x2000; + do_reset = true; + + ips = { + switch_0 = { + vlnv = "xilinx.com:ip:axis_interconnect:2.1" + baseaddr = 0x0000; + numports = 3; + + paths = ( + { in = "dma_0", out = "rtds_0" }, + { in = "rtds_0", out = "dma_0" } + ) + }, + rtds_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0" + baseaddr = 0x3000; + port = 0; + }, + dma_0 = { + vlnv = "xilinx.com:ip:axi_dma:7.1"; + baseaddr = 0x1000; + port = 2; + irq = 0 + } + } + } +} + +nodes = { + rtds = { + datamover = "dma_0"; + use_irqs = false; + } +} diff --git a/fpga/etc/fpga.conf b/fpga/etc/fpga.conf new file mode 100644 index 000000000..11daa06a1 --- /dev/null +++ b/fpga/etc/fpga.conf @@ -0,0 +1,168 @@ +/** Example configuration file for VILLASfpga / VILLASfpga. + * + * The syntax of this file is similar to JSON. + * A detailed description of the format can be found here: + * http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +# Some global settings are used by multiple configuration files +# and therefore defined in separate files +@include "global.conf" +@include "plugins.conf" + +############ Dictionary of FPGAs ############ + +fpgas = { + vc707 = { + id = "10ee:7022"; # Card identification + slot = "01:00.0"; # Usually only id or slot is required + + do_reset = true; # Perform a full reset of the FPGA board + # Requires a IP core named 'axi_reset_0' + + ############ List of IP cores on FPGA ############ + # + # Every IP core can have the following settings: + # baseaddr Baseaddress as accessible from BAR0 memory region + # irq Interrupt index of MSI interrupt controller + # port Port index of AXI4-Stream interconnect + + ips = { + ### Utility IPs + axi_pcie_intc_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0"; + baseaddr = 0xb000; + }, + switch_0 = { + vlnv = "xilinx.com:ip:axis_interconnect:2.1" + baseaddr = 0x5000; + num_ports = 10; + + paths = ( + // { in = "fifo_mm_s_0", out = "fifo_mm_s_0" }, # Loopback fifo_mm_s_0 + // { in = "dma_0", out = "dma_0" }, # Loopback dma_0 + // { in = "dma_1", out = "dma_1" } # Loopback dma_1 + // { in = "rtds_axis_0", out = "fifo_mm_s_0", reverse = true } # Linux <-> RTDS + // { in = "rtds_axis_0", out = "dma_0", reverse = true } # Linux (dma_0) <-> RTDS + { in = "rtds_axis_0", out = "dma_1", reverse = true } # Linux (dma_1) <-> RTDS + // { in = "rtds_axis_0", out = "fifo_mm_s_0", reverse = true } # Linux (fifo_mm_s_0) <-> RTDS + // { in = "dma_0", out = "hls_dft_0", reverse = true } # DFT <-> Linux + // { in = "rtds_axis_0", out = "hls_dft_0", reverse = true }, # DFT <-> RTDS + ) + }, + axi_reset_0 = { + vlnv = "xilinx.com:ip:axi_gpio:2.0"; + baseaddr = 0x7000; + }, + timer_0 = { + vlnv = "xilinx.com:ip:axi_timer:2.0"; + baseaddr = 0x4000; + irq = 0; + }, + + ### Data mover IPs + dma_0 = { + vlnv = "xilinx.com:ip:axi_dma:7.1"; + baseaddr = 0x3000; + port = 1; + irq = 3; /* 3 - 4 */ + }, + dma_1 = { + vlnv = "xilinx.com:ip:axi_dma:7.1"; + baseaddr = 0x2000; + port = 6; + irq = 3; /* 3 - 4 */ + }, + fifo_mm_s_0 = { + vlnv = "xilinx.com:ip:axi_fifo_mm_s:4.1"; + baseaddr = 0x6000; + baseaddr_axi4 = 0xC000; + port = 2; + irq = 2; + }, + + ### Interface IPs + rtds_axis_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0"; + baseaddr = 0x8000; + port = 0; + irq = 5; /* 5 -7 */ + }, + + ### Model IPs + hls_dft_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0"; + baseaddr = 0x9000; + port = 5; + irq = 1; + + period = 400; /* in samples: 20ms / 50uS = 400*/ + harmonics = [ 0, 1, 3, 5, 7 ] + decimation = 0; /* 0 = disabled */ + //harmonics = [ 0, 1, 2, 5, 22 ] + }, + axis_data_fifo_0 = { + vlnv = "xilinx.com:ip:axis_data_fifo:1.1"; + port = 3; + }, + axis_data_fifo_1 = { + vlnv = "xilinx.com:ip:axis_data_fifo:1.1"; + port = 6; + }, + } + } +} + +############ Dictionary of nodes ############ + +nodes = { + dma_0 = { + type = "fpga"; # Datamovers to VILLASfpga + datamover = "dma_0"; # Name of IP core in fpga.ips + use_irqs = false; # Use polling or MSI interrupts? + }, + dma_1 = { + type = "fpga"; + datamover = "dma_1"; + use_irqs = false; + }, + fifo_0 = { + type = "fpga"; + datamover = "fifo_mm_s_0"; + use_irqs = false; + }, + simple_circuit = { + type = "cbuilder"; + model = "simple_circuit", + timestep = 25e-6; # in seconds + parameters = [ + 1.0, # R2 = 1 Ohm + 0.001 # C2 = 1000 uF + ]; + } +} + +############ List of paths ############ + +paths = ( + { in = "dma_1", out = "simple_circuit", reverse = true } +) diff --git a/fpga/include/villas/common.h b/fpga/include/villas/common.h new file mode 100644 index 000000000..c837774e6 --- /dev/null +++ b/fpga/include/villas/common.h @@ -0,0 +1,36 @@ +/** Some common defines, enums and datastructures. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +/* Common states for most objects in VILLASfpga (paths, nodes, hooks, plugins) */ +enum state { + STATE_DESTROYED = 0, + STATE_INITIALIZED = 1, + STATE_PARSED = 2, + STATE_CHECKED = 3, + STATE_STARTED = 4, + STATE_LOADED = 4, /* alias for STATE_STARTED used by plugins */ + STATE_STOPPED = 5, + STATE_UNLOADED = 5 /* alias for STATE_STARTED used by plugins */ +}; diff --git a/fpga/include/villas/config.h b/fpga/include/villas/config.h new file mode 100644 index 000000000..f75ff7fba --- /dev/null +++ b/fpga/include/villas/config.h @@ -0,0 +1,71 @@ +/** Static server configuration + * + * This file contains some compiled-in settings. + * This settings are not part of the configuration file. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +#ifndef V + #define V 2 +#endif + +/* Paths */ +#define PLUGIN_PATH PREFIX "/share/villas/node/plugins" +#define WEB_PATH PREFIX "/share/villas/node/web" +#define SYSFS_PATH "/sys" +#define PROCFS_PATH "/proc" + +/** Default number of values in a sample */ +#define DEFAULT_SAMPLELEN 64 +#define DEFAULT_QUEUELEN 1024 + +/** Number of hugepages which are requested from the the kernel. + * @see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt */ +#define DEFAULT_NR_HUGEPAGES 100 + +/** Width of log output in characters */ +#define LOG_WIDTH 80 +#define LOG_HEIGHT 25 + +/** Socket priority */ +#define SOCKET_PRIO 7 + +/* Protocol numbers */ +#define IPPROTO_VILLAS 137 +#define ETH_P_VILLAS 0xBABE + +#define USER_AGENT "VILLASfpga (" BUILDID ")" + +/* Required kernel version */ +#define KERNEL_VERSION_MAJ 3 +#define KERNEL_VERSION_MIN 6 + +/** PCIe BAR number of VILLASfpga registers */ +#define FPGA_PCI_BAR 0 +#define FPGA_PCI_VID_XILINX 0x10ee +#define FPGA_PCI_PID_VFPGA 0x7022 + +/** AXI Bus frequency for all components + * except RTDS AXI Stream bridge which runs at RTDS_HZ (100 Mhz) */ +#define FPGA_AXI_HZ 125000000 // 125 MHz diff --git a/fpga/include/villas/fpga/card.h b/fpga/include/villas/fpga/card.h new file mode 100644 index 000000000..89d19c0c1 --- /dev/null +++ b/fpga/include/villas/fpga/card.h @@ -0,0 +1,95 @@ +/** FPGA card + * + * This class represents a FPGA device. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include "common.h" +#include "kernel/pci.h" +#include "kernel/vfio.h" + +/* Forward declarations */ +struct fpga_ip; +struct vfio_container; + +struct fpga_card { + char *name; /**< The name of the FPGA card */ + + enum state state; /**< The state of this FPGA card. */ + + struct pci *pci; + struct pci_device filter; /**< Filter for PCI device. */ + + struct vfio_container *vfio_container; + struct vfio_device vfio_device; /**< VFIO device handle. */ + + int do_reset; /**< Reset VILLASfpga during startup? */ + int affinity; /**< Affinity for MSI interrupts */ + + struct list ips; /**< List of IP components on FPGA. */ + + char *map; /**< PCI BAR0 mapping for register access */ + + size_t maplen; + size_t dmalen; + + /* Some IP cores are special and referenced here */ + struct fpga_ip *intc; + struct fpga_ip *reset; + struct fpga_ip *sw; +}; + +/** Initialize FPGA card and its IP components. */ +int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc); + +/** Parse configuration of FPGA card including IP cores from config. */ +int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name); + +int fpga_card_parse_list(struct list *l, json_t *cfg); + +/** Check if the FPGA card configuration is plausible. */ +int fpga_card_check(struct fpga_card *c); + +/** Start FPGA card. */ +int fpga_card_start(struct fpga_card *c); + +/** Stop FPGA card. */ +int fpga_card_stop(struct fpga_card *c); + +/** Destroy FPGA card. */ +int fpga_card_destroy(struct fpga_card *c); + +/** Dump details of FPGA card to stdout. */ +void fpga_card_dump(struct fpga_card *c); + +/** Reset the FPGA to a known state */ +int fpga_card_reset(struct fpga_card *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ip.h b/fpga/include/villas/fpga/ip.h new file mode 100644 index 000000000..b89fc5841 --- /dev/null +++ b/fpga/include/villas/fpga/ip.h @@ -0,0 +1,119 @@ +/** Interlectual Property component. + * + * This class represents a module within the FPGA. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include "common.h" + +#include "fpga/vlnv.h" + +#include "fpga/ips/dma.h" +#include "fpga/ips/switch.h" +#include "fpga/ips/fifo.h" +#include "fpga/ips/rtds_axis.h" +#include "fpga/ips/timer.h" +#include "fpga/ips/model.h" +#include "fpga/ips/dft.h" +#include "fpga/ips/intc.h" + +enum fpga_ip_types { + FPGA_IP_TYPE_DM_DMA, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ + FPGA_IP_TYPE_DM_FIFO, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ + FPGA_IP_TYPE_MODEL, /**< A model IP simulates a system on the FPGA. */ + FPGA_IP_TYPE_MATH, /**< A math IP performs some kind of mathematical operation on the streaming data */ + FPGA_IP_TYPE_MISC, /**< Other IP components like timer, counters, interrupt conctrollers or routing. */ + FPGA_IP_TYPE_INTERFACE /**< A interface IP connects the FPGA to another system or controller. */ +} type; + +struct fpga_ip_type { + struct fpga_vlnv vlnv; + + enum fpga_ip_types type; + + int (*init)(struct fpga_ip *c); + int (*parse)(struct fpga_ip *c, json_t *cfg); + int (*check)(struct fpga_ip *c); + int (*start)(struct fpga_ip *c); + int (*stop)(struct fpga_ip *c); + int (*destroy)(struct fpga_ip *c); + int (*reset)(struct fpga_ip *c); + void (*dump)(struct fpga_ip *c); + + size_t size; /**< Amount of memory which should be reserved for struct fpga_ip::_vd */ +}; + +struct fpga_ip { + char *name; /**< Name of the FPGA IP component. */ + struct fpga_vlnv vlnv; /**< The Vendor, Library, Name, Version tag of the FPGA IP component. */ + + enum state state; /**< The current state of the FPGA IP component. */ + + struct fpga_ip_type *_vt; /**< Vtable containing FPGA IP type function pointers. */ + void *_vd; /**< Virtual data (used by struct fpga_ip::_vt functions) */ + + uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ + uintptr_t baseaddr_axi4; /**< Used by AXI4 FIFO DM */ + + int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ + int irq; /**< The interrupt number of the FPGA IP component. */ + + struct fpga_card *card; /**< The FPGA to which this IP instance belongs to. */ +}; + +/** Initialize IP core. */ +int fpga_ip_init(struct fpga_ip *c, struct fpga_ip_type *vt); + +/** Parse IP core configuration from configuration file */ +int fpga_ip_parse(struct fpga_ip *c, json_t *cfg, const char *name); + +/** Check configuration of IP core. */ +int fpga_ip_check(struct fpga_ip *c); + +/** Start IP core. */ +int fpga_ip_start(struct fpga_ip *c); + +/** Stop IP core. */ +int fpga_ip_stop(struct fpga_ip *c); + +/** Release dynamic memory allocated by this IP core. */ +int fpga_ip_destroy(struct fpga_ip *c); + +/** Dump details about this IP core to stdout. */ +void fpga_ip_dump(struct fpga_ip *c); + +/** Reset IP component to its initial state. */ +int fpga_ip_reset(struct fpga_ip *c); + +/** Find a registered FPGA IP core type with the given VLNV identifier. */ +struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr); + + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/dft.h b/fpga/include/villas/fpga/ips/dft.h new file mode 100644 index 000000000..74835d5d4 --- /dev/null +++ b/fpga/include/villas/fpga/ips/dft.h @@ -0,0 +1,52 @@ +/** Moving window / Recursive DFT implementation based on HLS + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +/* Forward declaration */ +struct ip; + +struct dft { + XHls_dft inst; + + int period; /* in samples */ + int num_harmonics; + float *fharmonics; + int decimation; +}; + +int dft_parse(struct fpga_ip *c, json_t *cfg); + +int dft_start(struct fpga_ip *c); + +int dft_stop(struct fpga_ip *c); + +int dft_destroy(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/dma.h b/fpga/include/villas/fpga/ips/dma.h new file mode 100644 index 000000000..7a13fb903 --- /dev/null +++ b/fpga/include/villas/fpga/ips/dma.h @@ -0,0 +1,88 @@ +/** DMA related helper functions. + * + * These functions present a simpler interface to Xilinx' DMA driver (XAxiDma_*). + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include +#include + +#include + +/* Forward declarations */ +struct fpga_ip; + +#define FPGA_DMA_BASEADDR 0x00000000 +#define FPGA_DMA_BOUNDARY 0x1000 +#define FPGA_DMA_BD_OFFSET 0xC0000000 +#define FPGA_DMA_BD_SIZE (32 << 20) // 32 MB + +#define XAXIDMA_SR_SGINCL_MASK 0x00000008 + +struct dma_mem { + char *base_virt; + char *base_phys; + size_t len; +}; + +struct dma { + XAxiDma inst; + + struct dma_mem bd; +}; + +struct ip; + +int dma_mem_split(struct dma_mem *o, struct dma_mem *a, struct dma_mem *b); + +int dma_alloc(struct fpga_ip *c, struct dma_mem *mem, size_t len, int flags); +int dma_free(struct fpga_ip *c, struct dma_mem *mem); + +int dma_write(struct fpga_ip *c, char *buf, size_t len); +int dma_read(struct fpga_ip *c, char *buf, size_t len); +int dma_read_complete(struct fpga_ip *c, char **buf, size_t *len); +int dma_write_complete(struct fpga_ip *c, char **buf, size_t *len); + +int dma_sg_write(struct fpga_ip *c, char *buf, size_t len); +int dma_sg_read(struct fpga_ip *c, char *buf, size_t len); + +int dma_sg_write_complete(struct fpga_ip *c, char **buf, size_t *len); +int dma_sg_read_complete(struct fpga_ip *c, char **buf, size_t *len); + +int dma_simple_read(struct fpga_ip *c, char *buf, size_t len); +int dma_simple_write(struct fpga_ip *c, char *buf, size_t len); + +int dma_simple_read_complete(struct fpga_ip *c, char **buf, size_t *len); +int dma_simple_write_complete(struct fpga_ip *c, char **buf, size_t *len); + +int dma_ping_pong(struct fpga_ip *c, char *src, char *dst, size_t len); + +int dma_start(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/fifo.h b/fpga/include/villas/fpga/ips/fifo.h new file mode 100644 index 000000000..4724c1ac5 --- /dev/null +++ b/fpga/include/villas/fpga/ips/fifo.h @@ -0,0 +1,52 @@ +/** FIFO related helper functions + * + * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include +#include + +struct fifo { + XLlFifo inst; + + uint32_t baseaddr_axi4; +}; + +/* Forward declarations */ +struct ip; + +int fifo_start(struct fpga_ip *c); + +ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len); + +ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/intc.h b/fpga/include/villas/fpga/ips/intc.h new file mode 100644 index 000000000..bb7d44739 --- /dev/null +++ b/fpga/include/villas/fpga/ips/intc.h @@ -0,0 +1,56 @@ +/** AXI-PCIe Interrupt controller + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +enum intc_flags { + INTC_ENABLED = (1 << 0), + INTC_POLLING = (1 << 1) +}; + +struct intc { + int num_irqs; /**< Number of available MSI vectors */ + + int efds[32]; /**< Event FDs */ + int nos[32]; /**< Interrupt numbers from /proc/interrupts */ + + int flags[32]; /**< Mask of intc_flags */ +}; + +int intc_init(struct fpga_ip *c); + +int intc_destroy(struct fpga_ip *c); + +int intc_enable(struct fpga_ip *c, uint32_t mask, int poll); + +int intc_disable(struct fpga_ip *c, uint32_t mask); + +uint64_t intc_wait(struct fpga_ip *c, int irq); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/model.h b/fpga/include/villas/fpga/ips/model.h new file mode 100644 index 000000000..9d7f6b555 --- /dev/null +++ b/fpga/include/villas/fpga/ips/model.h @@ -0,0 +1,147 @@ +/** Interface to Xilinx System Generator Models via PCIe + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include "list.h" + +#define XSG_MAPLEN 0x1000 +#define XSG_MAGIC 0xDEADBABE + +/* Forward declaration */ +struct ip; + +enum model_type { + MODEL_TYPE_HLS, + MODEL_TYPE_XSG +}; + +enum model_xsg_block_type { + XSG_BLOCK_GATEWAY_IN = 0x1000, + XSG_BLOCK_GATEWAY_OUT = 0x1001, + XSG_BLOCK_INFO = 0x2000 +}; + +enum model_parameter_type { + MODEL_PARAMETER_TYPE_UFIX, + MODEL_PARAMETER_TYPE_FIX, + MODEL_PARAMETER_TYPE_FLOAT, + MODEL_PARAMETER_TYPE_BOOLEAN +}; + +enum model_parameter_direction { + MODEL_PARAMETER_IN, + MODEL_PARAMETER_OUT, + MODEL_PARAMETER_INOUT +}; + +union model_parameter_value { + uint32_t ufix; + int32_t fix; + float flt; + bool bol; +}; + +struct xsg_model { + uint32_t *map; + ssize_t maplen; +}; + +struct hls_model { + +}; + +struct model { + enum model_type type; /**< Either HLS or XSG model */ + + struct list parameters; /**< List of model parameters. */ + struct list infos; /**< A list of key / value pairs with model details */ + + union { + struct xsg_model xsg; /**< XSG specific model data */ + struct hls_model hls; /**< HLS specific model data */ + }; +}; + +struct model_info { + char *field; + char *value; +}; + +struct model_parameter { + char *name; /**< Name of the parameter */ + + enum model_parameter_direction direction; /**< Read / Write / Read-write? */ + enum model_parameter_type type; /**< Data type. Integers are represented by MODEL_GW_TYPE_(U)FIX with model_gw::binpt == 0 */ + + int binpt; /**< Binary point for type == MODEL_GW_TYPE_(U)FIX */ + uintptr_t offset; /**< Register offset to model::baseaddress */ + + union model_parameter_value default_value; + + struct fpga_ip *ip; /**< A pointer to the model structure to which this parameters belongs to. */ +}; + +/** Initialize a model */ +int model_init(struct fpga_ip *c); + +/** Parse model */ +int model_parse(struct fpga_ip *c, json_t *cfg); + +/** Destroy a model */ +int model_destroy(struct fpga_ip *c); + +/** Print detailed information about the model to the screen. */ +void model_dump(struct fpga_ip *c); + +/** Add a new parameter to the model */ +void model_parameter_add(struct fpga_ip *c, const char *name, enum model_parameter_direction dir, enum model_parameter_type type); + +/** Remove an existing parameter by its name */ +int model_parameter_remove(struct fpga_ip *c, const char *name); + +/** Read a model parameter. + * + * Note: the data type of the register is taken into account. + * All datatypes are converted to double. + */ +int model_parameter_read(struct model_parameter *p, double *v); + +/** Update a model parameter. + * + * Note: the data type of the register is taken into account. + * The double argument will be converted to the respective data type of the + * GatewayIn/Out block. + */ +int model_parameter_write(struct model_parameter *p, double v); + +int model_parameter_update(struct model_parameter *p, struct model_parameter *u); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/rtds_axis.h b/fpga/include/villas/fpga/ips/rtds_axis.h new file mode 100644 index 000000000..52f1f49d4 --- /dev/null +++ b/fpga/include/villas/fpga/ips/rtds_axis.h @@ -0,0 +1,62 @@ +/** Driver for AXI Stream wrapper around RTDS_InterfaceModule (rtds_axis ) + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +/* Forward declarations */ +struct ip; + +#define RTDS_HZ 100000000 // 100 MHz + +#define RTDS_AXIS_MAX_TX 64 /**< The amount of values which is supported by the vfpga card */ +#define RTDS_AXIS_MAX_RX 64 /**< The amount of values which is supported by the vfpga card */ + +/* Register offsets */ +#define RTDS_AXIS_SR_OFFSET 0x00 /**< Status Register (read-only). See RTDS_AXIS_SR_* constant. */ +#define RTDS_AXIS_CR_OFFSET 0x04 /**< Control Register (read/write) */ +#define RTDS_AXIS_TSCNT_LOW_OFFSET 0x08 /**< Lower 32 bits of timestep counter (read-only). */ +#define RTDS_AXIS_TSCNT_HIGH_OFFSET 0x0C /**< Higher 32 bits of timestep counter (read-only). */ +#define RTDS_AXIS_TS_PERIOD_OFFSET 0x10 /**< Period in clock cycles of previous timestep (read-only). */ +#define RTDS_AXIS_COALESC_OFFSET 0x14 /**< IRQ Coalescing register (read/write). */ +#define RTDS_AXIS_VERSION_OFFSET 0x18 /**< 16 bit version field passed back to the rack for version reporting (visible from “status” command, read/write). */ +#define RTDS_AXIS_MRATE 0x1C /**< Multi-rate register */ + +/* Status register bits */ +#define RTDS_AXIS_SR_CARDDETECTED (1 << 0)/**< ‘1’ when RTDS software has detected and configured card. */ +#define RTDS_AXIS_SR_LINKUP (1 << 1)/**< ‘1’ when RTDS communication link has been negotiated. */ +#define RTDS_AXIS_SR_TX_FULL (1 << 2)/**< Tx buffer is full, writes that happen when UserTxFull=’1’ will be dropped (Throttling / buffering is performed by hardware). */ +#define RTDS_AXIS_SR_TX_INPROGRESS (1 << 3)/**< Indicates when data is being put on link. */ +#define RTDS_AXIS_SR_CASE_RUNNING (1 << 4)/**< There is currently a simulation running. */ + +/* Control register bits */ +#define RTDS_AXIS_CR_DISABLE_LINK 0 /**< Disable SFP TX when set */ + +void rtds_axis_dump(struct fpga_ip *c); + +double rtds_axis_dt(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/switch.h b/fpga/include/villas/fpga/ips/switch.h new file mode 100644 index 000000000..7fcf08fa6 --- /dev/null +++ b/fpga/include/villas/fpga/ips/switch.h @@ -0,0 +1,67 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include "list.h" + +/* Forward declarations */ +struct ip; + +struct sw_path { + const char *in; + const char *out; +}; + +struct sw { + XAxis_Switch inst; + + int num_ports; + struct list paths; +}; + +struct ip; + +int switch_start(struct fpga_ip *c); + +/** Initialize paths which have been parsed by switch_parse() */ +int switch_init_paths(struct fpga_ip *c); + +int switch_destroy(struct fpga_ip *c); + +int switch_parse(struct fpga_ip *c, json_t *cfg); + +int switch_connect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si); + +int switch_disconnect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/timer.h b/fpga/include/villas/fpga/ips/timer.h new file mode 100644 index 000000000..31fac741e --- /dev/null +++ b/fpga/include/villas/fpga/ips/timer.h @@ -0,0 +1,43 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +/* Forward declarations */ +struct fpga_ip; + +struct timer { + XTmrCtr inst; +}; + +int timer_start(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/vlnv.h b/fpga/include/villas/fpga/vlnv.h new file mode 100644 index 000000000..d24385271 --- /dev/null +++ b/fpga/include/villas/fpga/vlnv.h @@ -0,0 +1,53 @@ +/** Vendor, Library, Name, Version (VLNV) tag. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#ifndef _FPGA_VLNV_H_ +#define _FPGA_VLNV_H_ + +/* Forward declarations */ +struct list; + +struct fpga_vlnv { + char *vendor; + char *library; + char *name; + char *version; +}; + +/** Return the first IP block in list \p l which matches the VLNV */ +struct fpga_ip * fpga_vlnv_lookup(struct list *l, struct fpga_vlnv *v); + +/** Check if IP block \p c matched VLNV. */ +int fpga_vlnv_cmp(struct fpga_vlnv *a, struct fpga_vlnv *b); + +/** Tokenizes VLNV \p vlnv and stores it into \p c */ +int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv); + +/** Release memory allocated by fpga_vlnv_parse(). */ +int fpga_vlnv_destroy(struct fpga_vlnv *v); + +#endif /** _FPGA_VLNV_H_ @} */ diff --git a/fpga/include/villas/kernel/kernel.h b/fpga/include/villas/kernel/kernel.h new file mode 100644 index 000000000..9a7f5eccb --- /dev/null +++ b/fpga/include/villas/kernel/kernel.h @@ -0,0 +1,90 @@ +/** Linux kernel related functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include +#include + +/* Forward declarations */ +struct version; + +//#include + +/** Check if current process has capability \p cap. + * + * @retval 0 If capabilty is present. + * @retval <0 If capability is not present. + */ +//int kernel_check_cap(cap_value_t cap); + +/** Get number of reserved hugepages. */ +int kernel_get_nr_hugepages(); + +/** Set number of reserved hugepages. */ +int kernel_set_nr_hugepages(int nr); + +/** Get kernel cmdline parameter + * + * See https://www.kernel.org/doc/Documentation/kernel-parameters.txt + * + * @param param The cmdline parameter to look for. + * @param buf The string buffer to which the parameter value will be copied to. + * @param len The length of the buffer \p value + * @retval 0 Parameter \p key was found and value was copied to \p value + * @reval <>0 Kernel was not booted with parameter \p key + */ +int kernel_get_cmdline_param(const char *param, char *buf, size_t len); + +/** Get the version of the kernel. */ +int kernel_get_version(struct version *v); + +/** Checks if a kernel module is loaded + * + * @param module the name of the module + * @retval 0 Module is loaded. + * @reval <>0 Module is not loaded. + */ +int kernel_module_loaded(const char *module); + +/** Load kernel module via modprobe */ +int kernel_module_load(const char *module); + +/** Set parameter of loaded kernel module */ +int kernel_module_set_param(const char *module, const char *param, const char *value); + +/** Get cacheline size in bytes */ +int kernel_get_cacheline_size(); + +/** Get the size of a standard page in bytes. */ +int kernel_get_page_size(); + +/** Get the size of a huge page in bytes. */ +int kernel_get_hugepage_size(); + +/** Set SMP affinity of IRQ */ +int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old); + +/** @} */ diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h new file mode 100644 index 000000000..821d0d6df --- /dev/null +++ b/fpga/include/villas/kernel/pci.h @@ -0,0 +1,62 @@ +/** Linux PCI helpers + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + **********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include "list.h" + +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_FUNC(devfn) ((devfn) & 0x07) + +struct pci_device { + struct { + int vendor; + int device; + int class; + } id; + + struct { + int domain; + int bus; + int device; + int function; + } slot; /**< Bus, Device, Function (BDF) */ +}; + +struct pci { + struct list devices; /**< List of available PCI devices in the system (struct pci_device) */ +}; + +/** Initialize Linux PCI handle. + * + * This search for all available PCI devices under /sys/bus/pci + * + * @retval 0 Success. Everything went well. + * @retval <0 Error. Something went wrong. + */ +int pci_init(struct pci *p); + +/** Destroy handle. */ +int pci_destroy(struct pci *p); + +int pci_device_parse_slot(struct pci_device *f, const char *str, const char **error); + +int pci_device_parse_id(struct pci_device *f, const char *str, const char **error); + +int pci_device_compare(const struct pci_device *d, const struct pci_device *f); + +struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *filter); + +/** Bind a new LKM to the PCI device */ +int pci_attach_driver(struct pci_device *d, const char *driver); + +/** Return the IOMMU group of this PCI device or -1 if the device is not in a group. */ +int pci_get_iommu_group(struct pci_device *d); + +/** @} */ diff --git a/fpga/include/villas/kernel/vfio.h b/fpga/include/villas/kernel/vfio.h new file mode 100644 index 000000000..bb6f12329 --- /dev/null +++ b/fpga/include/villas/kernel/vfio.h @@ -0,0 +1,112 @@ +/** Virtual Function IO wrapper around kernel API + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + *********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include "list.h" + +#define VFIO_DEV(x) "/dev/vfio/" x + +/* Forward declarations */ +struct pci_device; + +struct vfio_group { + int fd; /**< VFIO group file descriptor */ + int index; /**< Index of the IOMMU group as listed under /sys/kernel/iommu_groups/ */ + + struct vfio_group_status status; /**< Status of group */ + + struct list devices; + + struct vfio_container *container; /**< The VFIO container to which this group is belonging */ +}; + +struct vfio_device { + char *name; /**< Name of the device as listed under /sys/kernel/iommu_groups/[vfio_group::index]/devices/ */ + int fd; /**< VFIO device file descriptor */ + + struct vfio_device_info info; + struct vfio_irq_info *irqs; + struct vfio_region_info *regions; + + void **mappings; + + struct pci_device *pci_device; /**< libpci handle of the device */ + struct vfio_group *group; /**< The VFIO group this device belongs to */ +}; + +struct vfio_container { + int fd; + int version; + int extensions; + + uint64_t iova_next; /**< Next free IOVA address */ + + struct list groups; +}; + +/** Initialize a new VFIO container. */ +int vfio_init(struct vfio_container *c); + +/** Initialize a VFIO group and attach it to an existing VFIO container. */ +int vfio_group_attach(struct vfio_group *g, struct vfio_container *c, int index); + +/** Initialize a VFIO device, lookup the VFIO group it belongs to, create the group if not already existing. */ +int vfio_device_attach(struct vfio_device *d, struct vfio_container *c, const char *name, int index); + +/** Initialie a VFIO-PCI device (uses vfio_device_attach() internally) */ +int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_device *pdev); + +/** Hot resets a VFIO-PCI device */ +int vfio_pci_reset(struct vfio_device *d); + +int vfio_pci_msi_init(struct vfio_device *d, int efds[32]); + +int vfio_pci_msi_deinit(struct vfio_device *d, int efds[32]); + +int vfio_pci_msi_find(struct vfio_device *d, int nos[32]); + +/** Enable memory accesses and bus mastering for PCI device */ +int vfio_pci_enable(struct vfio_device *d); + +/** Reset a VFIO device */ +int vfio_device_reset(struct vfio_device *d); + +/** Release memory and close container */ +int vfio_destroy(struct vfio_container *c); + +/** Release memory of group */ +int vfio_group_destroy(struct vfio_group *g); + +/** Release memory of device */ +int vfio_device_destroy(struct vfio_device *g); + +/** Print a dump of all attached groups and their devices including regions and IRQs */ +void vfio_dump(struct vfio_container *c); + +/** Map a device memory region to the application address space (e.g. PCI BARs) */ +void * vfio_map_region(struct vfio_device *d, int idx); + +/** Map VM to an IOVA, which is accessible by devices in the container */ +int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); + +/** Unmap DMA memory */ +int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); + +/** munmap() a region which has been mapped by vfio_map_region() */ +int vfio_unmap_region(struct vfio_device *d, int idx); + +/** @} */ diff --git a/fpga/include/villas/list.h b/fpga/include/villas/list.h new file mode 100644 index 000000000..f6ba29dae --- /dev/null +++ b/fpga/include/villas/list.h @@ -0,0 +1,112 @@ +/** A generic list implementation. + * + * This is a generic implementation of a list which can store void pointers to + * arbitrary data. The data itself is not stored or managed by the list. + * + * Internally, an array of pointers is used to store the pointers. + * If needed, this array will grow by realloc(). + * + * @file + * @author Steffen Vogel + * @copyright 20177, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#pragma once + +#include +#include + +#include "common.h" + +#define LIST_CHUNKSIZE 16 + +/** Static list initialization */ +#define LIST_INIT() { \ + .array = NULL, \ + .length = 0, \ + .capacity = 0, \ + .lock = PTHREAD_MUTEX_INITIALIZER, \ + .state = STATE_INITIALIZED \ +} + +#define LIST_INIT_STATIC(l) \ +__attribute__((constructor(105))) static void UNIQUE(__ctor)() {\ + if ((l)->state == STATE_DESTROYED) \ + list_init(l); \ +} \ +__attribute__((destructor(105))) static void UNIQUE(__dtor)() { \ + list_destroy(l, NULL, false); \ +} + +#define list_length(list) ((list)->length) +#define list_at_safe(list, index) ((list)->length > index ? (list)->array[index] : NULL) +#define list_at(list, index) ((list)->array[index]) + +#define list_first(list) list_at(list, 0) +#define list_last(list) list_at(list, (list)->length-1) + +/** Callback to destroy list elements. + * + * @param data A pointer to the data which should be freed. + */ +typedef int (*dtor_cb_t)(void *); + +/** Callback to search or sort a list. */ +typedef int (*cmp_cb_t)(const void *, const void *); + +/* The list data structure. */ +struct list { + void **array; /**< Array of pointers to list elements */ + size_t capacity; /**< Size of list::array in elements */ + size_t length; /**< Number of elements of list::array which are in use */ + pthread_mutex_t lock; /**< A mutex to allow thread-safe accesses */ + enum state state; /**< The state of this list. */ +}; + +/** Initialize a list. + * + * @param l A pointer to the list data structure. + */ +int list_init(struct list *l); + +/** Destroy a list and call destructors for all list elements + * + * @param free free() all list members during when calling list_destroy() + * @param dtor A function pointer to a desctructor which will be called for every list item when the list is destroyed. + * @param l A pointer to the list data structure. + */ +int list_destroy(struct list *l, dtor_cb_t dtor, bool free); + +/** Append an element to the end of the list */ +void list_push(struct list *l, void *p); + +/** Remove all occurences of a list item */ +void list_remove(struct list *l, void *p); + +/** Return the first list element which is identified by a string in its first member variable. + * + * List elements are pointers to structures of the following form: + * + * struct obj { + * char *name; + * // more members + * } + * + * @see Only possible because of §1424 of http://c0x.coding-guidelines.com/6.7.2.1.html + */ +void * list_lookup(struct list *l, const char *name); + +/** Return the first element of the list for which cmp returns zero */ +void * list_search(struct list *l, cmp_cb_t cmp, void *ctx); + +/** Returns the number of occurences for which cmp returns zero when called on all list elements. */ +int list_count(struct list *l, cmp_cb_t cmp, void *ctx); + +/** Return 0 if list contains pointer p */ +int list_contains(struct list *l, void *p); + +/** Sort the list using the quicksort algorithm of libc */ +void list_sort(struct list *l, cmp_cb_t cmp); + +/** Set single element in list */ +int list_set(struct list *l, int index, void *value); diff --git a/fpga/include/villas/log.h b/fpga/include/villas/log.h new file mode 100644 index 000000000..6d8771ee8 --- /dev/null +++ b/fpga/include/villas/log.h @@ -0,0 +1,194 @@ +/** Logging and debugging routines + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "common.h" +#include "log_config.h" + +#ifdef __GNUC__ + #define INDENT int __attribute__ ((__cleanup__(log_outdent), unused)) _old_indent = log_indent(1); + #define NOINDENT int __attribute__ ((__cleanup__(log_outdent), unused)) _old_indent = log_noindent(); +#else + #define INDENT ; + #define NOINDENT ; +#endif + +/* The log level which is passed as first argument to print() */ +#define LOG_LVL_DEBUG CLR_GRY("Debug") +#define LOG_LVL_INFO CLR_WHT("Info ") +#define LOG_LVL_WARN CLR_YEL("Warn ") +#define LOG_LVL_ERROR CLR_RED("Error") +#define LOG_LVL_STATS CLR_MAG("Stats") + +/** Debug facilities. + * + * To be or-ed with the debug level + */ +enum log_facilities { + LOG_POOL = (1L << 8), + LOG_QUEUE = (1L << 9), + LOG_CONFIG = (1L << 10), + LOG_HOOK = (1L << 11), + LOG_PATH = (1L << 12), + LOG_NODE = (1L << 13), + LOG_MEM = (1L << 14), + LOG_WEB = (1L << 15), + LOG_API = (1L << 16), + LOG_LOG = (1L << 17), + LOG_VFIO = (1L << 18), + LOG_PCI = (1L << 19), + LOG_XIL = (1L << 20), + LOG_TC = (1L << 21), + LOG_IF = (1L << 22), + LOG_ADVIO = (1L << 23), + + /* Node-types */ + LOG_SOCKET = (1L << 24), + LOG_FILE = (1L << 25), + LOG_FPGA = (1L << 26), + LOG_NGSI = (1L << 27), + LOG_WEBSOCKET = (1L << 28), + LOG_OPAL = (1L << 30), + + /* Classes */ + LOG_NODES = LOG_NODE | LOG_SOCKET | LOG_FILE | LOG_FPGA | LOG_NGSI | LOG_WEBSOCKET | LOG_OPAL, + LOG_KERNEL = LOG_VFIO | LOG_PCI | LOG_TC | LOG_IF, + LOG_ALL = ~0xFF +}; + +struct log { + enum state state; + + struct timespec epoch; /**< A global clock used to prefix the log messages. */ + + struct winsize window; /**< Size of the terminal window. */ + int width; /**< The real usable log output width which fits into one line. */ + + /** Debug level used by the debug() macro. + * It defaults to V (defined by the Makefile) and can be + * overwritten by the 'debug' setting in the configuration file. */ + int level; + long facilities; /**< Debug facilities used by the debug() macro. */ + const char *path; /**< Path of the log file. */ + char *prefix; /**< Prefix each line with this string. */ + int syslog; /**< Whether or not to log to syslogd. */ + + FILE *file; /**< Send all log output to this file / stdout / stderr. */ +}; + +/** The global log instance. */ +struct log *global_log; +struct log default_log; + +/** Initialize log object */ +int log_init(struct log *l, int level, long faciltities); + +int log_start(struct log *l); + +int log_stop(struct log *l); + +/** Destroy log object */ +int log_destroy(struct log *l); + +/** Change log indention for current thread. + * + * The argument level can be negative! + */ +int log_indent(int levels); + +/** Disable log indention of current thread. */ +int log_noindent(); + +/** A helper function the restore the previous log indention level. + * + * This function is usually called by a __cleanup__ handler (GCC C Extension). + * See INDENT macro. + */ +void log_outdent(int *); + +/** Set logging facilities based on expression. + * + * Currently we support two types of expressions: + * 1. A comma seperated list of logging facilities + * 2. A comma seperated list of logging facilities which is prefixes with an exclamation mark '!' + * + * The first case enables only faciltities which are in the list. + * The second case enables all faciltities with exception of those which are in the list. + * + * @param expression The expression + * @return The new facilties mask (see enum log_faciltities) + */ +int log_set_facility_expression(struct log *l, const char *expression); + +/** Logs variadic messages to stdout. + * + * @param lvl The log level + * @param fmt The format string (printf alike) + */ +void log_print(struct log *l, const char *lvl, const char *fmt, ...) + __attribute__ ((format(printf, 3, 4))); + +/** Logs variadic messages to stdout. + * + * @param lvl The log level + * @param fmt The format string (printf alike) + * @param va The variadic argument list (see stdarg.h) + */ +void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list va); + +/** Printf alike debug message with level. */ +void debug(long lvl, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); + +/** Printf alike info message. */ +void info(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Printf alike warning message. */ +void warn(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Printf alike statistics message. */ +void stats(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Print error and exit. */ +void error(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Print error and strerror(errno). */ +void serror(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +#ifdef __cplusplus +} +#endif diff --git a/fpga/include/villas/log_config.h b/fpga/include/villas/log_config.h new file mode 100644 index 000000000..e2a71333f --- /dev/null +++ b/fpga/include/villas/log_config.h @@ -0,0 +1,37 @@ +/** Logging routines that depend on jansson. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +struct log; + +#include + +#include "log.h" + +/** Parse logging configuration. */ +int log_parse(struct log *l, json_t *cfg); + +/** Print configuration error and exit. */ +void jerror(json_error_t *err, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); diff --git a/fpga/include/villas/plugin.h b/fpga/include/villas/plugin.h new file mode 100644 index 000000000..8c915b573 --- /dev/null +++ b/fpga/include/villas/plugin.h @@ -0,0 +1,82 @@ +/** Loadable / plugin support. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +#include "utils.h" +#include "fpga/ip.h" + +/** @todo This is ugly as hell and broken on OS X / Clang anyway. */ +#define REGISTER_PLUGIN(p) \ +__attribute__((constructor(110))) static void UNIQUE(__ctor)() {\ + if (plugins.state == STATE_DESTROYED) \ + list_init(&plugins); \ + list_push(&plugins, p); \ +} \ +__attribute__((destructor(110))) static void UNIQUE(__dtor)() { \ + if (plugins.state != STATE_DESTROYED) \ + list_remove(&plugins, p); \ +} + +extern struct list plugins; + +enum plugin_type { + PLUGIN_TYPE_FPGA_IP, +}; + +struct plugin { + const char *name; + const char *description; + void *handle; + char *path; + + enum plugin_type type; + + enum state state; + + int (*load)(struct plugin *p); + int (*unload)(struct plugin *p); + + struct fpga_ip_type ip; +}; + +/** Return a pointer to the plugin structure */ +#define plugin(vt) ((struct plugin *) ((char *) (vt) - offsetof(struct plugin, api))) + +#define plugin_name(vt) plugin(vt)->name +#define plugin_description(vt) plugin(vt)->description + +int plugin_init(struct plugin *p); + +int plugin_destroy(struct plugin *p); + +int plugin_parse(struct plugin *p, json_t *cfg); + +int plugin_load(struct plugin *p); + +int plugin_unload(struct plugin *p); + +void plugin_dump(enum plugin_type type); + +/** Find registered and loaded plugin with given name and type. */ +struct plugin * plugin_lookup(enum plugin_type type, const char *name); diff --git a/fpga/include/villas/utils.h b/fpga/include/villas/utils.h new file mode 100644 index 000000000..b552cc52b --- /dev/null +++ b/fpga/include/villas/utils.h @@ -0,0 +1,276 @@ +/** Various helper functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "log.h" + +#ifdef __GNUC__ + #define LIKELY(x) __builtin_expect((x),1) + #define UNLIKELY(x) __builtin_expect((x),0) +#else + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) +#endif + +/* Some color escape codes for pretty log messages */ +#define CLR(clr, str) "\e[" XSTR(clr) "m" str "\e[0m" +#define CLR_GRY(str) CLR(30, str) /**< Print str in gray */ +#define CLR_RED(str) CLR(31, str) /**< Print str in red */ +#define CLR_GRN(str) CLR(32, str) /**< Print str in green */ +#define CLR_YEL(str) CLR(33, str) /**< Print str in yellow */ +#define CLR_BLU(str) CLR(34, str) /**< Print str in blue */ +#define CLR_MAG(str) CLR(35, str) /**< Print str in magenta */ +#define CLR_CYN(str) CLR(36, str) /**< Print str in cyan */ +#define CLR_WHT(str) CLR(37, str) /**< Print str in white */ +#define CLR_BLD(str) CLR( 1, str) /**< Print str in bold */ + +/* Alternate character set + * + * The suffixed of the BOX_ macro a constructed by + * combining the following letters in the written order: + * - U for a line facing upwards + * - D for a line facing downwards + * - L for a line facing leftwards + * - R for a line facing rightwards + * + * E.g. a cross can be constructed by combining all line fragments: + * BOX_UDLR + */ +#define BOX(chr) "\e(0" chr "\e(B" +#define BOX_LR BOX("\x71") /**< Boxdrawing: ─ */ +#define BOX_UD BOX("\x78") /**< Boxdrawing: │ */ +#define BOX_UDR BOX("\x74") /**< Boxdrawing: ├ */ +#define BOX_UDLR BOX("\x6E") /**< Boxdrawing: ┼ */ +#define BOX_UDL BOX("\x75") /**< Boxdrawing: ┤ */ +#define BOX_ULR BOX("\x76") /**< Boxdrawing: ┴ */ +#define BOX_UL BOX("\x6A") /**< Boxdrawing: ┘ */ +#define BOX_DLR BOX("\x77") /**< Boxdrawing: ┘ */ +#define BOX_DL BOX("\x6B") /**< Boxdrawing: ┘ */ + +/* CPP stringification */ +#define XSTR(x) STR(x) +#define STR(x) #x + +#define CONCAT_DETAIL(x, y) x##y +#define CONCAT(x, y) CONCAT_DETAIL(x, y) +#define UNIQUE(x) CONCAT(x, __COUNTER__) + +#define ALIGN(x, a) ALIGN_MASK(x, (uintptr_t) (a) - 1) +#define ALIGN_MASK(x, m) (((uintptr_t) (x) + (m)) & ~(m)) +#define IS_ALIGNED(x, a) (ALIGN(x, a) == (uintptr_t) x) + +#define SWAP(x,y) do { \ + __auto_type _x = x; \ + __auto_type _y = y; \ + x = _y; \ + y = _x; \ +} while(0) + +/** Round-up integer division */ +#define CEIL(x, y) (((x) + (y) - 1) / (y)) + +/** Get nearest up-rounded power of 2 */ +#define LOG2_CEIL(x) (1 << (log2i((x) - 1) + 1)) + +/** Check if the number is a power of 2 */ +#define IS_POW2(x) (((x) != 0) && !((x) & ((x) - 1))) + +/** Calculate the number of elements in an array. */ +#define ARRAY_LEN(a) ( sizeof (a) / sizeof (a)[0] ) + +/* Return the bigger value */ +#define MAX(a, b) ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +/* Return the smaller value */ +#define MIN(a, b) ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#ifndef offsetof + #define offsetof(type, member) __builtin_offsetof(type, member) +#endif + +#ifndef container_of + #define container_of(ptr, type, member) ({ const typeof( ((type *) 0)->member ) *__mptr = (ptr); \ + (type *) ( (char *) __mptr - offsetof(type, member) ); \ + }) +#endif + +#define BITS_PER_LONGLONG (sizeof(long long) * 8) + +/* Some helper macros */ +#define BITMASK(h, l) (((~0ULL) << (l)) & (~0ULL >> (BITS_PER_LONGLONG - 1 - (h)))) +#define BIT(nr) (1UL << (nr)) + +/* Forward declarations */ +struct timespec; + +/** Print copyright message to stdout. */ +void print_copyright(); + +/** Print version to stdout. */ +void print_version(); + +/** Normal random variate generator using the Box-Muller method + * + * @param m Mean + * @param s Standard deviation + * @return Normal variate random variable (Gaussian) + */ +double box_muller(float m, float s); + +/** Double precission uniform random variable */ +double randf(); + +/** Concat formatted string to an existing string. + * + * This function uses realloc() to resize the destination. + * Please make sure to only on dynamic allocated destionations!!! + * + * @param dest A pointer to a malloc() allocated memory region + * @param fmt A format string like for printf() + * @param ... Optional parameters like for printf() + * @retval The the new value of the dest buffer. + */ +char * strcatf(char **dest, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); + +/** Variadic version of strcatf() */ +char * vstrcatf(char **dest, const char *fmt, va_list va) + __attribute__ ((format(printf, 2, 0))); + +/** Format string like strcatf() just starting with empty string */ +#define strf(fmt, ...) strcatf(&(char *) { NULL }, fmt, ##__VA_ARGS__) +#define vstrf(fmt, va) vstrcatf(&(char *) { NULL }, fmt, va) + +#ifdef __linux__ +/** Convert integer to cpu_set_t. + * + * @param set An integer number which is used as the mask + * @param cset A pointer to the cpu_set_t datastructure + */ +void cpuset_from_integer(uintmax_t set, cpu_set_t *cset); + +/** Convert cpu_set_t to an integer. */ +void cpuset_to_integer(cpu_set_t *cset, uintmax_t *set); + +/** Parses string with list of CPU ranges. + * + * From: https://github.com/mmalecki/util-linux/blob/master/lib/cpuset.c + * + * @retval 0 On success. + * @retval 1 On error. + * @retval 2 If fail is set and a cpu number passed in the list doesn't fit + * into the cpu_set. If fail is not set cpu numbers that do not fit are + * ignored and 0 is returned instead. + */ +int cpulist_parse(const char *str, cpu_set_t *set, int fail); + +/** Returns human readable representation of the cpuset. + * + * From: https://github.com/mmalecki/util-linux/blob/master/lib/cpuset.c + * + * The output format is a list of CPUs with ranges (for example, "0,1,3-9"). + */ +char * cpulist_create(char *str, size_t len, cpu_set_t *set); +#endif + +/** Allocate and initialize memory. */ +void * alloc(size_t bytes); + +/** Allocate and copy memory. */ +void * memdup(const void *src, size_t bytes); + +/** Call quit() in the main thread. */ +void die(); + +/** Used by version_parse(), version_compare() */ +struct version { + int major; + int minor; +}; + +/** Compare two versions. */ +int version_cmp(struct version *a, struct version *b); + +/** Parse a dotted version string. */ +int version_parse(const char *s, struct version *v); + +/** Check assertion and exit if failed. */ +#ifndef assert + #define assert(exp) do { \ + if (!EXPECT(exp, 0)) \ + error("Assertion failed: '%s' in %s(), %s:%d", \ + XSTR(exp), __FUNCTION__, __BASE_FILE__, __LINE__); \ + } while (0) +#endif + +/** Fill buffer with random data */ +ssize_t read_random(char *buf, size_t len); + +/** Get CPU timestep counter */ +__attribute__((always_inline)) static inline uint64_t rdtsc() +{ + uint64_t tsc; + + __asm__ ("rdtsc;" + "shl $32, %%rdx;" + "or %%rdx,%%rax" + : "=a" (tsc) + : + : "%rcx", "%rdx", "memory"); + + return tsc; +} + +/** Get log2 of long long integers */ +static inline int log2i(long long x) { + if (x == 0) + return 1; + + return sizeof(x) * 8 - __builtin_clzll(x) - 1; +} + +/** Sleep with rdtsc */ +void rdtsc_sleep(uint64_t nanosecs, uint64_t start); + +/** Register a exit callback for program termination: SIGINT, SIGKILL & SIGALRM. */ +int signals_init(void (*cb)(int signal, siginfo_t *sinfo, void *ctx)); + +/** Send signal \p sig to main thread. */ +void killme(int sig); + +pid_t spawn(const char *name, char *const argv[]); + +/** Determines the string length as printed on the screen (ignores escable sequences). */ +size_t strlenp(const char *str); diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt new file mode 100644 index 000000000..e83e6382a --- /dev/null +++ b/fpga/lib/CMakeLists.txt @@ -0,0 +1,53 @@ +set(SOURCES + ip.c + vlnv.c + card.c + + ips/timer.c + ips/model.c + ips/switch.c + ips/dft.c + ips/fifo.c + ips/dma.c + ips/intc.c + ips/rtds_axis.c + + kernel/kernel.c + kernel/pci.c + kernel/vfio.c + + plugin.c + utils.c + list.c + log.c + log_config.c + log_helper.c +) + +include(FindPkgConfig) + +pkg_check_modules(JANSSON jansson) +pkg_check_modules(XIL libxil) + +find_package(Threads) + +add_library(villas-fpga ${SOURCES}) + +target_compile_definitions(villas-fpga PRIVATE + BUILDID=\"abc\" + _GNU_SOURCE +) + +target_include_directories(villas-fpga PUBLIC + ../include/villas + ${XIL_INCLUDE_DIRS} + ${JANSSON_INCLUDE_DIRS} +) + +target_link_libraries(villas-fpga PUBLIC + ${XIL_LIBRARIES} + ${JANSSON_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_DL_LIBS} + m +) diff --git a/fpga/lib/card.c b/fpga/lib/card.c new file mode 100644 index 000000000..2e07b130a --- /dev/null +++ b/fpga/lib/card.c @@ -0,0 +1,313 @@ +/** FPGA card. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" +#include "log.h" +#include "log_config.h" +#include "list.h" +#include "utils.h" + +#include "kernel/pci.h" +#include "kernel/vfio.h" + +#include "fpga/ip.h" +#include "fpga/card.h" + +int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) +{ + assert(c->state = STATE_DESTROYED); + + c->vfio_container = vc; + c->pci = pci; + + list_init(&c->ips); + + /* Default values */ + c->filter.id.vendor = FPGA_PCI_VID_XILINX; + c->filter.id.device = FPGA_PCI_PID_VFPGA; + + c->affinity = 0; + c->do_reset = 0; + + c->state = STATE_INITIALIZED; + + return 0; +} + +int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name) +{ + int ret; + + json_t *json_ips; + json_t *json_slot = NULL; + json_t *json_id = NULL; + json_error_t err; + + c->name = strdup(name); + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: b, s?: o, s?: o, s: o }", + "affinity", &c->affinity, + "do_reset", &c->do_reset, + "slot", &json_slot, + "id", &json_id, + "ips", &json_ips + ); + if (ret) + jerror(&err, "Failed to parse FPGA vard configuration"); + + if (json_slot) { + const char *err, *slot; + + slot = json_string_value(json_slot); + if (slot) { + ret = pci_device_parse_slot(&c->filter, slot, &err); + if (ret) + error("Failed to parse PCI slot: %s", err); + } + else + error("PCI slot must be a string"); + } + + if (json_id) { + const char *err, *id; + + id = json_string_value(json_id); + if (id) { + ret = pci_device_parse_id(&c->filter, (char*) id, &err); + if (ret) + error("Failed to parse PCI id: %s", err); + } + else + error("PCI ID must be a string"); + } + + if (!json_is_object(json_ips)) + error("FPGA card IPs section must be an object"); + + const char *name_ip; + json_t *json_ip; + json_object_foreach(json_ips, name_ip, json_ip) { + const char *vlnv; + + struct fpga_ip_type *vt; + struct fpga_ip *ip = (struct fpga_ip *) alloc(sizeof(struct fpga_ip)); + + ip->card = c; + + ret = json_unpack_ex(json_ip, &err, 0, "{ s: s }", "vlnv", &vlnv); + if (ret) + error("Failed to parse FPGA IP '%s' of card '%s'", name_ip, name); + + vt = fpga_ip_type_lookup(vlnv); + if (!vt) + error("FPGA IP core VLNV identifier '%s' is invalid", vlnv); + + ret = fpga_ip_init(ip, vt); + if (ret) + error("Failed to initalize FPGA IP core"); + + ret = fpga_ip_parse(ip, json_ip, name_ip); + if (ret) + error("Failed to parse FPGA IP core"); + + list_push(&c->ips, ip); + } + + c->state = STATE_PARSED; + + return 0; +} + +int fpga_card_parse_list(struct list *cards, json_t *cfg) +{ + int ret; + + if (!json_is_object(cfg)) + error("FPGA card configuration section must be a JSON object"); + + const char *name; + json_t *json_fpga; + json_object_foreach(cfg, name, json_fpga) { + struct fpga_card *c = (struct fpga_card *) alloc(sizeof(struct fpga_card)); + + ret = fpga_card_parse(c, json_fpga, name); + if (ret) + error("Failed to parse FPGA card configuration"); + + list_push(cards, c); + } + + return 0; +} + +int fpga_card_start(struct fpga_card *c) +{ + int ret; + + struct pci_device *pdev; + + assert(c->state == STATE_CHECKED); + + /* Search for FPGA card */ + pdev = pci_lookup_device(c->pci, &c->filter); + if (!pdev) + error("Failed to find PCI device"); + + /* Attach PCIe card to VFIO container */ + ret = vfio_pci_attach(&c->vfio_device, c->vfio_container, pdev); + if (ret) + error("Failed to attach VFIO device"); + + /* Map PCIe BAR */ + c->map = vfio_map_region(&c->vfio_device, VFIO_PCI_BAR0_REGION_INDEX); + if (c->map == MAP_FAILED) + serror("Failed to mmap() BAR0"); + + /* Enable memory access and PCI bus mastering for DMA */ + ret = vfio_pci_enable(&c->vfio_device); + if (ret) + serror("Failed to enable PCI device"); + + /* Reset system? */ + if (c->do_reset) { + /* Reset / detect PCI device */ + ret = vfio_pci_reset(&c->vfio_device); + if (ret) + serror("Failed to reset PCI device"); + + ret = fpga_card_reset(c); + if (ret) + error("Failed to reset FGPA card"); + } + + /* Initialize IP cores */ + for (size_t j = 0; j < list_length(&c->ips); j++) { + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + ret = fpga_ip_start(i); + if (ret) + error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); + } + + c->state = STATE_STARTED; + + return 0; +} + +int fpga_card_stop(struct fpga_card *c) +{ + int ret; + + assert(c->state == STATE_STOPPED); + + for (size_t j = 0; j < list_length(&c->ips); j++) { + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + ret = fpga_ip_stop(i); + if (ret) + error("Failed to stop FPGA IP core: %s (%u)", i->name, ret); + } + + c->state = STATE_STOPPED; + + return 0; +} + +void fpga_card_dump(struct fpga_card *c) +{ + info("VILLASfpga card:"); + { INDENT + info("Slot: %04x:%02x:%02x.%d", c->vfio_device.pci_device->slot.domain, c->vfio_device.pci_device->slot.bus, c->vfio_device.pci_device->slot.device, c->vfio_device.pci_device->slot.function); + info("Vendor ID: %04x", c->vfio_device.pci_device->id.vendor); + info("Device ID: %04x", c->vfio_device.pci_device->id.device); + info("Class ID: %04x", c->vfio_device.pci_device->id.class); + + info("BAR0 mapped at %p", c->map); + + info("IP blocks:"); + for (size_t j = 0; j < list_length(&c->ips); j++) { INDENT + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + fpga_ip_dump(i); + } + } + + vfio_dump(c->vfio_device.group->container); +} + +int fpga_card_check(struct fpga_card *c) +{ + /* Check FPGA configuration */ + c->reset = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_gpio", NULL }); + if (!c->reset) + error("FPGA is missing a reset controller"); + + c->intc = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }); + if (!c->intc) + error("FPGA is missing a interrupt controller"); + + c->sw = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axis_interconnect", NULL }); + if (!c->sw) + warn("FPGA is missing an AXI4-Stream switch"); + + return 0; +} + +int fpga_card_destroy(struct fpga_card *c) +{ + list_destroy(&c->ips, (dtor_cb_t) fpga_ip_destroy, true); + + return 0; +} + +int fpga_card_reset(struct fpga_card *c) +{ + int ret; + char state[4096]; + + /* Save current state of PCI configuration space */ + ret = pread(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); + if (ret != sizeof(state)) + return -1; + + uint32_t *rst_reg = (uint32_t *) (c->map + c->reset->baseaddr); + + debug(3, "FPGA: reset"); + rst_reg[0] = 1; + + usleep(100000); + + /* Restore previous state of PCI configuration space */ + ret = pwrite(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); + if (ret != sizeof(state)) + return -1; + + /* After reset the value should be zero again */ + if (rst_reg[0]) + return -2; + + c->state = STATE_INITIALIZED; + + return 0; +} diff --git a/fpga/lib/ip.c b/fpga/lib/ip.c new file mode 100644 index 000000000..816bf2e7f --- /dev/null +++ b/fpga/lib/ip.c @@ -0,0 +1,167 @@ +/** FPGA IP component. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "log_config.h" +#include "log.h" +#include "plugin.h" + +int fpga_ip_init(struct fpga_ip *c, struct fpga_ip_type *vt) +{ + int ret; + + assert(c->state == STATE_DESTROYED); + + c->_vt = vt; + c->_vd = alloc(vt->size); + + ret = c->_vt->init ? c->_vt->init(c) : 0; + if (ret) + return ret; + + c->state = STATE_INITIALIZED; + + debug(8, "IP Core %s initalized (%u)", c->name, ret); + + return ret; +} + +int fpga_ip_parse(struct fpga_ip *c, json_t *cfg, const char *name) +{ + int ret, baseaddr = -1; + + assert(c->state != STATE_STARTED && c->state != STATE_DESTROYED); + + c->name = strdup(name); + c->baseaddr = -1; + c->irq = -1; + c->port = -1; + + json_error_t err; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: i, s?: i }", + "baseaddr", &baseaddr, + "irq", &c->irq, + "port", &c->port + ); + if (ret) + jerror(&err, "Failed to parse configuration for FPGA IP '%s'", name); + + c->baseaddr = baseaddr; + + /* Type sepecific settings */ + ret = c->_vt && c->_vt->parse ? c->_vt->parse(c, cfg) : 0; + if (ret) + error("Failed to parse settings for IP core '%s'", name); + + c->state = STATE_PARSED; + + return 0; +} + +int fpga_ip_start(struct fpga_ip *c) +{ + int ret; + + assert(c->state == STATE_CHECKED); + + ret = c->_vt->start ? c->_vt->start(c) : 0; + if (ret) + return ret; + + c->state = STATE_STARTED; + + return 0; +} + +int fpga_ip_stop(struct fpga_ip *c) +{ + int ret; + + assert(c->state == STATE_STARTED); + + ret = c->_vt->stop ? c->_vt->stop(c) : 0; + if (ret) + return ret; + + c->state = STATE_STOPPED; + + return 0; +} + +int fpga_ip_destroy(struct fpga_ip *c) +{ + int ret; + + assert(c->state != STATE_DESTROYED); + + fpga_vlnv_destroy(&c->vlnv); + + ret = c->_vt->destroy ? c->_vt->destroy(c) : 0; + if (ret) + return ret; + + c->state = STATE_DESTROYED; + + free(c->_vd); + + return 0; +} + +int fpga_ip_reset(struct fpga_ip *c) +{ + debug(3, "Reset IP core: %s", c->name); + + return c->_vt->reset ? c->_vt->reset(c) : 0; +} + +void fpga_ip_dump(struct fpga_ip *c) +{ + assert(c->state != STATE_DESTROYED); + + info("IP %s: vlnv=%s:%s:%s:%s baseaddr=%#jx, irq=%d, port=%d", + c->name, c->vlnv.vendor, c->vlnv.library, c->vlnv.name, c->vlnv.version, + c->baseaddr, c->irq, c->port); + + if (c->_vt->dump) + c->_vt->dump(c); +} + +struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr) +{ + int ret; + + struct fpga_vlnv vlnv; + + ret = fpga_vlnv_parse(&vlnv, vstr); + if (ret) + return NULL; + + /* Try to find matching IP type */ + for (size_t i = 0; i < list_length(&plugins); i++) { + struct plugin *p = (struct plugin *) list_at(&plugins, i); + + if (p->type == PLUGIN_TYPE_FPGA_IP && !fpga_vlnv_cmp(&vlnv, &p->ip.vlnv)) + return &p->ip; + } + + return NULL; +} diff --git a/fpga/lib/ips/dft.c b/fpga/lib/ips/dft.c new file mode 100644 index 000000000..dcb0b95b4 --- /dev/null +++ b/fpga/lib/ips/dft.c @@ -0,0 +1,140 @@ +/** Moving window / Recursive DFT implementation based on HLS + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "log.h" +#include "log_config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/dft.h" + +int dft_parse(struct fpga_ip *c, json_t *cfg) +{ + struct dft *dft = (struct dft *) c->_vd; + + int ret; + + json_t *json_harms; + json_error_t err; + + ret = json_unpack_ex(cfg, &err, 0, "{ s: i, s: i, s: o }", + "period", &dft->period, + "decimation", &dft->decimation, + "harmonics", &json_harms + ); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + if (!json_is_array(json_harms)) + error("DFT IP core requires 'harmonics' to be an array of integers!"); + + dft->num_harmonics = json_array_size(json_harms); + if (dft->num_harmonics <= 0) + error("DFT IP core requires 'harmonics' to contain at least 1 value!"); + + dft->fharmonics = alloc(sizeof(float) * dft->num_harmonics); + + size_t index; + json_t *json_harm; + json_array_foreach(json_harms, index, json_harm) { + if (!json_is_real(json_harm)) + error("DFT IP core requires all 'harmonics' values to be of floating point type"); + + dft->fharmonics[index] = (float) json_number_value(json_harm) / dft->period; + } + + return 0; +} + +int dft_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct dft *dft = (struct dft *) c->_vd; + + XHls_dft *xdft = &dft->inst; + XHls_dft_Config xdft_cfg = { + .Ctrl_BaseAddress = (uintptr_t) f->map + c->baseaddr + }; + + ret = XHls_dft_CfgInitialize(xdft, &xdft_cfg); + if (ret != XST_SUCCESS) + return ret; + + int max_harmonics = XHls_dft_Get_fharmonics_TotalBytes(xdft) / sizeof(dft->fharmonics[0]); + + if (dft->num_harmonics > max_harmonics) + error("DFT IP core supports a maximum of %u harmonics", max_harmonics); + + XHls_dft_Set_num_harmonics_V(xdft, dft->num_harmonics); + + XHls_dft_Set_decimation_V(xdft, dft->decimation); + + memcpy((void *) (uintptr_t) XHls_dft_Get_fharmonics_BaseAddress(xdft), dft->fharmonics, dft->num_harmonics * sizeof(dft->fharmonics[0])); + + XHls_dft_EnableAutoRestart(xdft); + XHls_dft_Start(xdft); + + return 0; +} + +int dft_stop(struct fpga_ip *c) +{ + struct dft *dft = (struct dft *) c->_vd; + + XHls_dft *xdft = &dft->inst; + + XHls_dft_DisableAutoRestart(xdft); + + return 0; +} + +int dft_destroy(struct fpga_ip *c) +{ + struct dft *dft = (struct dft *) c->_vd; + + if (dft->fharmonics) { + free(dft->fharmonics); + dft->fharmonics = NULL; + } + + return 0; +} + +static struct plugin p = { + .name = "Discrete Fourier Transform", + .description = "Perfom Discrete Fourier Transforms with variable number of harmonics on the FPGA", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "hls", "hls_dft", NULL }, + .type = FPGA_IP_TYPE_MATH, + .start = dft_start, + .stop = dft_stop, + .destroy = dft_destroy, + .parse = dft_parse, + .size = sizeof(struct dft) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/dma.c b/fpga/lib/ips/dma.c new file mode 100644 index 000000000..c02175709 --- /dev/null +++ b/fpga/lib/ips/dma.c @@ -0,0 +1,657 @@ +/** DMA related helper functions + * + * These functions present a simpler interface to Xilinx' DMA driver (XAxiDma_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include "log.h" +#include "plugin.h" +#include "utils.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/dma.h" + +int dma_mem_split(struct dma_mem *o, struct dma_mem *a, struct dma_mem *b) +{ + int split = o->len / 2; + + a->base_virt = o->base_virt; + a->base_phys = o->base_phys; + + b->base_virt = a->base_virt + split; + b->base_phys = a->base_phys + split; + + a->len = split; + b->len = o->len - split; + + return 0; +} + +int dma_alloc(struct fpga_ip *c, struct dma_mem *mem, size_t len, int flags) +{ + int ret; + + struct fpga_card *f = c->card; + + /* Align to next bigger page size chunk */ + if (len & 0xFFF) { + len += 0x1000; + len &= ~0xFFF; + } + + mem->len = len; + mem->base_phys = (void *) -1; /* find free */ + mem->base_virt = mmap(0, mem->len, PROT_READ | PROT_WRITE, flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, 0, 0); + if (mem->base_virt == MAP_FAILED) + return -1; + + ret = vfio_map_dma(f->vfio_device.group->container, (uint64_t) mem->base_virt, (uint64_t) mem->base_phys, mem->len); + if (ret) + return -2; + + return 0; +} + +int dma_free(struct fpga_ip *c, struct dma_mem *mem) +{ + int ret; + + ret = vfio_unmap_dma(c->card->vfio_device.group->container, (uint64_t) mem->base_virt, (uint64_t) mem->base_phys, mem->len); + if (ret) + return ret; + + ret = munmap(mem->base_virt, mem->len); + if (ret) + return ret; + + return 0; +} + +int dma_ping_pong(struct fpga_ip *c, char *src, char *dst, size_t len) +{ + int ret; + + ret = dma_read(c, dst, len); + if (ret) + return ret; + + ret = dma_write(c, src, len); + if (ret) + return ret; + + ret = dma_write_complete(c, NULL, NULL); + if (ret) + return ret; + + ret = dma_read_complete(c, NULL, NULL); + if (ret) + return ret; + + return 0; +} + +int dma_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA write: dmac=%s buf=%p len=%#zx", c->name, buf, len); + + return xdma->HasSg + ? dma_sg_write(c, buf, len) + : dma_simple_write(c, buf, len); +} + +int dma_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA read: dmac=%s buf=%p len=%#zx", c->name, buf, len); + + return xdma->HasSg + ? dma_sg_read(c, buf, len) + : dma_simple_read(c, buf, len); +} + +int dma_read_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA read complete: dmac=%s", c->name); + + return xdma->HasSg + ? dma_sg_read_complete(c, buf, len) + : dma_simple_read_complete(c, buf, len); +} + +int dma_write_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA write complete: dmac=%s", c->name); + + return xdma->HasSg + ? dma_sg_write_complete(c, buf, len) + : dma_simple_write_complete(c, buf, len); +} + +int dma_sg_write(struct fpga_ip *c, char *buf, size_t len) +{ + int ret, bdcnt; + + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + XAxiDma_Bd *bds, *bd; + + uint32_t remaining, bdlen, bdbuf, cr; + + /* Checks */ + if (!xdma->HasSg) + return -1; + + if (len < 1) + return -2; + + if (!xdma->HasMm2S) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + bdcnt = CEIL(len, FPGA_DMA_BOUNDARY); + ret = XAxiDma_BdRingAlloc(ring, bdcnt, &bds); + if (ret != XST_SUCCESS) + return -5; + + remaining = len; + bdbuf = (uintptr_t) buf; + bd = bds; + for (int i = 0; i < bdcnt; i++) { + bdlen = MIN(remaining, FPGA_DMA_BOUNDARY); + + ret = XAxiDma_BdSetBufAddr(bd, bdbuf); + if (ret != XST_SUCCESS) + goto out; + + ret = XAxiDma_BdSetLength(bd, bdlen, ring->MaxTransferLen); + if (ret != XST_SUCCESS) + goto out; + + /* Set SOF / EOF / ID */ + cr = 0; + if (i == 0) + cr |= XAXIDMA_BD_CTRL_TXSOF_MASK; + if (i == bdcnt - 1) + cr |= XAXIDMA_BD_CTRL_TXEOF_MASK; + + XAxiDma_BdSetCtrl(bd, cr); + XAxiDma_BdSetId(bd, (uintptr_t) buf); + + remaining -= bdlen; + bdbuf += bdlen; + bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); + } + + /* Give the BD to DMA to kick off the transmission. */ + ret = XAxiDma_BdRingToHw(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -8; + + return 0; + +out: + ret = XAxiDma_BdRingUnAlloc(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -6; + + return -5; +} + +int dma_sg_read(struct fpga_ip *c, char *buf, size_t len) +{ + int ret, bdcnt; + + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + XAxiDma_Bd *bds, *bd; + + uint32_t remaining, bdlen, bdbuf; + + /* Checks */ + if (!xdma->HasSg) + return -1; + + if (len < 1) + return -2; + + if (!xdma->HasS2Mm) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + bdcnt = CEIL(len, FPGA_DMA_BOUNDARY); + ret = XAxiDma_BdRingAlloc(ring, bdcnt, &bds); + if (ret != XST_SUCCESS) + return -5; + + bdbuf = (uintptr_t) buf; + remaining = len; + bd = bds; + for (int i = 0; i < bdcnt; i++) { + bdlen = MIN(remaining, FPGA_DMA_BOUNDARY); + ret = XAxiDma_BdSetLength(bd, bdlen, ring->MaxTransferLen); + if (ret != XST_SUCCESS) + goto out; + + ret = XAxiDma_BdSetBufAddr(bd, bdbuf); + if (ret != XST_SUCCESS) + goto out; + + /* Receive BDs do not need to set anything for the control + * The hardware will set the SOF/EOF bits per stream ret */ + XAxiDma_BdSetCtrl(bd, 0); + XAxiDma_BdSetId(bd, (uintptr_t) buf); + + remaining -= bdlen; + bdbuf += bdlen; + bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); + } + + ret = XAxiDma_BdRingToHw(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -8; + + return 0; + +out: + ret = XAxiDma_BdRingUnAlloc(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -6; + + return -5; +} + +int dma_sg_write_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + XAxiDma_Bd *bds; + + int processed, ret; + + /* Wait until the one BD TX transaction is done */ + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + + processed = XAxiDma_BdRingFromHw(ring, XAXIDMA_ALL_BDS, &bds); + + if (len != NULL) + *len = XAxiDma_BdGetActualLength(bds, XAXIDMA_MAX_TRANSFER_LEN); + + if (buf != NULL) + *buf = (char *) (uintptr_t) XAxiDma_BdGetId(bds); + + /* Free all processed TX BDs for future transmission */ + ret = XAxiDma_BdRingFree(ring, processed, bds); + if (ret != XST_SUCCESS) + return -1; + + return 0; +} + +int dma_sg_read_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + XAxiDma_Bd *bds, *bd; + + int ret, bdcnt; + uint32_t recvlen, sr; + uintptr_t recvbuf = 0; + + if (!xdma->HasSg) + return -1; + + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq + 1); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + bdcnt = XAxiDma_BdRingFromHw(ring, XAXIDMA_ALL_BDS, &bds); + + recvlen = 0; + + bd = bds; + for (int i = 0; i < bdcnt; i++) { + recvlen += XAxiDma_BdGetActualLength(bd, ring->MaxTransferLen); + + sr = XAxiDma_BdGetSts(bd); + if (sr & XAXIDMA_BD_STS_RXSOF_MASK) + if (i != 0) + warn("sof not first"); + + if (sr & XAXIDMA_BD_STS_RXEOF_MASK) + if (i != bdcnt - 1) + warn("eof not last"); + + recvbuf = XAxiDma_BdGetId(bd); + + bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); + } + + if (len != NULL) + *len = recvlen; + if (buf != NULL) + *buf = (char *) recvbuf; + + /* Free all processed RX BDs for future transmission */ + ret = XAxiDma_BdRingFree(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -3; + + return 0; +} + +int dma_simple_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + + /* Checks */ + if (xdma->HasSg) + return -1; + + if ((len < 1) || (len > FPGA_DMA_BOUNDARY)) + return -2; + + if (!xdma->HasS2Mm) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + if(!(XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) { + if (XAxiDma_Busy(xdma, XAXIDMA_DEVICE_TO_DMA)) + return -5; + } + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET, LOWER_32_BITS((uintptr_t) buf)); + if (xdma->AddrWidth > 32) + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET, UPPER_32_BITS((uintptr_t) buf)); + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET) | XAXIDMA_CR_RUNSTOP_MASK); + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); + + return XST_SUCCESS; +} + +int dma_simple_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + + /* Checks */ + if (xdma->HasSg) + return -1; + + if ((len < 1) || (len > FPGA_DMA_BOUNDARY)) + return -2; + + if (!xdma->HasMm2S) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + /* If the engine is doing transfer, cannot submit */ + if(!(XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) { + if (XAxiDma_Busy(xdma, XAXIDMA_DMA_TO_DEVICE)) + return -5; + } + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET, LOWER_32_BITS((uintptr_t) buf)); + if (xdma->AddrWidth > 32) + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET, UPPER_32_BITS((uintptr_t) buf)); + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET) | XAXIDMA_CR_RUNSTOP_MASK); + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); + + return XST_SUCCESS; +} + +int dma_simple_read_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq + 1); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + if (len) + *len = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET); + + if (buf) { + *buf = (char *) (uintptr_t) XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET); + if (xdma->AddrWidth > 32) + *buf += XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET); + } + + return 0; +} + +int dma_simple_write_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + + if (len) + *len = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET); + + if (buf) { + *buf = (char *) (uintptr_t) XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET); + if (xdma->AddrWidth > 32) + *buf += XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET); + } + + return 0; +} + +static int dma_setup_ring(XAxiDma_BdRing *ring, struct dma_mem *bdbuf) +{ + int delay = 0; + int coalesce = 1; + int ret, cnt; + + XAxiDma_Bd clearbd; + + /* Disable all RX interrupts before RxBD space setup */ + XAxiDma_BdRingIntDisable(ring, XAXIDMA_IRQ_ALL_MASK); + + /* Set delay and coalescing */ + XAxiDma_BdRingSetCoalesce(ring, coalesce, delay); + + /* Setup Rx BD space */ + cnt = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, bdbuf->len); + + ret = XAxiDma_BdRingCreate(ring, (uintptr_t) bdbuf->base_phys, (uintptr_t) bdbuf->base_virt, XAXIDMA_BD_MINIMUM_ALIGNMENT, cnt); + if (ret != XST_SUCCESS) + return -1; + + XAxiDma_BdClear(&clearbd); + ret = XAxiDma_BdRingClone(ring, &clearbd); + if (ret != XST_SUCCESS) + return -2; + + /* Start the channel */ + ret = XAxiDma_BdRingStart(ring); + if (ret != XST_SUCCESS) + return -3; + + return XST_SUCCESS; +} + +static int dma_init_rings(XAxiDma *xdma, struct dma_mem *bd) +{ + int ret; + + struct dma_mem bd_rx, bd_tx; + + ret = dma_mem_split(bd, &bd_rx, &bd_tx); + if (ret) + return -1; + + ret = dma_setup_ring(XAxiDma_GetRxRing(xdma), &bd_rx); + if (ret != XST_SUCCESS) + return -2; + + ret = dma_setup_ring(XAxiDma_GetTxRing(xdma), &bd_tx); + if (ret != XST_SUCCESS) + return -3; + + return 0; +} + +int dma_start(struct fpga_ip *c) +{ + int ret, sg; + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + /* Guess DMA type */ + sg = (XAxiDma_In32((uintptr_t) c->card->map + c->baseaddr + XAXIDMA_TX_OFFSET+ XAXIDMA_SR_OFFSET) & + XAxiDma_In32((uintptr_t) c->card->map + c->baseaddr + XAXIDMA_RX_OFFSET+ XAXIDMA_SR_OFFSET) & XAXIDMA_SR_SGINCL_MASK) ? 1 : 0; + + XAxiDma_Config xdma_cfg = { + .BaseAddr = (uintptr_t) c->card->map + c->baseaddr, + .HasStsCntrlStrm = 0, + .HasMm2S = 1, + .HasMm2SDRE = 1, + .Mm2SDataWidth = 128, + .HasS2Mm = 1, + .HasS2MmDRE = 1, /* Data Realignment Engine */ + .HasSg = sg, + .S2MmDataWidth = 128, + .Mm2sNumChannels = 1, + .S2MmNumChannels = 1, + .Mm2SBurstSize = 64, + .S2MmBurstSize = 64, + .MicroDmaMode = 0, + .AddrWidth = 32 + }; + + ret = XAxiDma_CfgInitialize(xdma, &xdma_cfg); + if (ret != XST_SUCCESS) + return -1; + + /* Perform selftest */ + ret = XAxiDma_Selftest(xdma); + if (ret != XST_SUCCESS) + return -2; + + /* Map buffer descriptors */ + if (xdma->HasSg) { + ret = dma_alloc(c, &dma->bd, FPGA_DMA_BD_SIZE, 0); + if (ret) + return -3; + + ret = dma_init_rings(xdma, &dma->bd); + if (ret) + return -4; + } + + /* Enable completion interrupts for both channels */ + XAxiDma_IntrEnable(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + XAxiDma_IntrEnable(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + return 0; +} + +int dma_reset(struct fpga_ip *c) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma_Reset(&dma->inst); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's AXI4 Direct Memory Access Controller", + .description = "Transfer data streams between VILLASnode and VILLASfpga", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_dma", NULL }, + .type = FPGA_IP_TYPE_DM_DMA, + .init = dma_start, + .reset = dma_reset, + .size = sizeof(struct dma) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/fifo.c b/fpga/lib/ips/fifo.c new file mode 100644 index 000000000..1f4c058f3 --- /dev/null +++ b/fpga/lib/ips/fifo.c @@ -0,0 +1,153 @@ +/** FIFO related helper functions + * + * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "utils.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/fifo.h" +#include "fpga/ips/intc.h" + +int fifo_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xfifo = &fifo->inst; + XLlFifo_Config fifo_cfg = { + .BaseAddress = (uintptr_t) f->map + c->baseaddr, + .Axi4BaseAddress = (uintptr_t) c->card->map + fifo->baseaddr_axi4, + .Datainterface = (fifo->baseaddr_axi4 != -1) ? 1 : 0 /* use AXI4 for Data, AXI4-Lite for control */ + }; + + ret = XLlFifo_CfgInitialize(xfifo, &fifo_cfg, (uintptr_t) c->card->map + c->baseaddr); + if (ret != XST_SUCCESS) + return -1; + + XLlFifo_IntEnable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */ + + return 0; +} + +int fifo_stop(struct fpga_ip *c) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xfifo = &fifo->inst; + + XLlFifo_IntDisable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */ + + return 0; +} + +ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xllfifo = &fifo->inst; + + uint32_t tdfv; + + tdfv = XLlFifo_TxVacancy(xllfifo); + if (tdfv < len) + return -1; + + XLlFifo_Write(xllfifo, buf, len); + XLlFifo_TxSetLen(xllfifo, len); + + return len; +} + +ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xllfifo = &fifo->inst; + + size_t nextlen = 0; + uint32_t rxlen; + + while (!XLlFifo_IsRxDone(xllfifo)) + intc_wait(c->card->intc, c->irq); + XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK); + + /* Get length of next frame */ + rxlen = XLlFifo_RxGetLen(xllfifo); + nextlen = MIN(rxlen, len); + + /* Read from FIFO */ + XLlFifo_Read(xllfifo, buf, nextlen); + + return nextlen; +} + +int fifo_parse(struct fpga_ip *c, json_t *cfg) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + int baseaddr_axi4 = -1, ret; + + json_error_t err; + + fifo->baseaddr_axi4 = -1; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + fifo->baseaddr_axi4 = baseaddr_axi4; + + return 0; +} + +int fifo_reset(struct fpga_ip *c) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo_Reset(&fifo->inst); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's AXI4 FIFO data mover", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_fifo_mm_s", NULL }, + .type = FPGA_IP_TYPE_DM_FIFO, + .start = fifo_start, + .stop = fifo_stop, + .parse = fifo_parse, + .reset = fifo_reset, + .size = sizeof(struct fifo) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/intc.c b/fpga/lib/ips/intc.c new file mode 100644 index 000000000..7fcf109d6 --- /dev/null +++ b/fpga/lib/ips/intc.c @@ -0,0 +1,180 @@ +/** AXI-PCIe Interrupt controller + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" +#include "log.h" +#include "plugin.h" + +#include "kernel/vfio.h" +#include "kernel/kernel.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/intc.h" + +int intc_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + if (c != f->intc) + error("There can be only one interrupt controller per FPGA"); + + intc->num_irqs = vfio_pci_msi_init(&f->vfio_device, intc->efds); + if (intc->num_irqs < 0) + return -1; + + ret = vfio_pci_msi_find(&f->vfio_device, intc->nos); + if (ret) + return -2; + + /* For each IRQ */ + for (int i = 0; i < intc->num_irqs; i++) { + /* Pin to core */ + ret = kernel_irq_setaffinity(intc->nos[i], f->affinity, NULL); + if (ret) + serror("Failed to change affinity of VFIO-MSI interrupt"); + + /* Setup vector */ + XIntc_Out32(base + XIN_IVAR_OFFSET + i * 4, i); + } + + XIntc_Out32(base + XIN_IMR_OFFSET, 0); /* Use manual acknowlegement for all IRQs */ + XIntc_Out32(base + XIN_IAR_OFFSET, 0xFFFFFFFF); /* Acknowlege all pending IRQs manually */ + XIntc_Out32(base + XIN_IMR_OFFSET, 0xFFFFFFFF); /* Use fast acknowlegement for all IRQs */ + XIntc_Out32(base + XIN_IER_OFFSET, 0x00000000); /* Disable all IRQs by default */ + XIntc_Out32(base + XIN_MER_OFFSET, XIN_INT_HARDWARE_ENABLE_MASK | XIN_INT_MASTER_ENABLE_MASK); + + debug(4, "FPGA: enabled interrupts"); + + return 0; +} + +int intc_destroy(struct fpga_ip *c) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + vfio_pci_msi_deinit(&f->vfio_device, intc->efds); + + return 0; +} + +int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uint32_t ier, imr; + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + /* Current state of INTC */ + ier = XIntc_In32(base + XIN_IER_OFFSET); + imr = XIntc_In32(base + XIN_IMR_OFFSET); + + /* Clear pending IRQs */ + XIntc_Out32(base + XIN_IAR_OFFSET, mask); + + for (int i = 0; i < intc->num_irqs; i++) { + if (mask & (1 << i)) + intc->flags[i] = flags; + } + + if (flags & INTC_POLLING) { + XIntc_Out32(base + XIN_IMR_OFFSET, imr & ~mask); + XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); + } + else { + XIntc_Out32(base + XIN_IER_OFFSET, ier | mask); + XIntc_Out32(base + XIN_IMR_OFFSET, imr | mask); + } + + debug(3, "New ier = %#x", XIntc_In32(base + XIN_IER_OFFSET)); + debug(3, "New imr = %#x", XIntc_In32(base + XIN_IMR_OFFSET)); + debug(3, "New isr = %#x", XIntc_In32(base + XIN_ISR_OFFSET)); + + debug(8, "FPGA: Interupt enabled: mask=%#x flags=%#x", mask, flags); + + return 0; +} + +int intc_disable(struct fpga_ip *c, uint32_t mask) +{ + struct fpga_card *f = c->card; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); + + XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); + + return 0; +} + +uint64_t intc_wait(struct fpga_ip *c, int irq) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + if (intc->flags[irq] & INTC_POLLING) { + uint32_t isr, mask = 1 << irq; + + do { + isr = XIntc_In32(base + XIN_ISR_OFFSET); + pthread_testcancel(); + } while ((isr & mask) != mask); + + XIntc_Out32(base + XIN_IAR_OFFSET, mask); + + return 1; + } + else { + uint64_t cnt; + ssize_t ret = read(intc->efds[irq], &cnt, sizeof(cnt)); + if (ret != sizeof(cnt)) + return 0; + + return cnt; + } +} + +static struct plugin p = { + .name = "Xilinx's programmable interrupt controller", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = intc_start, + .destroy = intc_destroy, + .size = sizeof(struct intc) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/model.c b/fpga/lib/ips/model.c new file mode 100644 index 000000000..c69b4ba49 --- /dev/null +++ b/fpga/lib/ips/model.c @@ -0,0 +1,427 @@ +/** Interface to Xilinx System Generator Models via PCIe + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include "utils.h" +#include "log.h" +#include "log_config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/model.h" + +static int model_parameter_destroy(struct model_parameter *p) +{ + free(p->name); + + return 0; +} + +static int model_info_destroy(struct model_info *i) +{ + free(i->field); + free(i->value); + + return 0; +} + +static uint32_t model_xsg_map_checksum(uint32_t *map, size_t len) +{ + uint32_t chks = 0; + + for (int i = 2; i < len-1; i++) + chks += map[i]; + + return chks; /* moduluo 2^32 because of overflow */ +} + +static int model_xsg_map_parse(uint32_t *map, size_t len, struct list *parameters, struct list *infos) +{ +#define copy_string(off) strndup((char *) (data + (off)), (length - (off)) * 4); + int j; + struct model_info *i; + + /* Check magic */ + if (map[0] != XSG_MAGIC) + error("Invalid magic: %#x", map[0]); + + for (j = 2; j < len-1;) { + uint16_t type = map[j] & 0xFFFF; + uint16_t length = map[j] >> 16; + uint32_t *data = &map[j+1]; + + switch (type) { + case XSG_BLOCK_GATEWAY_IN: + case XSG_BLOCK_GATEWAY_OUT: + if (length < 4) + break; /* block is to small to describe a gateway */ + + struct model_parameter *e, *p = (struct model_parameter *) alloc(sizeof(struct model_parameter)); + + p->name = copy_string(3); + p->default_value.flt = *((float *) &data[1]); + p->offset = data[2]; + p->direction = type & 0x1; + p->type = (data[0] >> 0) & 0xFF; + p->binpt = (data[0] >> 8) & 0xFF; + + e = list_lookup(parameters, p->name); + if (e) + model_parameter_update(e, p); + else + list_push(parameters, p); + break; + + case XSG_BLOCK_INFO: + i = alloc(sizeof(struct model_info)); + + i->field = copy_string(0); + i->value = copy_string((int) ceil((double) (strlen(i->field) + 1) / 4)) + + list_push(infos, i); + break; + + default: + warn("Unknown block type: %#06x", type); + } + + j += length + 1; + } + + return 0; + +#undef copy_string +} + +static uint32_t model_xsg_map_read_word(uint32_t offset, void *baseaddr) +{ + volatile uint32_t *addr = baseaddr + 0x00; + volatile uint32_t *data = baseaddr + 0x04; + + *addr = offset; /* Update addr reg */ + + return *data; /* Read data reg */ +} + +static int model_xsg_map_read(uint32_t *map, size_t len, void *baseaddr) +{ + size_t maplen; + uint32_t magic; + + /* Check magic */ + magic = model_xsg_map_read_word(0, baseaddr); + if (magic != XSG_MAGIC) + return -1; + + maplen = model_xsg_map_read_word(1, baseaddr); + if (maplen < 3) + return -2; + + /* Read Data */ + int i; + for (i = 0; i < MIN(maplen, len); i++) + map[i] = model_xsg_map_read_word(i, baseaddr); + + return i; +} + +int model_parse(struct fpga_ip *c, json_t *cfg) +{ + struct model *m = (struct model *) c->_vd; + + int ret; + + json_t *json_params; + json_error_t err; + + if (strcmp(c->vlnv.library, "hls") == 0) + m->type = MODEL_TYPE_HLS; + else if (strcmp(c->vlnv.library, "sysgen") == 0) + m->type = MODEL_TYPE_XSG; + else + error("Unsupported model type: %s", c->vlnv.library); + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: o }", "parameters", &json_params); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + if (json_params) { + if (!json_is_object(json_params)) + error("Setting 'parameters' must be a JSON object"); + + const char *name; + json_t *value; + json_object_foreach(json_params, name, value) { + if (!json_is_real(value)) + error("Parameters of FPGA IP '%s' must be of type floating point", c->name); + + struct model_parameter *p = (struct model_parameter *) alloc(sizeof(struct model_parameter)); + + p->name = strdup(name); + p->default_value.flt = json_real_value(value); + + list_push(&m->parameters, p); + } + } + + return 0; +} + +static int model_init_from_xsg_map(struct model *m, void *baseaddr) +{ + int ret, chks; + + if (baseaddr == (void *) -1) + return -1; + + m->xsg.map = alloc(XSG_MAPLEN); + m->xsg.maplen = model_xsg_map_read(m->xsg.map, XSG_MAPLEN, baseaddr); + if (m->xsg.maplen < 0) + return -1; + + debug(5, "XSG: memory map length = %#zx", m->xsg.maplen); + + chks = m->xsg.map[m->xsg.maplen - 1]; + if (chks != model_xsg_map_checksum(m->xsg.map, m->xsg.maplen)) + return -2; + + ret = model_xsg_map_parse(m->xsg.map, m->xsg.maplen, &m->parameters, &m->infos); + if (ret) + return -3; + + debug(5, "XSG: Parsed %zu parameters and %zu model infos", list_length(&m->parameters), list_length(&m->infos)); + + return 0; +} + +int model_init(struct fpga_ip *c) +{ + int ret; + + struct model *m = (struct model *) c->_vd; + + list_init(&m->parameters); + list_init(&m->infos); + + if (!fpga_vlnv_cmp(&c->vlnv, &(struct fpga_vlnv) { NULL, "sysgen", NULL, NULL })) + ret = model_init_from_xsg_map(m, c->card->map + c->baseaddr); + else + ret = 0; + + /* Set default values for parameters */ + for (size_t i = 0; i < list_length(&m->parameters); i++) { + struct model_parameter *p = (struct model_parameter *) list_at(&m->parameters, i); + + p->ip = c; + + if (p->direction == MODEL_PARAMETER_IN) { + model_parameter_write(p, p->default_value.flt); + info("Set parameter '%s' updated to default value: %f", p->name, p->default_value.flt); + } + } + + if (ret) + error("Failed to init XSG model: %d", ret); + + return 0; +} + +int model_destroy(struct fpga_ip *c) +{ + struct model *m = (struct model *) c->_vd; + + list_destroy(&m->parameters, (dtor_cb_t) model_parameter_destroy, true); + list_destroy(&m->infos, (dtor_cb_t) model_info_destroy, true); + + if (m->xsg.map != NULL) + free(m->xsg.map); + + return 0; +} + +void model_dump(struct fpga_ip *c) +{ + struct model *m = (struct model *) c->_vd; + + const char *param_type[] = { "UFix", "Fix", "Float", "Boolean" }; + const char *parameter_dirs[] = { "In", "Out", "In/Out" }; + + { INDENT + info("Parameters:"); + for (size_t i = 0; i < list_length(&m->parameters); i++) { INDENT + struct model_parameter *p = (struct model_parameter *) list_at(&m->parameters, i); + + if (p->direction == MODEL_PARAMETER_IN) + info("%#jx: %s (%s) = %.3f %s %u", + p->offset, + p->name, + parameter_dirs[p->direction], + p->default_value.flt, + param_type[p->type], + p->binpt + ); + else if (p->direction == MODEL_PARAMETER_OUT) + info("%#jx: %s (%s)", + p->offset, + p->name, + parameter_dirs[p->direction] + ); + } + + info("Infos:"); + for (size_t j = 0; j < list_length(&m->infos); j++) { INDENT + struct model_info *i = (struct model_info *) list_at(&m->infos, j); + + info("%s: %s", i->field, i->value); + } + } +} + +int model_parameter_read(struct model_parameter *p, double *v) +{ + struct fpga_ip *c = p->ip; + + union model_parameter_value *ptr = (union model_parameter_value *) (c->card->map + c->baseaddr + p->offset); + + switch (p->type) { + case MODEL_PARAMETER_TYPE_UFIX: + *v = (double) ptr->ufix / (1 << p->binpt); + break; + + case MODEL_PARAMETER_TYPE_FIX: + *v = (double) ptr->fix / (1 << p->binpt); + break; + + case MODEL_PARAMETER_TYPE_FLOAT: + *v = (double) ptr->flt; + break; + + case MODEL_PARAMETER_TYPE_BOOLEAN: + *v = (double) ptr->ufix ? 1 : 0; + } + + return 0; +} + +int model_parameter_write(struct model_parameter *p, double v) +{ + struct fpga_ip *c = p->ip; + + union model_parameter_value *ptr = (union model_parameter_value *) (c->card->map + c->baseaddr + p->offset); + + switch (p->type) { + case MODEL_PARAMETER_TYPE_UFIX: + ptr->ufix = (uint32_t) (v * (1 << p->binpt)); + break; + + case MODEL_PARAMETER_TYPE_FIX: + ptr->fix = (int32_t) (v * (1 << p->binpt)); + break; + + case MODEL_PARAMETER_TYPE_FLOAT: + ptr->flt = (float) v; + break; + + case MODEL_PARAMETER_TYPE_BOOLEAN: + ptr->bol = (bool) v; + break; + } + + return 0; +} + +void model_parameter_add(struct fpga_ip *c, const char *name, enum model_parameter_direction dir, enum model_parameter_type type) +{ + struct model *m = (struct model *) c->_vd; + struct model_parameter *p = (struct model_parameter *) alloc(sizeof(struct model_parameter)); + + p->name = strdup(name); + p->type = type; + p->direction = dir; + + list_push(&m->parameters, p); +} + +int model_parameter_remove(struct fpga_ip *c, const char *name) +{ + struct model *m = (struct model *) c->_vd; + struct model_parameter *p; + + p = list_lookup(&m->parameters, name); + if (!p) + return -1; + + list_remove(&m->parameters, p); + + return 0; +} + +int model_parameter_update(struct model_parameter *p, struct model_parameter *u) +{ + if (strcmp(p->name, u->name) != 0) + return -1; + + p->direction = u->direction; + p->type = u->type; + p->binpt = u->binpt; + p->offset = u->offset; + + return 0; +} + +static struct plugin p_hls = { + .name = "Xilinx High Level Synthesis (HLS) model", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { NULL, "hls", NULL, NULL }, + .type = FPGA_IP_TYPE_MODEL, + .init = model_init, + .destroy = model_destroy, + .dump = model_dump, + .parse = model_parse + } +}; + +REGISTER_PLUGIN(&p_hls) + +static struct plugin p_sysgen = { + .name = "Xilinx System Generator for DSP (XSG) model", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { NULL, "sysgen", NULL, NULL }, + .type = FPGA_IP_TYPE_MODEL, + .init = model_init, + .destroy = model_destroy, + .dump = model_dump, + .parse = model_parse, + .size = sizeof(struct model) + } +}; + +REGISTER_PLUGIN(&p_sysgen) diff --git a/fpga/lib/ips/rtds_axis.c b/fpga/lib/ips/rtds_axis.c new file mode 100644 index 000000000..5b9137759 --- /dev/null +++ b/fpga/lib/ips/rtds_axis.c @@ -0,0 +1,80 @@ +/** Driver for AXI Stream wrapper around RTDS_InterfaceModule (rtds_axis ) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "log.h" +#include "utils.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/rtds_axis.h" + +void rtds_axis_dump(struct fpga_ip *c) +{ + /* Check RTDS_Axis registers */ + uint32_t *regs = (uint32_t *) (c->card->map + c->baseaddr); + + uint32_t sr = regs[RTDS_AXIS_SR_OFFSET/4]; + info("RTDS AXI Stream interface details"); + { INDENT + info("RTDS status: %#08x", sr); + { INDENT + info("Card detected: %s", sr & RTDS_AXIS_SR_CARDDETECTED ? CLR_GRN("yes") : CLR_RED("no")); + info("Link up: %s", sr & RTDS_AXIS_SR_LINKUP ? CLR_GRN("yes") : CLR_RED("no")); + info("TX queue full: %s", sr & RTDS_AXIS_SR_TX_FULL ? CLR_RED("yes") : CLR_GRN("no")); + info("TX in progress: %s", sr & RTDS_AXIS_SR_TX_INPROGRESS ? CLR_YEL("yes") : "no"); + info("Case running: %s", sr & RTDS_AXIS_SR_CASE_RUNNING ? CLR_GRN("yes") : CLR_RED("no")); + } + + info("RTDS control: %#08x", regs[RTDS_AXIS_CR_OFFSET/4]); + info("RTDS IRQ coalesc: %u", regs[RTDS_AXIS_COALESC_OFFSET/4]); + info("RTDS IRQ version: %#06x", regs[RTDS_AXIS_VERSION_OFFSET/4]); + info("RTDS IRQ multi-rate: %u", regs[RTDS_AXIS_MRATE/4]); + + info("RTDS timestep counter: %lu", (uint64_t) regs[RTDS_AXIS_TSCNT_LOW_OFFSET/4] | (uint64_t) regs[RTDS_AXIS_TSCNT_HIGH_OFFSET/4] << 32); + info("RTDS timestep period: %.3f uS", rtds_axis_dt(c) * 1e6); + } +} + +double rtds_axis_dt(struct fpga_ip *c) +{ + uint32_t *regs = (uint32_t *) (c->card->map + c->baseaddr); + uint16_t dt = regs[RTDS_AXIS_TS_PERIOD_OFFSET/4]; + + return (dt == 0xFFFF) ? -1.0 : (double) dt / RTDS_HZ; +} + +static struct plugin p = { + .name = "RTDS's AXI4-Stream - GTFPGA interface", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "rtds_axis", NULL }, + .type = FPGA_IP_TYPE_INTERFACE, + .dump = rtds_axis_dump, + .size = 0 + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/switch.c b/fpga/lib/ips/switch.c new file mode 100644 index 000000000..73c647be1 --- /dev/null +++ b/fpga/lib/ips/switch.c @@ -0,0 +1,221 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "list.h" +#include "log.h" +#include "log_config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/switch.h" + +int switch_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct sw *sw = (struct sw *) c->_vd; + + XAxis_Switch *xsw = &sw->inst; + + if (c != f->sw) + error("There can be only one AXI4-Stream interconnect per FPGA"); + + + /* Setup AXI-stream switch */ + XAxis_Switch_Config sw_cfg = { + .BaseAddress = (uintptr_t) f->map + c->baseaddr, + .MaxNumMI = sw->num_ports, + .MaxNumSI = sw->num_ports + }; + + ret = XAxisScr_CfgInitialize(xsw, &sw_cfg, (uintptr_t) c->card->map + c->baseaddr); + if (ret != XST_SUCCESS) + return -1; + + /* Disable all masters */ + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortDisableAll(xsw); + XAxisScr_RegUpdateEnable(xsw); + + switch_init_paths(c); + + return 0; +} + +int switch_init_paths(struct fpga_ip *c) +{ + int ret; + struct sw *sw = (struct sw *) c->_vd; + + XAxis_Switch *xsw = &sw->inst; + + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortDisableAll(xsw); + + for (size_t i = 0; i < list_length(&sw->paths); i++) { + struct sw_path *p = (struct sw_path *) list_at(&sw->paths, i); + struct fpga_ip *mi, *si; + + mi = list_lookup(&c->card->ips, p->out); + si = list_lookup(&c->card->ips, p->in); + + if (!mi || !si || mi->port == -1 || si->port == -1) + error("Invalid path configuration for FPGA"); + + ret = switch_connect(c, mi, si); + if (ret) + error("Failed to configure switch"); + } + + XAxisScr_RegUpdateEnable(xsw); + + return 0; +} + +int switch_destroy(struct fpga_ip *c) +{ + struct sw *sw = (struct sw *) c->_vd; + + list_destroy(&sw->paths, NULL, true); + + return 0; +} + +int switch_parse(struct fpga_ip *c, json_t *cfg) +{ + struct sw *sw = (struct sw *) c->_vd; + + int ret; + size_t index; + json_error_t err; + json_t *json_path, *json_paths = NULL; + + list_init(&sw->paths); + + ret = json_unpack_ex(cfg, &err, 0, "{ s: i, s?: o }", + "num_ports", &sw->num_ports, + "paths", &json_paths + ); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + if (!json_paths) + return 0; /* no switch config available */ + + if (!json_is_array(json_paths)) + error("Setting 'paths' of FPGA IP '%s' should be an array of JSON objects", c->name); + + json_array_foreach(json_paths, index, json_path) { + struct sw_path *p = (struct sw_path *) alloc(sizeof(struct sw_path)); + int reverse = 0; + + ret = json_unpack_ex(json_path, &err, 0, "{ s?: b, s: s, s: s }", + "reverse", &reverse, + "in", &p->in, + "out", &p->out + ); + if (ret) + jerror(&err, "Failed to parse path %zu of FPGA IP '%s'", index, c->name); + + list_push(&sw->paths, p); + + if (reverse) { + struct sw_path *r = memdup(p, sizeof(struct sw_path)); + + r->in = p->out; + r->out = p->in; + + list_push(&sw->paths, r); + } + } + + return 0; +} + +int switch_connect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si) +{ + struct sw *sw = (struct sw *) c->_vd; + XAxis_Switch *xsw = &sw->inst; + + uint32_t mux, port; + + /* Check if theres already something connected */ + for (int i = 0; i < sw->num_ports; i++) { + mux = XAxisScr_ReadReg(xsw->Config.BaseAddress, XAXIS_SCR_MI_MUX_START_OFFSET + i * 4); + if (!(mux & XAXIS_SCR_MI_X_DISABLE_MASK)) { + port = mux & ~XAXIS_SCR_MI_X_DISABLE_MASK; + + if (port == si->port) { + warn("Switch: Slave port %s (%u) has been connected already to port %u. Disconnecting...", si->name, si->port, i); + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortDisable(xsw, i); + XAxisScr_RegUpdateEnable(xsw); + } + } + } + + /* Reconfigure switch */ + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortEnable(xsw, mi->port, si->port); + XAxisScr_RegUpdateEnable(xsw); + + /* Reset IPs */ + /*ip_reset(mi); + ip_reset(si);*/ + + debug(8, "FPGA: Switch connected %s (%u) to %s (%u)", mi->name, mi->port, si->name, si->port); + + return 0; +} + +int switch_disconnect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si) +{ + struct sw *sw = (struct sw *) c->_vd; + XAxis_Switch *xsw = &sw->inst; + + if (!XAxisScr_IsMiPortEnabled(xsw, mi->port, si->port)) + return -1; + + XAxisScr_MiPortDisable(xsw, mi->port); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's AXI4-Stream switch", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axis_interconnect", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = switch_start, + .destroy = switch_destroy, + .parse = switch_parse, + .size = sizeof(struct sw) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/timer.c b/fpga/lib/ips/timer.c new file mode 100644 index 000000000..57eb1a67a --- /dev/null +++ b/fpga/lib/ips/timer.c @@ -0,0 +1,61 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/timer.h" + +int timer_start(struct fpga_ip *c) +{ + struct fpga_card *f = c->card; + struct timer *tmr = (struct timer *) c->_vd; + + XTmrCtr *xtmr = &tmr->inst; + XTmrCtr_Config xtmr_cfg = { + .BaseAddress = (uintptr_t) f->map + c->baseaddr, + .SysClockFreqHz = FPGA_AXI_HZ + }; + + XTmrCtr_CfgInitialize(xtmr, &xtmr_cfg, (uintptr_t) f->map + c->baseaddr); + XTmrCtr_InitHw(xtmr); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's programmable timer / counter", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_timer", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = timer_start, + .size = sizeof(struct timer) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/kernel/kernel.c b/fpga/lib/kernel/kernel.c new file mode 100644 index 000000000..78a60270a --- /dev/null +++ b/fpga/lib/kernel/kernel.c @@ -0,0 +1,282 @@ +/** Linux kernel related functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "utils.h" +#include "config.h" +#include "kernel/kernel.h" + +int kernel_get_cacheline_size() +{ +#ifdef __linux__ + return sysconf(_SC_LEVEL1_ICACHE_LINESIZE); +#else + return 64; /** @todo fixme */ +#endif +} + +#ifdef __linux__ + +int kernel_module_set_param(const char *module, const char *param, const char *value) +{ + FILE *f; + char fn[256]; + + snprintf(fn, sizeof(fn), "%s/module/%s/parameters/%s", SYSFS_PATH, module, param); + f = fopen(fn, "w"); + if (!f) + serror("Failed set parameter %s for kernel module %s to %s", module, param, value); + + debug(LOG_KERNEL | 5, "Set parameter %s of kernel module %s to %s", module, param, value); + fprintf(f, "%s", value); + fclose(f); + + return 0; +} + +int kernel_module_load(const char *module) +{ + int ret; + + ret = kernel_module_loaded(module); + if (!ret) { + debug(LOG_KERNEL | 5, "Kernel module %s already loaded...", module); + return 0; + } + + pid_t pid = fork(); + switch (pid) { + case -1: // error + return -1; + + case 0: // child + execlp("modprobe", "modprobe", module, (char *) 0); + exit(EXIT_FAILURE); // exec never returns + + default: + wait(&ret); + + return kernel_module_loaded(module); + } +} + +int kernel_module_loaded(const char *module) +{ + FILE *f; + int ret = -1; + char *line = NULL; + size_t len = 0; + + f = fopen(PROCFS_PATH "/modules", "r"); + if (!f) + return -1; + + while (getline(&line, &len, f) >= 0) { + if (strstr(line, module) == line) { + ret = 0; + break; + } + } + + free(line); + fclose(f); + + return ret; +} + +int kernel_get_version(struct version *v) +{ + struct utsname uts; + + if (uname(&uts) < 0) + return -1; + + if (version_parse(uts.release, v)) + return -1; + + return 0; +} + +int kernel_get_cmdline_param(const char *param, char *buf, size_t len) +{ + int ret; + char cmdline[512]; + + FILE *f = fopen(PROCFS_PATH "/cmdline", "r"); + if (!f) + return -1; + + if (!fgets(cmdline, sizeof(cmdline), f)) + goto out; + + char *tok = strtok(cmdline, " \t"); + do { + char key[128], value[128]; + + ret = sscanf(tok, "%127[^=]=%127s", key, value); + if (ret >= 1) { + if (ret >= 2) + debug(30, "Found kernel param: %s=%s", key, value); + else + debug(30, "Found kernel param: %s", key); + + if (strcmp(param, key) == 0) { + if (ret >= 2 && buf) + strncpy(buf, value, len); + + return 0; /* found */ + } + } + } while((tok = strtok(NULL, " \t"))); + +out: + fclose(f); + + return -1; /* not found or error */ +} + +int kernel_get_page_size() +{ + return sysconf(_SC_PAGESIZE); +} + +/* There is no sysconf interface to get the hugepage size */ +int kernel_get_hugepage_size() +{ + char *key, *value, *unit, *line = NULL; + int sz = -1; + size_t len = 0; + FILE *f; + + f = fopen(PROCFS_PATH "/meminfo", "r"); + if (!f) + return -1; + + while (getline(&line, &len, f) != -1) { + key = strtok(line, ": "); + value = strtok(NULL, " "); + unit = strtok(NULL, "\n"); + + if (!strcmp(key, "Hugepagesize") && !strcmp(unit, "kB")) { + sz = strtoul(value, NULL, 10) * 1024; + break; + } + } + + free(line); + fclose(f); + + return sz; +} + +int kernel_get_nr_hugepages() +{ + FILE *f; + int nr, ret; + + f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "r"); + if (!f) + serror("Failed to open %s", PROCFS_PATH "/sys/vm/nr_hugepages"); + + ret = fscanf(f, "%d", &nr); + if (ret != 1) + nr = -1; + + fclose(f); + + return nr; +} + +int kernel_set_nr_hugepages(int nr) +{ + FILE *f; + + f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "w"); + if (!f) + serror("Failed to open %s", PROCFS_PATH "/sys/vm/nr_hugepages"); + + fprintf(f, "%d\n", nr); + fclose(f); + + return 0; +} + +#if 0 +int kernel_has_cap(cap_value_t cap) +{ + int ret; + + cap_t caps; + cap_flag_value_t value; + + caps = cap_get_proc(); + if (caps == NULL) + return -1; + + ret = cap_get_proc(caps); + if (ret == -1) + return -1; + + ret = cap_get_flag(caps, cap, CAP_EFFECTIVE, &value); + if (ret == -1) + return -1; + + ret = cap_free(caps); + if (ret) + return -1; + + return value == CAP_SET ? 0 : -1; +} +#endif + +int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old) +{ + char fn[64]; + FILE *f; + int ret = 0; + + snprintf(fn, sizeof(fn), "/proc/irq/%u/smp_affinity", irq); + + f = fopen(fn, "w+"); + if (!f) + return -1; /* IRQ does not exist */ + + if (old) + ret = fscanf(f, "%jx", old); + + fprintf(f, "%jx", new); + fclose(f); + + return ret; +} + +#endif /* __linux__ */ diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c new file mode 100644 index 000000000..e251f00c8 --- /dev/null +++ b/fpga/lib/kernel/pci.c @@ -0,0 +1,294 @@ +/** Linux PCI helpers + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" + +#include "kernel/pci.h" +#include "config.h" + +int pci_init(struct pci *p) +{ + struct dirent *e; + DIR *dp; + FILE *f; + char path[PATH_MAX]; + int ret; + + snprintf(path, sizeof(path), "%s/bus/pci/devices", SYSFS_PATH); + + dp = opendir(path); + if (dp == NULL) { + serror("Failed to detect PCI devices"); + return -1; + } + + while ((e = readdir(dp))) { + struct pci_device *d = (struct pci_device *) alloc(sizeof(struct pci_device)); + + struct { const char *s; int *p; } map[] = { + { "vendor", &d->id.vendor }, + { "device", &d->id.device } + }; + + /* Read vendor & device id */ + for (int i = 0; i < 2; i++) { + snprintf(path, sizeof(path), "%s/bus/pci/devices/%s/%s", SYSFS_PATH, e->d_name, map[i].s); + + f = fopen(path, "r"); + if (!f) + serror("Failed to open '%s'", path); + + ret = fscanf(f, "%x", map[i].p); + if (ret != 1) + error("Failed to parse %s ID from: %s", map[i].s, path); + + fclose(f); + } + + /* Get slot id */ + ret = sscanf(e->d_name, "%4x:%2x:%2x.%u", &d->slot.domain, &d->slot.bus, &d->slot.device, &d->slot.function); + if (ret != 4) + error("Failed to parse PCI slot number: %s", e->d_name); + + list_push(&p->devices, d); + } + + closedir(dp); + + return 0; +} + +int pci_destroy(struct pci *p) +{ + list_destroy(&p->devices, NULL, true); + + return 0; +} + +int pci_device_parse_slot(struct pci_device *f, const char *s, const char **error) +{ + char *str = strdup(s); + char *colon = strrchr(str, ':'); + char *dot = strchr((colon ? colon + 1 : str), '.'); + char *mid = str; + char *e, *bus, *colon2; + + if (colon) { + *colon++ = 0; + mid = colon; + + colon2 = strchr(str, ':'); + if (colon2) { + *colon2++ = 0; + bus = colon2; + + if (str[0] && strcmp(str, "*")) { + long int x = strtol(str, &e, 16); + if ((e && *e) || (x < 0 || x > 0x7fffffff)) { + *error = "Invalid domain number"; + goto fail; + } + + f->slot.domain = x; + } + } + else + bus = str; + + if (bus[0] && strcmp(bus, "*")) { + long int x = strtol(bus, &e, 16); + if ((e && *e) || (x < 0 || x > 0xff)) { + *error = "Invalid bus number"; + goto fail; + } + + f->slot.bus = x; + } + } + + if (dot) + *dot++ = 0; + + if (mid[0] && strcmp(mid, "*")) { + long int x = strtol(mid, &e, 16); + + if ((e && *e) || (x < 0 || x > 0x1f)) { + *error = "Invalid slot number"; + goto fail; + } + + f->slot.device = x; + } + + if (dot && dot[0] && strcmp(dot, "*")) { + long int x = strtol(dot, &e, 16); + + if ((e && *e) || (x < 0 || x > 7)) { + *error = "Invalid function number"; + goto fail; + } + + f->slot.function = x; + } + + free(str); + return 0; + +fail: + free(str); + return -1; +} + +/* ID filter syntax: [vendor]:[device][:class] */ +int pci_device_parse_id(struct pci_device *f, const char *str, const char **error) +{ + char *s, *c, *e; + + if (!*str) + return 0; + + s = strchr(str, ':'); + if (!s) { + *error = "':' expected"; + goto fail; + } + + *s++ = 0; + if (str[0] && strcmp(str, "*")) { + long int x = strtol(str, &e, 16); + + if ((e && *e) || (x < 0 || x > 0xffff)) { + *error = "Invalid vendor ID"; + goto fail; + } + + f->id.vendor = x; + } + + c = strchr(s, ':'); + if (c) + *c++ = 0; + + if (s[0] && strcmp(s, "*")) { + long int x = strtol(s, &e, 16); + if ((e && *e) || (x < 0 || x > 0xffff)) { + *error = "Invalid device ID"; + goto fail; + } + + f->id.device = x; + } + + if (c && c[0] && strcmp(s, "*")) { + long int x = strtol(c, &e, 16); + + if ((e && *e) || (x < 0 || x > 0xffff)) { + *error = "Invalid class code"; + goto fail; + } + + f->id.class = x; + } + + return 0; + +fail: + return -1; +} + +int pci_device_compare(const struct pci_device *d, const struct pci_device *f) +{ + if ((f->slot.domain >= 0 && f->slot.domain != d->slot.domain) || + (f->slot.bus >= 0 && f->slot.bus != d->slot.bus) || + (f->slot.device >= 0 && f->slot.device != d->slot.device) || + (f->slot.function >= 0 && f->slot.function != d->slot.function)) + return 0; + + if (f->id.device >= 0 || f->id.vendor >= 0) { + if ((f->id.device >= 0 && f->id.device != d->id.device) || (f->id.vendor >= 0 && f->id.vendor != d->id.vendor)) + return 0; + } + + if (f->id.class >= 0) { + if (f->id.class != d->id.class) + return 0; + } + + return 1; +} + +struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *f) +{ + return list_search(&p->devices, (cmp_cb_t) pci_device_compare, (void *) f); +} + +int pci_attach_driver(struct pci_device *d, const char *driver) +{ + FILE *f; + char fn[256]; + + /* Add new ID to driver */ + snprintf(fn, sizeof(fn), "%s/bus/pci/drivers/%s/new_id", SYSFS_PATH, driver); + f = fopen(fn, "w"); + if (!f) + serror("Failed to add PCI id to %s driver (%s)", driver, fn); + + debug(5, "Adding ID to %s module: %04x %04x", driver, d->id.vendor, d->id.device); + fprintf(f, "%04x %04x", d->id.vendor, d->id.device); + fclose(f); + + /* Bind to driver */ + snprintf(fn, sizeof(fn), "%s/bus/pci/drivers/%s/bind", SYSFS_PATH, driver); + f = fopen(fn, "w"); + if (!f) + serror("Failed to bind PCI device to %s driver (%s)", driver, fn); + + debug(5, "Bind device to %s driver", driver); + fprintf(f, "%04x:%02x:%02x.%x\n", d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); + fclose(f); + + return 0; +} + +int pci_get_iommu_group(struct pci_device *d) +{ + int ret; + char *group, link[1024], sysfs[1024]; + + snprintf(sysfs, sizeof(sysfs), "%s/bus/pci/devices/%04x:%02x:%02x.%x/iommu_group", SYSFS_PATH, + d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); + + ret = readlink(sysfs, link, sizeof(link)); + if (ret < 0) + return -1; + + group = basename(link); + + return atoi(group); +} diff --git a/fpga/lib/kernel/vfio.c b/fpga/lib/kernel/vfio.c new file mode 100644 index 000000000..018f42dcf --- /dev/null +++ b/fpga/lib/kernel/vfio.c @@ -0,0 +1,641 @@ +/** Virtual Function IO wrapper around kernel API + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include + +#include +#include + +#include "utils.h" +#include "log.h" + +#include "config.h" + +#include "kernel/kernel.h" +#include "kernel/vfio.h" +#include "kernel/pci.h" + +static const char *vfio_pci_region_names[] = { + "PCI_BAR0", // VFIO_PCI_BAR0_REGION_INDEX, + "PCI_BAR1", // VFIO_PCI_BAR1_REGION_INDEX, + "PCI_BAR2", // VFIO_PCI_BAR2_REGION_INDEX, + "PCI_BAR3", // VFIO_PCI_BAR3_REGION_INDEX, + "PCI_BAR4", // VFIO_PCI_BAR4_REGION_INDEX, + "PCI_BAR5", // VFIO_PCI_BAR5_REGION_INDEX, + "PCI_ROM", // VFIO_PCI_ROM_REGION_INDEX, + "PCI_CONFIG", // VFIO_PCI_CONFIG_REGION_INDEX, + "PCI_VGA" // VFIO_PCI_INTX_IRQ_INDEX, +}; + +static const char *vfio_pci_irq_names[] = { + "PCI_INTX", // VFIO_PCI_INTX_IRQ_INDEX, + "PCI_MSI", // VFIO_PCI_MSI_IRQ_INDEX, + "PCI_MSIX", // VFIO_PCI_MSIX_IRQ_INDEX, + "PCI_ERR", // VFIO_PCI_ERR_IRQ_INDEX, + "PCI_REQ" // VFIO_PCI_REQ_IRQ_INDEX, +}; + +/* Helpers */ +int vfio_get_iommu_name(int index, char *buf, size_t len) +{ + FILE *f; + char fn[256]; + + snprintf(fn, sizeof(fn), "/sys/kernel/iommu_groups/%d/name", index); + + f = fopen(fn, "r"); + if (!f) + return -1; + + int ret = fgets(buf, len, f) == buf ? 0 : -1; + + /* Remove trailing newline */ + char *c = strrchr(buf, '\n'); + if (c) + *c = 0; + + fclose(f); + + return ret; +} + +/* Destructors */ +int vfio_destroy(struct vfio_container *v) +{ + int ret; + + /* Release memory and close fds */ + list_destroy(&v->groups, (dtor_cb_t) vfio_group_destroy, true); + + /* Close container */ + ret = close(v->fd); + if (ret < 0) + return -1; + + debug(5, "VFIO: closed container: fd=%d", v->fd); + + return 0; +} + +int vfio_group_destroy(struct vfio_group *g) +{ + int ret; + + list_destroy(&g->devices, (dtor_cb_t) vfio_device_destroy, false); + + ret = ioctl(g->fd, VFIO_GROUP_UNSET_CONTAINER); + if (ret) + return ret; + + debug(5, "VFIO: released group from container: group=%u", g->index); + + ret = close(g->fd); + if (ret) + return ret; + + debug(5, "VFIO: closed group: group=%u, fd=%d", g->index, g->fd); + + return 0; +} + +int vfio_device_destroy(struct vfio_device *d) +{ + int ret; + + for (int i = 0; i < d->info.num_regions; i++) + vfio_unmap_region(d, i); + + ret = close(d->fd); + if (ret) + return ret; + + debug(5, "VFIO: closed device: name=%s, fd=%d", d->name, d->fd); + + free(d->mappings); + free(d->name); + + return 0; +} + +/* Constructors */ +int vfio_init(struct vfio_container *v) +{ + int ret; + + /* Initialize datastructures */ + memset(v, 0, sizeof(*v)); + + list_init(&v->groups); + + /* Load VFIO kernel module */ + if (kernel_module_load("vfio")) + error("Failed to load kernel module: %s", "vfio"); + + /* Open VFIO API */ + v->fd = open(VFIO_DEV("vfio"), O_RDWR); + if (v->fd < 0) + error("Failed to open VFIO container"); + + /* Check VFIO API version */ + v->version = ioctl(v->fd, VFIO_GET_API_VERSION); + if (v->version < 0 || v->version != VFIO_API_VERSION) + error("Failed to get VFIO version"); + + /* Check available VFIO extensions (IOMMU types) */ + v->extensions = 0; + for (int i = 1; i < VFIO_DMA_CC_IOMMU; i++) { + ret = ioctl(v->fd, VFIO_CHECK_EXTENSION, i); + if (ret < 0) + error("Failed to get VFIO extensions"); + else if (ret > 0) + v->extensions |= (1 << i); + } + + return 0; +} + +int vfio_group_attach(struct vfio_group *g, struct vfio_container *c, int index) +{ + int ret; + char buf[128]; + + g->index = index; + g->container = c; + + list_init(&g->devices); + + /* Open group fd */ + snprintf(buf, sizeof(buf), VFIO_DEV("%u"), g->index); + g->fd = open(buf, O_RDWR); + if (g->fd < 0) + serror("Failed to open VFIO group: %u", g->index); + + /* Claim group ownership */ + ret = ioctl(g->fd, VFIO_GROUP_SET_CONTAINER, &c->fd); + if (ret < 0) + serror("Failed to attach VFIO group to container"); + + /* Set IOMMU type */ + ret = ioctl(c->fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); + if (ret < 0) + serror("Failed to set IOMMU type of container"); + + /* Check group viability and features */ + g->status.argsz = sizeof(g->status); + + ret = ioctl(g->fd, VFIO_GROUP_GET_STATUS, &g->status); + if (ret < 0) + serror("Failed to get VFIO group status"); + + if (!(g->status.flags & VFIO_GROUP_FLAGS_VIABLE)) + error("VFIO group is not available: bind all devices to the VFIO driver!"); + + list_push(&c->groups, g); + + return 0; +} + +int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_device *pdev) +{ + char name[32]; + int ret; + + /* Load PCI bus driver for VFIO */ + if (kernel_module_load("vfio_pci")) + error("Failed to load kernel driver: %s", "vfio_pci"); + + /* Bind PCI card to vfio-pci driver*/ + ret = pci_attach_driver(pdev, "vfio-pci"); + if (ret) + error("Failed to attach device to driver"); + + /* Get IOMMU group of device */ + int index = pci_get_iommu_group(pdev); + if (index < 0) + error("Failed to get IOMMU group of device"); + + /* VFIO device name consists of PCI BDF */ + snprintf(name, sizeof(name), "%04x:%02x:%02x.%x", pdev->slot.domain, pdev->slot.bus, pdev->slot.device, pdev->slot.function); + + ret = vfio_device_attach(d, c, name, index); + if (ret < 0) + return ret; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) { + vfio_device_destroy(d); + return -1; + } + + d->pci_device = pdev; + + return 0; +} + +int vfio_device_attach(struct vfio_device *d, struct vfio_container *c, const char *name, int index) +{ + int ret; + struct vfio_group *g = NULL; + + /* Check if group already exists */ + for (size_t i = 0; i < list_length(&c->groups); i++) { + struct vfio_group *h = (struct vfio_group *) list_at(&c->groups, i); + + if (h->index == index) + g = h; + } + + if (!g) { + g = alloc(sizeof(struct vfio_group)); + + /* Aquire group ownership */ + ret = vfio_group_attach(g, c, index); + if (ret) + error("Failed to attach to IOMMU group: %u", index); + + info("Attached new group %u to VFIO container", g->index); + } + + d->group = g; + d->name = strdup(name); + + /* Open device fd */ + d->fd = ioctl(g->fd, VFIO_GROUP_GET_DEVICE_FD, d->name); + if (d->fd < 0) + serror("Failed to open VFIO device: %s", d->name); + + /* Get device info */ + d->info.argsz = sizeof(d->info); + + ret = ioctl(d->fd, VFIO_DEVICE_GET_INFO, &d->info); + if (ret < 0) + serror("Failed to get VFIO device info for: %s", d->name); + + d->irqs = alloc(d->info.num_irqs * sizeof(struct vfio_irq_info)); + d->regions = alloc(d->info.num_regions * sizeof(struct vfio_region_info)); + d->mappings = alloc(d->info.num_regions * sizeof(void *)); + + /* Get device regions */ + for (int i = 0; i < d->info.num_regions && i < 8; i++) { + struct vfio_region_info *region = &d->regions[i]; + + region->argsz = sizeof(*region); + region->index = i; + + ret = ioctl(d->fd, VFIO_DEVICE_GET_REGION_INFO, region); + if (ret < 0) + serror("Failed to get regions of VFIO device: %s", d->name); + } + + /* Get device irqs */ + for (int i = 0; i < d->info.num_irqs; i++) { + struct vfio_irq_info *irq = &d->irqs[i]; + + irq->argsz = sizeof(*irq); + irq->index = i; + + ret = ioctl(d->fd, VFIO_DEVICE_GET_IRQ_INFO, irq); + if (ret < 0) + serror("Failed to get IRQs of VFIO device: %s", d->name); + } + + list_push(&d->group->devices, d); + + return 0; +} + +int vfio_pci_reset(struct vfio_device *d) +{ + int ret; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + size_t reset_infolen = sizeof(struct vfio_pci_hot_reset_info) + sizeof(struct vfio_pci_dependent_device) * 64; + size_t resetlen = sizeof(struct vfio_pci_hot_reset) + sizeof(int32_t) * 1; + + struct vfio_pci_hot_reset_info *reset_info = (struct vfio_pci_hot_reset_info *) alloc(reset_infolen); + struct vfio_pci_hot_reset *reset = (struct vfio_pci_hot_reset *) alloc(resetlen); + + reset_info->argsz = reset_infolen; + reset->argsz = resetlen; + + ret = ioctl(d->fd, VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, reset_info); + if (ret) + return ret; + + debug(5, "VFIO: dependent devices for hot-reset:"); + for (int i = 0; i < reset_info->count; i++) { INDENT + struct vfio_pci_dependent_device *dd = &reset_info->devices[i]; + debug(5, "%04x:%02x:%02x.%01x: iommu_group=%u", dd->segment, dd->bus, PCI_SLOT(dd->devfn), PCI_FUNC(dd->devfn), dd->group_id); + + if (dd->group_id != d->group->index) + return -3; + } + + reset->count = 1; + reset->group_fds[0] = d->group->fd; + + ret = ioctl(d->fd, VFIO_DEVICE_PCI_HOT_RESET, reset); + + free(reset_info); + + return ret; +} + +int vfio_pci_msi_find(struct vfio_device *d, int nos[32]) +{ + int ret, idx, irq; + char *end, *col, *last, line[1024], name[13]; + FILE *f; + + f = fopen("/proc/interrupts", "r"); + if (!f) + return -1; + + for (int i = 0; i < 32; i++) + nos[i] = -1; + + /* For each line in /proc/interruipts */ + while (fgets(line, sizeof(line), f)) { + col = strtok(line, " "); + + /* IRQ number is in first column */ + irq = strtol(col, &end, 10); + if (col == end) + continue; + + /* Find last column of line */ + do { + last = col; + } while ((col = strtok(NULL, " "))); + + + ret = sscanf(last, "vfio-msi[%u](%12[0-9:])", &idx, name); + if (ret == 2) { + if (strstr(d->name, name) == d->name) + nos[idx] = irq; + } + } + + fclose(f); + + return 0; +} + +int vfio_pci_msi_deinit(struct vfio_device *d, int efds[32]) +{ + int ret, irq_setlen, irq_count = d->irqs[VFIO_PCI_MSI_IRQ_INDEX].count; + struct vfio_irq_set *irq_set; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + irq_setlen = sizeof(struct vfio_irq_set) + sizeof(int) * irq_count; + irq_set = alloc(irq_setlen); + + irq_set->argsz = irq_setlen; + irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irq_set->index = VFIO_PCI_MSI_IRQ_INDEX; + irq_set->count = irq_count; + irq_set->start = 0; + + for (int i = 0; i < irq_count; i++) { + close(efds[i]); + efds[i] = -1; + } + + memcpy(irq_set->data, efds, sizeof(int) * irq_count); + + ret = ioctl(d->fd, VFIO_DEVICE_SET_IRQS, irq_set); + if (ret) + return -4; + + free(irq_set); + + return irq_count; +} + +int vfio_pci_msi_init(struct vfio_device *d, int efds[32]) +{ + int ret, irq_setlen, irq_count = d->irqs[VFIO_PCI_MSI_IRQ_INDEX].count; + struct vfio_irq_set *irq_set; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + irq_setlen = sizeof(struct vfio_irq_set) + sizeof(int) * irq_count; + irq_set = alloc(irq_setlen); + + irq_set->argsz = irq_setlen; + irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irq_set->index = VFIO_PCI_MSI_IRQ_INDEX; + irq_set->start = 0; + irq_set->count = irq_count; + + /* Now set the new eventfds */ + for (int i = 0; i < irq_count; i++) { + efds[i] = eventfd(0, 0); + if (efds[i] < 0) + return -3; + } + memcpy(irq_set->data, efds, sizeof(int) * irq_count); + + ret = ioctl(d->fd, VFIO_DEVICE_SET_IRQS, irq_set); + if (ret) + return -4; + + free(irq_set); + + return irq_count; +} + +int vfio_pci_enable(struct vfio_device *d) +{ + int ret; + uint32_t reg; + off_t offset = ((off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40) + PCI_COMMAND; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + ret = pread(d->fd, ®, sizeof(reg), offset); + if (ret != sizeof(reg)) + return -1; + + /* Enable memory access and PCI bus mastering which is required for DMA */ + reg |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + + ret = pwrite(d->fd, ®, sizeof(reg), offset); + if (ret != sizeof(reg)) + return -1; + + return 0; +} + +int vfio_device_reset(struct vfio_device *d) +{ + if (d->info.flags & VFIO_DEVICE_FLAGS_RESET) + return ioctl(d->fd, VFIO_DEVICE_RESET); + else + return -1; /* not supported by this device */ +} + +void vfio_dump(struct vfio_container *v) +{ + info("VFIO Version: %u", v->version); + info("VFIO Extensions: %#x", v->extensions); + + for (size_t i = 0; i < list_length(&v->groups); i++) { + struct vfio_group *g = (struct vfio_group *) list_at(&v->groups, i); + + info("VFIO Group %u, viable=%u, container=%d", g->index, + (g->status.flags & VFIO_GROUP_FLAGS_VIABLE) > 0, + (g->status.flags & VFIO_GROUP_FLAGS_CONTAINER_SET) > 0 + ); + + + for (size_t i = 0; i < list_length(&g->devices); i++) { INDENT + struct vfio_device *d = (struct vfio_device *) list_at(&g->devices, i); + + info("Device %s: regions=%u, irqs=%u, flags=%#x", d->name, + d->info.num_regions, + d->info.num_irqs, + d->info.flags + ); + + for (int i = 0; i < d->info.num_regions && i < 8; i++) { INDENT + struct vfio_region_info *region = &d->regions[i]; + + if (region->size > 0) + info("Region %u %s: size=%#llx, offset=%#llx, flags=%u", + region->index, (d->info.flags & VFIO_DEVICE_FLAGS_PCI) ? vfio_pci_region_names[i] : "", + region->size, + region->offset, + region->flags + ); + } + + for (int i = 0; i < d->info.num_irqs; i++) { INDENT + struct vfio_irq_info *irq = &d->irqs[i]; + + if (irq->count > 0) + info("IRQ %u %s: count=%u, flags=%u", + irq->index, (d->info.flags & VFIO_DEVICE_FLAGS_PCI ) ? vfio_pci_irq_names[i] : "", + irq->count, + irq->flags + ); + } + } + } +} + +void * vfio_map_region(struct vfio_device *d, int idx) +{ + struct vfio_region_info *r = &d->regions[idx]; + + if (!(r->flags & VFIO_REGION_INFO_FLAG_MMAP)) + return MAP_FAILED; + + d->mappings[idx] = mmap(NULL, r->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_32BIT, d->fd, r->offset); + + return d->mappings[idx]; +} + +int vfio_unmap_region(struct vfio_device *d, int idx) +{ + int ret; + struct vfio_region_info *r = &d->regions[idx]; + + if (!d->mappings[idx]) + return -1; /* was not mapped */ + + debug(3, "VFIO: unmap region %u from device", idx); + + ret = munmap(d->mappings[idx], r->size); + if (ret) + return -2; + + d->mappings[idx] = NULL; + + return 0; +} + +int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len) +{ + int ret; + + if (len & 0xFFF) { + len += 0x1000; + len &= ~0xFFF; + } + + /* Super stupid allocator */ + if (phys == -1) { + phys = c->iova_next; + c->iova_next += len; + } + + struct vfio_iommu_type1_dma_map dma_map = { + .argsz = sizeof(struct vfio_iommu_type1_dma_map), + .vaddr = virt, + .iova = phys, + .size = len, + .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE + }; + + ret = ioctl(c->fd, VFIO_IOMMU_MAP_DMA, &dma_map); + if (ret) + serror("Failed to create DMA mapping"); + + info("DMA map size=%#llx, iova=%#llx, vaddr=%#llx", dma_map.size, dma_map.iova, dma_map.vaddr); + + return 0; +} + +int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len) +{ + int ret; + + struct vfio_iommu_type1_dma_unmap dma_unmap = { + .argsz = sizeof(struct vfio_iommu_type1_dma_unmap), + .flags = 0, + .iova = phys, + .size = len, + }; + + ret = ioctl(c->fd, VFIO_IOMMU_UNMAP_DMA, &dma_unmap); + if (ret) + serror("Failed to unmap DMA mapping"); + + return 0; +} diff --git a/fpga/lib/list.c b/fpga/lib/list.c new file mode 100644 index 000000000..1550ee327 --- /dev/null +++ b/fpga/lib/list.c @@ -0,0 +1,203 @@ +/** A generic linked list + * + * Linked lists a used for several data structures in the code. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "list.h" +#include "utils.h" + +/* Compare functions */ +static int cmp_lookup(const void *a, const void *b) { + const struct { + char *name; + } *obj = a; + + return strcmp(obj->name, b); +} + +static int cmp_contains(const void *a, const void *b) { + return a == b ? 0 : 1; +} + +static int cmp_sort(const void *a, const void *b, void *thunk) { + cmp_cb_t cmp = (cmp_cb_t) thunk; + + return cmp(*(void **) a, *(void **) b); +} + +int list_init(struct list *l) +{ + assert(l->state == STATE_DESTROYED); + + pthread_mutex_init(&l->lock, NULL); + + l->length = 0; + l->capacity = 0; + l->array = NULL; + + l->state = STATE_INITIALIZED; + + return 0; +} + +int list_destroy(struct list *l, dtor_cb_t destructor, bool release) +{ + pthread_mutex_lock(&l->lock); + + assert(l->state != STATE_DESTROYED); + + for (size_t i = 0; i < list_length(l); i++) { + void *p = list_at(l, i); + + if (destructor) + destructor(p); + if (release) + free(p); + } + + free(l->array); + + l->array = NULL; + + l->length = -1; + l->capacity = 0; + + pthread_mutex_unlock(&l->lock); + pthread_mutex_destroy(&l->lock); + + l->state = STATE_DESTROYED; + + return 0; +} + +void list_push(struct list *l, void *p) +{ + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + /* Resize array if out of capacity */ + if (l->length >= l->capacity) { + l->capacity += LIST_CHUNKSIZE; + l->array = realloc(l->array, l->capacity * sizeof(void *)); + } + + l->array[l->length] = p; + l->length++; + + pthread_mutex_unlock(&l->lock); +} + +void list_remove(struct list *l, void *p) +{ + int removed = 0; + + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + for (size_t i = 0; i < list_length(l); i++) { + if (l->array[i] == p) + removed++; + else + l->array[i - removed] = l->array[i]; + } + + l->length -= removed; + + pthread_mutex_unlock(&l->lock); +} + +void * list_lookup(struct list *l, const char *name) +{ + return list_search(l, cmp_lookup, (void *) name); +} + +int list_contains(struct list *l, void *p) +{ + return list_count(l, cmp_contains, p); +} + +int list_count(struct list *l, cmp_cb_t cmp, void *ctx) +{ + int c = 0; + + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + for (size_t i = 0; i < list_length(l); i++) { + void *e = list_at(l, i); + + if (cmp(e, ctx) == 0) + c++; + } + + pthread_mutex_unlock(&l->lock); + + return c; +} + +void * list_search(struct list *l, cmp_cb_t cmp, void *ctx) +{ + void *e; + + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + for (size_t i = 0; i < list_length(l); i++) { + e = list_at(l, i); + if (cmp(e, ctx) == 0) + goto out; + } + + e = NULL; /* not found */ + +out: pthread_mutex_unlock(&l->lock); + + return e; +} + +void list_sort(struct list *l, cmp_cb_t cmp) +{ + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + qsort_r(l->array, l->length, sizeof(void *), cmp_sort, (void *) cmp); + + pthread_mutex_unlock(&l->lock); +} + +int list_set(struct list *l, int index, void *value) +{ + if (index >= l->length) + return -1; + + l->array[index] = value; + + return 0; +} diff --git a/fpga/lib/log.c b/fpga/lib/log.c new file mode 100644 index 000000000..b9af2d1b6 --- /dev/null +++ b/fpga/lib/log.c @@ -0,0 +1,309 @@ +/** Logging and debugging routines + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "log.h" +#include "utils.h" + +#ifdef ENABLE_OPAL_ASYNC +/* Define RTLAB before including OpalPrint.h for messages to be sent + * to the OpalDisplay. Otherwise stdout will be used. */ + #define RTLAB + #include "OpalPrint.h" +#endif + +struct log *global_log; + +/* We register a default log instance */ +__attribute__((constructor)) +void register_default_log() +{ + int ret; + static struct log default_log; + + ret = log_init(&default_log, V, LOG_ALL); + if (ret) + error("Failed to initalize log"); + + ret = log_start(&default_log); + if (ret) + error("Failed to start log"); +} + +/** List of debug facilities as strings */ +static const char *facilities_strs[] = { + "pool", /* LOG_POOL */ + "queue", /* LOG_QUEUE */ + "config", /* LOG_CONFIG */ + "hook", /* LOG_HOOK */ + "path", /* LOG_PATH */ + "node", /* LOG_NODE */ + "mem", /* LOG_MEM */ + "web", /* LOG_WEB */ + "api", /* LOG_API */ + "log", /* LOG_LOG */ + "vfio", /* LOG_VFIO */ + "pci", /* LOG_PCI */ + "xil", /* LOG_XIL */ + "tc", /* LOG_TC */ + "if", /* LOG_IF */ + "advio", /* LOG_ADVIO */ + + /* Node-types */ + "socket", /* LOG_SOCKET */ + "file", /* LOG_FILE */ + "fpga", /* LOG_FPGA */ + "ngsi", /* LOG_NGSI */ + "websocket", /* LOG_WEBSOCKET */ + "opal", /* LOG_OPAL */ +}; + +#ifdef __GNUC__ +/** The current log indention level (per thread!). */ +static __thread int indent = 0; + +int log_indent(int levels) +{ + int old = indent; + indent += levels; + return old; +} + +int log_noindent() +{ + int old = indent; + indent = 0; + return old; +} + +void log_outdent(int *old) +{ + indent = *old; +} +#endif + +static void log_resize(int signal, siginfo_t *sinfo, void *ctx) +{ + int ret; + + ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &global_log->window); + if (ret) + return; + + global_log->width = global_log->window.ws_col - 25; + if (global_log->prefix) + global_log->width -= strlenp(global_log->prefix); + + debug(LOG_LOG | 15, "New terminal size: %dx%x", global_log->window.ws_row, global_log->window.ws_col); +} + +int log_init(struct log *l, int level, long facilitites) +{ + int ret; + + /* Register this log instance globally */ + global_log = l; + + l->level = level; + l->syslog = 0; + l->facilities = facilitites; + l->file = stderr; + l->path = NULL; + + l->prefix = getenv("VILLAS_LOG_PREFIX"); + + /* Register signal handler which is called whenever the + * terminal size changes. */ + if (l->file == stderr) { + struct sigaction sa_resize = { + .sa_flags = SA_SIGINFO, + .sa_sigaction = log_resize + }; + + sigemptyset(&sa_resize.sa_mask); + + ret = sigaction(SIGWINCH, &sa_resize, NULL); + if (ret) + return ret; + + /* Try to get initial window size */ + ioctl(STDERR_FILENO, TIOCGWINSZ, &global_log->window); + } + else { + l->window.ws_col = LOG_WIDTH; + l->window.ws_row = LOG_HEIGHT; + } + + l->width = l->window.ws_col - 25; + if (l->prefix) + l->width -= strlenp(l->prefix); + + l->state = STATE_INITIALIZED; + + return 0; +} + +int log_start(struct log *l) +{ + if (l->path) { + l->file = fopen(l->path, "a+");; + if (!l->file) { + l->file = stderr; + error("Failed to open log file '%s'", l->path); + } + } + else + l->file = stderr; + + l->state = STATE_STARTED; + + if (l->syslog) { + openlog(NULL, LOG_PID, LOG_DAEMON); + } + + debug(LOG_LOG | 5, "Log sub-system started: level=%d, faciltities=%#lx, path=%s", l->level, l->facilities, l->path); + + return 0; +} + +int log_stop(struct log *l) +{ + if (l->state != STATE_STARTED) + return 0; + + if (l->file != stderr && l->file != stdout) { + fclose(l->file); + } + + if (l->syslog) { + closelog(); + } + + l->state = STATE_STOPPED; + + return 0; +} + +int log_destroy(struct log *l) +{ + default_log.epoch = l->epoch; + + global_log = &default_log; + + l->state = STATE_DESTROYED; + + return 0; +} + +int log_set_facility_expression(struct log *l, const char *expression) +{ + bool negate; + char *copy, *token; + long mask = 0, facilities = 0; + + copy = strdup(expression); + token = strtok(copy, ","); + + while (token != NULL) { + if (token[0] == '!') { + token++; + negate = true; + } + else + negate = false; + + /* Check for some classes */ + if (!strcmp(token, "all")) + mask = LOG_ALL; + else if (!strcmp(token, "nodes")) + mask = LOG_NODES; + else if (!strcmp(token, "kernel")) + mask = LOG_KERNEL; + else { + for (int ind = 0; ind < ARRAY_LEN(facilities_strs); ind++) { + if (!strcmp(token, facilities_strs[ind])) { + mask = (1 << (ind+8)); + goto found; + } + } + + error("Invalid log class '%s'", token); + } + +found: if (negate) + facilities &= ~mask; + else + facilities |= mask; + + token = strtok(NULL, ","); + } + + l->facilities = facilities; + + free(copy); + + return l->facilities; +} + +void log_print(struct log *l, const char *lvl, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_vprint(l, lvl, fmt, ap); + va_end(ap); +} + +void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list ap) +{ + char *buf = alloc(512); + + /* Optional prefix */ + if (l->prefix) + strcatf(&buf, "%s", l->prefix); + + /* Indention */ +#ifdef __GNUC__ + for (int i = 0; i < indent; i++) + strcatf(&buf, "%s ", BOX_UD); + + strcatf(&buf, "%s ", BOX_UDR); +#endif + + /* Format String */ + vstrcatf(&buf, fmt, ap); + + /* Output */ +#ifdef ENABLE_OPAL_ASYNC + OpalPrint("VILLASfpga: %s\n", buf); +#endif + fprintf(l->file ? l->file : stderr, "%s\n", buf); + + free(buf); +} diff --git a/fpga/lib/log_config.c b/fpga/lib/log_config.c new file mode 100644 index 000000000..3b6cf7cc4 --- /dev/null +++ b/fpga/lib/log_config.c @@ -0,0 +1,85 @@ +/** Logging routines that depend on jansson. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include "config.h" +#include "log.h" +#include "log_config.h" +#include "utils.h" +#include "string.h" + +int log_parse(struct log *l, json_t *cfg) +{ + const char *facilities = NULL; + const char *path = NULL; + int ret; + + json_error_t err; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: s, s?: s, s?: b }", + "level", &l->level, + "file", &path, + "facilities", &facilities, + "syslog", &l->syslog + ); + if (ret) + jerror(&err, "Failed to parse logging configuration"); + + if (path) + l->path = strdup(path); + + if (facilities) + log_set_facility_expression(l, facilities); + + l->state = STATE_PARSED; + + return 0; +} + +void jerror(json_error_t *err, const char *fmt, ...) +{ + va_list ap; + char *buf = NULL; + + struct log *l = global_log ? global_log : &default_log; + + va_start(ap, fmt); + vstrcatf(&buf, fmt, ap); + va_end(ap); + + log_print(l, LOG_LVL_ERROR, "%s:", buf); + { INDENT + log_print(l, LOG_LVL_ERROR, "%s in %s:%d:%d", err->text, err->source, err->line, err->column); + + if (l->syslog) + syslog(LOG_ERR, "%s in %s:%d:%d", err->text, err->source, err->line, err->column); + } + + free(buf); + + killme(SIGABRT); + pause(); +} diff --git a/fpga/lib/log_helper.c b/fpga/lib/log_helper.c new file mode 100644 index 000000000..6f99e3696 --- /dev/null +++ b/fpga/lib/log_helper.c @@ -0,0 +1,138 @@ +/** Logging and debugging routines + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include + +#include "utils.h" +#include "log.h" + +void debug(long class, const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + int lvl = class & 0xFF; + int fac = class & ~0xFF; + + if (((fac == 0) || (fac & l->facilities)) && (lvl <= l->level)) { + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_DEBUG, fmt, ap); + + if (l->syslog) + syslog(LOG_DEBUG, fmt, ap); + + va_end(ap); + } +} + +void info(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_INFO, fmt, ap); + + if (l->syslog) + syslog(LOG_INFO, fmt, ap); + + va_end(ap); +} + +void warn(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_WARN, fmt, ap); + + if (l->syslog) + syslog(LOG_WARNING, fmt, ap); + + va_end(ap); +} + +void stats(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_STATS, fmt, ap); + + if (l->syslog) + syslog(LOG_INFO, fmt, ap); + + va_end(ap); +} + +void error(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_ERROR, fmt, ap); + + if (l->syslog) + syslog(LOG_ERR, fmt, ap); + + va_end(ap); + + killme(SIGABRT); + pause(); +} + +void serror(const char *fmt, ...) +{ + va_list ap; + char *buf = NULL; + + struct log *l = global_log; + + va_start(ap, fmt); + vstrcatf(&buf, fmt, ap); + va_end(ap); + + log_print(l, LOG_LVL_ERROR, "%s: %m (%u)", buf, errno); + + if (l->syslog) + syslog(LOG_ERR, "%s: %m (%u)", buf, errno); + + free(buf); + + killme(SIGABRT); + pause(); +} diff --git a/fpga/lib/plugin.c b/fpga/lib/plugin.c new file mode 100644 index 000000000..908d8eea4 --- /dev/null +++ b/fpga/lib/plugin.c @@ -0,0 +1,111 @@ +/** Loadable / plugin support. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "plugin.h" + +/** Global list of all known plugins */ +struct list plugins = { .state = STATE_DESTROYED }; + +LIST_INIT_STATIC(&plugins) + +int plugin_init(struct plugin *p) +{ + assert(p->state == STATE_DESTROYED); + + p->state = STATE_INITIALIZED; + + return 0; +} + +int plugin_parse(struct plugin *p, json_t *cfg) +{ + const char *path; + + path = json_string_value(cfg); + if (!path) + return -1; + + p->path = strdup(path); + + return 0; +} + +int plugin_load(struct plugin *p) +{ + p->handle = dlopen(p->path, RTLD_NOW); + if (!p->path) + return -1; + + p->state = STATE_LOADED; + + return 0; +} + +int plugin_unload(struct plugin *p) +{ + int ret; + + assert(p->state == STATE_LOADED); + + ret = dlclose(p->handle); + if (ret) + return -1; + + p->state = STATE_UNLOADED; + + return 0; +} + +int plugin_destroy(struct plugin *p) +{ + assert(p->state != STATE_DESTROYED && p->state != STATE_LOADED); + + if (p->path) + free(p->path); + + return 0; +} + +struct plugin * plugin_lookup(enum plugin_type type, const char *name) +{ + for (size_t i = 0; i < list_length(&plugins); i++) { + struct plugin *p = (struct plugin *) list_at(&plugins, i); + + if (p->type == type && strcmp(p->name, name) == 0) + return p; + } + + return NULL; +} + +void plugin_dump(enum plugin_type type) +{ + for (size_t i = 0; i < list_length(&plugins); i++) { + struct plugin *p = (struct plugin *) list_at(&plugins, i); + + if (p->type == type) + printf(" - %-13s: %s\n", p->name, p->description); + } +} diff --git a/fpga/lib/utils.c b/fpga/lib/utils.c new file mode 100644 index 000000000..2e295191d --- /dev/null +++ b/fpga/lib/utils.c @@ -0,0 +1,401 @@ +/** General purpose helper functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "utils.h" + +pthread_t main_thread; + +void print_copyright() +{ + printf("VILLASfpga %s (built on %s %s)\n", + CLR_BLU(BUILDID), CLR_MAG(__DATE__), CLR_MAG(__TIME__)); + printf(" Copyright 2014-2017, Institute for Automation of Complex Power Systems, EONERC\n"); + printf(" Steffen Vogel \n"); +} + +void print_version() +{ + printf("%s\n", BUILDID); +} + +int version_parse(const char *s, struct version *v) +{ + return sscanf(s, "%u.%u", &v->major, &v->minor) != 2; +} + +int version_cmp(struct version *a, struct version *b) { + int major = a->major - b->major; + int minor = a->minor - b->minor; + + return major ? major : minor; +} + +double box_muller(float m, float s) +{ + double x1, x2, y1; + static double y2; + static int use_last = 0; + + if (use_last) { /* use value from previous call */ + y1 = y2; + use_last = 0; + } + else { + double w; + do { + x1 = 2.0 * randf() - 1.0; + x2 = 2.0 * randf() - 1.0; + w = x1*x1 + x2*x2; + } while (w >= 1.0); + + w = sqrt(-2.0 * log(w) / w); + y1 = x1 * w; + y2 = x2 * w; + use_last = 1; + } + + return m + y1 * s; +} + +double randf() +{ + return (double) random() / RAND_MAX; +} + +char * strcatf(char **dest, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vstrcatf(dest, fmt, ap); + va_end(ap); + + return *dest; +} + +char * vstrcatf(char **dest, const char *fmt, va_list ap) +{ + char *tmp; + int n = *dest ? strlen(*dest) : 0; + int i = vasprintf(&tmp, fmt, ap); + + *dest = (char *)(realloc(*dest, n + i + 1)); + if (*dest != NULL) + strncpy(*dest+n, tmp, i + 1); + + free(tmp); + + return *dest; +} + +#ifdef __linux__ + +void cpuset_to_integer(cpu_set_t *cset, uintmax_t *set) +{ + *set = 0; + for (int i = 0; i < MIN(sizeof(*set) * 8, CPU_SETSIZE); i++) { + if (CPU_ISSET(i, cset)) + *set |= 1ULL << i; + } +} + +void cpuset_from_integer(uintmax_t set, cpu_set_t *cset) +{ + CPU_ZERO(cset); + for (int i = 0; i < MIN(sizeof(set) * 8, CPU_SETSIZE); i++) { + if (set & (1L << i)) + CPU_SET(i, cset); + } +} + +/* From: https://github.com/mmalecki/util-linux/blob/master/lib/cpuset.c */ +static const char *nexttoken(const char *q, int sep) +{ + if (q) + q = strchr(q, sep); + if (q) + q++; + return q; +} + +int cpulist_parse(const char *str, cpu_set_t *set, int fail) +{ + const char *p, *q; + int r = 0; + + q = str; + CPU_ZERO(set); + + while (p = q, q = nexttoken(q, ','), p) { + unsigned int a; /* beginning of range */ + unsigned int b; /* end of range */ + unsigned int s; /* stride */ + const char *c1, *c2; + char c; + + if ((r = sscanf(p, "%u%c", &a, &c)) < 1) + return 1; + b = a; + s = 1; + + c1 = nexttoken(p, '-'); + c2 = nexttoken(p, ','); + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if ((r = sscanf(c1, "%u%c", &b, &c)) < 1) + return 1; + c1 = nexttoken(c1, ':'); + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if ((r = sscanf(c1, "%u%c", &s, &c)) < 1) + return 1; + if (s == 0) + return 1; + } + } + + if (!(a <= b)) + return 1; + while (a <= b) { + if (fail && (a >= CPU_SETSIZE)) + return 2; + CPU_SET(a, set); + a += s; + } + } + + if (r == 2) + return 1; + + return 0; +} + +char *cpulist_create(char *str, size_t len, cpu_set_t *set) +{ + size_t i; + char *ptr = str; + int entry_made = 0; + + for (i = 0; i < CPU_SETSIZE; i++) { + if (CPU_ISSET(i, set)) { + int rlen; + size_t j, run = 0; + entry_made = 1; + for (j = i + 1; j < CPU_SETSIZE; j++) { + if (CPU_ISSET(j, set)) + run++; + else + break; + } + if (!run) + rlen = snprintf(ptr, len, "%zd,", i); + else if (run == 1) { + rlen = snprintf(ptr, len, "%zd,%zd,", i, i + 1); + i++; + } else { + rlen = snprintf(ptr, len, "%zd-%zd,", i, i + run); + i += run; + } + if (rlen < 0 || (size_t) rlen + 1 > len) + return NULL; + ptr += rlen; + if (rlen > 0 && len > (size_t) rlen) + len -= rlen; + else + len = 0; + } + } + ptr -= entry_made; + *ptr = '\0'; + + return str; +} +#endif /* __linux__ */ + +void * alloc(size_t bytes) +{ + void *p = malloc(bytes); + if (!p) + error("Failed to allocate memory"); + + memset(p, 0, bytes); + + return p; +} + +void * memdup(const void *src, size_t bytes) +{ + void *dst = alloc(bytes); + + memcpy(dst, src, bytes); + + return dst; +} + +ssize_t read_random(char *buf, size_t len) +{ + int fd; + ssize_t bytes, total; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + + bytes = 0; + total = 0; + while (total < len) { + bytes = read(fd, buf + total, len - total); + if (bytes < 0) + break; + + total += bytes; + } + + close(fd); + + return bytes; +} + +void rdtsc_sleep(uint64_t nanosecs, uint64_t start) +{ + uint64_t cycles; + + /** @todo Replace the hard coded CPU clock frequency */ + cycles = (double) nanosecs / (1e9 / 3392389000); + + if (start == 0) + start = rdtsc(); + + do { + __asm__("nop"); + } while (rdtsc() - start < cycles); +} + +/* Setup exit handler */ +int signals_init(void (*cb)(int signal, siginfo_t *sinfo, void *ctx)) +{ + int ret; + + info("Initialize signals"); + + struct sigaction sa_quit = { + .sa_flags = SA_SIGINFO | SA_NODEFER, + .sa_sigaction = cb + }; + + struct sigaction sa_chld = { + .sa_flags = 0, + .sa_handler = SIG_IGN + }; + + main_thread = pthread_self(); + + sigemptyset(&sa_quit.sa_mask); + + ret = sigaction(SIGINT, &sa_quit, NULL); + if (ret) + return ret; + + ret = sigaction(SIGTERM, &sa_quit, NULL); + if (ret) + return ret; + + ret = sigaction(SIGALRM, &sa_quit, NULL); + if (ret) + return ret; + + ret = sigaction(SIGCHLD, &sa_chld, NULL); + if (ret) + return ret; + + return 0; +} + +void killme(int sig) +{ + /* Send only to main thread in case the ID was initilized by signals_init() */ + if (main_thread) + pthread_kill(main_thread, sig); + else + kill(0, sig); +} + +pid_t spawn(const char* name, char *const argv[]) +{ + pid_t pid; + + pid = fork(); + switch (pid) { + case -1: return -1; + case 0: return execvp(name, (char * const*) argv); + } + + return pid; +} + +size_t strlenp(const char *str) +{ + size_t sz = 0; + + for (const char *d = str; *d; d++) { + const unsigned char *c = (const unsigned char *) d; + + if (isprint(*c)) + sz++; + else if (c[0] == '\b') + sz--; + else if (c[0] == '\t') + sz += 4; /* tab width == 4 */ + /* CSI sequence */ + else if (c[0] == '\e' && c[1] == '[') { + c += 2; + while (*c && *c != 'm') + c++; + } + /* UTF-8 */ + else if (c[0] >= 0xc2 && c[0] <= 0xdf) { + sz++; + c += 1; + } + else if (c[0] >= 0xe0 && c[0] <= 0xef) { + sz++; + c += 2; + } + else if (c[0] >= 0xf0 && c[0] <= 0xf4) { + sz++; + c += 3; + } + + d = (const char *) c; + } + + return sz; +} diff --git a/fpga/lib/vlnv.c b/fpga/lib/vlnv.c new file mode 100644 index 000000000..e6c693302 --- /dev/null +++ b/fpga/lib/vlnv.c @@ -0,0 +1,64 @@ +/** Vendor, Library, Name, Version (VLNV) tag + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "fpga/vlnv.h" +#include "fpga/ip.h" + +struct fpga_ip * fpga_vlnv_lookup(struct list *l, struct fpga_vlnv *v) +{ + return (struct fpga_ip *) list_search(l, (cmp_cb_t) fpga_vlnv_cmp, v); +} + +int fpga_vlnv_cmp(struct fpga_vlnv *a, struct fpga_vlnv *b) +{ + return ((!a->vendor || !b->vendor || !strcmp(a->vendor, b->vendor )) && + (!a->library || !b->library || !strcmp(a->library, b->library)) && + (!a->name || !b->name || !strcmp(a->name, b->name )) && + (!a->version || !b->version || !strcmp(a->version, b->version))) ? 0 : 1; +} + +int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv) +{ + char *tmp = strdup(vlnv); + + c->vendor = strdup(strtok(tmp, ":")); + c->library = strdup(strtok(NULL, ":")); + c->name = strdup(strtok(NULL, ":")); + c->version = strdup(strtok(NULL, ":")); + + free(tmp); + + return 0; +} + +int fpga_vlnv_destroy(struct fpga_vlnv *v) +{ + free(v->vendor); + free(v->library); + free(v->name); + free(v->version); + + return 0; +} diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py new file mode 100755 index 000000000..f114d3a34 --- /dev/null +++ b/fpga/scripts/hwdef-parse.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +""" +Author: Daniel Krebs +Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC +License: GNU General Public License (version 3) + +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 +any later version. + +This program is distributed in the hope that 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 . +""" + +from lxml import etree +import zipfile +import sys +import re +import json + +whitelist = [ + [ 'xilinx.com', 'ip', 'axi_timer' ], + [ 'xilinx.com', 'ip', 'axis_switch' ], + [ 'xilinx.com', 'ip', 'axi_fifo_mm_s' ], + [ 'xilinx.com', 'ip', 'axi_dma' ], + [ 'acs.eonerc.rwth-aachen.de', 'user', 'axi_pcie_intc' ], + [ 'acs.eonerc.rwth-aachen.de', 'user', 'rtds_axis' ], + [ 'acs.eonerc.rwth-aachen.de', 'hls' ], + [ 'acs.eonerc.rwth-aachen.de', 'sysgen' ], + [ 'xilinx.com', 'ip', 'axi_gpio' ], + [ 'xilinx.com', 'ip', 'axi_bram_ctrl' ] +] + +# List of VLNI ids of AXI4-Stream infrastructure IP cores which do not alter data +# see PG085 (AXI4-Stream Infrastructure IP Suite v2.2) +axi_converter_whitelist = [ + [ 'xilinx.com', 'ip', 'axis_subset_converter' ], + [ 'xilinx.com', 'ip', 'axis_clock_converter' ], + [ 'xilinx.com', 'ip', 'axis_register_slice' ], + [ 'xilinx.com', 'ip', 'axis_data_fifo' ], + [ 'xilinx.com', 'ip', 'axis_dwidth_converter' ], + [ 'xilinx.com', 'ip', 'axis_register_slice' ] +] + +opponent = { + 'MASTER' : ('SLAVE', 'TARGET'), + 'SLAVE' : ('MASTER', 'INITIATOR'), + 'INITIATOR' : ('TARGET', 'SLAVE'), + 'TARGET' : ('INITIATOR', 'MASTER') +} + +def port_trace(root, signame, idx): + pass + +def port_find_driver(root, signame): + pass + +def bus_trace(root, busname, type, whitelist): + module = root.xpath('.//MODULE[.//BUSINTERFACE[@BUSNAME="{}" and (@TYPE="{}" or @TYPE="{}")]]'.format(busname, type[0], type[1])) + + vlnv = module[0].get('VLNV') + instance = module[0].get('INSTANCE') + + if vlnv_match(vlnv, whitelist): + return instance + elif vlnv_match(vlnv, axi_converter_whitelist): + next_bus = module[0].xpath('.//BUSINTERFACE[@TYPE="{}" or @TYPE="{}"]'.format(opponent[type[0]][0], opponent[type[0]][1])) + next_busname = next_bus[0].get('BUSNAME') + + return bus_trace(root, next_busname, type, whitelist) + else: + raise TypeError('Unsupported AXI4-Stream IP core: %s (%s)' % (instance, vlnv)) + +def vlnv_match(vlnv, whitelist): + c = vlnv.split(':') + + for w in whitelist: + if c[:len(w)] == w: + return True + + return False + +if len(sys.argv) < 2: + print('Usage: {} path/to/*.hwdef'.format(sys.argv[0])) + print(' {} path/to/*.xml'.format(sys.argv[0])) + sys.exit(1) + +try: + # read .hwdef which is actually a zip-file + zip = zipfile.ZipFile(sys.argv[1], 'r') + hwh = zip.read('top.hwh') +except: + f = open(sys.argv[1], 'r') + hwh = f.read() + +# parse .hwh file which is actually XML +try: + root = etree.XML(hwh) +except: + print('Bad format of "{}"! Did you choose the right file?'.format(sys.argv[1])) + sys.exit(1) + +ips = {} + +# find all whitelisted modules +modules = root.find('.//MODULES') +for module in modules: + instance = module.get('INSTANCE') + vlnv = module.get('VLNV') + + # Ignroing unkown + if not vlnv_match(vlnv, whitelist): + continue + + ips[instance] = { + 'vlnv' : vlnv, + 'irqs' : { }, + 'ports' : { } + } + +# find PCI-e module to extract memory map +pcie = root.find('.//MODULE[@MODTYPE="axi_pcie"]') +mmap = pcie.find('.//MEMORYMAP') +for mrange in mmap: + instance = mrange.get('INSTANCE') + + if instance in ips: + ips[instance]['baseaddr'] = int(mrange.get('BASEVALUE'), 16); + ips[instance]['highaddr'] = int(mrange.get('HIGHVALUE'), 16); + +# find AXI-Stream switch port mapping +switch = root.find('.//MODULE[@MODTYPE="axis_switch"]') +busifs = switch.find('.//BUSINTERFACES') +for busif in busifs: + if busif.get('VLNV') != 'xilinx.com:interface:axis:1.0': + continue + + busname = busif.get('BUSNAME') + name = busif.get('NAME') + type = busif.get('TYPE') + + r = re.compile('(M|S)([0-9]+)_AXIS') + m = r.search(name) + + port = int(m.group(2)) + + ep = bus_trace(root, busname, opponent[type], whitelist) + + if ep in ips: + ips[ep]['ports'][type.lower()] = port + +# find Interrupt assignments +intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') +intr = intc.xpath('.//PORT[@NAME="intr" and @DIR="I"]')[0] +concat = root.xpath('.//MODULE[@MODTYPE="xlconcat" and .//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(intr.get('SIGNAME')))[0] +ports = concat.xpath('.//PORT[@DIR="I"]') + +for port in ports: + name = port.get('NAME') + signame = port.get('SIGNAME') + + # Skip unconnected IRQs + if not signame: + continue + + r = re.compile('In([0-9+])') + m = r.search(name) + + irq = int(m.group(1)) + ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] + + instance = ip.get('INSTANCE') + vlnv = ip.get('VLNV') + + port = ip.xpath('.//PORT[@SIGNAME="{}" and @DIR="O"]'.format(signame))[0] + irqname = port.get('NAME') + + if instance in ips: + ips[instance]['irqs'][irqname] = irq + +# Find BRAM storage depths (size) +brams = root.xpath('.//MODULE[@MODTYPE="axi_bram_ctrl"]') +for bram in brams: + instance = bram.get('INSTANCE') + + width = bram.find('.//PARAMETER[@NAME="DATA_WIDTH"]').get('VALUE') + depth = bram.find('.//PARAMETER[@NAME="MEM_DEPTH"]').get('VALUE') + + size = int(width) * int(depth) / 8 + + if instance in ips: + ips[instance]['size'] = int(size) + +print(json.dumps(ips, indent=2)) diff --git a/fpga/scripts/hwdef-villas.xml b/fpga/scripts/hwdef-villas.xml new file mode 100644 index 000000000..91bef0b97 --- /dev/null +++ b/fpga/scripts/hwdef-villas.xml @@ -0,0 +1,8119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fpga/scripts/rebind_device.sh b/fpga/scripts/rebind_device.sh new file mode 100755 index 000000000..67fb75bbe --- /dev/null +++ b/fpga/scripts/rebind_device.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Detach and rebind a PCI device to a PCI kernel driver +# +# @author Steffen Vogel +# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC +# @license GNU General Public License (version 3) +# +# VILLASnode +# +# 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 +# any later version. +# +# This program is distributed in the hope that 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 . +################################################################################## + +if [ "$#" -ne 2 ]; then + echo "usage: $0 BUS:DEV:FNC DRIVER" + exit 1 +fi + +BDF=$1 +DRIVER=$2 + +VENDOR=$(cut -b3- /sys/bus/pci/devices/${BDF}/vendor) +DEVICE=$(cut -b3- /sys/bus/pci/devices/${BDF}/device) + +SYSFS_DEVICE=/sys/bus/pci/devices/${BDF} +SYSFS_DRIVER=/sys/bus/pci/drivers/${DRIVER} + +echo "Device: $VENDOR $DEVICE $BDF" + +if [ -L "${SYSFS_DEVICE}/driver" ] && [ -d "${SYSFS_DEVICE}/driver" ]; then + echo ${BDF} > ${SYSFS_DEVICE}/driver/unbind +fi + +echo "${VENDOR} ${DEVICE}" > ${SYSFS_DRIVER}/new_id +echo "${BDF}" > ${SYSFS_DRIVER}/bind +echo "${VENDOR} ${DEVICE}" > ${SYSFS_DRIVER}/remove_id diff --git a/fpga/scripts/reset_pci_device.sh b/fpga/scripts/reset_pci_device.sh new file mode 100755 index 000000000..ae3b14486 --- /dev/null +++ b/fpga/scripts/reset_pci_device.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Reset PCI devices like FPGAs after a reflash +# +# @author Steffen Vogel +# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC +# @license GNU General Public License (version 3) +# +# VILLASnode +# +# 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 +# any later version. +# +# This program is distributed in the hope that 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 . +################################################################################## + +if [ "$#" -ne 1 ]; then + echo "usage: $0 BUS:DEV:FNC" + exit 1 +fi + +BDF=$1 + +echo "1" > /sys/bus/pci/devices/$BDF/remove +echo "1" > /sys/bus/pci/rescan +echo "1" > /sys/bus/pci/devices/$BDF/enable + diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt new file mode 100644 index 000000000..9cd6d7317 --- /dev/null +++ b/fpga/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +set(SOURCES + main.c + dma.c + fifo.c + hls.c + intc.c + rtds_rtt.c + tmrctr.c + xsg.c +) + +add_executable(unit-tests ${SOURCES}) + +find_package(Criterion REQUIRED) + +target_include_directories(unit-tests PUBLIC + ../include + ${CRITERION_INCLUDE_DIRECTORIES} +) + +target_link_libraries(unit-tests PUBLIC + villas-fpga + ${CRITERION_LIBRARIES} +) diff --git a/fpga/tests/dma.c b/fpga/tests/dma.c new file mode 100644 index 000000000..894ef60e5 --- /dev/null +++ b/fpga/tests/dma.c @@ -0,0 +1,94 @@ +/** DMA unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +extern struct fpga_card *card; + +Test(fpga, dma, .description = "DMA") +{ + int ret = -1; + struct dma_mem mem, src, dst; + + for (size_t i = 0; i < list_length(&card->ips); i++) { INDENT + struct fpga_ip *dm = (struct fpga_ip *) list_at(&card->ips, i); + + if (fpga_vlnv_cmp(&dm->vlnv, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_dma", NULL })) + continue; /* skip non DMA IP cores */ + + struct dma *dma = (struct dma *) dm->_vd; + + /* Simple DMA can only transfer up to 4 kb due to + * PCIe page size burst limitation */ + ssize_t len2, len = dma->inst.HasSg ? 64 << 20 : 1 << 2; + + ret = dma_alloc(dm, &mem, 2 * len, 0); + cr_assert_eq(ret, 0); + + ret = dma_mem_split(&mem, &src, &dst); + cr_assert_eq(ret, 0); + + /* Get new random data */ + len2 = read_random(src.base_virt, len); + if (len2 != len) + serror("Failed to get random data"); + + int irq_mm2s = dm->irq; + int irq_s2mm = dm->irq + 1; + + ret = intc_enable(card->intc, (1 << irq_mm2s) | (1 << irq_s2mm), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + ret = switch_connect(card->sw, dm, dm); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + /* Start transfer */ + ret = dma_ping_pong(dm, src.base_phys, dst.base_phys, dst.len); + cr_assert_eq(ret, 0, "DMA ping pong failed"); + + ret = memcmp(src.base_virt, dst.base_virt, src.len); + + info("DMA %s (%s): %s", dm->name, dma->inst.HasSg ? "scatter-gather" : "simple", ret ? CLR_RED("failed") : CLR_GRN("passed")); + + ret = switch_disconnect(card->sw, dm, dm); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = intc_disable(card->intc, (1 << irq_mm2s) | (1 << irq_s2mm)); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + ret = dma_free(dm, &mem); + cr_assert_eq(ret, 0, "Failed to release DMA memory"); + } + + cr_assert_eq(ret, 0); +} diff --git a/fpga/tests/fifo.c b/fpga/tests/fifo.c new file mode 100644 index 000000000..0c5e70abf --- /dev/null +++ b/fpga/tests/fifo.c @@ -0,0 +1,74 @@ +/** FIFO unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, fifo, .description = "FIFO") +{ + int ret; + ssize_t len; + char src[255], dst[255]; + struct fpga_ip *fifo; + + fifo = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_fifo_mm_s", NULL }); + cr_assert(fifo); + + ret = intc_enable(card->intc, (1 << fifo->irq), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + ret = switch_connect(card->sw, fifo, fifo); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + /* Get some random data to compare */ + memset(dst, 0, sizeof(dst)); + len = read_random((char *) src, sizeof(src)); + if (len != sizeof(src)) + error("Failed to get random data"); + + len = fifo_write(fifo, (char *) src, sizeof(src)); + if (len != sizeof(src)) + error("Failed to send to FIFO"); + + len = fifo_read(fifo, (char *) dst, sizeof(dst)); + if (len != sizeof(dst)) + error("Failed to read from FIFO"); + + ret = intc_disable(card->intc, (1 << fifo->irq)); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + ret = switch_disconnect(card->sw, fifo, fifo); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + /* Compare data */ + cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); +} diff --git a/fpga/tests/hls.c b/fpga/tests/hls.c new file mode 100644 index 000000000..a1ec3bd74 --- /dev/null +++ b/fpga/tests/hls.c @@ -0,0 +1,80 @@ +/** HLS unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include +#include +#include + +extern struct fpga_card *card; + +Test(fpga, hls_dft, .description = "HLS: hls_dft") +{ + int ret; + struct fpga_ip *hls, *rtds; + + rtds = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "rtds_axis", NULL }); + hls = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { NULL, "hls", "hls_dft", NULL }); + + /* Check if required IP is available on FPGA */ + cr_assert(hls && rtds); + + ret = intc_enable(card->intc, (1 << rtds->irq), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + ret = switch_connect(card->sw, rtds, hls); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_connect(card->sw, hls, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + while(1) { + /* Dump RTDS AXI Stream state */ + rtds_axis_dump(rtds); + sleep(1); + } +#if 0 + int len = 2000; + int NSAMPLES = 400; + float src[len], dst[len]; + + for (int i = 0; i < len; i++) { + src[i] = 4 + 5.0 * sin(2.0 * M_PI * 1 * i / NSAMPLES) + + 2.0 * sin(2.0 * M_PI * 2 * i / NSAMPLES) + + 1.0 * sin(2.0 * M_PI * 5 * i / NSAMPLES) + + 0.5 * sin(2.0 * M_PI * 9 * i / NSAMPLES) + + 0.2 * sin(2.0 * M_PI * 15 * i / NSAMPLES); + + fifo_write() + } +#endif + + ret = switch_disconnect(card->sw, rtds, hls); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_disconnect(card->sw, hls, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); +} diff --git a/fpga/tests/intc.c b/fpga/tests/intc.c new file mode 100644 index 000000000..1ebdafbda --- /dev/null +++ b/fpga/tests/intc.c @@ -0,0 +1,57 @@ +/** Intc unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, intc, .description = "Interrupt Controller") +{ + int ret; + uint32_t isr; + + cr_assert(card->intc); + + ret = intc_enable(card->intc, 0xFF00, 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + /* Fake IRQs in software by writing to ISR */ + XIntc_Out32((uintptr_t) card->map + card->intc->baseaddr + XIN_ISR_OFFSET, 0xFF00); + + /* Wait for 8 SW triggered IRQs */ + for (int i = 0; i < 8; i++) + intc_wait(card->intc, i+8); + + /* Check ISR if all SW IRQs have been deliverd */ + isr = XIntc_In32((uintptr_t) card->map + card->intc->baseaddr + XIN_ISR_OFFSET); + + ret = intc_disable(card->intc, 0xFF00); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + cr_assert_eq(isr & 0xFF00, 0); /* ISR should get cleared by MSI_Grant_signal */ +} diff --git a/fpga/tests/main.c b/fpga/tests/main.c new file mode 100644 index 000000000..866f23131 --- /dev/null +++ b/fpga/tests/main.c @@ -0,0 +1,91 @@ +/** Main Unit Test entry point. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#define TEST_CONFIG "/villas/etc/fpga.conf" +#define TEST_LEN 0x1000 + +#define CPU_HZ 3392389000 +#define FPGA_AXI_HZ 125000000 + +struct list cards; +struct fpga_card *card; +struct pci pci; +struct vfio_container vc; + +static void init() +{ + int ret; + + FILE *f; + json_error_t err; + json_t *json; + + ret = pci_init(&pci); + cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); + + ret = vfio_init(&vc); + cr_assert_eq(ret, 0, "Failed to initiliaze VFIO sub-system"); + + /* Parse FPGA configuration */ + f = fopen(TEST_CONFIG, "r"); + cr_assert_not_null(f); + + json = json_loadf(f, 0, &err); + cr_assert_not_null(json); + + fclose(f); + + list_init(&cards); + ret = fpga_card_parse_list(&cards, json); + cr_assert_eq(ret, 0, "Failed to parse FPGA config"); + + json_decref(json); + + card = list_lookup(&cards, "vc707"); + cr_assert(card, "FPGA card not found"); + + if (criterion_options.logging_threshold < CRITERION_IMPORTANT) + fpga_card_dump(card); +} + +static void fini() +{ + int ret; + + ret = fpga_card_destroy(card); + cr_assert_eq(ret, 0, "Failed to de-initilize FPGA"); +} + +TestSuite(fpga, + .init = init, + .fini = fini, + .description = "VILLASfpga" +); diff --git a/fpga/tests/rtds_rtt.c b/fpga/tests/rtds_rtt.c new file mode 100644 index 000000000..8215ec604 --- /dev/null +++ b/fpga/tests/rtds_rtt.c @@ -0,0 +1,81 @@ +/** RTDS AXI-Stream RTT unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include + +#include +#include +#include + +extern struct fpga_card *card; + +Test(fpga, rtds_rtt, .description = "RTDS: tight rtt") +{ + int ret; + struct fpga_ip *ip, *rtds; + struct dma_mem buf; + size_t recvlen; + + /* Get IP cores */ + rtds = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "rtds_axis", NULL }); + cr_assert(rtds); + + ip = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_dma", NULL }); + cr_assert(ip); + + ret = switch_connect(card->sw, rtds, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_connect(card->sw, ip, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_alloc(ip, &buf, 0x100, 0); + cr_assert_eq(ret, 0, "Failed to allocate DMA memory"); + + while (1) { + + ret = dma_read(ip, buf.base_phys, buf.len); + cr_assert_eq(ret, 0, "Failed to start DMA read: %d", ret); + + ret = dma_read_complete(ip, NULL, &recvlen); + cr_assert_eq(ret, 0, "Failed to complete DMA read: %d", ret); + + ret = dma_write(ip, buf.base_phys, recvlen); + cr_assert_eq(ret, 0, "Failed to start DMA write: %d", ret); + + ret = dma_write_complete(ip, NULL, NULL); + cr_assert_eq(ret, 0, "Failed to complete DMA write: %d", ret); + } + + ret = switch_disconnect(card->sw, rtds, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_disconnect(card->sw, ip, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_free(ip, &buf); + cr_assert_eq(ret, 0, "Failed to release DMA memory"); +} diff --git a/fpga/tests/tmrctr.c b/fpga/tests/tmrctr.c new file mode 100644 index 000000000..45ecdab42 --- /dev/null +++ b/fpga/tests/tmrctr.c @@ -0,0 +1,70 @@ +/** Timer/Counter unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" + +#include + +#include +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, timer, .description = "Timer Counter") +{ + int ret; + struct fpga_ip *ip; + struct timer *tmr; + + ip = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_timer", NULL }); + cr_assert(ip); + + tmr = (struct timer *) ip->_vd; + + XTmrCtr *xtmr = &tmr->inst; + + ret = intc_enable(card->intc, (1 << ip->irq), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + XTmrCtr_SetOptions(xtmr, 0, XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION); + XTmrCtr_SetResetValue(xtmr, 0, FPGA_AXI_HZ / 125); + XTmrCtr_Start(xtmr, 0); + + uint64_t counter = intc_wait(card->intc, ip->irq); + info("Got IRQ: counter = %ju", counter); + + if (counter == 1) + return; + else + warn("Counter was not 1"); + + intc_disable(card->intc, (1 << ip->irq)); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + return; +} diff --git a/fpga/tests/xsg.c b/fpga/tests/xsg.c new file mode 100644 index 000000000..25da8ab1e --- /dev/null +++ b/fpga/tests/xsg.c @@ -0,0 +1,97 @@ +/** System Generator unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include + +#include +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, xsg, .description = "XSG: multiply_add") +{ + int ret; + double factor, err = 0; + + struct fpga_ip *ip, *dma; + struct model_parameter *p; + struct dma_mem mem; + + ip = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { NULL, "sysgen", "xsg_multiply", NULL }); + cr_assert(ip); + + dma = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_dma", NULL }); + cr_assert(dma); + + struct model *model = (struct model *) ip->_vd; + + p = list_lookup(&model->parameters, "factor"); + if (!p) + error("Missing parameter 'factor' for model '%s'", ip->name); + + ret = model_parameter_read(p, &factor); + cr_assert_eq(ret, 0, "Failed to read parameter 'factor' from model '%s'", ip->name); + + info("Model param: factor = %f", factor); + + ret = switch_connect(card->sw, dma, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_connect(card->sw, ip, dma); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_alloc(dma, &mem, 0x1000, 0); + cr_assert_eq(ret, 0, "Failed to allocate DMA memory"); + + float *src = (float *) mem.base_virt; + float *dst = (float *) mem.base_virt + 0x800; + + for (int i = 0; i < 6; i++) + src[i] = 1.1 * (i+1); + + ret = dma_ping_pong(dma, (char *) src, (char *) dst, 6 * sizeof(float)); + cr_assert_eq(ret, 0, "Failed to to ping pong DMA transfer: %d", ret); + + for (int i = 0; i < 6; i++) + err += fabs(factor * src[i] - dst[i]); + + info("Error after FPGA operation: err = %f", err); + + ret = switch_disconnect(card->sw, dma, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_disconnect(card->sw, ip, dma); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_free(dma, &mem); + cr_assert_eq(ret, 0, "Failed to release DMA memory"); + + cr_assert(err < 1e-3); +} From f95d65a45f4437689003507b461a52b9fb2a9ed0 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 21:33:19 +0100 Subject: [PATCH 004/560] added fodler for bitstreams --- fpga/bitstreams/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 fpga/bitstreams/.gitkeep diff --git a/fpga/bitstreams/.gitkeep b/fpga/bitstreams/.gitkeep new file mode 100644 index 000000000..e69de29bb From 8e0e7a4098398b9af3eab9dbe2d444a96a5845b8 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 21:33:50 +0100 Subject: [PATCH 005/560] added gitignore --- fpga/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 fpga/.gitignore diff --git a/fpga/.gitignore b/fpga/.gitignore new file mode 100644 index 000000000..d405b7a8d --- /dev/null +++ b/fpga/.gitignore @@ -0,0 +1,5 @@ +build/ + +*.a +*.o +*.so From a7c15e618c134b6ec5f974a66c724f5b7b417ccb Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 22:30:21 +0100 Subject: [PATCH 006/560] add missing benchmarks --- fpga/CMakeLists.txt | 1 + fpga/src/CMakeLists.txt | 28 +++++++ fpga/src/bench-datamovers.c | 119 ++++++++++++++++++++++++++++ fpga/src/bench-jitter.c | 65 +++++++++++++++ fpga/src/bench-latency.c | 49 ++++++++++++ fpga/src/bench-memcpy.c | 46 +++++++++++ fpga/src/bench-overruns.c | 152 ++++++++++++++++++++++++++++++++++++ fpga/src/bench.c | 74 ++++++++++++++++++ fpga/src/bench.h | 22 ++++++ fpga/src/fpga.c | 111 ++++++++++++++++++++++++++ 10 files changed, 667 insertions(+) create mode 100644 fpga/src/CMakeLists.txt create mode 100644 fpga/src/bench-datamovers.c create mode 100644 fpga/src/bench-jitter.c create mode 100644 fpga/src/bench-latency.c create mode 100644 fpga/src/bench-memcpy.c create mode 100644 fpga/src/bench-overruns.c create mode 100644 fpga/src/bench.c create mode 100644 fpga/src/bench.h create mode 100644 fpga/src/fpga.c diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index 3bb0552ad..4d355ed0e 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -6,3 +6,4 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) add_subdirectory(lib) add_subdirectory(tests) +add_subdirectory(src) diff --git a/fpga/src/CMakeLists.txt b/fpga/src/CMakeLists.txt new file mode 100644 index 000000000..f38667024 --- /dev/null +++ b/fpga/src/CMakeLists.txt @@ -0,0 +1,28 @@ +set(SOURCES + bench-datamovers.c + bench-jitter.c + bench-latency.c + bench-memcpy.c + bench.c + fpga.c +) + +add_executable(fpga ${SOURCES}) + +target_include_directories(fpga PUBLIC + ../include/villas + ../include +) + +target_link_libraries(fpga PUBLIC + villas-fpga +) + +find_package(LAPACK) + +if(LAPACK_FOUND) + target_sources(fpga PUBLIC bench-overruns.c) + target_link_libraries(fpga PUBLIC ${LAPACK_LIBRARIES}) + target_include_directories(fpga PUBLIC ${LAPACK_INCLUDE_DIRS}) + target_compile_definitions(fpga PUBLIC WITH_LAPACK) +endif() diff --git a/fpga/src/bench-datamovers.c b/fpga/src/bench-datamovers.c new file mode 100644 index 000000000..e98dc2507 --- /dev/null +++ b/fpga/src/bench-datamovers.c @@ -0,0 +1,119 @@ +#include +#include + +#include +#include + +#include +#include +#include + +#include "bench.h" + +int fpga_benchmark_datamover(struct fpga_card *c) +{ + int ret; + + struct fpga_ip *dm; + struct dma_mem mem, src, dst; + +#if BENCH_DM == 1 + char *dm_name = "fifo_mm_s_0"; +#elif BENCH_DM == 2 + char *dm_name = "dma_0"; +#elif BENCH_DM == 3 + char *dm_name = "dma_1"; +#else + #error "Invalid DM selected" +#endif + + dm = list_lookup(&c->ips, dm_name); + if (!dm) + error("Unknown datamover"); + + ret = switch_connect(c->sw, dm, dm); + if (ret) + error("Failed to configure switch"); + + ret = intc_enable(c->intc, (1 << dm->irq) | (1 << (dm->irq + 1)), intc_flags); + if (ret) + error("Failed to enable interrupt"); + + /* Allocate DMA memory */ + ret = dma_alloc(dm, &mem, 2 * (1 << BENCH_DM_EXP_MAX), 0); + if (ret) + error("Failed to allocate DMA memory"); + + ret = dma_mem_split(&mem, &src, &dst); + if (ret) + return -1; + + /* Open file for results */ + char fn[256]; + snprintf(fn, sizeof(fn), "results/datamover_%s_%s_%s.dat", dm_name, intc_flags & INTC_POLLING ? "polling" : "irq", uts.release); + FILE *g = fopen(fn, "w"); + + for (int exp = BENCH_DM_EXP_MIN; exp <= BENCH_DM_EXP_MAX; exp++) { + uint64_t start, stop, total = 0, len = 1 << exp; + +#if BENCH_DM == 1 + if (exp > 11) + break; /* FIFO and Simple DMA are limited to 4kb */ +#elif BENCH_DM == 3 + if (exp >= 12) + break; /* FIFO and Simple DMA are limited to 4kb */ +#endif + + read_random(src.base_virt, len); + memset(dst.base_virt, 0, len); + + info("Start DM bench: len=%#jx", len); + + uint64_t runs = BENCH_RUNS >> exp; + for (int i = 0; i < runs + BENCH_WARMUP; i++) { + start = rdtsc(); +#if BENCH_DM == 1 + ssize_t ret; + + ret = fifo_write(dm, src.base_virt, len); + if (ret < 0) + error("Failed write to FIFO with len = %zu", len); + + ret = fifo_read(dm, dst.base_virt, dst.len); + if (ret < 0) + error("Failed read from FIFO with len = %zu", len); +#else + ret = dma_ping_pong(dm, src.base_phys, dst.base_phys, len); + if (ret) + error("DMA ping pong failed"); +#endif + stop = rdtsc(); + + if (memcmp(src.base_virt, dst.base_virt, len)) + warn("Compare failed"); + + if (i > BENCH_WARMUP) + total += stop - start; + } + + info("exp %u avg %lu", exp, total / runs); + fprintf(g, "%lu %lu\n", len, total / runs); + } + + fclose(g); + + ret = switch_disconnect(c->sw, dm, dm); + if (ret) + error("Failed to configure switch"); + + ret = dma_free(dm, &mem); + if (ret) + error("Failed to release DMA memory"); + + ret = intc_disable(c->intc, (1 << dm->irq) | (1 << (dm->irq + 1))); + if (ret) + error("Failed to enable interrupt"); + + + return 0; +} diff --git a/fpga/src/bench-jitter.c b/fpga/src/bench-jitter.c new file mode 100644 index 000000000..fe4309755 --- /dev/null +++ b/fpga/src/bench-jitter.c @@ -0,0 +1,65 @@ +#include + +#include +#include + +#include + +#include "bench.h" + +int fpga_benchmark_jitter(struct fpga_card *c) +{ + int ret; + + struct fpga_ip *ip = list_lookup(&c->ips, "timer_0"); + if (!ip || !c->intc) + return -1; + + struct timer *tmr = (struct timer *) ip->_vd; + + XTmrCtr *xtmr = &tmr->inst; + + ret = intc_enable(c->intc, (1 << ip->irq), intc_flags); + if (ret) + error("Failed to enable interrupt"); + + float period = 50e-6; + int runs = 300.0 / period; + + int *hist = alloc(8 << 20); + + XTmrCtr_SetOptions(xtmr, 0, XTC_INT_MODE_OPTION | XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION | XTC_AUTO_RELOAD_OPTION); + XTmrCtr_SetResetValue(xtmr, 0, period * FPGA_AXI_HZ); + XTmrCtr_Start(xtmr, 0); + + uint64_t end, start = rdtsc(); + for (int i = 0; i < runs; i++) { + uint64_t cnt = intc_wait(c->intc, ip->irq); + if (cnt != 1) + warn("fail"); + + /* Ackowledge IRQ */ + XTmrCtr_WriteReg((uintptr_t) c->map + ip->baseaddr, 0, XTC_TCSR_OFFSET, XTmrCtr_ReadReg((uintptr_t) c->map + ip->baseaddr, 0, XTC_TCSR_OFFSET)); + + end = rdtsc(); + hist[i] = end - start; + start = end; + } + + XTmrCtr_Stop(xtmr, 0); + + char fn[256]; + snprintf(fn, sizeof(fn), "results/jitter_%s_%s.dat", intc_flags & INTC_POLLING ? "polling" : "irq", uts.release); + FILE *g = fopen(fn, "w"); + for (int i = 0; i < runs; i++) + fprintf(g, "%u\n", hist[i]); + fclose(g); + + free(hist); + + ret = intc_disable(c->intc, (1 << ip->irq)); + if (ret) + error("Failed to disable interrupt"); + + return 0; +} diff --git a/fpga/src/bench-latency.c b/fpga/src/bench-latency.c new file mode 100644 index 000000000..f0affc4e0 --- /dev/null +++ b/fpga/src/bench-latency.c @@ -0,0 +1,49 @@ +#include + +#include +#include + +#include +#include + +#include "bench.h" + +int fpga_benchmark_latency(struct fpga_card *c) +{ + int ret; + + uint64_t start, end; + + if (!c->intc) + return -1; + + int runs = 1000000; + int hist[runs]; + + ret = intc_enable(c->intc, 0x100, intc_flags); + if (ret) + error("Failed to enable interrupts"); + + for (int i = 0; i < runs; i++) { + start = rdtsc(); + XIntc_Out32((uintptr_t) c->map + c->intc->baseaddr + XIN_ISR_OFFSET, 0x100); + + intc_wait(c->intc, 8); + end = rdtsc(); + + hist[i] = end - start; + } + + char fn[256]; + snprintf(fn, sizeof(fn), "results/latency_%s_%s.dat", intc_flags & INTC_POLLING ? "polling" : "irq", uts.release); + FILE *g = fopen(fn, "w"); + for (int i = 0; i < runs; i++) + fprintf(g, "%u\n", hist[i]); + fclose(g); + + ret = intc_disable(c->intc, 0x100); + if (ret) + error("Failed to disable interrupt"); + + return 0; +} diff --git a/fpga/src/bench-memcpy.c b/fpga/src/bench-memcpy.c new file mode 100644 index 000000000..b34788d17 --- /dev/null +++ b/fpga/src/bench-memcpy.c @@ -0,0 +1,46 @@ +#include + +#include + +#include + +#include "bench.h" + +int fpga_benchmark_memcpy(struct fpga_card *c) +{ + char *map = c->map + 0x200000; + uint32_t *mapi = (uint32_t *) map; + + char fn[256]; + snprintf(fn, sizeof(fn), "results/bar0_%s_%s.dat", intc_flags & INTC_POLLING ? "polling" : "irq", uts.release); + FILE *g = fopen(fn, "w"); + fprintf(g, "# bytes cycles\n"); + + uint32_t dummy = 0; + + for (int exp = BENCH_DM_EXP_MIN; exp <= BENCH_DM_EXP_MAX; exp++) { + uint64_t len = 1 << exp; + uint64_t start, end, total = 0; + uint64_t runs = (BENCH_RUNS << 2) >> exp; + + for (int i = 0; i < runs + BENCH_WARMUP; i++) { + start = rdtsc(); + + for (int j = 0; j < len / 4; j++) +// mapi[j] = j; // write + dummy += mapi[j]; // read + + end = rdtsc(); + + if (i > BENCH_WARMUP) + total += end - start; + } + + info("exp = %u\truns = %ju\ttotal = %ju\tavg = %ju\tavgw = %ju", exp, runs, total, total / runs, total / (runs * len)); + fprintf(g, "%zu %lu %ju\n", len, total / runs, runs); + } + + fclose(g); + + return 0; +} diff --git a/fpga/src/bench-overruns.c b/fpga/src/bench-overruns.c new file mode 100644 index 000000000..28e82a5db --- /dev/null +++ b/fpga/src/bench-overruns.c @@ -0,0 +1,152 @@ +/** Benchmarks for VILLASfpga: LAPACK & BLAS + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + **********************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bench.h" + +/* Some hard-coded configuration for the FPGA benchmarks */ +#define BENCH_WARMUP 100 + +/* Declared in fpga-bench.c */ +extern int intc_flags; +extern struct utsname uts; + +/* LAPACK & BLAS Fortran prototypes */ +extern int dgemm_(char *transa, char *transb, int *m, int *n, int *k, double *alpha, double *a, int *lda, double *b, int *ldb, double *beta, double *c, int *ldc); +extern int dgetrf_(int *m, int *n, double *a, int *lda, int *ipiv, int *info); +extern int dgetri_(int *n, double *a, int *lda, int *ipiv, double *work, int *lwork, int *info); + +static int lapack_generate_workload(int N, double *C) +{ + double *A = alloc(N * N * sizeof(double)); + + srand(time(NULL)); + + for (int i = 0; i < N * N; i++) + A[i] = 100 * (double) rand() / RAND_MAX + 1; + + char transA = 'T'; + char transB = 'N'; + double alpha = 1; + double beta = 1; + + /* C = A' * A, to get an invertible matrix */ + dgemm_(&transA, &transB, &N, &N, &N, &alpha, A, &N, A, &N, &beta, C, &N); + + free(A); + + return 0; +} + +static int lapack_workload(int N, double *A) +{ + int info = 0; + int lworkspace = N; + int ipiv[N]; + double workspace[N]; + + dgetrf_(&N, &N, A, &N, ipiv, &info); + if (info > 0) + error("Failed to pivot matrix"); + + dgetri_(&N, A, &N, ipiv, workspace, &lworkspace, &info); + if (info > 0) + error("Failed to LU factorized matrix"); + + return 0; +} + +int fpga_benchmark_overruns(struct fpga_card *c) +{ + struct fpga_ip *rtds, *dm; + + dm = list_lookup(&c->ips, "dma_1"); + rtds = list_lookup(&c->ips, "rtds_axis_0"); + if (!rtds || !c->intc) + return -1; + + int ret; + float period = 50e-6; + int runs = 1.0 / period; + int overruns; + + info("runs = %u", runs); + + switch_connect(c->sw, dm, rtds); + switch_connect(c->sw, rtds, dm); + + intc_enable(c->intc, (1 << (dm->irq + 1 )), intc_flags); + + /* Dump results */ + char fn[256]; + snprintf(fn, sizeof(fn), "results/overruns_lu_rtds_axis_%s_%s.dat", intc_flags & INTC_POLLING ? "polling" : "irq", uts.release); + FILE *g = fopen(fn, "w"); + fprintf(g, "# period = %f\n", period); + fprintf(g, "# runs = %u\n", runs); + + struct dma_mem mem; + ret = dma_alloc(dm, &mem, 0x1000, 0); + if (ret) + error("Failed to allocate DMA memory"); + + uint32_t *data_rx = (uint32_t *) mem.base_virt; + uint32_t *data_tx = (uint32_t *) mem.base_virt + 0x200; + uint64_t total, start, stop; + for (int p = 3; p < 45; p++) { + double *A = alloc(p*p*sizeof(double)); + + lapack_generate_workload(p, A); + + overruns = 0; + total = 0; + + for (int i = 0; i < 2000; i++) { + dma_read(dm, mem.base_phys, 0x200); + dma_read_complete(dm, NULL, NULL); + } + + for (int i = 0; i < runs + BENCH_WARMUP; i++) { + dma_read(dm, mem.base_phys, 0x200); + + start = rdtsc(); + lapack_workload(p, A); + stop = rdtsc(); + + dma_read_complete(dm, NULL, NULL); + + /* Send data to rtds */ + data_tx[0] = i; + dma_write(dm, mem.base_phys + 0x200, 64 * sizeof(data_tx[0])); + + if (i < BENCH_WARMUP) + continue; + + if (i - data_rx[0] > 2) + overruns++; + total += stop - start; + } + + free(A); + + info("iter = %u clks = %ju overruns = %u", p, total / runs, overruns); + fprintf(g, "%u %ju %u\n", p, total / runs, overruns); + + if (overruns >= runs) + break; + } + + fclose(g); + return 0; +} diff --git a/fpga/src/bench.c b/fpga/src/bench.c new file mode 100644 index 000000000..afe97b0cb --- /dev/null +++ b/fpga/src/bench.c @@ -0,0 +1,74 @@ +/** Benchmarks for VILLASfpga + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + **********************************************************************************/ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "bench.h" + +#ifdef WITH_LAPACK +int fpga_benchmark_overruns(struct fpga_card *c); +#endif + +int intc_flags = 0; +struct utsname uts; + +int fpga_benchmarks(int argc, char *argv[], struct fpga_card *c) +{ + int ret; + struct bench { + const char *name; + int (*func)(struct fpga_card *c); + } benchmarks[] = { + { "datamover", fpga_benchmark_datamover }, + { "jitter", fpga_benchmark_jitter }, + { "memcpy", fpga_benchmark_memcpy }, +#ifdef WITH_LAPACK + { "overruns", fpga_benchmark_overruns }, +#endif + { "latency", fpga_benchmark_latency } + }; + + if (argc < 2) + error("Usage: fpga benchmark (bench)"); + + struct bench *bench = NULL; + for (int i = 0; i < ARRAY_LEN(benchmarks); i++) { + if (strcmp(benchmarks[i].name, argv[1]) == 0) { + bench = &benchmarks[i]; + break; + } + } + + if (bench == NULL) + error("There is no benchmark named: %s", argv[1]); + + ret = uname(&uts); + if (ret) + return -1; + +again: ret = bench->func(c); + if (ret) + error("Benchmark %s failed", bench->name); + + /* Rerun test with polling */ + if (intc_flags == 0) { + intc_flags |= INTC_POLLING; + getchar(); + goto again; + } + + return -1; +} + diff --git a/fpga/src/bench.h b/fpga/src/bench.h new file mode 100644 index 000000000..a056230aa --- /dev/null +++ b/fpga/src/bench.h @@ -0,0 +1,22 @@ +#include + +#include "config.h" + +/* Some hard-coded configuration for the FPGA benchmarks */ +#define BENCH_DM 3 +// 1 FIFO +// 2 DMA SG +// 3 DMA Simple + +#define BENCH_RUNS 3000000 +#define BENCH_WARMUP 100 +#define BENCH_DM_EXP_MIN 0 +#define BENCH_DM_EXP_MAX 20 + +int fpga_benchmark_datamover(struct fpga_card *c); +int fpga_benchmark_jitter(struct fpga_card *c); +int fpga_benchmark_memcpy(struct fpga_card *c); +int fpga_benchmark_latency(struct fpga_card *c); + +extern int intc_flags; +extern struct utsname uts; diff --git a/fpga/src/fpga.c b/fpga/src/fpga.c new file mode 100644 index 000000000..602e392ff --- /dev/null +++ b/fpga/src/fpga.c @@ -0,0 +1,111 @@ +/** VILLASfpga utility for tests and benchmarks + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + **********************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +/* Declarations */ +int fpga_benchmarks(int argc, char *argv[], struct fpga_card *c); + +void usage() +{ + printf("Usage: villas-fpga [OPTIONS] CONFIG CARD\n\n"); + printf(" CONFIG path to a configuration file\n"); + printf(" CARD name of the FPGA card\n"); + printf(" OPTIONS is one or more of the following options:\n"); + printf(" -h show this help\n"); + printf(" -V show the version of the tool\n"); + printf("\n"); + print_copyright(); +} + +int main(int argc, char *argv[]) +{ + int ret; + + struct list cards; + struct vfio_container vc; + struct pci pci; + struct fpga_card *card; + + /* Parse arguments */ + char c, *endptr; + while ((c = getopt(argc, argv, "Vh")) != -1) { + switch (c) { + case 'V': + print_version(); + exit(EXIT_SUCCESS); + + case 'h': + case '?': + default: + usage(); + exit(EXIT_SUCCESS); + } + +check: if (optarg == endptr) + error("Failed to parse parse option argument '-%c %s'", c, optarg); + } + + if (argc != optind + 2) { + usage(); + exit(EXIT_FAILURE); + } + + char *configfile = argv[optind]; + char *cardname = argv[optind+1]; + + FILE *f; + json_error_t err; + json_t *json; + + ret = pci_init(&pci); + if (ret) + return -1; + + ret = vfio_init(&vc); + if (ret) + return -1; + + /* Parse FPGA configuration */ + f = fopen(configfile, "r"); + if (!f) + return -1; + + json = json_loadf(f, 0, &err); + if (!json) + return -1; + + fclose(f); + + list_init(&cards); + ret = fpga_card_parse_list(&cards, json); + if (ret) + return -1; + + json_decref(json); + + card = list_lookup(&cards, cardname); + if (!card) + return -1; + + fpga_card_dump(card); + + /* Run benchmarks */ + fpga_benchmarks(argc-optind-1, argv+optind+1, card); + + return 0; +} From 668fa2eb783a0bd85930440b5f50d634e91e122a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 23:01:17 +0100 Subject: [PATCH 007/560] use https submodules --- fpga/.gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/.gitmodules b/fpga/.gitmodules index a451f96b4..f6d6f0188 100644 --- a/fpga/.gitmodules +++ b/fpga/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/Snaipe/Criterion.git [submodule "thirdparty/libxil"] path = thirdparty/libxil - url = git@git.rwth-aachen.de:VILLASframework/libxil.git + url = https://git.rwth-aachen.de/VILLASframework/libxil.git From 149139aa3453b2fd1c40087a4310b0f3c25e772f Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 23:05:22 +0100 Subject: [PATCH 008/560] fix CI --- fpga/.gitlab-ci.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 8c92f218a..68e545a06 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -8,13 +8,8 @@ variables: stages: - prepare - build - - test - - deploy - - docker - -# For some reason, GitLab CI prunes the contents of the submodules so we need to restore them. -before_script: - - git submodule foreach git checkout . +# - test +# - deploy # Stage: prepare ############################################################################## From a5845eb397734a96e8c84799a8b649e58603492f Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 23:06:42 +0100 Subject: [PATCH 009/560] add LAPACK dependency to Dockerfile --- fpga/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fpga/Dockerfile b/fpga/Dockerfile index a4ddf76a4..5e2a8fb69 100644 --- a/fpga/Dockerfile +++ b/fpga/Dockerfile @@ -59,7 +59,8 @@ ADD https://villas.fein-aachen.org/packages/villas.repo /etc/yum.repos.d/ # Dependencies RUN dnf -y install \ jansson-devel \ - libxil-devel + libxil-devel \ + lapack-devel # Build & Install Criterion COPY thirdparty/criterion /tmp/criterion From 1fcabd1bdd4eb2a51face641549cc035decacdcb Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 22 Nov 2017 11:20:44 +0100 Subject: [PATCH 010/560] lib/card: fix assignment in assertion --- fpga/lib/card.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/lib/card.c b/fpga/lib/card.c index 2e07b130a..87d327466 100644 --- a/fpga/lib/card.c +++ b/fpga/lib/card.c @@ -36,7 +36,7 @@ int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) { - assert(c->state = STATE_DESTROYED); + assert(c->state == STATE_DESTROYED); c->vfio_container = vc; c->pci = pci; From db79fe48272a5b7987292ec270f3ed36c6638aa5 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 22 Nov 2017 11:21:27 +0100 Subject: [PATCH 011/560] lib/pci: initialize list and ignore special dir entries --- fpga/lib/kernel/pci.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index e251f00c8..0de89f260 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -40,6 +40,8 @@ int pci_init(struct pci *p) char path[PATH_MAX]; int ret; + list_init(&p->devices); + snprintf(path, sizeof(path), "%s/bus/pci/devices", SYSFS_PATH); dp = opendir(path); @@ -49,6 +51,12 @@ int pci_init(struct pci *p) } while ((e = readdir(dp))) { + + // ignore special entries + if ((strcmp(e->d_name, ".") == 0) || + (strcmp(e->d_name, "..") == 0) ) + continue; + struct pci_device *d = (struct pci_device *) alloc(sizeof(struct pci_device)); struct { const char *s; int *p; } map[] = { From 1cde762fc0b4800673c724faf5a41846308ce2a9 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 22 Nov 2017 18:41:53 +0100 Subject: [PATCH 012/560] ips/gpio: add skeleton for GPIO IP --- fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/gpio.c | 48 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 fpga/lib/ips/gpio.c diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index e83e6382a..c5f25ef58 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES ips/fifo.c ips/dma.c ips/intc.c + ips/gpio.c ips/rtds_axis.c kernel/kernel.c diff --git a/fpga/lib/ips/gpio.c b/fpga/lib/ips/gpio.c new file mode 100644 index 000000000..b6f30ebd6 --- /dev/null +++ b/fpga/lib/ips/gpio.c @@ -0,0 +1,48 @@ +/** GPIO related helper functions + * + * @author Daniel Krebs + * @copyright 2017, Daniel Krebs + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" + +static int gpio_start(struct fpga_ip *c) +{ + (void) c; + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's GPIO controller", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_gpio", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = gpio_start, + .size = 0 + } +}; + +REGISTER_PLUGIN(&p) From 0bf00d51d7a569b73c01cee3a47700a7a5e24170 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 22 Nov 2017 19:36:03 +0100 Subject: [PATCH 013/560] lib: create shared instead of static library --- fpga/lib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index c5f25ef58..8bf8c343c 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -32,7 +32,7 @@ pkg_check_modules(XIL libxil) find_package(Threads) -add_library(villas-fpga ${SOURCES}) +add_library(villas-fpga SHARED ${SOURCES}) target_compile_definitions(villas-fpga PRIVATE BUILDID=\"abc\" From c67c8aac5bc7df9b0eada359389ca9296e7bc634 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 22 Nov 2017 19:46:07 +0100 Subject: [PATCH 014/560] tests: add fpga.json and correctly parse it for unit tests --- fpga/etc/fpga.json | 138 +++++++++++++++++++++++++++++++++++++++++++++ fpga/tests/main.c | 29 ++++++---- 2 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 fpga/etc/fpga.json diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json new file mode 100644 index 000000000..276e8441e --- /dev/null +++ b/fpga/etc/fpga.json @@ -0,0 +1,138 @@ +{ + "affinity": 1, + "stats": 3, + "name": "villas-acs", + "logging": { + "level": 5, + "faciltities": [ + "path", + "socket" + ], + "file": "/var/log/villas-node.log", + "syslog": true + }, + "http": { + "enabled": true, + "htdocs": "/villas/web/socket/", + "port": 80 + }, + "plugins": [ + "simple_circuit.so", + "example_hook.so" + ], + "fpgas": { + "vc707": { + "id": "10ee:7022", + "slot": "01:00.0", + "do_reset": true, + "ips": { + "axi_pcie_intc_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0", + "baseaddr": 45056 + }, + "switch_0": { + "vlnv": "xilinx.com:ip:axis_interconnect:2.1", + "baseaddr": 20480, + "num_ports": 10, + "paths": [ + { + "in": "rtds_axis_0", + "out": "dma_1", + "reverse": true + } + ] + }, + "axi_reset_0": { + "vlnv": "xilinx.com:ip:axi_gpio:2.0", + "baseaddr": 28672 + }, + "timer_0": { + "vlnv": "xilinx.com:ip:axi_timer:2.0", + "baseaddr": 16384, + "irq": 0 + }, + "dma_0": { + "vlnv": "xilinx.com:ip:axi_dma:7.1", + "baseaddr": 12288, + "port": 1, + "irq": 3 + }, + "dma_1": { + "vlnv": "xilinx.com:ip:axi_dma:7.1", + "baseaddr": 8192, + "port": 6, + "irq": 3 + }, + "fifo_mm_s_0": { + "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", + "baseaddr": 24576, + "baseaddr_axi4": 49152, + "port": 2, + "irq": 2 + }, + "rtds_axis_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", + "baseaddr": 32768, + "port": 0, + "irq": 5 + }, + "hls_dft_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", + "baseaddr": 36864, + "port": 5, + "irq": 1, + "period": 400, + "harmonics": [ + 0, + 1, + 3, + 5, + 7 + ], + "decimation": 0 + }, + "axis_data_fifo_0": { + "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", + "port": 3 + }, + "axis_data_fifo_1": { + "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", + "port": 6 + } + } + } + }, + "nodes": { + "dma_0": { + "type": "fpga", + "datamover": "dma_0", + "use_irqs": false + }, + "dma_1": { + "type": "fpga", + "datamover": "dma_1", + "use_irqs": false + }, + "fifo_0": { + "type": "fpga", + "datamover": "fifo_mm_s_0", + "use_irqs": false + }, + "simple_circuit": { + "type": "cbuilder", + "model": "simple_circuit", + "timestep": 2.5000000000000001e-5, + "parameters": [ + 1.0, + 0.001 + ] + } + }, + "paths": [ + { + "in": "dma_1", + "out": "simple_circuit", + "reverse": true + } + ] +} \ No newline at end of file diff --git a/fpga/tests/main.c b/fpga/tests/main.c index 866f23131..5c9a62d9e 100644 --- a/fpga/tests/main.c +++ b/fpga/tests/main.c @@ -29,7 +29,8 @@ #include #include -#define TEST_CONFIG "/villas/etc/fpga.conf" +#define FPGA_CARD "vc707" +#define TEST_CONFIG "/villas/etc/fpga.json" #define TEST_LEN 0x1000 #define CPU_HZ 3392389000 @@ -46,7 +47,6 @@ static void init() FILE *f; json_error_t err; - json_t *json; ret = pci_init(&pci); cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); @@ -56,22 +56,31 @@ static void init() /* Parse FPGA configuration */ f = fopen(TEST_CONFIG, "r"); - cr_assert_not_null(f); + cr_assert_not_null(f, "Cannot open config file"); - json = json_loadf(f, 0, &err); - cr_assert_not_null(json); + json_t *json = json_loadf(f, 0, &err); + cr_assert_not_null(json, "Cannot load JSON config"); fclose(f); - list_init(&cards); - ret = fpga_card_parse_list(&cards, json); + json_t *fpgas = json_object_get(json, "fpgas"); + cr_assert_not_null(fpgas, "No section 'fpgas' found in config"); + cr_assert(json_object_size(json) > 0, "No FPGAs defined in config"); + + json_t *json_card = json_object_get(fpgas, FPGA_CARD); + cr_assert_not_null(json_card, "FPGA card " FPGA_CARD " not found"); + + card = (struct fpga_card *) alloc(sizeof(struct fpga_card)); + cr_assert_not_null(card, "Cannot allocate memory for FPGA card"); + + ret = fpga_card_init(card, &pci, &vc); + cr_assert_eq(ret, 0, "FPGA card initialization failed"); + + ret = fpga_card_parse(card, json_card, FPGA_CARD); cr_assert_eq(ret, 0, "Failed to parse FPGA config"); json_decref(json); - card = list_lookup(&cards, "vc707"); - cr_assert(card, "FPGA card not found"); - if (criterion_options.logging_threshold < CRITERION_IMPORTANT) fpga_card_dump(card); } From d67a120902eacf74cf4e8a2bee6270e1d1c2bda7 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 22 Nov 2017 19:47:04 +0100 Subject: [PATCH 015/560] tests/dma: fix chunk size for simple DMA (should have been 4k) --- fpga/tests/dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/tests/dma.c b/fpga/tests/dma.c index 894ef60e5..de784d452 100644 --- a/fpga/tests/dma.c +++ b/fpga/tests/dma.c @@ -50,7 +50,7 @@ Test(fpga, dma, .description = "DMA") /* Simple DMA can only transfer up to 4 kb due to * PCIe page size burst limitation */ - ssize_t len2, len = dma->inst.HasSg ? 64 << 20 : 1 << 2; + ssize_t len2, len = dma->inst.HasSg ? 64 << 20 : 1 << 12; ret = dma_alloc(dm, &mem, 2 * len, 0); cr_assert_eq(ret, 0); From 737a5851dfe7f8bb9e293d4caa924f3d54697502 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 28 Nov 2017 11:26:41 +0100 Subject: [PATCH 016/560] lib/card: start FPGA card prior to parsing Initializing IPs may want to probe the actual hardware for feature detection (e.g. DMA), so the card has to be started in order to access any memory on the card. --- fpga/lib/card.c | 4 +++- fpga/tests/main.c | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/fpga/lib/card.c b/fpga/lib/card.c index 87d327466..f7606d0a0 100644 --- a/fpga/lib/card.c +++ b/fpga/lib/card.c @@ -167,7 +167,7 @@ int fpga_card_start(struct fpga_card *c) struct pci_device *pdev; - assert(c->state == STATE_CHECKED); + assert(c->state == STATE_INITIALIZED); /* Search for FPGA card */ pdev = pci_lookup_device(c->pci, &c->filter); @@ -258,6 +258,8 @@ void fpga_card_dump(struct fpga_card *c) int fpga_card_check(struct fpga_card *c) { + assert(c->state == STATE_PARSED); + /* Check FPGA configuration */ c->reset = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_gpio", NULL }); if (!c->reset) diff --git a/fpga/tests/main.c b/fpga/tests/main.c index 5c9a62d9e..41549943f 100644 --- a/fpga/tests/main.c +++ b/fpga/tests/main.c @@ -76,9 +76,15 @@ static void init() ret = fpga_card_init(card, &pci, &vc); cr_assert_eq(ret, 0, "FPGA card initialization failed"); + ret = fpga_card_start(card); + cr_assert_eq(ret, 0, "FPGA card cannot be started"); + ret = fpga_card_parse(card, json_card, FPGA_CARD); cr_assert_eq(ret, 0, "Failed to parse FPGA config"); + ret = fpga_card_check(card); + cr_assert_eq(ret, 0, "FPGA card check failed"); + json_decref(json); if (criterion_options.logging_threshold < CRITERION_IMPORTANT) From eeafb2bcc62b1bd257f1f4c2fa873508bf1da385 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 28 Nov 2017 12:06:26 +0100 Subject: [PATCH 017/560] etc/fpga: card is in slot 03:00.0 currently --- fpga/etc/fpga.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index 276e8441e..6f7b20e2d 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -23,7 +23,7 @@ "fpgas": { "vc707": { "id": "10ee:7022", - "slot": "01:00.0", + "slot": "03:00.0", "do_reset": true, "ips": { "axi_pcie_intc_0": { @@ -135,4 +135,4 @@ "reverse": true } ] -} \ No newline at end of file +} From babec9a5744714562988ebff98762aafe7d16ed0 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 28 Nov 2017 12:08:32 +0100 Subject: [PATCH 018/560] kernel/pci: fix pci device compare function list_search's compare function has to return 0 on match. --- fpga/lib/kernel/pci.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index 0de89f260..98b68d7d5 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -232,23 +232,21 @@ fail: int pci_device_compare(const struct pci_device *d, const struct pci_device *f) { - if ((f->slot.domain >= 0 && f->slot.domain != d->slot.domain) || - (f->slot.bus >= 0 && f->slot.bus != d->slot.bus) || - (f->slot.device >= 0 && f->slot.device != d->slot.device) || - (f->slot.function >= 0 && f->slot.function != d->slot.function)) - return 0; + if ((f->slot.domain != 0 && f->slot.domain != d->slot.domain) || + (f->slot.bus != 0 && f->slot.bus != d->slot.bus) || + (f->slot.device != 0 && f->slot.device != d->slot.device) || + (f->slot.function != 0 && f->slot.function != d->slot.function)) + return 1; - if (f->id.device >= 0 || f->id.vendor >= 0) { - if ((f->id.device >= 0 && f->id.device != d->id.device) || (f->id.vendor >= 0 && f->id.vendor != d->id.vendor)) - return 0; - } + if ((f->id.device != 0 && f->id.device != d->id.device) || + (f->id.vendor != 0 && f->id.vendor != d->id.vendor)) + return 1; - if (f->id.class >= 0) { - if (f->id.class != d->id.class) - return 0; - } + if ((f->id.class != 0) || (f->id.class != d->id.class)) + return 1; - return 1; + // found + return 0; } struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *f) From 14150f158fbc46a9c5664b974359fbc4907f5ade Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 29 Dec 2017 11:22:24 +0100 Subject: [PATCH 019/560] fix CI --- fpga/.gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 68e545a06..f726ace65 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -5,6 +5,10 @@ variables: DOCKER_TAG_DEV: ${CI_COMMIT_REF_NAME} DOCKER_IMAGE_DEV: villas/fpga-dev +# For some reason, GitLab CI prunes the contents of the submodules so we need to restore them. +before_script: + - git submodule foreach git checkout . + stages: - prepare - build From 4adb8895275236b27fcd07a217415a349745afc2 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 28 Nov 2017 15:30:26 +0100 Subject: [PATCH 020/560] make ips/intc C++ --- fpga/CMakeLists.txt | 2 +- fpga/lib/CMakeLists.txt | 2 +- fpga/lib/ips/{intc.c => intc.cpp} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename fpga/lib/ips/{intc.c => intc.cpp} (100%) diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index 4d355ed0e..ef7a90124 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(VILLASfpga C) +project(VILLASfpga C CXX) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 8bf8c343c..77eeb70ff 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -9,7 +9,7 @@ set(SOURCES ips/dft.c ips/fifo.c ips/dma.c - ips/intc.c + ips/intc.cpp ips/gpio.c ips/rtds_axis.c diff --git a/fpga/lib/ips/intc.c b/fpga/lib/ips/intc.cpp similarity index 100% rename from fpga/lib/ips/intc.c rename to fpga/lib/ips/intc.cpp From f0c089f719bdcdb6f93750e61a212dfcea61a8df Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 5 Dec 2017 17:50:02 +0100 Subject: [PATCH 021/560] simple renames to not use reserved names --- fpga/include/villas/fpga/ip.h | 2 +- fpga/include/villas/kernel/kernel.h | 2 +- fpga/include/villas/kernel/pci.h | 2 +- fpga/include/villas/log.h | 4 ++-- fpga/lib/card.c | 2 +- fpga/lib/kernel/kernel.c | 4 ++-- fpga/lib/kernel/pci.c | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fpga/include/villas/fpga/ip.h b/fpga/include/villas/fpga/ip.h index b89fc5841..5c078f596 100644 --- a/fpga/include/villas/fpga/ip.h +++ b/fpga/include/villas/fpga/ip.h @@ -51,7 +51,7 @@ enum fpga_ip_types { FPGA_IP_TYPE_MATH, /**< A math IP performs some kind of mathematical operation on the streaming data */ FPGA_IP_TYPE_MISC, /**< Other IP components like timer, counters, interrupt conctrollers or routing. */ FPGA_IP_TYPE_INTERFACE /**< A interface IP connects the FPGA to another system or controller. */ -} type; +}; struct fpga_ip_type { struct fpga_vlnv vlnv; diff --git a/fpga/include/villas/kernel/kernel.h b/fpga/include/villas/kernel/kernel.h index 9a7f5eccb..9837e03b7 100644 --- a/fpga/include/villas/kernel/kernel.h +++ b/fpga/include/villas/kernel/kernel.h @@ -85,6 +85,6 @@ int kernel_get_page_size(); int kernel_get_hugepage_size(); /** Set SMP affinity of IRQ */ -int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old); +int kernel_irq_setaffinity(unsigned irq, uintmax_t affinity, uintmax_t *old); /** @} */ diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h index 821d0d6df..b88da6bf4 100644 --- a/fpga/include/villas/kernel/pci.h +++ b/fpga/include/villas/kernel/pci.h @@ -18,7 +18,7 @@ struct pci_device { struct { int vendor; int device; - int class; + int class_code; } id; struct { diff --git a/fpga/include/villas/log.h b/fpga/include/villas/log.h index 6d8771ee8..80de7c2f0 100644 --- a/fpga/include/villas/log.h +++ b/fpga/include/villas/log.h @@ -106,8 +106,8 @@ struct log { }; /** The global log instance. */ -struct log *global_log; -struct log default_log; +extern struct log *global_log; +extern struct log default_log; /** Initialize log object */ int log_init(struct log *l, int level, long faciltities); diff --git a/fpga/lib/card.c b/fpga/lib/card.c index f7606d0a0..651ab76ac 100644 --- a/fpga/lib/card.c +++ b/fpga/lib/card.c @@ -241,7 +241,7 @@ void fpga_card_dump(struct fpga_card *c) info("Slot: %04x:%02x:%02x.%d", c->vfio_device.pci_device->slot.domain, c->vfio_device.pci_device->slot.bus, c->vfio_device.pci_device->slot.device, c->vfio_device.pci_device->slot.function); info("Vendor ID: %04x", c->vfio_device.pci_device->id.vendor); info("Device ID: %04x", c->vfio_device.pci_device->id.device); - info("Class ID: %04x", c->vfio_device.pci_device->id.class); + info("Class ID: %04x", c->vfio_device.pci_device->id.class_code); info("BAR0 mapped at %p", c->map); diff --git a/fpga/lib/kernel/kernel.c b/fpga/lib/kernel/kernel.c index 78a60270a..dcac932db 100644 --- a/fpga/lib/kernel/kernel.c +++ b/fpga/lib/kernel/kernel.c @@ -258,7 +258,7 @@ int kernel_has_cap(cap_value_t cap) } #endif -int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old) +int kernel_irq_setaffinity(unsigned irq, uintmax_t affinity, uintmax_t *old) { char fn[64]; FILE *f; @@ -273,7 +273,7 @@ int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old) if (old) ret = fscanf(f, "%jx", old); - fprintf(f, "%jx", new); + fprintf(f, "%jx", affinity); fclose(f); return ret; diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index 98b68d7d5..56a8c17bd 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -221,7 +221,7 @@ int pci_device_parse_id(struct pci_device *f, const char *str, const char **erro goto fail; } - f->id.class = x; + f->id.class_code = x; } return 0; @@ -242,7 +242,7 @@ int pci_device_compare(const struct pci_device *d, const struct pci_device *f) (f->id.vendor != 0 && f->id.vendor != d->id.vendor)) return 1; - if ((f->id.class != 0) || (f->id.class != d->id.class)) + if ((f->id.class_code != 0) || (f->id.class_code != d->id.class_code)) return 1; // found From 5d4040adedbc85c8017b85d092db797aa0b3a2f8 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 5 Dec 2017 17:49:27 +0100 Subject: [PATCH 022/560] first port to C++ of plugin and fpga ip infrastructure --- fpga/include/villas/fpga/ip.hpp | 128 ++++++++++++++++++++++++ fpga/include/villas/fpga/ips/intc.hpp | 88 ++++++++++++++++ fpga/include/villas/fpga/vlnv.hpp | 86 ++++++++++++++++ fpga/include/villas/plugin.hpp | 81 +++++++++++++++ fpga/lib/CMakeLists.txt | 6 +- fpga/lib/ip.cpp | 95 ++++++++++++++++++ fpga/lib/ips/intc.cpp | 107 ++++++++------------ fpga/lib/plugin.cpp | 139 ++++++++++++++++++++++++++ fpga/lib/vlnv.cpp | 69 +++++++++++++ 9 files changed, 731 insertions(+), 68 deletions(-) create mode 100644 fpga/include/villas/fpga/ip.hpp create mode 100644 fpga/include/villas/fpga/ips/intc.hpp create mode 100644 fpga/include/villas/fpga/vlnv.hpp create mode 100644 fpga/include/villas/plugin.hpp create mode 100644 fpga/lib/ip.cpp create mode 100644 fpga/lib/plugin.cpp create mode 100644 fpga/lib/vlnv.cpp diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp new file mode 100644 index 000000000..0ed4bdca1 --- /dev/null +++ b/fpga/include/villas/fpga/ip.hpp @@ -0,0 +1,128 @@ +/** Interlectual Property component. + * + * This class represents a module within the FPGA. + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include "common.h" + +#include "log.h" +#include "utils.h" + +#include "fpga/vlnv.hpp" + +#include "plugin.hpp" +#include "card.h" + +#include + +#include + +namespace villas { + +//enum fpga_ip_types { +// FPGA_IP_TYPE_DM_DMA, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ +// FPGA_IP_TYPE_DM_FIFO, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ +// FPGA_IP_TYPE_MODEL, /**< A model IP simulates a system on the FPGA. */ +// FPGA_IP_TYPE_MATH, /**< A math IP performs some kind of mathematical operation on the streaming data */ +// FPGA_IP_TYPE_MISC, /**< Other IP components like timer, counters, interrupt conctrollers or routing. */ +// FPGA_IP_TYPE_INTERFACE /**< A interface IP connects the FPGA to another system or controller. */ +//} type; + + +class FpgaIpFactory; + +class FpgaIp { +public: + + friend FpgaIpFactory; + + FpgaIp() : card(nullptr), baseaddr(0), irq(-1), port(-1) {} + virtual ~FpgaIp() {} + + // IPs can implement this interface + virtual bool check() { return true; } + virtual bool start() { return true; } + virtual bool stop() { return true; } + virtual bool reset() { return true; } + virtual void dump() + { + info("IP %s: vlnv=%s baseaddr=%#jx, irq=%d, port=%d", + name.c_str(), vlnv.toString().c_str(), baseaddr, irq, port); + } + +protected: + uintptr_t + getBaseaddr() const + { + assert(card != nullptr); + return reinterpret_cast(card->map) + this->baseaddr; + } + +protected: + // populated by FpgaIpFactory + struct fpga_card *card; /**< FPGA card this IP is instantiated on */ + std::string name; /**< Name defined in JSON config */ + FpgaVlnv vlnv; /**< VLNV defined in JSON config */ + uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ + int irq; /**< The interrupt number of the FPGA IP component. */ + int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ +}; + + +class FpgaIpFactory : public Plugin { +public: + FpgaIpFactory() + { pluginType = Plugin::Type::FpgaIp; } + + /// Returns a running and checked FPGA IP + static FpgaIp* make(struct fpga_card* card, json_t *json, std::string name); + +private: + /// Create a concrete IP instance + virtual FpgaIp* create() = 0; + + /// Configure IP instance from JSON config + virtual bool configureJson(FpgaIp* ip, json_t *json) = 0; + + virtual FpgaVlnv getCompatibleVlnv() const = 0; + virtual std::string getName() const = 0; + virtual std::string getDescription() const = 0; + +private: + static FpgaIpFactory* + lookup(const FpgaVlnv& vlnv); +}; + +/** @} */ + +} // namespace villas diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp new file mode 100644 index 000000000..1facaa38c --- /dev/null +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -0,0 +1,88 @@ +/** AXI-PCIe Interrupt controller + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include "fpga/ip.hpp" +#include + +namespace villas { + +class InterruptController : public FpgaIp +{ +public: + using IrqMaskType = uint32_t; + static constexpr int maxIrqs = 32; + + ~InterruptController(); + + bool start(); + + int enableInterrupt(IrqMaskType mask, bool polling); + int disableInterrupt(IrqMaskType mask); + uint64_t waitForInterrupt(int irq); + +private: + struct Interrupt { + int eventFd; /**< Event file descriptor */ + int number; /**< Interrupt number from /proc/interrupts */ + bool polling; /**< Polled or not */ + }; + + int num_irqs; /**< Number of available MSI vectors */ + int efds[maxIrqs]; + int nos[maxIrqs]; + bool polling[maxIrqs]; +// Interrupt irqs[maxIrqs]; /**< State of available interrupts */ +}; + + + +class InterruptControllerFactory : public FpgaIpFactory { +public: + + FpgaIp* create() + { return new InterruptController; } + + std::string + getName() const + { return "InterruptController"; } + + std::string + getDescription() const + { return "Xilinx's programmable interrupt controller"; } + + FpgaVlnv getCompatibleVlnv() const + { return FpgaVlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } + + bool configureJson(FpgaIp* ip, json_t *json); +}; + +} // namespace villas + +/** @} */ diff --git a/fpga/include/villas/fpga/vlnv.hpp b/fpga/include/villas/fpga/vlnv.hpp new file mode 100644 index 000000000..c089ce4e9 --- /dev/null +++ b/fpga/include/villas/fpga/vlnv.hpp @@ -0,0 +1,86 @@ +/** Vendor, Library, Name, Version (VLNV) tag. + * + * @file + * @author Daniel Krebs + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include +#include + +namespace villas { + +class FpgaVlnv { +public: + + static constexpr char delimiter = ':'; + static constexpr char wildcard[] = "*"; + + FpgaVlnv() : + vendor(""), library(""), name(""), version("") {} + + FpgaVlnv(std::string s) { + parseFromString(s); + } + + std::string + toString() const + { + std::stringstream stream; + std::string string; + + stream << *this; + stream >> string; + + return string; + } + + bool + operator==(const FpgaVlnv& other) const; + + friend std::ostream& + operator<< (std::ostream& stream, const FpgaVlnv& vlnv) + { + return stream + << (vlnv.vendor.empty() ? "*" : vlnv.vendor) << ":" + << (vlnv.library.empty() ? "*" : vlnv.library) << ":" + << (vlnv.name.empty() ? "*" : vlnv.name) << ":" + << (vlnv.version.empty() ? "*" : vlnv.version); + } + +private: + void + parseFromString(std::string vlnv); + + std::string vendor; + std::string library; + std::string name; + std::string version; +}; + +} // namespace villas + +/** _FPGA_VLNV_HPP_ @} */ diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp new file mode 100644 index 000000000..1691c15ff --- /dev/null +++ b/fpga/include/villas/plugin.hpp @@ -0,0 +1,81 @@ +/** Loadable / plugin support. + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "utils.h" + +namespace villas { + +class Plugin { +public: + + enum class Type { + Unknown, + FpgaIp, + }; + + Plugin(); + virtual ~Plugin(); + + // each plugin is a singleton, so copying is not allowed + Plugin(Plugin const&) = delete; + void operator=(Plugin const&) = delete; + + int load(); + int unload(); + + virtual int parse(json_t *cfg); + virtual void dump(); + + /** Find registered and loaded plugin with given name and type. */ + static Plugin * + lookup(Type type, std::string name); + + static std::list + lookup(Type type); + + // check if this makes sense! (no intermediate plugins) + bool + operator==(const Plugin& other) const; + + Type pluginType; + + std::string name; + std::string description; + std::string path; + void *handle; + + enum state state; + +private: + using PluginList = std::list; + static std::list pluginList; +}; + +} // namespace villas diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 77eeb70ff..e13b43edd 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,6 +1,6 @@ set(SOURCES - ip.c - vlnv.c + ip.cpp + vlnv.cpp card.c ips/timer.c @@ -17,7 +17,7 @@ set(SOURCES kernel/pci.c kernel/vfio.c - plugin.c + plugin.cpp utils.c list.c log.c diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp new file mode 100644 index 000000000..d573ef03d --- /dev/null +++ b/fpga/lib/ip.cpp @@ -0,0 +1,95 @@ +/** FPGA IP component. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "log_config.h" +#include "log.h" +#include "plugin.h" + +#include "fpga/ip.hpp" + +#include + +namespace villas { + + +FpgaIpFactory* FpgaIpFactory::lookup(const FpgaVlnv &vlnv) +{ + for(auto& ip : Plugin::lookup(Plugin::Type::FpgaIp)) { + FpgaIpFactory* fpgaIpFactory = dynamic_cast(ip); + + if(fpgaIpFactory->getCompatibleVlnv() == vlnv) + return fpgaIpFactory; + } + + return nullptr; +} + +FpgaIp *FpgaIpFactory::make(fpga_card *card, json_t *json, std::string name) +{ + // extract VLNV from JSON + const char* vlnv_raw; + if(json_unpack(json, "{ s: s }", "vlnv", &vlnv_raw) != 0) + error("IP '%s' has no entry 'vlnv'", name.c_str()); + + // find the appropriate factory that can create the specified VLNV + // Note: + // This is the magic part! Factories automatically register as a plugin + // as soon as they are instantiated. If there are multiple candidates, + // the first suitable factory will be used. + FpgaVlnv vlnv(vlnv_raw); + FpgaIpFactory* fpgaIpFactory = lookup(vlnv); + + if(fpgaIpFactory == nullptr) { + error("No ip factory registered to handle VLNV '%s'", vlnv.toString().c_str()); + } else { + info("Using %s for IP %s", fpgaIpFactory->getName().c_str(), vlnv.toString().c_str()); + } + + // create new IP instance + FpgaIp* ip = fpgaIpFactory->create(); + + // setup generic IP type properties + ip->card = card; + ip->name = name; + ip->vlnv = vlnv; + + // extract some optional properties + int ret = json_unpack(json, "{ s?: i, s?: i, s?: i }", + "baseaddr", &ip->baseaddr, + "irq", &ip->irq, + "port", &ip->port); + if(ret != 0) + error("Problem while parsing JSON"); + + // IP-specific setup via JSON config + fpgaIpFactory->configureJson(ip, json); + + if(not ip->start()) + error("Cannot start IP"); + + if(not ip->check()) + error("Checking IP failed"); + + return ip; +} + +} // namespace villas diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 7fcf109d6..d17a2c9a1 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -24,40 +24,37 @@ #include "config.h" #include "log.h" -#include "plugin.h" +#include "plugin.hpp" #include "kernel/vfio.h" #include "kernel/kernel.h" #include "fpga/ip.h" #include "fpga/card.h" -#include "fpga/ips/intc.h" +#include "fpga/ips/intc.hpp" -int intc_start(struct fpga_ip *c) +namespace villas { + +InterruptController::~InterruptController() { - int ret; + vfio_pci_msi_deinit(&card->vfio_device , this->efds); +} - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; +bool InterruptController::start() +{ + const uintptr_t base = getBaseaddr(); - uintptr_t base = (uintptr_t) f->map + c->baseaddr; + num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); + if (num_irqs < 0) + return false; - if (c != f->intc) - error("There can be only one interrupt controller per FPGA"); - - intc->num_irqs = vfio_pci_msi_init(&f->vfio_device, intc->efds); - if (intc->num_irqs < 0) - return -1; - - ret = vfio_pci_msi_find(&f->vfio_device, intc->nos); - if (ret) - return -2; + if(vfio_pci_msi_find(&card->vfio_device, nos) != 0) + return false; /* For each IRQ */ - for (int i = 0; i < intc->num_irqs; i++) { + for (int i = 0; i < num_irqs; i++) { /* Pin to core */ - ret = kernel_irq_setaffinity(intc->nos[i], f->affinity, NULL); - if (ret) + if(kernel_irq_setaffinity(nos[i], card->affinity, NULL) !=0) serror("Failed to change affinity of VFIO-MSI interrupt"); /* Setup vector */ @@ -72,26 +69,14 @@ int intc_start(struct fpga_ip *c) debug(4, "FPGA: enabled interrupts"); - return 0; + return true; } -int intc_destroy(struct fpga_ip *c) +int +InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool polling) { - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; - - vfio_pci_msi_deinit(&f->vfio_device, intc->efds); - - return 0; -} - -int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) -{ - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; - uint32_t ier, imr; - uintptr_t base = (uintptr_t) f->map + c->baseaddr; + const uintptr_t base = getBaseaddr(); /* Current state of INTC */ ier = XIntc_In32(base + XIN_IER_OFFSET); @@ -100,12 +85,12 @@ int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) /* Clear pending IRQs */ XIntc_Out32(base + XIN_IAR_OFFSET, mask); - for (int i = 0; i < intc->num_irqs; i++) { + for (int i = 0; i < num_irqs; i++) { if (mask & (1 << i)) - intc->flags[i] = flags; + this->polling[i] = polling; } - if (flags & INTC_POLLING) { + if (polling) { XIntc_Out32(base + XIN_IMR_OFFSET, imr & ~mask); XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); } @@ -118,31 +103,29 @@ int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) debug(3, "New imr = %#x", XIntc_In32(base + XIN_IMR_OFFSET)); debug(3, "New isr = %#x", XIntc_In32(base + XIN_ISR_OFFSET)); - debug(8, "FPGA: Interupt enabled: mask=%#x flags=%#x", mask, flags); + debug(8, "FPGA: Interupts enabled: mask=%#x polling=%d", mask, polling); - return 0; + return true; } -int intc_disable(struct fpga_ip *c, uint32_t mask) +int +InterruptController::disableInterrupt(InterruptController::IrqMaskType mask) { - struct fpga_card *f = c->card; - - uintptr_t base = (uintptr_t) f->map + c->baseaddr; + const uintptr_t base = getBaseaddr(); uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); - return 0; + return true; } -uint64_t intc_wait(struct fpga_ip *c, int irq) +uint64_t InterruptController::waitForInterrupt(int irq) { - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; + assert(irq < maxIrqs); - uintptr_t base = (uintptr_t) f->map + c->baseaddr; + const uintptr_t base = getBaseaddr(); - if (intc->flags[irq] & INTC_POLLING) { + if (this->polling[irq]) { uint32_t isr, mask = 1 << irq; do { @@ -156,7 +139,7 @@ uint64_t intc_wait(struct fpga_ip *c, int irq) } else { uint64_t cnt; - ssize_t ret = read(intc->efds[irq], &cnt, sizeof(cnt)); + ssize_t ret = read(efds[irq], &cnt, sizeof(cnt)); if (ret != sizeof(cnt)) return 0; @@ -164,17 +147,11 @@ uint64_t intc_wait(struct fpga_ip *c, int irq) } } -static struct plugin p = { - .name = "Xilinx's programmable interrupt controller", - .description = "", - .type = PLUGIN_TYPE_FPGA_IP, - .ip = { - .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }, - .type = FPGA_IP_TYPE_MISC, - .start = intc_start, - .destroy = intc_destroy, - .size = sizeof(struct intc) - } -}; -REGISTER_PLUGIN(&p) +bool InterruptControllerFactory::configureJson(FpgaIp *ip, json_t *json) +{ + // parse json and configure instance here + return true; +} + +} // namespace villas diff --git a/fpga/lib/plugin.cpp b/fpga/lib/plugin.cpp new file mode 100644 index 000000000..309314da7 --- /dev/null +++ b/fpga/lib/plugin.cpp @@ -0,0 +1,139 @@ +/** Loadable / plugin support. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "plugin.hpp" + + +namespace villas { + +// list of all registered plugins +Plugin::PluginList Plugin::pluginList; + + +Plugin::Plugin() : + name(""), + description(""), + path(""), + pluginType(Plugin::Type::Unknown), + state(STATE_INITIALIZED) +{ + // push to global plugin list + pluginList.push_back(this); +} + +Plugin::~Plugin() +{ + // clean from global plugin list + pluginList.remove(this); +} + + +int +Plugin::parse(json_t *cfg) +{ + const char *path; + + path = json_string_value(cfg); + if (!path) + return -1; + + this->path = std::string(path); + this->state = STATE_PARSED; + + return 0; +} + +int +Plugin::load() +{ + assert(this->state == STATE_PARSED); + assert(not this->path.empty()); + + this->handle = dlopen(this->path.c_str(), RTLD_NOW); + + if (this->handle == nullptr) + return -1; + + this->state = STATE_LOADED; + + return 0; +} + +int +Plugin::unload() +{ + int ret; + + assert(this->state == STATE_LOADED); + + ret = dlclose(this->handle); + if (ret != 0) + return -1; + + this->state = STATE_UNLOADED; + + return 0; +} + +void +Plugin::dump() +{ + std::cout << " - " << this->name << ": " << this->description << std::endl; +} + +Plugin* +Plugin::lookup(Plugin::Type type, std::string name) +{ + for(auto& p : pluginList) { + if(p->pluginType == type and p->name == name) + return p; + } + + return nullptr; +} + +std::list +Plugin::lookup(Plugin::Type type) +{ + std::list list; + for(auto& p : pluginList) { + if(p->pluginType == type) + list.push_back(p); + } + + return list; +} + +bool +Plugin::operator==(const Plugin &other) const +{ + return (this->pluginType == other.pluginType) and (this->name == other.name); +} + +} // namespace villas diff --git a/fpga/lib/vlnv.cpp b/fpga/lib/vlnv.cpp new file mode 100644 index 000000000..b9b007ce4 --- /dev/null +++ b/fpga/lib/vlnv.cpp @@ -0,0 +1,69 @@ +/** Vendor, Library, Name, Version (VLNV) tag + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include +#include + +#include "fpga/vlnv.hpp" +#include "fpga/ip.hpp" + +namespace villas { + +bool +FpgaVlnv::operator==(const FpgaVlnv &other) const +{ + // if a field is empty, it means wildcard matching everything + const bool vendorWildcard = vendor.empty() or other.vendor.empty(); + const bool libraryWildcard = library.empty() or other.library.empty(); + const bool nameWildcard = name.empty() or other.name.empty(); + const bool versionWildcard = version.empty() or other.version.empty(); + + const bool vendorMatch = vendorWildcard or vendor == other.vendor; + const bool libraryMatch = libraryWildcard or library == other.library; + const bool nameMatch = nameWildcard or name == other.name; + const bool versionMatch = versionWildcard or version == other.version; + + return vendorMatch and libraryMatch and nameMatch and versionMatch; +} + +void +FpgaVlnv::parseFromString(std::string vlnv) +{ + // tokenize by delimiter + std::stringstream sstream(vlnv); + std::getline(sstream, vendor, delimiter); + std::getline(sstream, library, delimiter); + std::getline(sstream, name, delimiter); + std::getline(sstream, version, delimiter); + + // represent wildcard internally as empty string + if(vendor == wildcard) vendor = ""; + if(library == wildcard) library = ""; + if(name == wildcard) name = ""; + if(version == wildcard) version = ""; +} + + +} // namespace villas From e735c7e24837fc2a3a8da0f5d09833fe8e79316c Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 5 Dec 2017 18:07:10 +0100 Subject: [PATCH 023/560] make linking of the lib work by using old C-symbols until replaced --- fpga/include/villas/fpga/ip.h | 7 +++++++ fpga/include/villas/fpga/vlnv.h | 8 ++++++++ fpga/include/villas/fpga/vlnv.hpp | 1 - fpga/include/villas/kernel/kernel.h | 8 ++++++++ fpga/include/villas/kernel/vfio.h | 8 ++++++++ fpga/lib/CMakeLists.txt | 3 +++ fpga/lib/ips/intc.cpp | 3 +++ fpga/lib/log.c | 1 + fpga/lib/vlnv.cpp | 8 ++++---- 9 files changed, 42 insertions(+), 5 deletions(-) diff --git a/fpga/include/villas/fpga/ip.h b/fpga/include/villas/fpga/ip.h index 5c078f596..b6723043d 100644 --- a/fpga/include/villas/fpga/ip.h +++ b/fpga/include/villas/fpga/ip.h @@ -44,6 +44,10 @@ #include "fpga/ips/dft.h" #include "fpga/ips/intc.h" +#ifdef __cplusplus +extern "C" { +#endif + enum fpga_ip_types { FPGA_IP_TYPE_DM_DMA, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ FPGA_IP_TYPE_DM_FIFO, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ @@ -115,5 +119,8 @@ int fpga_ip_reset(struct fpga_ip *c); /** Find a registered FPGA IP core type with the given VLNV identifier. */ struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr); +#ifdef __cplusplus +} +#endif /** @} */ diff --git a/fpga/include/villas/fpga/vlnv.h b/fpga/include/villas/fpga/vlnv.h index d24385271..2de8bbbcb 100644 --- a/fpga/include/villas/fpga/vlnv.h +++ b/fpga/include/villas/fpga/vlnv.h @@ -28,6 +28,10 @@ #ifndef _FPGA_VLNV_H_ #define _FPGA_VLNV_H_ +#ifdef __cplusplus +extern "C" { +#endif + /* Forward declarations */ struct list; @@ -50,4 +54,8 @@ int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv); /** Release memory allocated by fpga_vlnv_parse(). */ int fpga_vlnv_destroy(struct fpga_vlnv *v); +#ifdef __cplusplus +} +#endif + #endif /** _FPGA_VLNV_H_ @} */ diff --git a/fpga/include/villas/fpga/vlnv.hpp b/fpga/include/villas/fpga/vlnv.hpp index c089ce4e9..7785c56d2 100644 --- a/fpga/include/villas/fpga/vlnv.hpp +++ b/fpga/include/villas/fpga/vlnv.hpp @@ -37,7 +37,6 @@ class FpgaVlnv { public: static constexpr char delimiter = ':'; - static constexpr char wildcard[] = "*"; FpgaVlnv() : vendor(""), library(""), name(""), version("") {} diff --git a/fpga/include/villas/kernel/kernel.h b/fpga/include/villas/kernel/kernel.h index 9837e03b7..93cfa3130 100644 --- a/fpga/include/villas/kernel/kernel.h +++ b/fpga/include/villas/kernel/kernel.h @@ -28,6 +28,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* Forward declarations */ struct version; @@ -87,4 +91,8 @@ int kernel_get_hugepage_size(); /** Set SMP affinity of IRQ */ int kernel_irq_setaffinity(unsigned irq, uintmax_t affinity, uintmax_t *old); +#ifdef __cplusplus +} +#endif + /** @} */ diff --git a/fpga/include/villas/kernel/vfio.h b/fpga/include/villas/kernel/vfio.h index bb6f12329..1b4a9c336 100644 --- a/fpga/include/villas/kernel/vfio.h +++ b/fpga/include/villas/kernel/vfio.h @@ -20,6 +20,10 @@ #define VFIO_DEV(x) "/dev/vfio/" x +#ifdef __cplusplus +extern "C" { +#endif + /* Forward declarations */ struct pci_device; @@ -109,4 +113,8 @@ int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_ /** munmap() a region which has been mapped by vfio_map_region() */ int vfio_unmap_region(struct vfio_device *d, int idx); +#ifdef __cplusplus +} +#endif + /** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index e13b43edd..1c03327a8 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,6 +1,8 @@ set(SOURCES ip.cpp + ip.c vlnv.cpp + vlnv.c card.c ips/timer.c @@ -17,6 +19,7 @@ set(SOURCES kernel/pci.c kernel/vfio.c + plugin.c plugin.cpp utils.c list.c diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index d17a2c9a1..9471636e9 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -35,6 +35,9 @@ namespace villas { +// instantiate factory to make available to plugin infrastructure +static InterruptControllerFactory factory; + InterruptController::~InterruptController() { vfio_pci_msi_deinit(&card->vfio_device , this->efds); diff --git a/fpga/lib/log.c b/fpga/lib/log.c index b9af2d1b6..0fe097288 100644 --- a/fpga/lib/log.c +++ b/fpga/lib/log.c @@ -39,6 +39,7 @@ #endif struct log *global_log; +struct log default_log; /* We register a default log instance */ __attribute__((constructor)) diff --git a/fpga/lib/vlnv.cpp b/fpga/lib/vlnv.cpp index b9b007ce4..cf44c483f 100644 --- a/fpga/lib/vlnv.cpp +++ b/fpga/lib/vlnv.cpp @@ -59,10 +59,10 @@ FpgaVlnv::parseFromString(std::string vlnv) std::getline(sstream, version, delimiter); // represent wildcard internally as empty string - if(vendor == wildcard) vendor = ""; - if(library == wildcard) library = ""; - if(name == wildcard) name = ""; - if(version == wildcard) version = ""; + if(vendor == "*") vendor = ""; + if(library == "*") library = ""; + if(name == "*") name = ""; + if(version == "*") version = ""; } From 151abd2fd5bf275986056185499d5fbc346423da Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 5 Dec 2017 18:12:22 +0100 Subject: [PATCH 024/560] re-add old interrupt controller to make project compile again --- fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/intc.c | 180 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 fpga/lib/ips/intc.c diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 1c03327a8..30a98fddc 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES ips/fifo.c ips/dma.c ips/intc.cpp + ips/intc.c ips/gpio.c ips/rtds_axis.c diff --git a/fpga/lib/ips/intc.c b/fpga/lib/ips/intc.c new file mode 100644 index 000000000..7fcf109d6 --- /dev/null +++ b/fpga/lib/ips/intc.c @@ -0,0 +1,180 @@ +/** AXI-PCIe Interrupt controller + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" +#include "log.h" +#include "plugin.h" + +#include "kernel/vfio.h" +#include "kernel/kernel.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/intc.h" + +int intc_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + if (c != f->intc) + error("There can be only one interrupt controller per FPGA"); + + intc->num_irqs = vfio_pci_msi_init(&f->vfio_device, intc->efds); + if (intc->num_irqs < 0) + return -1; + + ret = vfio_pci_msi_find(&f->vfio_device, intc->nos); + if (ret) + return -2; + + /* For each IRQ */ + for (int i = 0; i < intc->num_irqs; i++) { + /* Pin to core */ + ret = kernel_irq_setaffinity(intc->nos[i], f->affinity, NULL); + if (ret) + serror("Failed to change affinity of VFIO-MSI interrupt"); + + /* Setup vector */ + XIntc_Out32(base + XIN_IVAR_OFFSET + i * 4, i); + } + + XIntc_Out32(base + XIN_IMR_OFFSET, 0); /* Use manual acknowlegement for all IRQs */ + XIntc_Out32(base + XIN_IAR_OFFSET, 0xFFFFFFFF); /* Acknowlege all pending IRQs manually */ + XIntc_Out32(base + XIN_IMR_OFFSET, 0xFFFFFFFF); /* Use fast acknowlegement for all IRQs */ + XIntc_Out32(base + XIN_IER_OFFSET, 0x00000000); /* Disable all IRQs by default */ + XIntc_Out32(base + XIN_MER_OFFSET, XIN_INT_HARDWARE_ENABLE_MASK | XIN_INT_MASTER_ENABLE_MASK); + + debug(4, "FPGA: enabled interrupts"); + + return 0; +} + +int intc_destroy(struct fpga_ip *c) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + vfio_pci_msi_deinit(&f->vfio_device, intc->efds); + + return 0; +} + +int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uint32_t ier, imr; + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + /* Current state of INTC */ + ier = XIntc_In32(base + XIN_IER_OFFSET); + imr = XIntc_In32(base + XIN_IMR_OFFSET); + + /* Clear pending IRQs */ + XIntc_Out32(base + XIN_IAR_OFFSET, mask); + + for (int i = 0; i < intc->num_irqs; i++) { + if (mask & (1 << i)) + intc->flags[i] = flags; + } + + if (flags & INTC_POLLING) { + XIntc_Out32(base + XIN_IMR_OFFSET, imr & ~mask); + XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); + } + else { + XIntc_Out32(base + XIN_IER_OFFSET, ier | mask); + XIntc_Out32(base + XIN_IMR_OFFSET, imr | mask); + } + + debug(3, "New ier = %#x", XIntc_In32(base + XIN_IER_OFFSET)); + debug(3, "New imr = %#x", XIntc_In32(base + XIN_IMR_OFFSET)); + debug(3, "New isr = %#x", XIntc_In32(base + XIN_ISR_OFFSET)); + + debug(8, "FPGA: Interupt enabled: mask=%#x flags=%#x", mask, flags); + + return 0; +} + +int intc_disable(struct fpga_ip *c, uint32_t mask) +{ + struct fpga_card *f = c->card; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); + + XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); + + return 0; +} + +uint64_t intc_wait(struct fpga_ip *c, int irq) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + if (intc->flags[irq] & INTC_POLLING) { + uint32_t isr, mask = 1 << irq; + + do { + isr = XIntc_In32(base + XIN_ISR_OFFSET); + pthread_testcancel(); + } while ((isr & mask) != mask); + + XIntc_Out32(base + XIN_IAR_OFFSET, mask); + + return 1; + } + else { + uint64_t cnt; + ssize_t ret = read(intc->efds[irq], &cnt, sizeof(cnt)); + if (ret != sizeof(cnt)) + return 0; + + return cnt; + } +} + +static struct plugin p = { + .name = "Xilinx's programmable interrupt controller", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = intc_start, + .destroy = intc_destroy, + .size = sizeof(struct intc) + } +}; + +REGISTER_PLUGIN(&p) From 68c9f084576ae406a6401cfea86293fc023a66e7 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 12:08:33 +0100 Subject: [PATCH 025/560] plugin: use Nifty Counter Idiom to intialize plugin list Just using a standard std::list<> to hold plugins is problematic, because we want to push Plugins to the list from within each Plugin's constructor that is executed during static initialization. Since the order of static initialization is undefined in C++, it may happen that a Plugin constructor is executed before the list could be initialized. Therefore, we use the Nifty Counter Idiom [1] to initialize the list ourself before the first usage. In short: - allocate a buffer for the list - initialize list before first usage - (complicatedly) declaring a buffer is neccessary in order to avoid that the constructor of the static list is executed again --- fpga/include/villas/plugin.hpp | 23 ++++++++++++++++++++++- fpga/lib/plugin.cpp | 19 ++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index 1691c15ff..b2ce41755 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -74,8 +74,29 @@ public: enum state state; private: + /* Just using a standard std::list<> to hold plugins is problematic, because + we want to push Plugins to the list from within each Plugin's constructor + that is executed during static initialization. Since the order of static + initialization is undefined in C++, it may happen that a Plugin + constructor is executed before the list could be initialized. Therefore, + we use the Nifty Counter Idiom [1] to initialize the list ourself before + the first usage. + + In short: + - allocate a buffer for the list + - initialize list before first usage + - (complicatedly) declaring a buffer is neccessary in order to avoid + that the constructor of the static list is executed again + + [1] https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter + */ + using PluginList = std::list; - static std::list pluginList; + using PluginListBuffer = typename std::aligned_storage::type; + + static PluginListBuffer pluginListBuffer; ///< buffer to hold a PluginList + static PluginList& pluginList; ///< reference to pluginListBuffer + static int pluginListNiftyCounter; ///< track if pluginList has been initialized }; } // namespace villas diff --git a/fpga/lib/plugin.cpp b/fpga/lib/plugin.cpp index 309314da7..fd3c83494 100644 --- a/fpga/lib/plugin.cpp +++ b/fpga/lib/plugin.cpp @@ -25,7 +25,9 @@ #include #include -#include + +#include +#include #include "plugin.hpp" @@ -33,16 +35,27 @@ namespace villas { // list of all registered plugins -Plugin::PluginList Plugin::pluginList; +Plugin::PluginList& +Plugin::pluginList = reinterpret_cast(Plugin::pluginListBuffer); + +Plugin::PluginListBuffer +Plugin::pluginListBuffer; + +// relies on zero initialization +int Plugin::pluginListNiftyCounter; Plugin::Plugin() : + pluginType(Plugin::Type::Unknown), name(""), description(""), path(""), - pluginType(Plugin::Type::Unknown), state(STATE_INITIALIZED) { + // see comment in plugin.hpp on why we need to do this (Nifty Counter Idiom) + if(pluginListNiftyCounter++ == 0) + new (&pluginList) PluginList; + // push to global plugin list pluginList.push_back(this); } From 7d883089c2136ed573f89dfc17c474387dbe4c32 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 12:37:24 +0100 Subject: [PATCH 026/560] lib/card: copy C->C++ and just make it compile --- fpga/include/villas/fpga/card.h | 8 + fpga/include/villas/fpga/card.hpp | 102 ++++++++++ fpga/include/villas/kernel/pci.h | 8 + fpga/include/villas/list.h | 8 + fpga/include/villas/utils.h | 8 + fpga/lib/CMakeLists.txt | 1 + fpga/lib/card.cpp | 324 ++++++++++++++++++++++++++++++ 7 files changed, 459 insertions(+) create mode 100644 fpga/include/villas/fpga/card.hpp create mode 100644 fpga/lib/card.cpp diff --git a/fpga/include/villas/fpga/card.h b/fpga/include/villas/fpga/card.h index 89d19c0c1..f352fb081 100644 --- a/fpga/include/villas/fpga/card.h +++ b/fpga/include/villas/fpga/card.h @@ -35,6 +35,10 @@ #include "kernel/pci.h" #include "kernel/vfio.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Forward declarations */ struct fpga_ip; struct vfio_container; @@ -92,4 +96,8 @@ void fpga_card_dump(struct fpga_card *c); /** Reset the FPGA to a known state */ int fpga_card_reset(struct fpga_card *c); +#ifdef __cplusplus +} +#endif + /** @} */ diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp new file mode 100644 index 000000000..a1111d804 --- /dev/null +++ b/fpga/include/villas/fpga/card.hpp @@ -0,0 +1,102 @@ +/** FPGA card + * + * This class represents a FPGA device. + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include "common.h" +#include "kernel/pci.h" +#include "kernel/vfio.h" + +#include "plugin.hpp" + +namespace villas { + +/* Forward declarations */ +struct fpga_ip; +struct vfio_container; + +struct fpga_card { + char *name; /**< The name of the FPGA card */ + + enum state state; /**< The state of this FPGA card. */ + + struct pci *pci; + struct pci_device filter; /**< Filter for PCI device. */ + + struct vfio_container *vfio_container; + struct vfio_device vfio_device; /**< VFIO device handle. */ + + int do_reset; /**< Reset VILLASfpga during startup? */ + int affinity; /**< Affinity for MSI interrupts */ + + struct list ips; /**< List of IP components on FPGA. */ + + char *map; /**< PCI BAR0 mapping for register access */ + + size_t maplen; + size_t dmalen; + + /* Some IP cores are special and referenced here */ + struct fpga_ip *intc; + struct fpga_ip *reset; + struct fpga_ip *sw; +}; + +/** Initialize FPGA card and its IP components. */ +int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc); + +/** Parse configuration of FPGA card including IP cores from config. */ +int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name); + +int fpga_card_parse_list(struct list *l, json_t *cfg); + +/** Check if the FPGA card configuration is plausible. */ +int fpga_card_check(struct fpga_card *c); + +/** Start FPGA card. */ +int fpga_card_start(struct fpga_card *c); + +/** Stop FPGA card. */ +int fpga_card_stop(struct fpga_card *c); + +/** Destroy FPGA card. */ +int fpga_card_destroy(struct fpga_card *c); + +/** Dump details of FPGA card to stdout. */ +void fpga_card_dump(struct fpga_card *c); + +/** Reset the FPGA to a known state */ +int fpga_card_reset(struct fpga_card *c); + +} // namespace villas + +/** @} */ diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h index b88da6bf4..d784cedf8 100644 --- a/fpga/include/villas/kernel/pci.h +++ b/fpga/include/villas/kernel/pci.h @@ -14,6 +14,10 @@ #define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) #define PCI_FUNC(devfn) ((devfn) & 0x07) +#ifdef __cplusplus +extern "C" { +#endif + struct pci_device { struct { int vendor; @@ -59,4 +63,8 @@ int pci_attach_driver(struct pci_device *d, const char *driver); /** Return the IOMMU group of this PCI device or -1 if the device is not in a group. */ int pci_get_iommu_group(struct pci_device *d); +#ifdef __cplusplus +} +#endif + /** @} */ diff --git a/fpga/include/villas/list.h b/fpga/include/villas/list.h index f6ba29dae..b6d8c56e2 100644 --- a/fpga/include/villas/list.h +++ b/fpga/include/villas/list.h @@ -45,6 +45,10 @@ __attribute__((destructor(105))) static void UNIQUE(__dtor)() { \ #define list_first(list) list_at(list, 0) #define list_last(list) list_at(list, (list)->length-1) +#ifdef __cplusplus +extern "C" { +#endif + /** Callback to destroy list elements. * * @param data A pointer to the data which should be freed. @@ -110,3 +114,7 @@ void list_sort(struct list *l, cmp_cb_t cmp); /** Set single element in list */ int list_set(struct list *l, int index, void *value); + +#ifdef __cplusplus +} +#endif diff --git a/fpga/include/villas/utils.h b/fpga/include/villas/utils.h index b552cc52b..272afd6eb 100644 --- a/fpga/include/villas/utils.h +++ b/fpga/include/villas/utils.h @@ -32,6 +32,10 @@ #include "log.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifdef __GNUC__ #define LIKELY(x) __builtin_expect((x),1) #define UNLIKELY(x) __builtin_expect((x),0) @@ -274,3 +278,7 @@ pid_t spawn(const char *name, char *const argv[]); /** Determines the string length as printed on the screen (ignores escable sequences). */ size_t strlenp(const char *str); + +#ifdef __cplusplus +} +#endif diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 30a98fddc..f80b17e8a 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES vlnv.cpp vlnv.c card.c + card.cpp ips/timer.c ips/model.c diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp new file mode 100644 index 000000000..f225cbd98 --- /dev/null +++ b/fpga/lib/card.cpp @@ -0,0 +1,324 @@ +/** FPGA card. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "config.h" +#include "log.h" +#include "log_config.h" +#include "list.h" +#include "utils.h" + +#include "kernel/pci.h" +#include "kernel/vfio.h" + +#include "fpga/ip.h" +#include "fpga/card.h" + +namespace villas { + +int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) +{ + assert(c->state == STATE_DESTROYED); + + c->vfio_container = vc; + c->pci = pci; + + list_init(&c->ips); + + /* Default values */ + c->filter.id.vendor = FPGA_PCI_VID_XILINX; + c->filter.id.device = FPGA_PCI_PID_VFPGA; + + c->affinity = 0; + c->do_reset = 0; + + c->state = STATE_INITIALIZED; + + return 0; +} + +int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name) +{ + int ret; + + json_t *json_ips; + json_t *json_slot = NULL; + json_t *json_id = NULL; + json_error_t err; + + c->name = strdup(name); + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: b, s?: o, s?: o, s: o }", + "affinity", &c->affinity, + "do_reset", &c->do_reset, + "slot", &json_slot, + "id", &json_id, + "ips", &json_ips + ); + if (ret) + jerror(&err, "Failed to parse FPGA vard configuration"); + + if (json_slot) { + const char *err, *slot; + + slot = json_string_value(json_slot); + if (slot) { + ret = pci_device_parse_slot(&c->filter, slot, &err); + if (ret) + error("Failed to parse PCI slot: %s", err); + } + else + error("PCI slot must be a string"); + } + + if (json_id) { + const char *err, *id; + + id = json_string_value(json_id); + if (id) { + ret = pci_device_parse_id(&c->filter, (char*) id, &err); + if (ret) + error("Failed to parse PCI id: %s", err); + } + else + error("PCI ID must be a string"); + } + + if (!json_is_object(json_ips)) + error("FPGA card IPs section must be an object"); + + const char *name_ip; + json_t *json_ip; + json_object_foreach(json_ips, name_ip, json_ip) { + const char *vlnv; + + struct fpga_ip_type *vt; + struct fpga_ip *ip = (struct fpga_ip *) alloc(sizeof(struct fpga_ip)); + + ip->card = c; + + ret = json_unpack_ex(json_ip, &err, 0, "{ s: s }", "vlnv", &vlnv); + if (ret) + error("Failed to parse FPGA IP '%s' of card '%s'", name_ip, name); + + vt = fpga_ip_type_lookup(vlnv); + if (!vt) + error("FPGA IP core VLNV identifier '%s' is invalid", vlnv); + + ret = fpga_ip_init(ip, vt); + if (ret) + error("Failed to initalize FPGA IP core"); + + ret = fpga_ip_parse(ip, json_ip, name_ip); + if (ret) + error("Failed to parse FPGA IP core"); + + list_push(&c->ips, ip); + } + + c->state = STATE_PARSED; + + return 0; +} + +int fpga_card_parse_list(struct list *cards, json_t *cfg) +{ + int ret; + + if (!json_is_object(cfg)) + error("FPGA card configuration section must be a JSON object"); + + const char *name; + json_t *json_fpga; + json_object_foreach(cfg, name, json_fpga) { + struct fpga_card *c = (struct fpga_card *) alloc(sizeof(struct fpga_card)); + + ret = ::fpga_card_parse(c, json_fpga, name); + if (ret) + error("Failed to parse FPGA card configuration"); + + list_push(cards, c); + } + + return 0; +} + +int fpga_card_start(struct fpga_card *c) +{ + int ret; + + struct pci_device *pdev; + + assert(c->state == STATE_INITIALIZED); + + /* Search for FPGA card */ + pdev = pci_lookup_device(c->pci, &c->filter); + if (!pdev) + error("Failed to find PCI device"); + + /* Attach PCIe card to VFIO container */ + ret = vfio_pci_attach(&c->vfio_device, c->vfio_container, pdev); + if (ret) + error("Failed to attach VFIO device"); + + /* Map PCIe BAR */ + c->map = (char*) vfio_map_region(&c->vfio_device, VFIO_PCI_BAR0_REGION_INDEX); + if (c->map == MAP_FAILED) + serror("Failed to mmap() BAR0"); + + /* Enable memory access and PCI bus mastering for DMA */ + ret = vfio_pci_enable(&c->vfio_device); + if (ret) + serror("Failed to enable PCI device"); + + /* Reset system? */ + if (c->do_reset) { + /* Reset / detect PCI device */ + ret = vfio_pci_reset(&c->vfio_device); + if (ret) + serror("Failed to reset PCI device"); + + ret = fpga_card_reset(c); + if (ret) + error("Failed to reset FGPA card"); + } + + /* Initialize IP cores */ + for (size_t j = 0; j < list_length(&c->ips); j++) { + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + ret = fpga_ip_start(i); + if (ret) + error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); + } + + c->state = STATE_STARTED; + + return 0; +} + +int fpga_card_stop(struct fpga_card *c) +{ + int ret; + + assert(c->state == STATE_STOPPED); + + for (size_t j = 0; j < list_length(&c->ips); j++) { + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + ret = fpga_ip_stop(i); + if (ret) + error("Failed to stop FPGA IP core: %s (%u)", i->name, ret); + } + + c->state = STATE_STOPPED; + + return 0; +} + +void fpga_card_dump(struct fpga_card *c) +{ + info("VILLASfpga card:"); + { INDENT + info("Slot: %04x:%02x:%02x.%d", c->vfio_device.pci_device->slot.domain, c->vfio_device.pci_device->slot.bus, c->vfio_device.pci_device->slot.device, c->vfio_device.pci_device->slot.function); + info("Vendor ID: %04x", c->vfio_device.pci_device->id.vendor); + info("Device ID: %04x", c->vfio_device.pci_device->id.device); + info("Class ID: %04x", c->vfio_device.pci_device->id.class_code); + + info("BAR0 mapped at %p", c->map); + + info("IP blocks:"); + for (size_t j = 0; j < list_length(&c->ips); j++) { INDENT + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + fpga_ip_dump(i); + } + } + + vfio_dump(c->vfio_device.group->container); +} + +int fpga_card_check(struct fpga_card *c) +{ + assert(c->state == STATE_PARSED); + + + /* Check FPGA configuration */ + struct fpga_vlnv vlnv_reset = { "xilinx.com", "ip", "axi_gpio", NULL }; + c->reset = fpga_vlnv_lookup(&c->ips, &vlnv_reset); + if (!c->reset) + error("FPGA is missing a reset controller"); + + struct fpga_vlnv vlnv_intc = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }; + c->intc = fpga_vlnv_lookup(&c->ips, &vlnv_intc); + if (!c->intc) + error("FPGA is missing a interrupt controller"); + + struct fpga_vlnv vlnv_sw = { "xilinx.com", "ip", "axis_interconnect", NULL }; + c->sw = fpga_vlnv_lookup(&c->ips, &vlnv_sw); + if (!c->sw) + warn("FPGA is missing an AXI4-Stream switch"); + + return 0; +} + +int fpga_card_destroy(struct fpga_card *c) +{ + list_destroy(&c->ips, (dtor_cb_t) fpga_ip_destroy, true); + + return 0; +} + +int fpga_card_reset(struct fpga_card *c) +{ + int ret; + char state[4096]; + + /* Save current state of PCI configuration space */ + ret = pread(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); + if (ret != sizeof(state)) + return -1; + + uint32_t *rst_reg = (uint32_t *) (c->map + c->reset->baseaddr); + + debug(3, "FPGA: reset"); + rst_reg[0] = 1; + + usleep(100000); + + /* Restore previous state of PCI configuration space */ + ret = pwrite(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); + if (ret != sizeof(state)) + return -1; + + /* After reset the value should be zero again */ + if (rst_reg[0]) + return -2; + + c->state = STATE_INITIALIZED; + + return 0; +} + +} // namespace villas From d63c2b30bffd2ac25d94c639b51da5d1aa3950d4 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 14:43:08 +0100 Subject: [PATCH 027/560] tests: compile main as C++ --- fpga/tests/CMakeLists.txt | 2 +- fpga/tests/{main.c => main.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename fpga/tests/{main.c => main.cpp} (100%) diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index 9cd6d7317..9d66512a2 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -1,5 +1,5 @@ set(SOURCES - main.c + main.cpp dma.c fifo.c hls.c diff --git a/fpga/tests/main.c b/fpga/tests/main.cpp similarity index 100% rename from fpga/tests/main.c rename to fpga/tests/main.cpp From a194f8d0b253231969ea968b0df77bd6a723288c Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 16:58:17 +0100 Subject: [PATCH 028/560] plugin: require name and add method to dump list --- fpga/include/villas/fpga/ip.hpp | 3 ++- fpga/include/villas/fpga/ips/intc.hpp | 4 ++++ fpga/include/villas/plugin.hpp | 5 ++++- fpga/lib/plugin.cpp | 13 +++++++++++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 0ed4bdca1..2f31562da 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -101,7 +101,8 @@ protected: class FpgaIpFactory : public Plugin { public: - FpgaIpFactory() + FpgaIpFactory(std::string concreteName) : + Plugin(std::string("FPGA IP Factory: ") + concreteName) { pluginType = Plugin::Type::FpgaIp; } /// Returns a running and checked FPGA IP diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 1facaa38c..2bf36fecf 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -66,6 +66,10 @@ private: class InterruptControllerFactory : public FpgaIpFactory { public: + InterruptControllerFactory() : + FpgaIpFactory(getName()) + {} + FpgaIp* create() { return new InterruptController; } diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index b2ce41755..54ad8d568 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -40,7 +40,7 @@ public: FpgaIp, }; - Plugin(); + Plugin(std::string name); virtual ~Plugin(); // each plugin is a singleton, so copying is not allowed @@ -53,6 +53,9 @@ public: virtual int parse(json_t *cfg); virtual void dump(); + static void + dumpList(); + /** Find registered and loaded plugin with given name and type. */ static Plugin * lookup(Type type, std::string name); diff --git a/fpga/lib/plugin.cpp b/fpga/lib/plugin.cpp index fd3c83494..0c887ed48 100644 --- a/fpga/lib/plugin.cpp +++ b/fpga/lib/plugin.cpp @@ -45,9 +45,9 @@ Plugin::pluginListBuffer; int Plugin::pluginListNiftyCounter; -Plugin::Plugin() : +Plugin::Plugin(std::string name) : pluginType(Plugin::Type::Unknown), - name(""), + name(name), description(""), path(""), state(STATE_INITIALIZED) @@ -120,6 +120,15 @@ Plugin::dump() std::cout << " - " << this->name << ": " << this->description << std::endl; } +void +Plugin::dumpList() +{ + std::cout << "Registered plugins:" << std::endl; + for(auto& p : pluginList) { + std::cout << " - " << p->name << std::endl; + } +} + Plugin* Plugin::lookup(Plugin::Type type, std::string name) { From 2bf8bf93bdc98071deab4b628b42447631853a51 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 17:29:20 +0100 Subject: [PATCH 029/560] ips/intc: remove configureJson() method because not needed --- fpga/include/villas/fpga/ips/intc.hpp | 3 --- fpga/lib/ips/intc.cpp | 7 ------- 2 files changed, 10 deletions(-) diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 2bf36fecf..922bacca7 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -58,7 +58,6 @@ private: int efds[maxIrqs]; int nos[maxIrqs]; bool polling[maxIrqs]; -// Interrupt irqs[maxIrqs]; /**< State of available interrupts */ }; @@ -83,8 +82,6 @@ public: FpgaVlnv getCompatibleVlnv() const { return FpgaVlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } - - bool configureJson(FpgaIp* ip, json_t *json); }; } // namespace villas diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 9471636e9..8f4946824 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -150,11 +150,4 @@ uint64_t InterruptController::waitForInterrupt(int irq) } } - -bool InterruptControllerFactory::configureJson(FpgaIp *ip, json_t *json) -{ - // parse json and configure instance here - return true; -} - } // namespace villas From 63a443527cda9d65552450f6dc4282e54664b05a Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 17:30:24 +0100 Subject: [PATCH 030/560] plugin: treat empty name in lookup() as wildcard --- fpga/include/villas/plugin.hpp | 3 ++- fpga/lib/plugin.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index 54ad8d568..7ee980df8 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -56,7 +56,8 @@ public: static void dumpList(); - /** Find registered and loaded plugin with given name and type. */ + /// Find plugin by type and (optional if empty) by name. If more match, it + /// is not defined which one will be returned. static Plugin * lookup(Type type, std::string name); diff --git a/fpga/lib/plugin.cpp b/fpga/lib/plugin.cpp index 0c887ed48..b0e2cbd6a 100644 --- a/fpga/lib/plugin.cpp +++ b/fpga/lib/plugin.cpp @@ -133,7 +133,7 @@ Plugin* Plugin::lookup(Plugin::Type type, std::string name) { for(auto& p : pluginList) { - if(p->pluginType == type and p->name == name) + if(p->pluginType == type and (name.empty() or p->name == name)) return p; } From b0e55e6fb2a04ea9ece1e9ec0ab1869cdeab24eb Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 17:37:15 +0100 Subject: [PATCH 031/560] current wip implementing card, many changes in ip too --- fpga/include/villas/fpga/card.hpp | 68 +++++++++++-- fpga/include/villas/fpga/ip.hpp | 26 +++-- fpga/include/villas/plugin.hpp | 8 +- fpga/lib/card.cpp | 152 +++++++++++++++++++++++++++++- fpga/lib/ip.cpp | 71 ++++++++++---- fpga/tests/main.cpp | 17 +++- 6 files changed, 292 insertions(+), 50 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index a1111d804..0f4c002f1 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -30,35 +30,67 @@ #pragma once + #include #include "common.h" #include "kernel/pci.h" #include "kernel/vfio.h" + +#include +#include + #include "plugin.hpp" +#include "config.h" + +#define PCI_FILTER_DEFAULT_FPGA { \ + .id = { \ + .vendor = FPGA_PCI_VID_XILINX, \ + .device = FPGA_PCI_PID_VFPGA \ + } \ +} + namespace villas { /* Forward declarations */ struct fpga_ip; struct vfio_container; +class FpgaCardPlugin; +class FpgaIp; -struct fpga_card { - char *name; /**< The name of the FPGA card */ +class FpgaCard { +public: - enum state state; /**< The state of this FPGA card. */ + friend FpgaCardPlugin; + + FpgaCard() : filter(PCI_FILTER_DEFAULT_FPGA) {} + + bool start(); + bool stop() { return true; } + bool check() { return true; } + bool reset() { return true; } + void dump() { } + + + using IpList = std::list; + IpList ips; ///< IPs located on this FPGA card + + bool do_reset; /**< Reset VILLASfpga during startup? */ + int affinity; /**< Affinity for MSI interrupts */ + + + std::string name; /**< The name of the FPGA card */ struct pci *pci; struct pci_device filter; /**< Filter for PCI device. */ - struct vfio_container *vfio_container; + ::vfio_container *vfio_container; struct vfio_device vfio_device; /**< VFIO device handle. */ - int do_reset; /**< Reset VILLASfpga during startup? */ - int affinity; /**< Affinity for MSI interrupts */ - struct list ips; /**< List of IP components on FPGA. */ +// struct list ips; /**< List of IP components on FPGA. */ char *map; /**< PCI BAR0 mapping for register access */ @@ -66,9 +98,25 @@ struct fpga_card { size_t dmalen; /* Some IP cores are special and referenced here */ - struct fpga_ip *intc; - struct fpga_ip *reset; - struct fpga_ip *sw; +// struct fpga_ip *intc; +// struct fpga_ip *reset; +// struct fpga_ip *sw; +}; + + + +class FpgaCardPlugin : public Plugin { +public: + + FpgaCardPlugin() : + Plugin("FPGA Card plugin") + { pluginType = Plugin::Type::FpgaCard; } + + static std::list + make(json_t *json, struct pci* pci, ::vfio_container* vc); + + static FpgaCard* + create(); }; /** Initialize FPGA card and its IP components. */ diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 2f31562da..9b61414d9 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -41,7 +41,7 @@ #include "fpga/vlnv.hpp" #include "plugin.hpp" -#include "card.h" +#include "card.hpp" #include @@ -74,11 +74,7 @@ public: virtual bool start() { return true; } virtual bool stop() { return true; } virtual bool reset() { return true; } - virtual void dump() - { - info("IP %s: vlnv=%s baseaddr=%#jx, irq=%d, port=%d", - name.c_str(), vlnv.toString().c_str(), baseaddr, irq, port); - } + virtual void dump(); protected: uintptr_t @@ -90,12 +86,12 @@ protected: protected: // populated by FpgaIpFactory - struct fpga_card *card; /**< FPGA card this IP is instantiated on */ - std::string name; /**< Name defined in JSON config */ - FpgaVlnv vlnv; /**< VLNV defined in JSON config */ - uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ - int irq; /**< The interrupt number of the FPGA IP component. */ - int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ + FpgaCard* card; /**< FPGA card this IP is instantiated on */ + std::string name; /**< Name defined in JSON config */ + FpgaVlnv vlnv; /**< VLNV defined in JSON config */ + uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ + int irq; /**< The interrupt number of the FPGA IP component. */ + int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ }; @@ -106,14 +102,16 @@ public: { pluginType = Plugin::Type::FpgaIp; } /// Returns a running and checked FPGA IP - static FpgaIp* make(struct fpga_card* card, json_t *json, std::string name); + static FpgaIp* + make(FpgaCard* card, json_t *json, std::string name); private: /// Create a concrete IP instance virtual FpgaIp* create() = 0; /// Configure IP instance from JSON config - virtual bool configureJson(FpgaIp* ip, json_t *json) = 0; + virtual bool configureJson(FpgaIp* ip, json_t *json) + { return true; } virtual FpgaVlnv getCompatibleVlnv() const = 0; virtual std::string getName() const = 0; diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index 7ee980df8..26a220fd2 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -38,12 +38,13 @@ public: enum class Type { Unknown, FpgaIp, + FpgaCard, }; Plugin(std::string name); virtual ~Plugin(); - // each plugin is a singleton, so copying is not allowed + // copying a plugin doesn't make sense, so explicitly deny it Plugin(Plugin const&) = delete; void operator=(Plugin const&) = delete; @@ -61,20 +62,19 @@ public: static Plugin * lookup(Type type, std::string name); + /// Get all plugins of a given type. static std::list lookup(Type type); - // check if this makes sense! (no intermediate plugins) + // TODO: check if this makes sense! (no intermediate plugins) bool operator==(const Plugin& other) const; Type pluginType; - std::string name; std::string description; std::string path; void *handle; - enum state state; private: diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index f225cbd98..a2f69ab83 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -32,11 +32,157 @@ #include "kernel/pci.h" #include "kernel/vfio.h" -#include "fpga/ip.h" -#include "fpga/card.h" +#include + +#include "fpga/ip.hpp" +#include "fpga/card.hpp" namespace villas { +static FpgaCardPlugin +fpgaCardPlugin; + +std::list +FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) +{ + std::list cards; + + const char *card_name; + json_t *json_card; + json_object_foreach(json, card_name, json_card) { + std::cout << "Found config for FPGA card " << card_name << std::endl; + + json_t* json_ips = nullptr; + const char* pci_slot = nullptr; + const char* pci_id = nullptr; + int do_reset = 0; + int affinity = 0; + + int ret = json_unpack(json_card, "{ s: o, s?: i, s?: b, s?: s, s?: s }", + "ips", &json_ips, + "affinity", &affinity, + "do_reset", &do_reset, + "slot", &pci_slot, + "id", &pci_id); + + if(ret != 0) { + std::cout << " Cannot parse JSON config" << std::endl; + continue; + } + + FpgaCard* card = create(); + + // populate generic properties + card->name = std::string(card_name); + card->pci = pci; + card->vfio_container = vc; + card->affinity = affinity; + card->do_reset = do_reset != 0; + + const char* error; + + if (pci_slot != nullptr and pci_device_parse_slot(&card->filter, pci_slot, &error) != 0) + std::cout << " Failed to parse PCI slot: " << error << std::endl + << " -> ignoring" << std::endl; + + if (pci_id != nullptr and pci_device_parse_id(&card->filter, pci_id, &error) != 0) + std::cout << " Failed to parse PCI ID: " << error << std::endl + << " -> ignoring" << std::endl;; + + + // TODO: currently fails, fix and remove comment +// if(not card->start()) { +// std::cout << " cannot start, destroying ..." << std::endl; +// delete card; +// continue; +// } + + const char *ip_name; + json_t *json_ip; + json_object_foreach(json_ips, ip_name, json_ip) { + std::cout << " Found IP " << ip_name << std::endl; + + FpgaIp* ip = FpgaIpFactory::make(card, json_ip, ip_name); + if(ip == nullptr) { + std::cout << " -> cannot initialize" << std::endl; + continue; + } + + card->ips.push_back(ip); + } + + if(not card->check()) { + std::cout << " checking failed, destroying ..." << std::endl; + delete card; + continue; + } + + cards.push_back(card); + } + + return cards; +} + +FpgaCard* +FpgaCardPlugin::create() +{ + return new FpgaCard; +} + + +bool FpgaCard::start() +{ + int ret; + struct pci_device *pdev; + + /* Search for FPGA card */ + pdev = pci_lookup_device(pci, &filter); + if (!pdev) + error("Failed to find PCI device"); + + /* Attach PCIe card to VFIO container */ + ret = ::vfio_pci_attach(&vfio_device, vfio_container, pdev); + if (ret) + error("Failed to attach VFIO device"); + + /* Map PCIe BAR */ + map = (char*) vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX); + if (map == MAP_FAILED) + serror("Failed to mmap() BAR0"); + + /* Enable memory access and PCI bus mastering for DMA */ + ret = vfio_pci_enable(&vfio_device); + if (ret) + serror("Failed to enable PCI device"); + + /* Reset system? */ + if (do_reset) { + /* Reset / detect PCI device */ + ret = vfio_pci_reset(&vfio_device); + if (ret) + serror("Failed to reset PCI device"); + + if(not reset()) { + std::cout << "Failed to reset FGPA card" << std::endl; + return false; + } + } + + /* Initialize IP cores */ +// for (size_t j = 0; j < list_length(&ips); j++) { +// struct fpga_ip *i = (struct fpga_ip *) list_at(&ips, j); + +// ret = fpga_ip_start(i); +// if (ret) +// error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); +// } + + return 0; +} + + +#if 0 + int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) { assert(c->state == STATE_DESTROYED); @@ -321,4 +467,6 @@ int fpga_card_reset(struct fpga_card *c) return 0; } +#endif + } // namespace villas diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index d573ef03d..4e96228ae 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -27,9 +27,15 @@ #include "fpga/ip.hpp" #include +#include namespace villas { +void FpgaIp::dump() { + info("IP %s: vlnv=%s baseaddr=%#jx, irq=%d, port=%d", + name.c_str(), vlnv.toString().c_str(), baseaddr, irq, port); +} + FpgaIpFactory* FpgaIpFactory::lookup(const FpgaVlnv &vlnv) { @@ -43,29 +49,45 @@ FpgaIpFactory* FpgaIpFactory::lookup(const FpgaVlnv &vlnv) return nullptr; } -FpgaIp *FpgaIpFactory::make(fpga_card *card, json_t *json, std::string name) +FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) { - // extract VLNV from JSON + int ret; const char* vlnv_raw; - if(json_unpack(json, "{ s: s }", "vlnv", &vlnv_raw) != 0) - error("IP '%s' has no entry 'vlnv'", name.c_str()); + + // extract VLNV from JSON + ret = json_unpack(json, "{ s: s }", + "vlnv", &vlnv_raw); + if(ret != 0) { + std::cout << "IP " << name << " has no entry 'vlnv'" << std::endl; + return nullptr; + } + + // parse VLNV + FpgaVlnv vlnv(vlnv_raw); // find the appropriate factory that can create the specified VLNV // Note: // This is the magic part! Factories automatically register as a plugin // as soon as they are instantiated. If there are multiple candidates, // the first suitable factory will be used. - FpgaVlnv vlnv(vlnv_raw); FpgaIpFactory* fpgaIpFactory = lookup(vlnv); if(fpgaIpFactory == nullptr) { - error("No ip factory registered to handle VLNV '%s'", vlnv.toString().c_str()); - } else { - info("Using %s for IP %s", fpgaIpFactory->getName().c_str(), vlnv.toString().c_str()); + std::cout << "No IP plugin registered to handle VLNV " << vlnv << std::endl; + return nullptr; } - // create new IP instance + std::cout << "Using " << fpgaIpFactory->getName() << " for IP " << vlnv << std::endl; + + + // Create new IP instance. Since this function is virtual, it will construct + // the right, specialized type without knowing it here because we have + // already picked the right factory. FpgaIp* ip = fpgaIpFactory->create(); + if(ip == nullptr) { + std::cout << "Cannot create an instance of " << fpgaIpFactory->getName() << std::endl; + goto fail; + } // setup generic IP type properties ip->card = card; @@ -73,23 +95,34 @@ FpgaIp *FpgaIpFactory::make(fpga_card *card, json_t *json, std::string name) ip->vlnv = vlnv; // extract some optional properties - int ret = json_unpack(json, "{ s?: i, s?: i, s?: i }", - "baseaddr", &ip->baseaddr, - "irq", &ip->irq, - "port", &ip->port); - if(ret != 0) - error("Problem while parsing JSON"); + ret = json_unpack(json, "{ s?: i, s?: i, s?: i }", + "baseaddr", &ip->baseaddr, + "irq", &ip->irq, + "port", &ip->port); + if(ret != 0) { + std::cout << "Problem while parsing JSON" << std::endl; + goto fail; + } // IP-specific setup via JSON config fpgaIpFactory->configureJson(ip, json); - if(not ip->start()) - error("Cannot start IP"); + // TODO: currently fails, fix and remove comment +// if(not ip->start()) { +// std::cout << "Cannot start IP" << ip->name << std::endl; +// goto fail; +// } - if(not ip->check()) - error("Checking IP failed"); + if(not ip->check()) { + std::cout << "Checking IP " << ip->name << " failed" << std::endl; + goto fail; + } return ip; + +fail: + delete ip; + return nullptr; } } // namespace villas diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 41549943f..900caee81 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -29,8 +29,11 @@ #include #include +#include +#include + #define FPGA_CARD "vc707" -#define TEST_CONFIG "/villas/etc/fpga.json" +#define TEST_CONFIG "../etc/fpga.json" #define TEST_LEN 0x1000 #define CPU_HZ 3392389000 @@ -48,6 +51,8 @@ static void init() FILE *f; json_error_t err; + villas::Plugin::dumpList(); + ret = pci_init(&pci); cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); @@ -67,6 +72,16 @@ static void init() cr_assert_not_null(fpgas, "No section 'fpgas' found in config"); cr_assert(json_object_size(json) > 0, "No FPGAs defined in config"); + // get the FPGA card plugin + villas::Plugin* plugin = villas::Plugin::lookup(villas::Plugin::Type::FpgaCard, ""); + cr_assert_not_null(plugin, "No plugin for FPGA card found"); + villas::FpgaCardPlugin* fpgaCardPlugin = dynamic_cast(plugin); + + // create an FPGA card instance using the corresponding plugin +// villas::FpgaCard* fpgaCard = fpgaCardPlugin->make(json_); + + std::list fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); + json_t *json_card = json_object_get(fpgas, FPGA_CARD); cr_assert_not_null(json_card, "FPGA card " FPGA_CARD " not found"); From 09815a661ec65271dde1be66013546fa07747fd0 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 6 Dec 2017 20:58:27 +0100 Subject: [PATCH 032/560] rough implementation of a C++ style logger class with many sharp edges :) --- fpga/include/villas/log.hpp | 95 +++++++++++++++++++++++++++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/card.cpp | 32 ++++++++----- fpga/lib/ip.cpp | 16 ++++--- fpga/lib/log.cpp | 26 ++++++++++ 5 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 fpga/include/villas/log.hpp create mode 100644 fpga/lib/log.cpp diff --git a/fpga/include/villas/log.hpp b/fpga/include/villas/log.hpp new file mode 100644 index 000000000..ed4d65bad --- /dev/null +++ b/fpga/include/villas/log.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +class LoggerIndent; + +class Logger { + friend LoggerIndent; +public: + + class LoggerNewline { + public: + LoggerNewline(bool enabled = true) : enabled(enabled) {} + ~LoggerNewline() { + if(enabled) + std::cout << std::endl; + } + template + LoggerNewline& operator<< (T const& value) { + if(enabled) + std::cout << value; + return *this; + } + bool enabled; + }; + + class Indenter { + public: + Indenter(Logger* l) : logger(l) + { logger->increaseIndention(); } + + ~Indenter() + { logger->decreaseIndention(); } + private: + Logger* logger; + }; + + Logger(int level, std::string prefix = "") : level(level), prefix(prefix) {} + + Indenter indent() + { return Indenter(this); } + + static std::string + getPadding() + { + std::string out = ""; + for(int i = 0; i < depthCurrent; i++) out.append("\u2551 "); + return out; + } + + template + LoggerNewline operator<< (T const& value) { + if(level >= global_level) { + + if(depth > depthCurrent) { + std::cout << Logger::getPadding() << "\u255f\u2500\u2556" << std::endl; + depthCurrent++; + } + std::cout << Logger::getPadding() << "\u255f " << prefix << value; + return LoggerNewline(); + } else { + return LoggerNewline(false); + } + } + + + void + increaseIndention() + { + depth++; + } + + void + decreaseIndention() + { + if(depth == depthCurrent) + std::cout << Logger::getPadding() << std::endl; + + depthCurrent = --depth; + } + +private: + int level; + std::string prefix; + static int depth; + static int global_level; + static int depthCurrent; +}; + + +extern Logger cpp_debug; +extern Logger cpp_info; +extern Logger cpp_warn; +extern Logger cpp_error; diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index f80b17e8a..a4962cb49 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES log.c log_config.c log_helper.c + log.cpp ) include(FindPkgConfig) diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index a2f69ab83..872275dd9 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -37,6 +37,8 @@ #include "fpga/ip.hpp" #include "fpga/card.hpp" +#include "log.hpp" + namespace villas { static FpgaCardPlugin @@ -50,7 +52,8 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) const char *card_name; json_t *json_card; json_object_foreach(json, card_name, json_card) { - std::cout << "Found config for FPGA card " << card_name << std::endl; + cpp_info << "Found config for FPGA card " << card_name; + Logger::Indenter indent = cpp_debug.indent(); json_t* json_ips = nullptr; const char* pci_slot = nullptr; @@ -66,7 +69,7 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) "id", &pci_id); if(ret != 0) { - std::cout << " Cannot parse JSON config" << std::endl; + cpp_warn << "Cannot parse JSON config"; continue; } @@ -81,18 +84,20 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) const char* error; - if (pci_slot != nullptr and pci_device_parse_slot(&card->filter, pci_slot, &error) != 0) - std::cout << " Failed to parse PCI slot: " << error << std::endl - << " -> ignoring" << std::endl; + if (pci_slot != nullptr and pci_device_parse_slot(&card->filter, pci_slot, &error) != 0) { + cpp_warn << "Failed to parse PCI slot: " << error; +// cpp_info << "... ignoring"; + } - if (pci_id != nullptr and pci_device_parse_id(&card->filter, pci_id, &error) != 0) - std::cout << " Failed to parse PCI ID: " << error << std::endl - << " -> ignoring" << std::endl;; + if (pci_id != nullptr and pci_device_parse_id(&card->filter, pci_id, &error) != 0) { + cpp_warn << "Failed to parse PCI ID: " << error; +// cpp_info << "ignoring ..."; + } // TODO: currently fails, fix and remove comment // if(not card->start()) { -// std::cout << " cannot start, destroying ..." << std::endl; +// cpp_warn << " cannot start, destroying ..."; // delete card; // continue; // } @@ -100,11 +105,12 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) const char *ip_name; json_t *json_ip; json_object_foreach(json_ips, ip_name, json_ip) { - std::cout << " Found IP " << ip_name << std::endl; + cpp_info << "Found IP: " << ip_name; + Logger::Indenter indent = cpp_debug.indent(); FpgaIp* ip = FpgaIpFactory::make(card, json_ip, ip_name); if(ip == nullptr) { - std::cout << " -> cannot initialize" << std::endl; + cpp_warn << "Cannot initialize, ignoring ..."; continue; } @@ -112,7 +118,7 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) } if(not card->check()) { - std::cout << " checking failed, destroying ..." << std::endl; + cpp_warn << "Checking failed, destroying ..."; delete card; continue; } @@ -163,7 +169,7 @@ bool FpgaCard::start() serror("Failed to reset PCI device"); if(not reset()) { - std::cout << "Failed to reset FGPA card" << std::endl; + cpp_debug << "Failed to reset FGPA card"; return false; } } diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 4e96228ae..bb7cb724a 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -29,6 +29,8 @@ #include #include +#include "log.hpp" + namespace villas { void FpgaIp::dump() { @@ -58,7 +60,7 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) ret = json_unpack(json, "{ s: s }", "vlnv", &vlnv_raw); if(ret != 0) { - std::cout << "IP " << name << " has no entry 'vlnv'" << std::endl; + cpp_warn << "IP " << name << " has no entry 'vlnv'"; return nullptr; } @@ -73,11 +75,11 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) FpgaIpFactory* fpgaIpFactory = lookup(vlnv); if(fpgaIpFactory == nullptr) { - std::cout << "No IP plugin registered to handle VLNV " << vlnv << std::endl; + cpp_warn << "No plugin found to handle " << vlnv; return nullptr; } - std::cout << "Using " << fpgaIpFactory->getName() << " for IP " << vlnv << std::endl; + cpp_debug << "Using " << fpgaIpFactory->getName() << " for IP " << vlnv; // Create new IP instance. Since this function is virtual, it will construct @@ -85,7 +87,7 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) // already picked the right factory. FpgaIp* ip = fpgaIpFactory->create(); if(ip == nullptr) { - std::cout << "Cannot create an instance of " << fpgaIpFactory->getName() << std::endl; + cpp_warn << "Cannot create an instance of " << fpgaIpFactory->getName(); goto fail; } @@ -100,7 +102,7 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) "irq", &ip->irq, "port", &ip->port); if(ret != 0) { - std::cout << "Problem while parsing JSON" << std::endl; + cpp_warn << "Problem while parsing JSON"; goto fail; } @@ -109,12 +111,12 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) // TODO: currently fails, fix and remove comment // if(not ip->start()) { -// std::cout << "Cannot start IP" << ip->name << std::endl; +// cpp_error << "Cannot start IP" << ip->name; // goto fail; // } if(not ip->check()) { - std::cout << "Checking IP " << ip->name << " failed" << std::endl; + cpp_error << "Checking IP " << ip->name << " failed"; goto fail; } diff --git a/fpga/lib/log.cpp b/fpga/lib/log.cpp new file mode 100644 index 000000000..ff19f30c3 --- /dev/null +++ b/fpga/lib/log.cpp @@ -0,0 +1,26 @@ +#include "log.hpp" +#include "utils.h" + +int Logger::depth; +int Logger::depthCurrent; +int Logger::global_level = 0; + +Logger cpp_debug(0, CLR_BLU("Debug: ")); +Logger cpp_info(20); +Logger cpp_warn(80, CLR_YEL("Warning: ")); +Logger cpp_error(100, CLR_RED("Error: ")); + +void test() +{ + cpp_debug << "Hello"; + { + Logger::Indenter indent = cpp_debug.indent(); + cpp_debug << "indented"; + { + Logger::Indenter indent = cpp_debug.indent(); + cpp_debug << "indented"; + } + } + + cpp_debug << "and root again"; +} From e590d1a350a0c887c2d32375bd86899729c95fd0 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 13 Dec 2017 14:31:32 +0100 Subject: [PATCH 033/560] add namespace villas::fpga and villas::fpga::ip and some renaming --- fpga/include/villas/fpga/card.hpp | 65 ++++++++------------------- fpga/include/villas/fpga/ip.hpp | 48 +++++++++----------- fpga/include/villas/fpga/ips/intc.hpp | 17 ++++--- fpga/include/villas/fpga/vlnv.hpp | 13 +++--- fpga/lib/card.cpp | 24 +++++----- fpga/lib/ip.cpp | 31 +++++++------ fpga/lib/ips/intc.cpp | 5 +++ fpga/lib/vlnv.cpp | 7 +-- fpga/tests/main.cpp | 4 +- 9 files changed, 100 insertions(+), 114 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 0f4c002f1..840c49a74 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -37,7 +37,6 @@ #include "kernel/pci.h" #include "kernel/vfio.h" - #include #include @@ -45,7 +44,7 @@ #include "config.h" -#define PCI_FILTER_DEFAULT_FPGA { \ +#define PCI_FILTER_DEFAULT_FPGA { \ .id = { \ .vendor = FPGA_PCI_VID_XILINX, \ .device = FPGA_PCI_PID_VFPGA \ @@ -53,19 +52,24 @@ } namespace villas { +namespace fpga { + /* Forward declarations */ -struct fpga_ip; struct vfio_container; -class FpgaCardPlugin; -class FpgaIp; +class PCIeCardFactory; -class FpgaCard { +namespace ip { +class IpCore; +} + + +class PCIeCard { public: - friend FpgaCardPlugin; + friend PCIeCardFactory; - FpgaCard() : filter(PCI_FILTER_DEFAULT_FPGA) {} + PCIeCard() : filter(PCI_FILTER_DEFAULT_FPGA) {} bool start(); bool stop() { return true; } @@ -74,7 +78,7 @@ public: void dump() { } - using IpList = std::list; + using IpList = std::list; IpList ips; ///< IPs located on this FPGA card bool do_reset; /**< Reset VILLASfpga during startup? */ @@ -89,62 +93,29 @@ public: ::vfio_container *vfio_container; struct vfio_device vfio_device; /**< VFIO device handle. */ - -// struct list ips; /**< List of IP components on FPGA. */ - char *map; /**< PCI BAR0 mapping for register access */ size_t maplen; size_t dmalen; - - /* Some IP cores are special and referenced here */ -// struct fpga_ip *intc; -// struct fpga_ip *reset; -// struct fpga_ip *sw; }; -class FpgaCardPlugin : public Plugin { +class PCIeCardFactory : public Plugin { public: - FpgaCardPlugin() : + PCIeCardFactory() : Plugin("FPGA Card plugin") { pluginType = Plugin::Type::FpgaCard; } - static std::list + static std::list make(json_t *json, struct pci* pci, ::vfio_container* vc); - static FpgaCard* + static PCIeCard* create(); }; -/** Initialize FPGA card and its IP components. */ -int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc); - -/** Parse configuration of FPGA card including IP cores from config. */ -int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name); - -int fpga_card_parse_list(struct list *l, json_t *cfg); - -/** Check if the FPGA card configuration is plausible. */ -int fpga_card_check(struct fpga_card *c); - -/** Start FPGA card. */ -int fpga_card_start(struct fpga_card *c); - -/** Stop FPGA card. */ -int fpga_card_stop(struct fpga_card *c); - -/** Destroy FPGA card. */ -int fpga_card_destroy(struct fpga_card *c); - -/** Dump details of FPGA card to stdout. */ -void fpga_card_dump(struct fpga_card *c); - -/** Reset the FPGA to a known state */ -int fpga_card_reset(struct fpga_card *c); - +} // namespace fpga } // namespace villas /** @} */ diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 9b61414d9..df0d17422 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -45,29 +45,21 @@ #include -#include namespace villas { - -//enum fpga_ip_types { -// FPGA_IP_TYPE_DM_DMA, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ -// FPGA_IP_TYPE_DM_FIFO, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ -// FPGA_IP_TYPE_MODEL, /**< A model IP simulates a system on the FPGA. */ -// FPGA_IP_TYPE_MATH, /**< A math IP performs some kind of mathematical operation on the streaming data */ -// FPGA_IP_TYPE_MISC, /**< Other IP components like timer, counters, interrupt conctrollers or routing. */ -// FPGA_IP_TYPE_INTERFACE /**< A interface IP connects the FPGA to another system or controller. */ -//} type; +namespace fpga { +namespace ip { -class FpgaIpFactory; +class IpCoreFactory; -class FpgaIp { +class IpCore { public: - friend FpgaIpFactory; + friend IpCoreFactory; - FpgaIp() : card(nullptr), baseaddr(0), irq(-1), port(-1) {} - virtual ~FpgaIp() {} + IpCore() : card(nullptr), baseaddr(0), irq(-1), port(-1) {} + virtual ~IpCore() {} // IPs can implement this interface virtual bool check() { return true; } @@ -86,42 +78,44 @@ protected: protected: // populated by FpgaIpFactory - FpgaCard* card; /**< FPGA card this IP is instantiated on */ + PCIeCard* card; /**< FPGA card this IP is instantiated on */ std::string name; /**< Name defined in JSON config */ - FpgaVlnv vlnv; /**< VLNV defined in JSON config */ + Vlnv vlnv; /**< VLNV defined in JSON config */ uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ int irq; /**< The interrupt number of the FPGA IP component. */ int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ }; -class FpgaIpFactory : public Plugin { +class IpCoreFactory : public Plugin { public: - FpgaIpFactory(std::string concreteName) : - Plugin(std::string("FPGA IP Factory: ") + concreteName) + IpCoreFactory(std::string concreteName) : + Plugin(std::string("FPGA IpCore Factory: ") + concreteName) { pluginType = Plugin::Type::FpgaIp; } /// Returns a running and checked FPGA IP - static FpgaIp* - make(FpgaCard* card, json_t *json, std::string name); + static IpCore* + make(PCIeCard* card, json_t *json, std::string name); private: /// Create a concrete IP instance - virtual FpgaIp* create() = 0; + virtual IpCore* create() = 0; /// Configure IP instance from JSON config - virtual bool configureJson(FpgaIp* ip, json_t *json) + virtual bool configureJson(IpCore* ip, json_t *json) { return true; } - virtual FpgaVlnv getCompatibleVlnv() const = 0; + virtual Vlnv getCompatibleVlnv() const = 0; virtual std::string getName() const = 0; virtual std::string getDescription() const = 0; private: - static FpgaIpFactory* - lookup(const FpgaVlnv& vlnv); + static IpCoreFactory* + lookup(const Vlnv& vlnv); }; /** @} */ +} // namespace ip +} // namespace fpga } // namespace villas diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 922bacca7..7d8d5daf5 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -32,8 +32,11 @@ #include namespace villas { +namespace fpga { +namespace ip { -class InterruptController : public FpgaIp + +class InterruptController : public IpCore { public: using IrqMaskType = uint32_t; @@ -62,14 +65,14 @@ private: -class InterruptControllerFactory : public FpgaIpFactory { +class InterruptControllerFactory : public IpCoreFactory { public: InterruptControllerFactory() : - FpgaIpFactory(getName()) + IpCoreFactory(getName()) {} - FpgaIp* create() + IpCore* create() { return new InterruptController; } std::string @@ -80,10 +83,12 @@ public: getDescription() const { return "Xilinx's programmable interrupt controller"; } - FpgaVlnv getCompatibleVlnv() const - { return FpgaVlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } + Vlnv getCompatibleVlnv() const + { return Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } }; +} // namespace ip +} // namespace fpga } // namespace villas /** @} */ diff --git a/fpga/include/villas/fpga/vlnv.hpp b/fpga/include/villas/fpga/vlnv.hpp index 7785c56d2..97e603cba 100644 --- a/fpga/include/villas/fpga/vlnv.hpp +++ b/fpga/include/villas/fpga/vlnv.hpp @@ -32,16 +32,18 @@ #include namespace villas { +namespace fpga { -class FpgaVlnv { + +class Vlnv { public: static constexpr char delimiter = ':'; - FpgaVlnv() : + Vlnv() : vendor(""), library(""), name(""), version("") {} - FpgaVlnv(std::string s) { + Vlnv(std::string s) { parseFromString(s); } @@ -58,10 +60,10 @@ public: } bool - operator==(const FpgaVlnv& other) const; + operator==(const Vlnv& other) const; friend std::ostream& - operator<< (std::ostream& stream, const FpgaVlnv& vlnv) + operator<< (std::ostream& stream, const Vlnv& vlnv) { return stream << (vlnv.vendor.empty() ? "*" : vlnv.vendor) << ":" @@ -80,6 +82,7 @@ private: std::string version; }; +} // namespace fpga } // namespace villas /** _FPGA_VLNV_HPP_ @} */ diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 872275dd9..68662a448 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -40,14 +40,15 @@ #include "log.hpp" namespace villas { +namespace fpga { -static FpgaCardPlugin -fpgaCardPlugin; +// instantiate factory to register +static PCIeCardFactory PCIeCardFactory; -std::list -FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) +std::list +fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) { - std::list cards; + std::list cards; const char *card_name; json_t *json_card; @@ -73,7 +74,7 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) continue; } - FpgaCard* card = create(); + fpga::PCIeCard* card = create(); // populate generic properties card->name = std::string(card_name); @@ -108,7 +109,7 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) cpp_info << "Found IP: " << ip_name; Logger::Indenter indent = cpp_debug.indent(); - FpgaIp* ip = FpgaIpFactory::make(card, json_ip, ip_name); + ip::IpCore* ip = ip::IpCoreFactory::make(card, json_ip, ip_name); if(ip == nullptr) { cpp_warn << "Cannot initialize, ignoring ..."; continue; @@ -129,14 +130,14 @@ FpgaCardPlugin::make(json_t *json, struct pci* pci, ::vfio_container* vc) return cards; } -FpgaCard* -FpgaCardPlugin::create() +fpga::PCIeCard* +fpga::PCIeCardFactory::create() { - return new FpgaCard; + return new fpga::PCIeCard; } -bool FpgaCard::start() +bool fpga::PCIeCard::start() { int ret; struct pci_device *pdev; @@ -475,4 +476,5 @@ int fpga_card_reset(struct fpga_card *c) #endif +} // namespace fpga } // namespace villas diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index bb7cb724a..f94074408 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -32,26 +32,29 @@ #include "log.hpp" namespace villas { +namespace fpga { +namespace ip { -void FpgaIp::dump() { + +void IpCore::dump() { info("IP %s: vlnv=%s baseaddr=%#jx, irq=%d, port=%d", name.c_str(), vlnv.toString().c_str(), baseaddr, irq, port); } -FpgaIpFactory* FpgaIpFactory::lookup(const FpgaVlnv &vlnv) +IpCoreFactory* IpCoreFactory::lookup(const Vlnv &vlnv) { for(auto& ip : Plugin::lookup(Plugin::Type::FpgaIp)) { - FpgaIpFactory* fpgaIpFactory = dynamic_cast(ip); + IpCoreFactory* ipCoreFactory = dynamic_cast(ip); - if(fpgaIpFactory->getCompatibleVlnv() == vlnv) - return fpgaIpFactory; + if(ipCoreFactory->getCompatibleVlnv() == vlnv) + return ipCoreFactory; } return nullptr; } -FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) +IpCore *IpCoreFactory::make(fpga::PCIeCard* card, json_t *json, std::string name) { int ret; const char* vlnv_raw; @@ -65,29 +68,29 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) } // parse VLNV - FpgaVlnv vlnv(vlnv_raw); + Vlnv vlnv(vlnv_raw); // find the appropriate factory that can create the specified VLNV // Note: // This is the magic part! Factories automatically register as a plugin // as soon as they are instantiated. If there are multiple candidates, // the first suitable factory will be used. - FpgaIpFactory* fpgaIpFactory = lookup(vlnv); + IpCoreFactory* ipCoreFactory = lookup(vlnv); - if(fpgaIpFactory == nullptr) { + if(ipCoreFactory == nullptr) { cpp_warn << "No plugin found to handle " << vlnv; return nullptr; } - cpp_debug << "Using " << fpgaIpFactory->getName() << " for IP " << vlnv; + cpp_debug << "Using " << ipCoreFactory->getName() << " for IP " << vlnv; // Create new IP instance. Since this function is virtual, it will construct // the right, specialized type without knowing it here because we have // already picked the right factory. - FpgaIp* ip = fpgaIpFactory->create(); + IpCore* ip = ipCoreFactory->create(); if(ip == nullptr) { - cpp_warn << "Cannot create an instance of " << fpgaIpFactory->getName(); + cpp_warn << "Cannot create an instance of " << ipCoreFactory->getName(); goto fail; } @@ -107,7 +110,7 @@ FpgaIp *FpgaIpFactory::make(FpgaCard* card, json_t *json, std::string name) } // IP-specific setup via JSON config - fpgaIpFactory->configureJson(ip, json); + ipCoreFactory->configureJson(ip, json); // TODO: currently fails, fix and remove comment // if(not ip->start()) { @@ -127,4 +130,6 @@ fail: return nullptr; } +} // namespace ip +} // namespace fpga } // namespace villas diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 8f4946824..78f598aab 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -34,6 +34,9 @@ #include "fpga/ips/intc.hpp" namespace villas { +namespace fpga { +namespace ip { + // instantiate factory to make available to plugin infrastructure static InterruptControllerFactory factory; @@ -150,4 +153,6 @@ uint64_t InterruptController::waitForInterrupt(int irq) } } +} // namespace ip +} // namespace fpga } // namespace villas diff --git a/fpga/lib/vlnv.cpp b/fpga/lib/vlnv.cpp index cf44c483f..f9eaa6bb1 100644 --- a/fpga/lib/vlnv.cpp +++ b/fpga/lib/vlnv.cpp @@ -30,9 +30,10 @@ #include "fpga/ip.hpp" namespace villas { +namespace fpga { bool -FpgaVlnv::operator==(const FpgaVlnv &other) const +Vlnv::operator==(const Vlnv &other) const { // if a field is empty, it means wildcard matching everything const bool vendorWildcard = vendor.empty() or other.vendor.empty(); @@ -49,7 +50,7 @@ FpgaVlnv::operator==(const FpgaVlnv &other) const } void -FpgaVlnv::parseFromString(std::string vlnv) +Vlnv::parseFromString(std::string vlnv) { // tokenize by delimiter std::stringstream sstream(vlnv); @@ -65,5 +66,5 @@ FpgaVlnv::parseFromString(std::string vlnv) if(version == "*") version = ""; } - +} // namespace fpga } // namespace villas diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 900caee81..1d3cd11ea 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -75,12 +75,12 @@ static void init() // get the FPGA card plugin villas::Plugin* plugin = villas::Plugin::lookup(villas::Plugin::Type::FpgaCard, ""); cr_assert_not_null(plugin, "No plugin for FPGA card found"); - villas::FpgaCardPlugin* fpgaCardPlugin = dynamic_cast(plugin); + villas::fpga::PCIeCardFactory* fpgaCardPlugin = dynamic_cast(plugin); // create an FPGA card instance using the corresponding plugin // villas::FpgaCard* fpgaCard = fpgaCardPlugin->make(json_); - std::list fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); + std::list fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); json_t *json_card = json_object_get(fpgas, FPGA_CARD); cr_assert_not_null(json_card, "FPGA card " FPGA_CARD " not found"); From 35d96ed277f345ec7f6e2f64fd37452f199c75ab Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 19 Dec 2017 18:58:51 +0100 Subject: [PATCH 034/560] lib: add dependency graph implementation --- fpga/include/villas/dependency_graph.hpp | 55 +++++++++ fpga/include/villas/dependency_graph_impl.hpp | 107 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 fpga/include/villas/dependency_graph.hpp create mode 100644 fpga/include/villas/dependency_graph_impl.hpp diff --git a/fpga/include/villas/dependency_graph.hpp b/fpga/include/villas/dependency_graph.hpp new file mode 100644 index 000000000..f488e8ad7 --- /dev/null +++ b/fpga/include/villas/dependency_graph.hpp @@ -0,0 +1,55 @@ +#ifndef VILLAS_DEPENDENCY_GRAPH_HPP +#define VILLAS_DEPENDENCY_GRAPH_HPP + +#include +#include +#include + +#include "log.hpp" + +namespace villas { +namespace utils { + + +template +class DependencyGraph { +public: + using NodeList = std::list; + + /// Create a node without dependencies if it not yet exists, return if a new + /// node has been created. + bool addNode(const T& node); + + /// Remove a node and all other nodes that depend on it + void removeNode(const T& node); + + /// Add a dependency to a node. Will create the node if it not yet exists + void addDependency(const T& node, const T& dependency); + + void dump(); + + /// Return a sequential evaluation order list. If a circular dependency has been + /// detected, all nodes involved will not be part of that list. + NodeList getEvaluationOrder() const; + +private: + /// Return whether a node already exists or not + bool nodeExists(const T& node) + { return graph.find(node) != graph.end(); } + + static bool + nodeInList(const NodeList& list, const T& node) + { return list.end() != std::find(list.begin(), list.end(), node); } + +private: + using Graph = std::map; + + Graph graph; +}; + +} // namespace utils +} // namespace villas + +#include "dependency_graph_impl.hpp" + +#endif // VILLAS_DEPENDENCY_GRAPH_HPP diff --git a/fpga/include/villas/dependency_graph_impl.hpp b/fpga/include/villas/dependency_graph_impl.hpp new file mode 100644 index 000000000..f5d5b32d7 --- /dev/null +++ b/fpga/include/villas/dependency_graph_impl.hpp @@ -0,0 +1,107 @@ +#ifndef VILLAS_DEPENDENCY_GRAPH_HPP +#error "Do not include this file directly, please include depedency_graph.hpp" +#endif + +#include "dependency_graph.hpp" +#include + +namespace villas { +namespace utils { + +template +bool +DependencyGraph::addNode(const T &node) +{ + bool existedBefore = nodeExists(node); + + // accessing is enough to create if not exists + graph[node]; + + return existedBefore; +} + +template +void +DependencyGraph::removeNode(const T &node) +{ + graph.erase(node); + + // check if other nodes depend on this one + for(auto& [key, dependencies] : graph) { + if(nodeInList(dependencies, node)) { + // remove other node that depends on the one to delete + removeNode(key); + } + } + +} + +template +void +DependencyGraph::addDependency(const T &node, const T &dependency) +{ + NodeList& dependencies = graph[node]; + if(not nodeInList(dependencies, dependency)) + dependencies.push_back(dependency); +} + +template +void +DependencyGraph::dump() { + for(auto& node : graph) { + std::stringstream ss; + for(auto& dep : node.second) { + ss << dep << " "; + } + cpp_debug << node.first << ": " << ss.str(); + } +} + +template +typename DependencyGraph::NodeList +DependencyGraph::getEvaluationOrder() const +{ + // copy graph to preserve information (we have to delete entries later) + Graph graph = this->graph; + + // output list + NodeList out; + + while(graph.size() > 0) { + int added = 0; + + // look for nodes with no dependencies + for(auto& [key, dependencies] : graph) { + + for(auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) { + if(nodeInList(out, *dep)) { + // dependency has been pushed to list in last round + dep = dependencies.erase(dep); + } + } + + // nodes with no dependencies can be pushed to list + if(dependencies.empty()) { + out.push_back(key); + graph.erase(key); + added++; + } + } + + // if a round doesn't add any elements and is not the last, then + // there is a circular dependency + if(added == 0 and graph.size() > 0) { + cpp_error << "Circular dependency detected! IPs not available:"; + Logger::Indenter indent = cpp_debug.indent(); + for(auto& [key, value] : graph) { + cpp_error << key; + } + break; + } + } + + return out; +} + +} // namespace utils +} // namespace villas From 3d0afd671ee86de191d994db59cf4e6a4797d57c Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 19 Dec 2017 19:00:22 +0100 Subject: [PATCH 035/560] lib/utils: add string tokenizer --- fpga/include/villas/utils.hpp | 15 +++++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/utils.cpp | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 fpga/include/villas/utils.hpp create mode 100644 fpga/lib/utils.cpp diff --git a/fpga/include/villas/utils.hpp b/fpga/include/villas/utils.hpp new file mode 100644 index 000000000..b2b5b314c --- /dev/null +++ b/fpga/include/villas/utils.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace villas { +namespace utils { + +std::vector +tokenize(std::string s, std::string delimiter); + +} // namespace utils +} // namespace villas + diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index a4962cb49..1d639f14e 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -24,6 +24,7 @@ set(SOURCES plugin.c plugin.cpp utils.c + utils.cpp list.c log.c log_config.c diff --git a/fpga/lib/utils.cpp b/fpga/lib/utils.cpp new file mode 100644 index 000000000..f0cd982f1 --- /dev/null +++ b/fpga/lib/utils.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include "utils.hpp" + +namespace villas { +namespace utils { + +std::vector +tokenize(std::string s, std::string delimiter) +{ + std::vector tokens; + + size_t lastPos = 0; + size_t curentPos; + + while((curentPos = s.find(delimiter, lastPos)) != std::string::npos) { + const size_t tokenLength = curentPos - lastPos; + tokens.push_back(s.substr(lastPos, tokenLength)); + + // advance in string + lastPos = curentPos + delimiter.length(); + } + + // check if there's a last token behind the last delimiter + if(lastPos != s.length()) { + const size_t lastTokenLength = s.length() - lastPos; + tokens.push_back(s.substr(lastPos, lastTokenLength)); + } + + return tokens; +} + +} // namespace utils +} // namespace villas From 61ca7aa44f1c29d653114238cd0e87ad7cccecd7 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 19 Dec 2017 19:02:14 +0100 Subject: [PATCH 036/560] fpga/ip: add C++ timer implementation --- fpga/include/villas/fpga/ips/timer.hpp | 82 ++++++++++++++++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/timer.cpp | 54 +++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 fpga/include/villas/fpga/ips/timer.hpp create mode 100644 fpga/lib/ips/timer.cpp diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp new file mode 100644 index 000000000..0f28c79b5 --- /dev/null +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -0,0 +1,82 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include "fpga/ip.hpp" +#include + +namespace villas { +namespace fpga { +namespace ip { + + +class Timer : public IpCore +{ +public: +// ~Timer(); + + bool start(); + +private: + XTmrCtr xtmr; +}; + + + +class TimerFactory : public IpCoreFactory { +public: + + TimerFactory() : + IpCoreFactory(getName()) + {} + + IpCore* create() + { return new Timer; } + + std::string + getName() const + { return "Timer"; } + + std::string + getDescription() const + { return "Xilinx's programmable timer / counter"; } + + Vlnv getCompatibleVlnv() const + { return {"xilinx.com:ip:axi_timer:"}; } + + std::list getDependencies() const + { return { {"intc", Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:") } }; } +}; + +} // namespace ip +} // namespace fpga +} // namespace villas + +/** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 1d639f14e..40e5df6bd 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES card.cpp ips/timer.c + ips/timer.cpp ips/model.c ips/switch.c ips/dft.c diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp new file mode 100644 index 000000000..246bc9a78 --- /dev/null +++ b/fpga/lib/ips/timer.cpp @@ -0,0 +1,54 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + + +#include "config.h" + +#include "log.hpp" +#include "fpga/ips/timer.hpp" + +namespace villas { +namespace fpga { +namespace ip { + + +// instantiate factory to make available to plugin infrastructure +static TimerFactory factory; + +bool Timer::start() +{ + XTmrCtr_Config xtmr_cfg; + xtmr_cfg.SysClockFreqHz = FPGA_AXI_HZ; + + XTmrCtr_CfgInitialize(&xtmr, &xtmr_cfg, getBaseaddr()); + XTmrCtr_InitHw(&xtmr); + + return true; +} + + +} // namespace ip +} // namespace fpga +} // namespace villas From 6a721847e465832f7f68923faf96f72f30f1cecc Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 19 Dec 2017 19:02:50 +0100 Subject: [PATCH 037/560] set C++ standard to C++17 Primarily for structured binding declaration in range-based for loops --- fpga/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index ef7a90124..af9f08dc1 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -4,6 +4,8 @@ project(VILLASfpga C CXX) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) +set (CMAKE_CXX_STANDARD 17) + add_subdirectory(lib) add_subdirectory(tests) add_subdirectory(src) From a5b5e317d4f5a443c711d74e7de3bb01d023e135 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 19 Dec 2017 19:06:30 +0100 Subject: [PATCH 038/560] wip implementing dependency parsing and proper memeory handling works and compiles so for. next is to implement different IP interfaces (Model, Interface, DataMover, Infrastructure, ...) --- fpga/etc/fpga.json | 109 +++++----- fpga/include/villas/fpga/card.hpp | 13 +- fpga/include/villas/fpga/ip.hpp | 72 +++++-- fpga/include/villas/fpga/ips/intc.hpp | 3 + fpga/include/villas/fpga/vlnv.hpp | 4 + fpga/include/villas/log.hpp | 22 +- fpga/lib/card.cpp | 37 ++-- fpga/lib/ip.cpp | 283 ++++++++++++++++++++------ fpga/lib/ips/intc.cpp | 4 +- fpga/lib/log.cpp | 10 +- fpga/tests/main.cpp | 5 +- 11 files changed, 387 insertions(+), 175 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index 6f7b20e2d..5fcc31556 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -26,10 +26,64 @@ "slot": "03:00.0", "do_reset": true, "ips": { + + "axi_reset_0": { + "vlnv": "xilinx.com:ip:axi_gpio:2.0", + "baseaddr": 28672 + }, + "timer_0": { + "vlnv": "xilinx.com:ip:axi_timer:2.0", + "baseaddr": 16384, + "irq": "axi_pcie_intc_0:0" + }, + "dma_0": { + "vlnv": "xilinx.com:ip:axi_dma:7.1", + "baseaddr": 12288, + "port": "switch_0:1", + "irq": "axi_pcie_intc_0:3" + }, "axi_pcie_intc_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0", "baseaddr": 45056 }, + "dma_1": { + "vlnv": "xilinx.com:ip:axi_dma:7.1", + "baseaddr": 8192, + "port": "switch_0:6", + "irq": "axi_pcie_intc_0:3" + }, + "fifo_mm_s_0": { + "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", + "baseaddr": 24576, + "baseaddr_axi4": 49152, + "port": "switch_0:2", + "irq": "axi_pcie_intc_0:2" + }, + "rtds_axis_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", + "baseaddr": 32768, + "port": "switch_0:0", + "irq": "axi_pcie_intc_0:5" + }, + "hls_dft_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", + "baseaddr": 36864, + "port": "switch_0:5", + "irq": "axi_pcie_intc_0:1", + "period": 400, + "harmonics": [ + 0, + 1, + 3, + 5, + 7 + ], + "decimation": 0 + }, + "axis_data_fifo_0": { + "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", + "port": "switch_0:3" + }, "switch_0": { "vlnv": "xilinx.com:ip:axis_interconnect:2.1", "baseaddr": 20480, @@ -42,62 +96,9 @@ } ] }, - "axi_reset_0": { - "vlnv": "xilinx.com:ip:axi_gpio:2.0", - "baseaddr": 28672 - }, - "timer_0": { - "vlnv": "xilinx.com:ip:axi_timer:2.0", - "baseaddr": 16384, - "irq": 0 - }, - "dma_0": { - "vlnv": "xilinx.com:ip:axi_dma:7.1", - "baseaddr": 12288, - "port": 1, - "irq": 3 - }, - "dma_1": { - "vlnv": "xilinx.com:ip:axi_dma:7.1", - "baseaddr": 8192, - "port": 6, - "irq": 3 - }, - "fifo_mm_s_0": { - "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", - "baseaddr": 24576, - "baseaddr_axi4": 49152, - "port": 2, - "irq": 2 - }, - "rtds_axis_0": { - "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", - "baseaddr": 32768, - "port": 0, - "irq": 5 - }, - "hls_dft_0": { - "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", - "baseaddr": 36864, - "port": 5, - "irq": 1, - "period": 400, - "harmonics": [ - 0, - 1, - 3, - 5, - 7 - ], - "decimation": 0 - }, - "axis_data_fifo_0": { - "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", - "port": 3 - }, "axis_data_fifo_1": { "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", - "port": 6 + "port": "switch_0:6" } } } diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 840c49a74..755926b67 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -41,6 +41,7 @@ #include #include "plugin.hpp" +#include "fpga/ip.hpp" #include "config.h" @@ -59,11 +60,6 @@ namespace fpga { struct vfio_container; class PCIeCardFactory; -namespace ip { -class IpCore; -} - - class PCIeCard { public: @@ -78,8 +74,7 @@ public: void dump() { } - using IpList = std::list; - IpList ips; ///< IPs located on this FPGA card + ip::IpCoreList ips; ///< IPs located on this FPGA card bool do_reset; /**< Reset VILLASfpga during startup? */ int affinity; /**< Affinity for MSI interrupts */ @@ -99,7 +94,7 @@ public: size_t dmalen; }; - +using CardList = std::list>; class PCIeCardFactory : public Plugin { public: @@ -108,7 +103,7 @@ public: Plugin("FPGA Card plugin") { pluginType = Plugin::Type::FpgaCard; } - static std::list + static CardList make(json_t *json, struct pci* pci, ::vfio_container* vc); static PCIeCard* diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index df0d17422..0929bd3b8 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -41,16 +41,41 @@ #include "fpga/vlnv.hpp" #include "plugin.hpp" -#include "card.hpp" + +#include +#include +#include #include namespace villas { namespace fpga { + +class PCIeCard; + namespace ip { +class IpIdentifier { +public: + IpIdentifier(Vlnv vlnv = Vlnv::getWildcard(), std::string name = "") : + vlnv(vlnv), name(name) {} + + IpIdentifier(std::string vlnvString, std::string name = "") : + vlnv(vlnvString), name(name) {} + + friend std::ostream& + operator<< (std::ostream& stream, const IpIdentifier& id) + { return stream << "VLNV: " << id.vlnv << " Name: " << id.name; } + + Vlnv vlnv; + std::string name; +}; + +using IpDependency = std::pair; + +// forward declarations class IpCoreFactory; class IpCore { @@ -58,7 +83,7 @@ public: friend IpCoreFactory; - IpCore() : card(nullptr), baseaddr(0), irq(-1), port(-1) {} + IpCore() : card(nullptr), baseaddr(0) {} //, irq(-1), port(-1) {} virtual ~IpCore() {} // IPs can implement this interface @@ -68,25 +93,41 @@ public: virtual bool reset() { return true; } virtual void dump(); + bool + operator== (const IpIdentifier& otherId) { + const bool vlnvMatch = id.vlnv == otherId.vlnv; + const bool nameWildcard = id.name.empty() or otherId.name.empty(); + + return vlnvMatch and (nameWildcard or id.name == otherId.name); + } + + bool + operator== (const Vlnv& otherVlnv) + { return id.vlnv == otherVlnv; } + + friend std::ostream& + operator<< (std::ostream& stream, const IpCore& ip) + { return stream << ip.id; } + protected: uintptr_t - getBaseaddr() const - { - assert(card != nullptr); - return reinterpret_cast(card->map) + this->baseaddr; - } + getBaseaddr() const; protected: // populated by FpgaIpFactory - PCIeCard* card; /**< FPGA card this IP is instantiated on */ - std::string name; /**< Name defined in JSON config */ - Vlnv vlnv; /**< VLNV defined in JSON config */ + PCIeCard* card; /**< FPGA card this IP is instantiated on */ + IpIdentifier id; /**< VLNV and name defined in JSON config */ uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ - int irq; /**< The interrupt number of the FPGA IP component. */ - int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ +// int irq; /**< The interrupt number of the FPGA IP component. */ +// int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ + + std::map dependencies; }; +using IpCoreList = std::list>; + + class IpCoreFactory : public Plugin { public: IpCoreFactory(std::string concreteName) : @@ -94,20 +135,21 @@ public: { pluginType = Plugin::Type::FpgaIp; } /// Returns a running and checked FPGA IP - static IpCore* - make(PCIeCard* card, json_t *json, std::string name); + static IpCoreList + make(PCIeCard* card, json_t *json_ips); private: /// Create a concrete IP instance virtual IpCore* create() = 0; /// Configure IP instance from JSON config - virtual bool configureJson(IpCore* ip, json_t *json) + virtual bool configureJson(const std::unique_ptr& ip, json_t *json) { return true; } virtual Vlnv getCompatibleVlnv() const = 0; virtual std::string getName() const = 0; virtual std::string getDescription() const = 0; + virtual std::list getDependencies() const = 0; private: static IpCoreFactory* diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 7d8d5daf5..1a9f525be 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -85,6 +85,9 @@ public: Vlnv getCompatibleVlnv() const { return Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } + + std::list getDependencies() const + { return {}; } }; } // namespace ip diff --git a/fpga/include/villas/fpga/vlnv.hpp b/fpga/include/villas/fpga/vlnv.hpp index 97e603cba..52066a8d7 100644 --- a/fpga/include/villas/fpga/vlnv.hpp +++ b/fpga/include/villas/fpga/vlnv.hpp @@ -47,6 +47,10 @@ public: parseFromString(s); } + static Vlnv + getWildcard() + { return Vlnv(); } + std::string toString() const { diff --git a/fpga/include/villas/log.hpp b/fpga/include/villas/log.hpp index ed4d65bad..13ee951f3 100644 --- a/fpga/include/villas/log.hpp +++ b/fpga/include/villas/log.hpp @@ -3,12 +3,23 @@ #include #include +#define _ESCAPE "\x1b" +#define TXT_BOLD(s) _ESCAPE "[1m" + std::string(s) + _ESCAPE "[0m" + class LoggerIndent; class Logger { friend LoggerIndent; public: + enum class LogLevel : int { + Debug, + Info, + Warning, + Error, + Disabled + }; + class LoggerNewline { public: LoggerNewline(bool enabled = true) : enabled(enabled) {} @@ -36,7 +47,7 @@ public: Logger* logger; }; - Logger(int level, std::string prefix = "") : level(level), prefix(prefix) {} + Logger(LogLevel level, std::string prefix = "") : level(level), prefix(prefix) {} Indenter indent() { return Indenter(this); } @@ -80,11 +91,16 @@ public: depthCurrent = --depth; } + static + void + setLogLevel(LogLevel level) + { global_level = level; } + private: - int level; + LogLevel level; std::string prefix; static int depth; - static int global_level; + static LogLevel global_level; static int depthCurrent; }; diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 68662a448..f03272cba 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -33,6 +33,12 @@ #include "kernel/vfio.h" #include +#include +#include +#include +#include +#include +#include #include "fpga/ip.hpp" #include "fpga/card.hpp" @@ -45,10 +51,11 @@ namespace fpga { // instantiate factory to register static PCIeCardFactory PCIeCardFactory; -std::list + +CardList fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) { - std::list cards; + CardList cards; const char *card_name; json_t *json_card; @@ -74,7 +81,7 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) continue; } - fpga::PCIeCard* card = create(); + auto card = std::unique_ptr(create()); // populate generic properties card->name = std::string(card_name); @@ -87,44 +94,32 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) if (pci_slot != nullptr and pci_device_parse_slot(&card->filter, pci_slot, &error) != 0) { cpp_warn << "Failed to parse PCI slot: " << error; -// cpp_info << "... ignoring"; } if (pci_id != nullptr and pci_device_parse_id(&card->filter, pci_id, &error) != 0) { cpp_warn << "Failed to parse PCI ID: " << error; -// cpp_info << "ignoring ..."; } // TODO: currently fails, fix and remove comment // if(not card->start()) { -// cpp_warn << " cannot start, destroying ..."; +// cpp_warn << "Cannot start FPGA card " << card_name; // delete card; // continue; // } - const char *ip_name; - json_t *json_ip; - json_object_foreach(json_ips, ip_name, json_ip) { - cpp_info << "Found IP: " << ip_name; - Logger::Indenter indent = cpp_debug.indent(); - - ip::IpCore* ip = ip::IpCoreFactory::make(card, json_ip, ip_name); - if(ip == nullptr) { - cpp_warn << "Cannot initialize, ignoring ..."; - continue; - } - - card->ips.push_back(ip); + card->ips = ip::IpCoreFactory::make(card.get(), json_ips); + if(card->ips.empty()) { + cpp_error << "Cannot initialize IPs"; + continue; } if(not card->check()) { cpp_warn << "Checking failed, destroying ..."; - delete card; continue; } - cards.push_back(card); + cards.push_back(std::move(card)); } return cards; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index f94074408..b8e020ba1 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -21,24 +21,112 @@ *********************************************************************************/ #include "log_config.h" -#include "log.h" +#include "log.hpp" #include "plugin.h" +#include "dependency_graph.hpp" +#include "utils.hpp" #include "fpga/ip.hpp" +#include "fpga/card.hpp" #include #include +#include +#include +#include +#include #include "log.hpp" +using DependencyGraph = villas::utils::DependencyGraph; + +static +std::list +dependencyTokens = {"irq", "port", "memory"}; + +static +bool +buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::string name) +{ +// cpp_debug << "preparse " << name; + + const bool nodeExists = dependencyGraph.addNode(name); + + // do not add IP multiple times + // this happens if more than 1 IP depends on a certain other IP + if(nodeExists) { + return true; + } + + json_t* json_ip = json_object_get(json_ips, name.c_str()); + if(json_ip == nullptr) { + cpp_error << "IP " << name << " not found in config"; + return false; + } + + for(auto& dependencyToken : dependencyTokens) { + json_t* json_dependency = json_object_get(json_ip, dependencyToken.c_str()); + if(json_dependency == nullptr) { + cpp_debug << "Property " << dependencyToken << " of " << TXT_BOLD(name) + << " not present"; + continue; + } + + const char* value = json_string_value(json_dependency); + if(value == nullptr) { + cpp_warn << "Property " << dependencyToken << " of " << TXT_BOLD(name) + << " is invalid"; + continue; + } + + auto mapping = villas::utils::tokenize(value, ":"); + + + if(mapping.size() != 2) { + cpp_error << "Invalid " << dependencyToken << " mapping" + << " of " << TXT_BOLD(name); + + dependencyGraph.removeNode(name); + return false; + } + + if(name == mapping[0]) { + cpp_error << "IP " << TXT_BOLD(name)<< " cannot depend on itself"; + + dependencyGraph.removeNode(name); + return false; + } + + // already add dependency, if adding it fails, removing the dependency + // will also remove the current one + dependencyGraph.addDependency(name, mapping[0]); + + if(not buildDependencyGraph(dependencyGraph, json_ips, mapping[0])) { + cpp_error << "Dependency " << mapping[0] << " of " << TXT_BOLD(name) + << " not satified"; + + dependencyGraph.removeNode(mapping[0]); + return false; + } + } + + return true; +} + + namespace villas { namespace fpga { namespace ip { void IpCore::dump() { - info("IP %s: vlnv=%s baseaddr=%#jx, irq=%d, port=%d", - name.c_str(), vlnv.toString().c_str(), baseaddr, irq, port); + cpp_info << id; + { + Logger::Indenter indent = cpp_info.indent(); + cpp_info << " Baseaddr: 0x" << std::hex << baseaddr << std::dec; +// cpp_info << " IRQ: " << irq; +// cpp_info << " Port: " << port; + } } @@ -54,80 +142,145 @@ IpCoreFactory* IpCoreFactory::lookup(const Vlnv &vlnv) return nullptr; } -IpCore *IpCoreFactory::make(fpga::PCIeCard* card, json_t *json, std::string name) +uintptr_t +IpCore::getBaseaddr() const { + assert(card != nullptr); + return reinterpret_cast(card->map) + this->baseaddr; +} + + +IpCoreList +IpCoreFactory::make(PCIeCard* card, json_t *json_ips) { - int ret; - const char* vlnv_raw; + DependencyGraph dependencyGraph; + IpCoreList initializedIps; - // extract VLNV from JSON - ret = json_unpack(json, "{ s: s }", - "vlnv", &vlnv_raw); - if(ret != 0) { - cpp_warn << "IP " << name << " has no entry 'vlnv'"; - return nullptr; + { + Logger::Indenter indent = cpp_debug.indent(); + cpp_debug << "Parsing IP dependency graph:"; + + void* iter = json_object_iter(json_ips); + while(iter != nullptr) { + buildDependencyGraph(dependencyGraph, json_ips, json_object_iter_key(iter)); + iter = json_object_iter_next(json_ips, iter); + } } - // parse VLNV - Vlnv vlnv(vlnv_raw); + { + Logger::Indenter indent = cpp_debug.indent(); + cpp_debug << "IP initialization order:"; - // find the appropriate factory that can create the specified VLNV - // Note: - // This is the magic part! Factories automatically register as a plugin - // as soon as they are instantiated. If there are multiple candidates, - // the first suitable factory will be used. - IpCoreFactory* ipCoreFactory = lookup(vlnv); - - if(ipCoreFactory == nullptr) { - cpp_warn << "No plugin found to handle " << vlnv; - return nullptr; + for(auto& ipName : dependencyGraph.getEvaluationOrder()) { + cpp_debug << TXT_BOLD(ipName); + } } - cpp_debug << "Using " << ipCoreFactory->getName() << " for IP " << vlnv; - // Create new IP instance. Since this function is virtual, it will construct - // the right, specialized type without knowing it here because we have - // already picked the right factory. - IpCore* ip = ipCoreFactory->create(); - if(ip == nullptr) { - cpp_warn << "Cannot create an instance of " << ipCoreFactory->getName(); - goto fail; + cpp_info << "Initializing IP cores"; + + Logger::Indenter indent = cpp_info.indent(); + for(auto& ipName : dependencyGraph.getEvaluationOrder()) { + cpp_debug << TXT_BOLD(ipName); + json_t* json_ip = json_object_get(json_ips, ipName.c_str()); + + // extract VLNV from JSON + const char* vlnv; + if(json_unpack(json_ip, "{ s: s }", "vlnv", &vlnv) != 0) { + cpp_warn << "IP " << ipName << " has no entry 'vlnv'"; + continue; + } + + IpIdentifier id(Vlnv(vlnv), ipName); + + // find the appropriate factory that can create the specified VLNV + // Note: + // This is the magic part! Factories automatically register as a + // plugin as soon as they are instantiated. If there are multiple + // candidates, the first suitable factory will be used. + IpCoreFactory* ipCoreFactory = lookup(id.vlnv); + + if(ipCoreFactory == nullptr) { + cpp_warn << "No plugin found to handle " << vlnv; + continue; + } else { + cpp_debug << "Using " << ipCoreFactory->getName() + << " for IP " << vlnv; + } + + // Create new IP instance. Since this function is virtual, it will + // construct the right, specialized type without knowing it here + // because we have already picked the right factory. + // If something goes wrong with initialization, the shared_ptr will + // take care to desctruct the IpCore again as it is not pushed to + // the list and will run out of scope. + auto ip = std::unique_ptr(ipCoreFactory->create()); + + if(ip == nullptr) { + cpp_warn << "Cannot create an instance of " + << ipCoreFactory->getName(); + continue; + } + + // setup generic IP type properties + ip->card = card; + ip->id = id; + + // extract some optional properties + int ret = json_unpack(json_ip, "{ s?: i}", //, s?: i, s?: i }", + "baseaddr", &ip->baseaddr); + // "irq", &ip->irq, + // "port", &ip->port); + if(ret != 0) { + cpp_warn << "Problem while parsing JSON for IP " + << TXT_BOLD(ipName); + continue; + } + + bool dependenciesOk = true; + for(auto& [depName, depVlnv] : ipCoreFactory->getDependencies()) { + // lookup dependency IP core in list of already initialized IPs + auto iter = std::find_if(initializedIps.begin(), + initializedIps.end(), + [&](const std::unique_ptr& ip) { + return *ip == depVlnv; + }); + + if(iter == initializedIps.end()) { + cpp_error << "Cannot find '" << depName << "' dependency " + << depVlnv.toString() + << "of " << TXT_BOLD(ipName); + dependenciesOk = false; + break; + } + + cpp_debug << "Found dependency IP " << (*iter)->id; + ip->dependencies[depName] = (*iter).get(); + } + + if(not dependenciesOk) { + continue; + } + + // IP-specific setup via JSON config + ipCoreFactory->configureJson(ip, json_ip); + + // TODO: currently fails, fix and remove comment +// if(not ip->start()) { +// cpp_error << "Cannot start IP" << ip->id.name; +// continue; +// } + + if(not ip->check()) { + cpp_error << "Checking IP " << ip->id.name << " failed"; + continue; + } + + initializedIps.push_back(std::move(ip)); } - // setup generic IP type properties - ip->card = card; - ip->name = name; - ip->vlnv = vlnv; - // extract some optional properties - ret = json_unpack(json, "{ s?: i, s?: i, s?: i }", - "baseaddr", &ip->baseaddr, - "irq", &ip->irq, - "port", &ip->port); - if(ret != 0) { - cpp_warn << "Problem while parsing JSON"; - goto fail; - } - - // IP-specific setup via JSON config - ipCoreFactory->configureJson(ip, json); - - // TODO: currently fails, fix and remove comment -// if(not ip->start()) { -// cpp_error << "Cannot start IP" << ip->name; -// goto fail; -// } - - if(not ip->check()) { - cpp_error << "Checking IP " << ip->name << " failed"; - goto fail; - } - - return ip; - -fail: - delete ip; - return nullptr; + return initializedIps; } } // namespace ip diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 78f598aab..860c97677 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -29,8 +29,7 @@ #include "kernel/vfio.h" #include "kernel/kernel.h" -#include "fpga/ip.h" -#include "fpga/card.h" +#include "fpga/card.hpp" #include "fpga/ips/intc.hpp" namespace villas { @@ -48,6 +47,7 @@ InterruptController::~InterruptController() bool InterruptController::start() { + return true; const uintptr_t base = getBaseaddr(); num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); diff --git a/fpga/lib/log.cpp b/fpga/lib/log.cpp index ff19f30c3..a16ebc375 100644 --- a/fpga/lib/log.cpp +++ b/fpga/lib/log.cpp @@ -3,12 +3,12 @@ int Logger::depth; int Logger::depthCurrent; -int Logger::global_level = 0; +Logger::LogLevel Logger::global_level = Logger::LogLevel::Info; -Logger cpp_debug(0, CLR_BLU("Debug: ")); -Logger cpp_info(20); -Logger cpp_warn(80, CLR_YEL("Warning: ")); -Logger cpp_error(100, CLR_RED("Error: ")); +Logger cpp_debug(Logger::LogLevel::Debug, "" CLR_BLU(" Debug ") "| "); +Logger cpp_info(Logger::LogLevel::Info); +Logger cpp_warn(Logger::LogLevel::Warning, "" CLR_YEL("Warning") "| "); +Logger cpp_error(Logger::LogLevel::Error, "" CLR_RED(" Error ") "| "); void test() { diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 1d3cd11ea..ef53ea1b0 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -53,6 +54,8 @@ static void init() villas::Plugin::dumpList(); + Logger::setLogLevel(Logger::LogLevel::Debug); + ret = pci_init(&pci); cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); @@ -80,7 +83,7 @@ static void init() // create an FPGA card instance using the corresponding plugin // villas::FpgaCard* fpgaCard = fpgaCardPlugin->make(json_); - std::list fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); + auto fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); json_t *json_card = json_object_get(fpgas, FPGA_CARD); cr_assert_not_null(json_card, "FPGA card " FPGA_CARD " not found"); From c105f1c925fa465263a0099a3a632b04bd1692fa Mon Sep 17 00:00:00 2001 From: daniel-k Date: Thu, 21 Dec 2017 21:16:58 +0100 Subject: [PATCH 039/560] lib/ip: remove unused includes and replace pragma by include guard --- fpga/include/villas/fpga/ip.hpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 0929bd3b8..0e0001a34 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -28,18 +28,10 @@ * @{ */ -#pragma once - -#include -#include - -#include "common.h" - -#include "log.h" -#include "utils.h" +#ifndef VILLAS_IP_HPP +#define VILLAS_IP_HPP #include "fpga/vlnv.hpp" - #include "plugin.hpp" #include @@ -161,3 +153,5 @@ private: } // namespace ip } // namespace fpga } // namespace villas + +#endif // VILLAS_IP_HPP From ab183d211152444663dd4d2f3f50421328dc5116 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Thu, 21 Dec 2017 21:17:26 +0100 Subject: [PATCH 040/560] lib/ip: flip output of IpIdentifier (name first, VLNV second) --- fpga/include/villas/fpga/ip.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 0e0001a34..8da572d7e 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -59,7 +59,7 @@ public: friend std::ostream& operator<< (std::ostream& stream, const IpIdentifier& id) - { return stream << "VLNV: " << id.vlnv << " Name: " << id.name; } + { return stream << " Name: " << id.name << "(VLNV: " << id.vlnv << ")"; } Vlnv vlnv; std::string name; From 9676a9535ddae8c696a8278b50307489e23bc07f Mon Sep 17 00:00:00 2001 From: daniel-k Date: Thu, 21 Dec 2017 21:18:52 +0100 Subject: [PATCH 041/560] lib/ip: re-add IRQ to IpCore --- fpga/include/villas/fpga/ip.hpp | 7 +++---- fpga/lib/ip.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 8da572d7e..33f78a904 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -75,7 +75,7 @@ public: friend IpCoreFactory; - IpCore() : card(nullptr), baseaddr(0) {} //, irq(-1), port(-1) {} + IpCore() : card(nullptr), baseaddr(0), irq(-1) {} virtual ~IpCore() {} // IPs can implement this interface @@ -108,10 +108,9 @@ protected: protected: // populated by FpgaIpFactory PCIeCard* card; /**< FPGA card this IP is instantiated on */ - IpIdentifier id; /**< VLNV and name defined in JSON config */ + IpIdentifier id; /**< VLNV and name defined in JSON config */ uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ -// int irq; /**< The interrupt number of the FPGA IP component. */ -// int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ + int irq; /**< The interrupt number of the FPGA IP component. */ std::map dependencies; }; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index b8e020ba1..6fef6270e 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -227,10 +227,10 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) ip->id = id; // extract some optional properties - int ret = json_unpack(json_ip, "{ s?: i}", //, s?: i, s?: i }", - "baseaddr", &ip->baseaddr); - // "irq", &ip->irq, - // "port", &ip->port); + int ret = json_unpack(json_ip, "{ s?: i, s?: i }", + "baseaddr", &ip->baseaddr, // required + "irq", &ip->irq); // optional + if(ret != 0) { cpp_warn << "Problem while parsing JSON for IP " << TXT_BOLD(ipName); From f3f0f4d630498f8f572d60fade4876701604cd2d Mon Sep 17 00:00:00 2001 From: daniel-k Date: Thu, 21 Dec 2017 21:19:52 +0100 Subject: [PATCH 042/560] lib/ip: pass ip core by reference to factory instead of unique_ptr --- fpga/include/villas/fpga/ip.hpp | 2 +- fpga/lib/ip.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 33f78a904..8912c3190 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -134,7 +134,7 @@ private: virtual IpCore* create() = 0; /// Configure IP instance from JSON config - virtual bool configureJson(const std::unique_ptr& ip, json_t *json) + virtual bool configureJson(IpCore& ip, json_t *json) { return true; } virtual Vlnv getCompatibleVlnv() const = 0; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 6fef6270e..6e5e4cb51 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -263,7 +263,7 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) } // IP-specific setup via JSON config - ipCoreFactory->configureJson(ip, json_ip); + ipCoreFactory->configureJson(*ip, json_ip); // TODO: currently fails, fix and remove comment // if(not ip->start()) { From 12024d53e51dbd9f6762eac0e58975ee8f8d6f21 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Thu, 21 Dec 2017 21:22:05 +0100 Subject: [PATCH 043/560] lib/ip-node: add IpNode class, IpCore which has streaming ports --- fpga/etc/fpga.json | 66 ++++++++++++++----- fpga/include/villas/fpga/ip.hpp | 2 +- fpga/include/villas/fpga/ip_node.hpp | 81 +++++++++++++++++++++++ fpga/include/villas/fpga/ips/intc.hpp | 2 + fpga/lib/CMakeLists.txt | 1 + fpga/lib/ip_node.cpp | 94 +++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 fpga/include/villas/fpga/ip_node.hpp create mode 100644 fpga/lib/ip_node.cpp diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index 5fcc31556..b1cbb50ea 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -26,7 +26,6 @@ "slot": "03:00.0", "do_reset": true, "ips": { - "axi_reset_0": { "vlnv": "xilinx.com:ip:axi_gpio:2.0", "baseaddr": 28672 @@ -39,7 +38,6 @@ "dma_0": { "vlnv": "xilinx.com:ip:axi_dma:7.1", "baseaddr": 12288, - "port": "switch_0:1", "irq": "axi_pcie_intc_0:3" }, "axi_pcie_intc_0": { @@ -49,26 +47,38 @@ "dma_1": { "vlnv": "xilinx.com:ip:axi_dma:7.1", "baseaddr": 8192, - "port": "switch_0:6", + "ports": { + "master": [ { "num": 0, "other": "switch_0:6" } ], + "slave": [ { "num": 0, "other": "switch_0:6" } ] + }, "irq": "axi_pcie_intc_0:3" }, "fifo_mm_s_0": { "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", "baseaddr": 24576, "baseaddr_axi4": 49152, - "port": "switch_0:2", + "ports": { + "master": [ { "num": 0, "other": "switch_0:2" } ], + "slave": [ { "num": 0, "other": "switch_0:2" } ] + }, "irq": "axi_pcie_intc_0:2" }, "rtds_axis_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", "baseaddr": 32768, - "port": "switch_0:0", + "ports": { + "master": [ { "num": 0, "other": "switch_0:0" } ], + "slave": [ { "num": 0, "other": "switch_0:0" } ] + }, "irq": "axi_pcie_intc_0:5" }, "hls_dft_0": { "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", "baseaddr": 36864, - "port": "switch_0:5", + "ports": { + "master": [ { "num": 0, "other": "switch_0:5" } ], + "slave": [ { "num": 0, "other": "switch_0:5" } ] + }, "irq": "axi_pcie_intc_0:1", "period": 400, "harmonics": [ @@ -82,23 +92,47 @@ }, "axis_data_fifo_0": { "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", - "port": "switch_0:3" + "ports": { + "master": [ { "num": 0, "other": "switch_0:3" } ], + "slave": [ { "num": 0, "other": "switch_0:3" } ] + } }, "switch_0": { "vlnv": "xilinx.com:ip:axis_interconnect:2.1", "baseaddr": 20480, - "num_ports": 10, - "paths": [ - { - "in": "rtds_axis_0", - "out": "dma_1", - "reverse": true - } - ] + "ports": { + "master": [ + { "num": 0 }, + { "num": 1 }, + { "num": 2 }, + { "num": 3 }, + { "num": 4 }, + { "num": 5 }, + { "num": 6 }, + { "num": 7 }, + { "num": 8 }, + { "num": 9 } + ], + "slave": [ + { "num": 0 }, + { "num": 1 }, + { "num": 2 }, + { "num": 3 }, + { "num": 4 }, + { "num": 5 }, + { "num": 6 }, + { "num": 7 }, + { "num": 8 }, + { "num": 9 } + ] + } }, "axis_data_fifo_1": { "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", - "port": "switch_0:6" + "ports": { + "master": [ { "num": 0, "other": "switch_0:6" } ], + "slave": [ { "num": 0, "other": "switch_0:6" } ] + } } } } diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 8912c3190..d265ecc50 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -122,7 +122,7 @@ using IpCoreList = std::list>; class IpCoreFactory : public Plugin { public: IpCoreFactory(std::string concreteName) : - Plugin(std::string("FPGA IpCore Factory: ") + concreteName) + Plugin(std::string("IpCore - ") + concreteName) { pluginType = Plugin::Type::FpgaIp; } /// Returns a running and checked FPGA IP diff --git a/fpga/include/villas/fpga/ip_node.hpp b/fpga/include/villas/fpga/ip_node.hpp new file mode 100644 index 000000000..0548988d9 --- /dev/null +++ b/fpga/include/villas/fpga/ip_node.hpp @@ -0,0 +1,81 @@ +/** Interlectual Property component. + * + * This class represents a module within the FPGA. + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#ifndef VILLAS_IP_NODE_HPP +#define VILLAS_IP_NODE_HPP + +#include +#include +#include + +#include "ip.hpp" +#include "log.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +// TODO: reflect on interface that an IpNode exposes and how to design it to +// blend in with VILLASnode software nodes +class IpNode : public IpCore { +public: + + friend class IpNodeFactory; + + struct OtherIpNode { + int otherPortNum; + std::string otherName; + }; + + bool connectTo(int port, const OtherIpNode& other); + bool disconnect(int port); + +protected: + std::map portsMaster; + std::map portsSlave; +}; + +class IpNodeFactory : public IpCoreFactory { +public: + IpNodeFactory(std::string name) : IpCoreFactory("Ip Node - " + name) {} + + virtual bool configureJson(IpCore& ip, json_t *json_ip); + +private: + bool populatePorts(std::map& portMap, json_t* json); +}; + +/** @} */ + +} // namespace ip +} // namespace fpga +} // namespace villas + +#endif // VILLAS_IP_NODE_HPP diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 1a9f525be..fe7e20f32 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -31,6 +31,8 @@ #include "fpga/ip.hpp" #include +#include "fpga/ip_node.hpp" + namespace villas { namespace fpga { namespace ip { diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 40e5df6bd..e4bb46b76 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES ip.cpp ip.c + ip_node.cpp vlnv.cpp vlnv.c card.c diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp new file mode 100644 index 000000000..cca7fc579 --- /dev/null +++ b/fpga/lib/ip_node.cpp @@ -0,0 +1,94 @@ +#include +#include +#include + +#include "utils.hpp" +#include "fpga/ip_node.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +bool +IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) +{ + auto ipNode = reinterpret_cast(ip); + + json_t* json_ports = json_object_get(json_ip, "ports"); + if(json_ports == nullptr) { + cpp_error << "IpNode " << ip << " has no ports property"; + return false; + } + + json_t* json_master = json_object_get(json_ports, "master"); + json_t* json_slave = json_object_get(json_ports, "slave"); + + const bool hasMasterPorts = json_is_array(json_master); + const bool hasSlavePorts = json_is_array(json_slave); + + if( (not hasMasterPorts) and (not hasSlavePorts)) { + cpp_error << "IpNode " << ip << " has not ports"; + return false; + } + + // intentionally use short-circuit evaluation to only call populatePorts + // if the property exists + bool masterPortsSuccess = + (hasMasterPorts and populatePorts(ipNode.portsMaster, json_master)) + or (not hasMasterPorts); + + bool slavePortsSuccess = + (hasSlavePorts and populatePorts(ipNode.portsSlave, json_slave)) + or (not hasSlavePorts); + + return (masterPortsSuccess and slavePortsSuccess); +} + +bool +IpNodeFactory::populatePorts(std::map& portMap, json_t* json) +{ + size_t index; + json_t* json_port; + json_array_foreach(json, index, json_port) { + int myPortNum; + const char* other = nullptr; + + int ret = json_unpack(json_port, "{ s : i, s? : s}", + "num", &myPortNum, + "other", &other); + if(ret != 0) { + cpp_error << "Port definition required field 'num'"; + return false; + } + + if(other == nullptr) { + cpp_warn << "Nothing connected to port " << myPortNum; + portMap[myPortNum] = {}; + continue; + } + + const auto tokens = utils::tokenize(other, ":"); + if(tokens.size() != 2) { + cpp_error << "Too many tokens in property 'other'"; + return false; + } + + int otherPortNum; + + try { + otherPortNum = std::stoi(tokens[1]); + } catch(const std::invalid_argument&) { + cpp_error << "Other port number is not an integral number"; + return false; + } + + cpp_debug << "Adding port mapping: " << myPortNum << ":" << other; + portMap[myPortNum] = { otherPortNum, tokens[0] }; + } + + return true; +} + +} // namespace ip +} // namespace fpga +} // namespace villas From 79f37ce35215f7358bdef6b16bbaeb4ea24fad74 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Thu, 21 Dec 2017 21:23:54 +0100 Subject: [PATCH 044/560] ips/switch: add C++ implementation of switch --- fpga/include/villas/fpga/ips/switch.hpp | 94 ++++++++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/switch.cpp | 121 ++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 fpga/include/villas/fpga/ips/switch.hpp create mode 100644 fpga/lib/ips/switch.cpp diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp new file mode 100644 index 000000000..bc1fbd7c8 --- /dev/null +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -0,0 +1,94 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include +#include + +#include "fpga/ip_node.hpp" +#include "fpga/vlnv.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +class AxiStreamSwitch : public IpNode { +public: + friend class AxiStreamSwitchFactory; + + bool start(); + + bool connect(int portSlave, int portMaster); + bool disconnectMaster(int port); + bool disconnectSlave(int port); + +private: + static constexpr int PORT_DISABLED = -1; + + struct Path { + IpCore* masterOut; + IpCore* slaveIn; + }; + + XAxis_Switch xilinxDriver; + std::map portMapping; +}; + + +class AxiStreamSwitchFactory : public IpNodeFactory { +public: + AxiStreamSwitchFactory() : + IpNodeFactory(getName()) {} + + IpCore* create() + { return new AxiStreamSwitch; } + + std::string getName() const + { return "AxiStreamSwitch"; } + + std::string getDescription() const + { return "Xilinx's AXI4-Stream switch"; } + + Vlnv getCompatibleVlnv() const + { return Vlnv("xilinx.com:ip:axis_interconnect:"); } + + std::list getDependencies() const + { return {}; } +}; + +} // namespace ip +} // namespace fpga +} // namespace villas + +/** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index e4bb46b76..f249174c0 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES ips/timer.cpp ips/model.c ips/switch.c + ips/switch.cpp ips/dft.c ips/fifo.c ips/dma.c diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp new file mode 100644 index 000000000..d01e4791e --- /dev/null +++ b/fpga/lib/ips/switch.cpp @@ -0,0 +1,121 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "log.hpp" +#include "fpga/ips/switch.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +static AxiStreamSwitchFactory factory; + +bool +AxiStreamSwitch::start() +{ + /* Setup AXI-stream switch */ + XAxis_Switch_Config sw_cfg; + sw_cfg.MaxNumMI = portsMaster.size(); + sw_cfg.MaxNumSI = portsSlave.size(); + + if(XAxisScr_CfgInitialize(&xilinxDriver, &sw_cfg, getBaseaddr()) != XST_SUCCESS) { + cpp_error << "Cannot start " << *this; + return false; + } + + /* Disable all masters */ + XAxisScr_RegUpdateDisable(&xilinxDriver); + XAxisScr_MiPortDisableAll(&xilinxDriver); + XAxisScr_RegUpdateEnable(&xilinxDriver); + + // initialize internal mapping + for(int portMaster = 0; portMaster < portsMaster.size(); portMaster++) { + portMapping[portMaster] = PORT_DISABLED; + } + + + return true; +} + +bool +AxiStreamSwitch::connect(int portSlave, int portMaster) +{ + if(portMapping[portMaster] == portSlave) { + cpp_debug << "Ports already connected"; + return true; + } + + for(auto [master, slave] : portMapping) { + if(slave == portSlave) { + cpp_warn << "Slave " << slave << " has already been connected to " + << "master " << master << ". Disabling master " << master; + + XAxisScr_RegUpdateDisable(&xilinxDriver); + XAxisScr_MiPortDisable(&xilinxDriver, master); + XAxisScr_RegUpdateEnable(&xilinxDriver); + } + } + + /* Reconfigure switch */ + XAxisScr_RegUpdateDisable(&xilinxDriver); + XAxisScr_MiPortEnable(&xilinxDriver, portMaster, portSlave); + XAxisScr_RegUpdateEnable(&xilinxDriver); + + cpp_debug << "Connect slave " << portSlave << " to master " << portMaster; + + return true; +} + +bool +AxiStreamSwitch::disconnectMaster(int port) +{ + cpp_debug << "Disconnect slave " << portMapping[port] + << " from master " << port; + + XAxisScr_MiPortDisable(&xilinxDriver, port); + portMapping[port] = PORT_DISABLED; + return true; +} + +bool +AxiStreamSwitch::disconnectSlave(int port) +{ + for(auto [master, slave] : portMapping) { + if(slave == port) { + cpp_debug << "Disconnect slave " << slave << " from master " << master; + XAxisScr_MiPortDisable(&xilinxDriver, master); + return true; + } + } + + cpp_debug << "Slave " << port << " hasn't been connected to any master"; + return true; +} + +} // namespace ip +} // namespace fpga +} // namespace villas From 4d3e4dd9315f5f9583d0b006d64612559140d9bb Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 10:20:46 +0100 Subject: [PATCH 045/560] ips: make irqs a list --- fpga/etc/fpga.json | 12 +++++------ fpga/include/villas/fpga/ip.hpp | 19 +++++++++++------ fpga/lib/ip.cpp | 36 ++++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index b1cbb50ea..aaac01297 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -33,12 +33,12 @@ "timer_0": { "vlnv": "xilinx.com:ip:axi_timer:2.0", "baseaddr": 16384, - "irq": "axi_pcie_intc_0:0" + "irqs": [ "axi_pcie_intc_0:0" ] }, "dma_0": { "vlnv": "xilinx.com:ip:axi_dma:7.1", "baseaddr": 12288, - "irq": "axi_pcie_intc_0:3" + "irqs": [ "axi_pcie_intc_0:3" ] }, "axi_pcie_intc_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0", @@ -51,7 +51,7 @@ "master": [ { "num": 0, "other": "switch_0:6" } ], "slave": [ { "num": 0, "other": "switch_0:6" } ] }, - "irq": "axi_pcie_intc_0:3" + "irqs": [ "axi_pcie_intc_0:3" ] }, "fifo_mm_s_0": { "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", @@ -61,7 +61,7 @@ "master": [ { "num": 0, "other": "switch_0:2" } ], "slave": [ { "num": 0, "other": "switch_0:2" } ] }, - "irq": "axi_pcie_intc_0:2" + "irqs": [ "axi_pcie_intc_0:2" ] }, "rtds_axis_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", @@ -70,7 +70,7 @@ "master": [ { "num": 0, "other": "switch_0:0" } ], "slave": [ { "num": 0, "other": "switch_0:0" } ] }, - "irq": "axi_pcie_intc_0:5" + "irqs": [ "axi_pcie_intc_0:5", "axi_pcie_intc_0:6" ] }, "hls_dft_0": { "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", @@ -79,7 +79,7 @@ "master": [ { "num": 0, "other": "switch_0:5" } ], "slave": [ { "num": 0, "other": "switch_0:5" } ] }, - "irq": "axi_pcie_intc_0:1", + "irqs": [ "axi_pcie_intc_0:1" ], "period": 400, "harmonics": [ 0, diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index d265ecc50..a9cef05a9 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -75,7 +75,7 @@ public: friend IpCoreFactory; - IpCore() : card(nullptr), baseaddr(0), irq(-1) {} + IpCore() : card(nullptr), baseaddr(0) {} virtual ~IpCore() {} // IPs can implement this interface @@ -105,14 +105,21 @@ protected: uintptr_t getBaseaddr() const; + struct IrqPort { + int num; + std::string controllerName; + std::string description; + }; + protected: // populated by FpgaIpFactory - PCIeCard* card; /**< FPGA card this IP is instantiated on */ - IpIdentifier id; /**< VLNV and name defined in JSON config */ - uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ - int irq; /**< The interrupt number of the FPGA IP component. */ + PCIeCard* card; ///< FPGA card this IP is instantiated on + IpIdentifier id; ///< VLNV and name defined in JSON config + uintptr_t baseaddr; ///< The baseadress of this IP component + std::map irqs; ///< Interrupts of this IP component - std::map dependencies; +private: + std::map dependencies; ///< dependencies on other IPs }; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 6e5e4cb51..e84c7cb0e 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -226,17 +226,39 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) ip->card = card; ip->id = id; - // extract some optional properties - int ret = json_unpack(json_ip, "{ s?: i, s?: i }", - "baseaddr", &ip->baseaddr, // required - "irq", &ip->irq); // optional - - if(ret != 0) { - cpp_warn << "Problem while parsing JSON for IP " + // extract base address if it has one + if(json_unpack(json_ip, "{ s?: i }", "baseaddr", &ip->baseaddr) != 0) { + cpp_warn << "Problem while parsing base address of IP " << TXT_BOLD(ipName); continue; } + json_t* json_irqs = json_object_get(json_ip, "irqs"); + if(json_is_array(json_irqs)) { + size_t index; + json_t* json_irq; + json_array_foreach(json_irqs, index, json_irq) { + const char* irq = json_string_value(json_irq); + auto tokens = utils::tokenize(irq, ":"); + if(tokens.size() != 2) { + cpp_warn << "Cannot parse IRQ '" << irq << "' of" + << TXT_BOLD(ipName); + continue; + } + + int num; + try { + num = std::stoi(tokens[1]); + } catch(const std::invalid_argument&) { + cpp_warn << "IRQ number is not an integer: '" << irq << "'"; + continue; + } + + ip->irqs[index] = {num, tokens[0]}; + } + } + + bool dependenciesOk = true; for(auto& [depName, depVlnv] : ipCoreFactory->getDependencies()) { // lookup dependency IP core in list of already initialized IPs From f94476b716966e28e3426afec300ac6110ef8c4b Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 10:23:04 +0100 Subject: [PATCH 046/560] ip/node: rename `OtherIpNode` to `StreamPort` and `other` to `to` --- fpga/etc/fpga.json | 24 ++++++++++++------------ fpga/include/villas/fpga/ip_node.hpp | 14 +++++++------- fpga/lib/ip_node.cpp | 13 ++++++------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index aaac01297..de9e93980 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -48,8 +48,8 @@ "vlnv": "xilinx.com:ip:axi_dma:7.1", "baseaddr": 8192, "ports": { - "master": [ { "num": 0, "other": "switch_0:6" } ], - "slave": [ { "num": 0, "other": "switch_0:6" } ] + "master": [ { "num": 0, "to": "switch_0:6" } ], + "slave": [ { "num": 0, "to": "switch_0:6" } ] }, "irqs": [ "axi_pcie_intc_0:3" ] }, @@ -58,8 +58,8 @@ "baseaddr": 24576, "baseaddr_axi4": 49152, "ports": { - "master": [ { "num": 0, "other": "switch_0:2" } ], - "slave": [ { "num": 0, "other": "switch_0:2" } ] + "master": [ { "num": 0, "to": "switch_0:2" } ], + "slave": [ { "num": 0, "to": "switch_0:2" } ] }, "irqs": [ "axi_pcie_intc_0:2" ] }, @@ -67,8 +67,8 @@ "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", "baseaddr": 32768, "ports": { - "master": [ { "num": 0, "other": "switch_0:0" } ], - "slave": [ { "num": 0, "other": "switch_0:0" } ] + "master": [ { "num": 0, "to": "switch_0:0" } ], + "slave": [ { "num": 0, "to": "switch_0:0" } ] }, "irqs": [ "axi_pcie_intc_0:5", "axi_pcie_intc_0:6" ] }, @@ -76,8 +76,8 @@ "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", "baseaddr": 36864, "ports": { - "master": [ { "num": 0, "other": "switch_0:5" } ], - "slave": [ { "num": 0, "other": "switch_0:5" } ] + "master": [ { "num": 0, "to": "switch_0:5" } ], + "slave": [ { "num": 0, "to": "switch_0:5" } ] }, "irqs": [ "axi_pcie_intc_0:1" ], "period": 400, @@ -93,8 +93,8 @@ "axis_data_fifo_0": { "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", "ports": { - "master": [ { "num": 0, "other": "switch_0:3" } ], - "slave": [ { "num": 0, "other": "switch_0:3" } ] + "master": [ { "num": 0, "to": "switch_0:3" } ], + "slave": [ { "num": 0, "to": "switch_0:3" } ] } }, "switch_0": { @@ -130,8 +130,8 @@ "axis_data_fifo_1": { "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", "ports": { - "master": [ { "num": 0, "other": "switch_0:6" } ], - "slave": [ { "num": 0, "other": "switch_0:6" } ] + "master": [ { "num": 0, "to": "switch_0:6" } ], + "slave": [ { "num": 0, "to": "switch_0:6" } ] } } } diff --git a/fpga/include/villas/fpga/ip_node.hpp b/fpga/include/villas/fpga/ip_node.hpp index 0548988d9..3a319a27d 100644 --- a/fpga/include/villas/fpga/ip_node.hpp +++ b/fpga/include/villas/fpga/ip_node.hpp @@ -49,17 +49,17 @@ public: friend class IpNodeFactory; - struct OtherIpNode { - int otherPortNum; - std::string otherName; + struct StreamPort { + int portNumber; + std::string nodeName; }; - bool connectTo(int port, const OtherIpNode& other); + bool connect(int port, const StreamPort& to); bool disconnect(int port); protected: - std::map portsMaster; - std::map portsSlave; + std::map portsMaster; + std::map portsSlave; }; class IpNodeFactory : public IpCoreFactory { @@ -69,7 +69,7 @@ public: virtual bool configureJson(IpCore& ip, json_t *json_ip); private: - bool populatePorts(std::map& portMap, json_t* json); + bool populatePorts(std::map& portMap, json_t* json); }; /** @} */ diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index cca7fc579..31503e31a 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -45,36 +45,35 @@ IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) } bool -IpNodeFactory::populatePorts(std::map& portMap, json_t* json) +IpNodeFactory::populatePorts(std::map& portMap, json_t* json) { size_t index; json_t* json_port; json_array_foreach(json, index, json_port) { int myPortNum; - const char* other = nullptr; + const char* to = nullptr; int ret = json_unpack(json_port, "{ s : i, s? : s}", "num", &myPortNum, - "other", &other); + "to", &to); if(ret != 0) { cpp_error << "Port definition required field 'num'"; return false; } - if(other == nullptr) { + if(to == nullptr) { cpp_warn << "Nothing connected to port " << myPortNum; portMap[myPortNum] = {}; continue; } - const auto tokens = utils::tokenize(other, ":"); + const auto tokens = utils::tokenize(to, ":"); if(tokens.size() != 2) { cpp_error << "Too many tokens in property 'other'"; return false; } int otherPortNum; - try { otherPortNum = std::stoi(tokens[1]); } catch(const std::invalid_argument&) { @@ -82,7 +81,7 @@ IpNodeFactory::populatePorts(std::map& portMap, json_t return false; } - cpp_debug << "Adding port mapping: " << myPortNum << ":" << other; + cpp_debug << "Adding port mapping: " << myPortNum << ":" << to; portMap[myPortNum] = { otherPortNum, tokens[0] }; } From 25d55521970cffe0ff80e5e9e0bea9ea3f56e753 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 10:25:43 +0100 Subject: [PATCH 047/560] ips/intc: remove early return in start() --- fpga/lib/ips/intc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 860c97677..e7ae502d5 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -47,7 +47,6 @@ InterruptController::~InterruptController() bool InterruptController::start() { - return true; const uintptr_t base = getBaseaddr(); num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); From 018c89a2b036a629cec5ae3b423e8e4b5d74b35a Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 10:32:11 +0100 Subject: [PATCH 048/560] tests/main: C++-ify --- fpga/tests/main.cpp | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index ef53ea1b0..973e1eaca 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -40,10 +40,12 @@ #define CPU_HZ 3392389000 #define FPGA_AXI_HZ 125000000 -struct list cards; -struct fpga_card *card; struct pci pci; struct vfio_container vc; +villas::fpga::CardList fpgaCards; + +// keep to make it compile with old C tests +struct fpga_card* card; static void init() { @@ -80,41 +82,15 @@ static void init() cr_assert_not_null(plugin, "No plugin for FPGA card found"); villas::fpga::PCIeCardFactory* fpgaCardPlugin = dynamic_cast(plugin); - // create an FPGA card instance using the corresponding plugin -// villas::FpgaCard* fpgaCard = fpgaCardPlugin->make(json_); - - auto fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); - - json_t *json_card = json_object_get(fpgas, FPGA_CARD); - cr_assert_not_null(json_card, "FPGA card " FPGA_CARD " not found"); - - card = (struct fpga_card *) alloc(sizeof(struct fpga_card)); - cr_assert_not_null(card, "Cannot allocate memory for FPGA card"); - - ret = fpga_card_init(card, &pci, &vc); - cr_assert_eq(ret, 0, "FPGA card initialization failed"); - - ret = fpga_card_start(card); - cr_assert_eq(ret, 0, "FPGA card cannot be started"); - - ret = fpga_card_parse(card, json_card, FPGA_CARD); - cr_assert_eq(ret, 0, "Failed to parse FPGA config"); - - ret = fpga_card_check(card); - cr_assert_eq(ret, 0, "FPGA card check failed"); + // create all FPGA card instances using the corresponding plugin + fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); json_decref(json); - - if (criterion_options.logging_threshold < CRITERION_IMPORTANT) - fpga_card_dump(card); } static void fini() { - int ret; - - ret = fpga_card_destroy(card); - cr_assert_eq(ret, 0, "Failed to de-initilize FPGA"); + fpgaCards.clear(); } TestSuite(fpga, From cb25861c7ed23875b12e6be084bf9249dea6b530 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 10:41:20 +0100 Subject: [PATCH 049/560] ip: remove excess space in output of identifier --- fpga/include/villas/fpga/ip.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index a9cef05a9..ee1b0e99c 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -33,6 +33,7 @@ #include "fpga/vlnv.hpp" #include "plugin.hpp" +#include "log.hpp" #include #include @@ -59,7 +60,7 @@ public: friend std::ostream& operator<< (std::ostream& stream, const IpIdentifier& id) - { return stream << " Name: " << id.name << "(VLNV: " << id.vlnv << ")"; } + { return stream << "Name: " << TXT_BOLD(id.name) << " (VLNV: " << id.vlnv << ")"; } Vlnv vlnv; std::string name; From af234e29c6d7c3ac23bc73f3cf7be08064a5b5a8 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 11:15:59 +0100 Subject: [PATCH 050/560] lib/ip: provide default implementation for getDependencies() --- fpga/include/villas/fpga/ip.hpp | 2 +- fpga/include/villas/fpga/ips/intc.hpp | 3 --- fpga/include/villas/fpga/ips/switch.hpp | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index ee1b0e99c..191cf4e56 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -148,7 +148,7 @@ private: virtual Vlnv getCompatibleVlnv() const = 0; virtual std::string getName() const = 0; virtual std::string getDescription() const = 0; - virtual std::list getDependencies() const = 0; + virtual std::list getDependencies() const { return {}; } private: static IpCoreFactory* diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index fe7e20f32..919f7ab08 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -87,9 +87,6 @@ public: Vlnv getCompatibleVlnv() const { return Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } - - std::list getDependencies() const - { return {}; } }; } // namespace ip diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index bc1fbd7c8..1d5e08877 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -82,9 +82,6 @@ public: Vlnv getCompatibleVlnv() const { return Vlnv("xilinx.com:ip:axis_interconnect:"); } - - std::list getDependencies() const - { return {}; } }; } // namespace ip From 8345a15db01c2d84278f0d0a198c33c6db1f1e8a Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:27:02 +0100 Subject: [PATCH 051/560] lib/card: implement IP lookup Search for an initialized IP by name. --- fpga/include/villas/fpga/card.hpp | 1 + fpga/lib/card.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 755926b67..d3a79a4fc 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -73,6 +73,7 @@ public: bool reset() { return true; } void dump() { } + ip::IpCore* lookupIp(std::string name) const; ip::IpCoreList ips; ///< IPs located on this FPGA card diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index f03272cba..96da37c74 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -132,6 +132,17 @@ fpga::PCIeCardFactory::create() } +ip::IpCore* +PCIeCard::lookupIp(std::string name) const { + for(auto& ip : ips) { + if(*ip == name) { + return ip.get(); + } + } + return nullptr; +} + + bool fpga::PCIeCard::start() { int ret; From e96b08c53c5d0902e08b38c35a3feafe1170ab79 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:27:57 +0100 Subject: [PATCH 052/560] lib/ip: implement more comparison operators for IpCore --- fpga/include/villas/fpga/ip.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 191cf4e56..ae467750f 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -94,10 +94,20 @@ public: return vlnvMatch and (nameWildcard or id.name == otherId.name); } + bool + operator!= (const IpIdentifier& otherId) { + return !(*this == otherId); + } + bool operator== (const Vlnv& otherVlnv) { return id.vlnv == otherVlnv; } + bool + operator== (const std::string& otherName) + { return id.name == otherName; } + + friend std::ostream& operator<< (std::ostream& stream, const IpCore& ip) { return stream << ip.id; } From 4035aab49f9d190f1aec748b84397b769f18478a Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:28:46 +0100 Subject: [PATCH 053/560] lib/ip: dependencies cannot be private Has to be used by derived classes of course! --- fpga/include/villas/fpga/ip.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index ae467750f..172cbcb9e 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -128,8 +128,6 @@ protected: IpIdentifier id; ///< VLNV and name defined in JSON config uintptr_t baseaddr; ///< The baseadress of this IP component std::map irqs; ///< Interrupts of this IP component - -private: std::map dependencies; ///< dependencies on other IPs }; From c710a9535261528ccea40cf5c433bb3c37c89851 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:29:33 +0100 Subject: [PATCH 054/560] lib/ip: generalize getting mapped addresses --- fpga/include/villas/fpga/ip.hpp | 6 +++++- fpga/lib/ip.cpp | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 172cbcb9e..d78edb850 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -114,7 +114,11 @@ public: protected: uintptr_t - getBaseaddr() const; + getBaseaddr() const + { return getAddrMapped(this->baseaddr); } + + uintptr_t + getAddrMapped(uintptr_t address) const; struct IrqPort { int num; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index e84c7cb0e..8324c86fe 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -143,9 +143,10 @@ IpCoreFactory* IpCoreFactory::lookup(const Vlnv &vlnv) } uintptr_t -IpCore::getBaseaddr() const { +IpCore::getAddrMapped(uintptr_t address) const +{ assert(card != nullptr); - return reinterpret_cast(card->map) + this->baseaddr; + return reinterpret_cast(card->map) + address; } From 6ee860971a232d56d750cd69aaf85eff479bf0d5 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:30:17 +0100 Subject: [PATCH 055/560] ips/intc: supply methods to use with new IrqPort --- fpga/include/villas/fpga/ips/intc.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 919f7ab08..d4ec73e66 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -49,7 +49,13 @@ public: bool start(); int enableInterrupt(IrqMaskType mask, bool polling); + int enableInterrupt(IrqPort irq, bool polling) + { return enableInterrupt(1 << irq.num, polling); } + int disableInterrupt(IrqMaskType mask); + int disableInterrupt(IrqPort irq) + { return disableInterrupt(1 << irq.num); } + uint64_t waitForInterrupt(int irq); private: From 81db98e44897dd2546ff99d2746df53c4c5b8c0c Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:32:05 +0100 Subject: [PATCH 056/560] lib/ip-node: implement loopback mode if available --- fpga/include/villas/fpga/ip_node.hpp | 6 ++++ fpga/lib/ip_node.cpp | 47 +++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/fpga/include/villas/fpga/ip_node.hpp b/fpga/include/villas/fpga/ip_node.hpp index 3a319a27d..6f7f92486 100644 --- a/fpga/include/villas/fpga/ip_node.hpp +++ b/fpga/include/villas/fpga/ip_node.hpp @@ -57,6 +57,12 @@ public: bool connect(int port, const StreamPort& to); bool disconnect(int port); + bool loopbackPossible() const; + bool connectLoopback(); + +private: + std::pair getLoopbackPorts() const; + protected: std::map portsMaster; std::map portsSlave; diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index 31503e31a..e456949a5 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -4,15 +4,18 @@ #include "utils.hpp" #include "fpga/ip_node.hpp" +#include "fpga/ips/switch.hpp" +#include "fpga/card.hpp" namespace villas { namespace fpga { namespace ip { + bool IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) { - auto ipNode = reinterpret_cast(ip); + auto& ipNode = reinterpret_cast(ip); json_t* json_ports = json_object_get(json_ip, "ports"); if(json_ports == nullptr) { @@ -88,6 +91,48 @@ IpNodeFactory::populatePorts(std::map& portMap, json_t* return true; } +std::pair +IpNode::getLoopbackPorts() const +{ + for(auto& [masterNum, masterTo] : portsMaster) { + for(auto& [slaveNum, slaveTo] : portsSlave) { + // TODO: should we also check which IP both ports are connected to? + if(masterTo.nodeName == slaveTo.nodeName) { + return { masterNum, slaveNum }; + } + } + } + + return { -1, -1 }; +} + +bool +IpNode::loopbackPossible() const +{ + auto ports = getLoopbackPorts(); + return (ports.first != -1) and (ports.second != -1); +} + +bool +IpNode::connectLoopback() +{ + auto ports = getLoopbackPorts(); + const auto& portMaster = portsMaster[ports.first]; + const auto& portSlave = portsSlave[ports.second]; + + // TODO: verify this is really a switch! + auto axiStreamSwitch = reinterpret_cast( + card->lookupIp(portMaster.nodeName)); + + if(axiStreamSwitch == nullptr) { + cpp_error << "Cannot find IP " << *axiStreamSwitch; + return false; + } + + // switch's slave port is our master port and vice versa + return axiStreamSwitch->connect(portMaster.portNumber, portSlave.portNumber); +} + } // namespace ip } // namespace fpga } // namespace villas From da88b15af3ff2cf6ae204188d4421199288f2b6a Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:34:26 +0100 Subject: [PATCH 057/560] lib/ip: fail making if configureJson() fails --- fpga/lib/ip.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 8324c86fe..0fdb365fe 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -286,7 +286,10 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) } // IP-specific setup via JSON config - ipCoreFactory->configureJson(*ip, json_ip); + if(not ipCoreFactory->configureJson(*ip, json_ip)) { + cpp_warn << "Cannot configure IP from JSON"; + continue; + } // TODO: currently fails, fix and remove comment // if(not ip->start()) { From e2e78cf8b3f33104ba08247a5f9352e7c2f84112 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:35:47 +0100 Subject: [PATCH 058/560] lib: minor cleanup --- fpga/include/villas/fpga/ips/switch.hpp | 2 +- fpga/include/villas/fpga/ips/timer.hpp | 4 +--- fpga/lib/ip_node.cpp | 2 +- fpga/lib/ips/switch.cpp | 24 ++++++++++++------------ fpga/lib/ips/timer.cpp | 4 ++-- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index 1d5e08877..489e61920 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -61,7 +61,7 @@ private: IpCore* slaveIn; }; - XAxis_Switch xilinxDriver; + XAxis_Switch xSwitch; std::map portMapping; }; diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp index 0f28c79b5..dd1147f89 100644 --- a/fpga/include/villas/fpga/ips/timer.hpp +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -40,12 +40,10 @@ namespace ip { class Timer : public IpCore { public: -// ~Timer(); - bool start(); private: - XTmrCtr xtmr; + XTmrCtr xTmr; }; diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index e456949a5..47539c674 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -30,7 +30,7 @@ IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) const bool hasSlavePorts = json_is_array(json_slave); if( (not hasMasterPorts) and (not hasSlavePorts)) { - cpp_error << "IpNode " << ip << " has not ports"; + cpp_error << "IpNode " << ip << " has no ports"; return false; } diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index d01e4791e..51148a895 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -42,15 +42,15 @@ AxiStreamSwitch::start() sw_cfg.MaxNumMI = portsMaster.size(); sw_cfg.MaxNumSI = portsSlave.size(); - if(XAxisScr_CfgInitialize(&xilinxDriver, &sw_cfg, getBaseaddr()) != XST_SUCCESS) { + if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseaddr()) != XST_SUCCESS) { cpp_error << "Cannot start " << *this; return false; } /* Disable all masters */ - XAxisScr_RegUpdateDisable(&xilinxDriver); - XAxisScr_MiPortDisableAll(&xilinxDriver); - XAxisScr_RegUpdateEnable(&xilinxDriver); + XAxisScr_RegUpdateDisable(&xSwitch); + XAxisScr_MiPortDisableAll(&xSwitch); + XAxisScr_RegUpdateEnable(&xSwitch); // initialize internal mapping for(int portMaster = 0; portMaster < portsMaster.size(); portMaster++) { @@ -74,16 +74,16 @@ AxiStreamSwitch::connect(int portSlave, int portMaster) cpp_warn << "Slave " << slave << " has already been connected to " << "master " << master << ". Disabling master " << master; - XAxisScr_RegUpdateDisable(&xilinxDriver); - XAxisScr_MiPortDisable(&xilinxDriver, master); - XAxisScr_RegUpdateEnable(&xilinxDriver); + XAxisScr_RegUpdateDisable(&xSwitch); + XAxisScr_MiPortDisable(&xSwitch, master); + XAxisScr_RegUpdateEnable(&xSwitch); } } /* Reconfigure switch */ - XAxisScr_RegUpdateDisable(&xilinxDriver); - XAxisScr_MiPortEnable(&xilinxDriver, portMaster, portSlave); - XAxisScr_RegUpdateEnable(&xilinxDriver); + XAxisScr_RegUpdateDisable(&xSwitch); + XAxisScr_MiPortEnable(&xSwitch, portMaster, portSlave); + XAxisScr_RegUpdateEnable(&xSwitch); cpp_debug << "Connect slave " << portSlave << " to master " << portMaster; @@ -96,7 +96,7 @@ AxiStreamSwitch::disconnectMaster(int port) cpp_debug << "Disconnect slave " << portMapping[port] << " from master " << port; - XAxisScr_MiPortDisable(&xilinxDriver, port); + XAxisScr_MiPortDisable(&xSwitch, port); portMapping[port] = PORT_DISABLED; return true; } @@ -107,7 +107,7 @@ AxiStreamSwitch::disconnectSlave(int port) for(auto [master, slave] : portMapping) { if(slave == port) { cpp_debug << "Disconnect slave " << slave << " from master " << master; - XAxisScr_MiPortDisable(&xilinxDriver, master); + XAxisScr_MiPortDisable(&xSwitch, master); return true; } } diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index 246bc9a78..8a67f515b 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -42,8 +42,8 @@ bool Timer::start() XTmrCtr_Config xtmr_cfg; xtmr_cfg.SysClockFreqHz = FPGA_AXI_HZ; - XTmrCtr_CfgInitialize(&xtmr, &xtmr_cfg, getBaseaddr()); - XTmrCtr_InitHw(&xtmr); + XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseaddr()); + XTmrCtr_InitHw(&xTmr); return true; } From 71a54eeab6d42e19e929f8d4f15f931c58f6474f Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 9 Jan 2018 16:37:02 +0100 Subject: [PATCH 059/560] lib/ips: implement fifo driver and adapt test --- fpga/include/villas/fpga/ips/fifo.hpp | 89 ++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/fifo.cpp | 193 ++++++++++++++++++++++++++ fpga/tests/CMakeLists.txt | 14 +- fpga/tests/{fifo.c => fifo.cpp} | 53 +++---- fpga/tests/main.cpp | 7 + 6 files changed, 325 insertions(+), 32 deletions(-) create mode 100644 fpga/include/villas/fpga/ips/fifo.hpp create mode 100644 fpga/lib/ips/fifo.cpp rename fpga/tests/{fifo.c => fifo.cpp} (54%) diff --git a/fpga/include/villas/fpga/ips/fifo.hpp b/fpga/include/villas/fpga/ips/fifo.hpp new file mode 100644 index 000000000..bf26089b3 --- /dev/null +++ b/fpga/include/villas/fpga/ips/fifo.hpp @@ -0,0 +1,89 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include "fpga/ip_node.hpp" +#include + + +namespace villas { +namespace fpga { +namespace ip { + + +class Fifo : public IpNode +{ +public: + friend class FifoFactory; + + bool start(); + bool stop(); + + size_t write(const void* buf, size_t len); + size_t read(void* buf, size_t len); + +private: + XLlFifo xFifo; + uintptr_t baseaddr_axi4; +}; + + + +class FifoFactory : public IpNodeFactory { +public: + FifoFactory() : + IpNodeFactory(getName()) + {} + + bool configureJson(IpCore& ip, json_t *json_ip); + + IpCore* create() + { return new Fifo; } + + std::string + getName() const + { return "Fifo"; } + + std::string + getDescription() const + { return "Xilinx's AXI4 FIFO data mover"; } + + Vlnv getCompatibleVlnv() const + { return {"xilinx.com:ip:axi_fifo_mm_s:"}; } + + std::list getDependencies() const + { return { {"intc", Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:") } }; } +}; + +} // namespace ip +} // namespace fpga +} // namespace villas + +/** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index f249174c0..db8b7f023 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES ips/switch.cpp ips/dft.c ips/fifo.c + ips/fifo.cpp ips/dma.c ips/intc.cpp ips/intc.c diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp new file mode 100644 index 000000000..410e745ac --- /dev/null +++ b/fpga/lib/ips/fifo.cpp @@ -0,0 +1,193 @@ +/** FIFO related helper functions + * + * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) + * + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include + +#include "log.hpp" +#include "fpga/ips/fifo.hpp" +#include "fpga/ips/intc.hpp" + + +namespace villas { +namespace fpga { +namespace ip { + +// instantiate factory to make available to plugin infrastructure +static FifoFactory factory; + +bool +FifoFactory::configureJson(IpCore &ip, json_t *json_ip) +{ + if(not IpNodeFactory::configureJson(ip, json_ip)) { + cpp_error << "Configuring IpNode failed"; + return false; + } + + auto& fifo = reinterpret_cast(ip); + if(json_unpack(json_ip, "{ s: i }", "baseaddr_axi4", &fifo.baseaddr_axi4) != 0) { + cpp_warn << "Cannot parse property 'baseaddr_axi4' of " << ip; + return false; + } + + return true; +} + + +bool Fifo::start() +{ + XLlFifo_Config fifo_cfg; + + fifo_cfg.Axi4BaseAddress = getAddrMapped(this->baseaddr_axi4); + + // use AXI4 for Data, AXI4-Lite for control + fifo_cfg.Datainterface = (this->baseaddr_axi4 != -1) ? 1 : 0; + + if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseaddr()) != XST_SUCCESS) + return false; + + // Receive complete IRQ + XLlFifo_IntEnable(&xFifo, XLLF_INT_RC_MASK); + + auto intc = reinterpret_cast(dependencies["intc"]); + intc->enableInterrupt(irqs[0], false); + + return true; +} + +bool Fifo::stop() +{ + // Receive complete IRQ + XLlFifo_IntDisable(&xFifo, XLLF_INT_RC_MASK); + + return true; +} + +size_t Fifo::write(const void *buf, size_t len) +{ + + uint32_t tdfv; + + tdfv = XLlFifo_TxVacancy(&xFifo); + if (tdfv < len) + return -1; + + // buf has to be re-casted because Xilinx driver doesn't use const + XLlFifo_Write(&xFifo, (void*) buf, len); + XLlFifo_TxSetLen(&xFifo, len); + + return len; +} + +size_t Fifo::read(void *buf, size_t len) +{ + size_t nextlen = 0; + uint32_t rxlen; + + auto intc = reinterpret_cast(dependencies["intc"]); + + while (!XLlFifo_IsRxDone(&xFifo)) + intc->waitForInterrupt(irqs[0].num); + + XLlFifo_IntClear(&xFifo, XLLF_INT_RC_MASK); + + /* Get length of next frame */ + rxlen = XLlFifo_RxGetLen(&xFifo); + nextlen = MIN(rxlen, len); + + /* Read from FIFO */ + XLlFifo_Read(&xFifo, buf, nextlen); + + return nextlen; +} + +#if 0 + + +ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xllfifo = &fifo->inst; + +} + +ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xllfifo = &fifo->inst; + + size_t nextlen = 0; + uint32_t rxlen; + + while (!XLlFifo_IsRxDone(xllfifo)) + intc_wait(c->card->intc, c->irq); + XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK); + + /* Get length of next frame */ + rxlen = XLlFifo_RxGetLen(xllfifo); + nextlen = MIN(rxlen, len); + + /* Read from FIFO */ + XLlFifo_Read(xllfifo, buf, nextlen); + + return nextlen; +} + +int fifo_parse(struct fpga_ip *c, json_t *cfg) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + int baseaddr_axi4 = -1, ret; + + json_error_t err; + + fifo->baseaddr_axi4 = -1; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + fifo->baseaddr_axi4 = baseaddr_axi4; + + return 0; +} + +int fifo_reset(struct fpga_ip *c) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo_Reset(&fifo->inst); + + return 0; +} +#endif + +} // namespace ip +} // namespace fpga +} // namespace villas diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index 9d66512a2..103abb6c4 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -1,12 +1,12 @@ set(SOURCES main.cpp - dma.c - fifo.c - hls.c - intc.c - rtds_rtt.c - tmrctr.c - xsg.c +# dma.c + fifo.cpp +# hls.c +# intc.c +# rtds_rtt.c +# tmrctr.c +# xsg.c ) add_executable(unit-tests ${SOURCES}) diff --git a/fpga/tests/fifo.c b/fpga/tests/fifo.cpp similarity index 54% rename from fpga/tests/fifo.c rename to fpga/tests/fifo.cpp index 0c5e70abf..409ee06e7 100644 --- a/fpga/tests/fifo.c +++ b/fpga/tests/fifo.cpp @@ -31,7 +31,10 @@ #include -extern struct fpga_card *card; +#include +#include + +extern villas::fpga::PCIeCard* fpga; Test(fpga, fifo, .description = "FIFO") { @@ -40,35 +43,35 @@ Test(fpga, fifo, .description = "FIFO") char src[255], dst[255]; struct fpga_ip *fifo; - fifo = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_fifo_mm_s", NULL }); - cr_assert(fifo); + for(auto& ip : fpga->ips) { + // skip non-fifo IPs + if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_fifo_mm_s:")) + continue; - ret = intc_enable(card->intc, (1 << fifo->irq), 0); - cr_assert_eq(ret, 0, "Failed to enable interrupt"); + auto fifo = reinterpret_cast(*ip); - ret = switch_connect(card->sw, fifo, fifo); - cr_assert_eq(ret, 0, "Failed to configure switch"); + if(not fifo.loopbackPossible()) { + cpp_info << "Loopback test not possible for " << *ip; + continue; + } - /* Get some random data to compare */ - memset(dst, 0, sizeof(dst)); - len = read_random((char *) src, sizeof(src)); - if (len != sizeof(src)) - error("Failed to get random data"); + fifo.connectLoopback(); - len = fifo_write(fifo, (char *) src, sizeof(src)); - if (len != sizeof(src)) - error("Failed to send to FIFO"); + /* Get some random data to compare */ + memset(dst, 0, sizeof(dst)); + len = read_random((char *) src, sizeof(src)); + if (len != sizeof(src)) + error("Failed to get random data"); - len = fifo_read(fifo, (char *) dst, sizeof(dst)); - if (len != sizeof(dst)) - error("Failed to read from FIFO"); + len = fifo.write(src, sizeof(src)); + if (len != sizeof(src)) + cpp_error << "Failed to send to FIFO"; - ret = intc_disable(card->intc, (1 << fifo->irq)); - cr_assert_eq(ret, 0, "Failed to disable interrupt"); + len = fifo.read(dst, sizeof(dst)); + if (len != sizeof(dst)) + cpp_error << "Failed to read from FIFO"; - ret = switch_disconnect(card->sw, fifo, fifo); - cr_assert_eq(ret, 0, "Failed to configure switch"); - - /* Compare data */ - cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); + /* Compare data */ + cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); + } } diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 973e1eaca..3c9c52bd1 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -43,6 +43,7 @@ struct pci pci; struct vfio_container vc; villas::fpga::CardList fpgaCards; +villas::fpga::PCIeCard* fpga; // keep to make it compile with old C tests struct fpga_card* card; @@ -85,6 +86,12 @@ static void init() // create all FPGA card instances using the corresponding plugin fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); + if(fpgaCards.size() == 0) { + cpp_error << "No FPGA cards found!"; + } else { + fpga = fpgaCards.front().get(); + } + json_decref(json); } From 2cfd26b6ee2696a0e23b9caa72b89078d8428b94 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 10 Jan 2018 15:24:09 +0100 Subject: [PATCH 060/560] Squashed 'thirdparty/spdlog/' content from commit 32177aa git-subtree-dir: thirdparty/spdlog git-subtree-split: 32177aa77a0a20453d3e7455cac42406fc49b6ca --- fpga/.gitignore | 67 + fpga/.travis.yml | 91 + fpga/CMakeLists.txt | 116 + fpga/INSTALL | 13 + fpga/LICENSE | 22 + fpga/README.md | 221 + fpga/appveyor.yml | 32 + fpga/astyle.sh | 5 + fpga/bench/Makefile | 62 + fpga/bench/Makefile.mingw | 57 + fpga/bench/boost-bench-mt.cpp | 84 + fpga/bench/boost-bench.cpp | 47 + fpga/bench/easyl.conf | 10 + fpga/bench/easylogging-bench-mt.cpp | 52 + fpga/bench/easylogging-bench.cpp | 22 + fpga/bench/g2log-async.cpp | 62 + fpga/bench/glog-bench-mt.cpp | 50 + fpga/bench/glog-bench.cpp | 21 + fpga/bench/latency/Makefile | 32 + fpga/bench/latency/compare.sh | 13 + fpga/bench/latency/g3log-crush.cpp | 37 + fpga/bench/latency/g3log-latency.cpp | 129 + fpga/bench/latency/spdlog-latency.cpp | 128 + fpga/bench/latency/utils.h | 35 + fpga/bench/logs/.gitignore | 4 + fpga/bench/spdlog-async.cpp | 62 + fpga/bench/spdlog-bench-mt.cpp | 55 + fpga/bench/spdlog-bench.cpp | 20 + fpga/bench/spdlog-null-async.cpp | 112 + fpga/bench/utils.h | 35 + fpga/cmake/Config.cmake.in | 24 + fpga/cmake/spdlog.pc.in | 6 + fpga/example/CMakeLists.txt | 49 + fpga/example/Makefile | 29 + fpga/example/Makefile.clang | 32 + fpga/example/Makefile.mingw | 32 + fpga/example/bench.cpp | 144 + fpga/example/example.cpp | 167 + fpga/example/example.sln | 26 + fpga/example/example.vcxproj | 126 + fpga/example/jni/Android.mk | 15 + fpga/example/jni/Application.mk | 2 + fpga/example/jni/example.cpp | 1 + fpga/example/logs/.gitignore | 1 + fpga/example/multisink.cpp | 47 + fpga/example/utils.h | 35 + fpga/include/spdlog/async_logger.h | 82 + fpga/include/spdlog/common.h | 161 + .../include/spdlog/details/async_log_helper.h | 399 + .../spdlog/details/async_logger_impl.h | 107 + fpga/include/spdlog/details/file_helper.h | 145 + fpga/include/spdlog/details/log_msg.h | 50 + fpga/include/spdlog/details/logger_impl.h | 373 + fpga/include/spdlog/details/mpmc_bounded_q.h | 176 + fpga/include/spdlog/details/null_mutex.h | 45 + fpga/include/spdlog/details/os.h | 479 + .../spdlog/details/pattern_formatter_impl.h | 686 ++ fpga/include/spdlog/details/registry.h | 225 + fpga/include/spdlog/details/spdlog_impl.h | 268 + fpga/include/spdlog/fmt/bundled/LICENSE.rst | 23 + fpga/include/spdlog/fmt/bundled/format.cc | 535 + fpga/include/spdlog/fmt/bundled/format.h | 4659 ++++++++ fpga/include/spdlog/fmt/bundled/ostream.cc | 35 + fpga/include/spdlog/fmt/bundled/ostream.h | 114 + fpga/include/spdlog/fmt/bundled/posix.cc | 241 + fpga/include/spdlog/fmt/bundled/posix.h | 424 + fpga/include/spdlog/fmt/bundled/printf.cc | 32 + fpga/include/spdlog/fmt/bundled/printf.h | 712 ++ fpga/include/spdlog/fmt/bundled/time.h | 183 + fpga/include/spdlog/fmt/fmt.h | 34 + fpga/include/spdlog/fmt/ostr.h | 17 + fpga/include/spdlog/formatter.h | 47 + fpga/include/spdlog/logger.h | 110 + fpga/include/spdlog/sinks/android_sink.h | 90 + fpga/include/spdlog/sinks/ansicolor_sink.h | 133 + fpga/include/spdlog/sinks/base_sink.h | 51 + fpga/include/spdlog/sinks/dist_sink.h | 72 + fpga/include/spdlog/sinks/file_sinks.h | 253 + fpga/include/spdlog/sinks/msvc_sink.h | 51 + fpga/include/spdlog/sinks/null_sink.h | 34 + fpga/include/spdlog/sinks/ostream_sink.h | 47 + fpga/include/spdlog/sinks/sink.h | 53 + fpga/include/spdlog/sinks/stdout_sinks.h | 77 + fpga/include/spdlog/sinks/syslog_sink.h | 81 + fpga/include/spdlog/sinks/wincolor_sink.h | 121 + fpga/include/spdlog/sinks/windebug_sink.h | 29 + fpga/include/spdlog/spdlog.h | 192 + fpga/include/spdlog/tweakme.h | 160 + fpga/tests/CMakeLists.txt | 19 + fpga/tests/Makefile | 28 + fpga/tests/catch.hpp | 9427 +++++++++++++++++ fpga/tests/catch.license | 23 + fpga/tests/errors.cpp | 126 + fpga/tests/file_helper.cpp | 118 + fpga/tests/file_log.cpp | 248 + fpga/tests/format.cpp | 56 + fpga/tests/includes.h | 18 + fpga/tests/install_libcxx.sh | 12 + fpga/tests/main.cpp | 2 + fpga/tests/registry.cpp | 84 + fpga/tests/test_macros.cpp | 50 + fpga/tests/tests.sln | 28 + fpga/tests/tests.vcxproj | 145 + fpga/tests/tests.vcxproj.filters | 54 + fpga/tests/utils.cpp | 56 + fpga/tests/utils.h | 16 + 106 files changed, 24748 insertions(+) create mode 100644 fpga/.gitignore create mode 100644 fpga/.travis.yml create mode 100644 fpga/CMakeLists.txt create mode 100644 fpga/INSTALL create mode 100644 fpga/LICENSE create mode 100644 fpga/README.md create mode 100644 fpga/appveyor.yml create mode 100755 fpga/astyle.sh create mode 100644 fpga/bench/Makefile create mode 100644 fpga/bench/Makefile.mingw create mode 100644 fpga/bench/boost-bench-mt.cpp create mode 100644 fpga/bench/boost-bench.cpp create mode 100644 fpga/bench/easyl.conf create mode 100644 fpga/bench/easylogging-bench-mt.cpp create mode 100644 fpga/bench/easylogging-bench.cpp create mode 100644 fpga/bench/g2log-async.cpp create mode 100644 fpga/bench/glog-bench-mt.cpp create mode 100644 fpga/bench/glog-bench.cpp create mode 100644 fpga/bench/latency/Makefile create mode 100755 fpga/bench/latency/compare.sh create mode 100644 fpga/bench/latency/g3log-crush.cpp create mode 100644 fpga/bench/latency/g3log-latency.cpp create mode 100644 fpga/bench/latency/spdlog-latency.cpp create mode 100644 fpga/bench/latency/utils.h create mode 100644 fpga/bench/logs/.gitignore create mode 100644 fpga/bench/spdlog-async.cpp create mode 100644 fpga/bench/spdlog-bench-mt.cpp create mode 100644 fpga/bench/spdlog-bench.cpp create mode 100644 fpga/bench/spdlog-null-async.cpp create mode 100644 fpga/bench/utils.h create mode 100644 fpga/cmake/Config.cmake.in create mode 100644 fpga/cmake/spdlog.pc.in create mode 100644 fpga/example/CMakeLists.txt create mode 100644 fpga/example/Makefile create mode 100644 fpga/example/Makefile.clang create mode 100644 fpga/example/Makefile.mingw create mode 100644 fpga/example/bench.cpp create mode 100644 fpga/example/example.cpp create mode 100644 fpga/example/example.sln create mode 100644 fpga/example/example.vcxproj create mode 100644 fpga/example/jni/Android.mk create mode 100644 fpga/example/jni/Application.mk create mode 120000 fpga/example/jni/example.cpp create mode 100644 fpga/example/logs/.gitignore create mode 100644 fpga/example/multisink.cpp create mode 100644 fpga/example/utils.h create mode 100644 fpga/include/spdlog/async_logger.h create mode 100644 fpga/include/spdlog/common.h create mode 100644 fpga/include/spdlog/details/async_log_helper.h create mode 100644 fpga/include/spdlog/details/async_logger_impl.h create mode 100644 fpga/include/spdlog/details/file_helper.h create mode 100644 fpga/include/spdlog/details/log_msg.h create mode 100644 fpga/include/spdlog/details/logger_impl.h create mode 100644 fpga/include/spdlog/details/mpmc_bounded_q.h create mode 100644 fpga/include/spdlog/details/null_mutex.h create mode 100644 fpga/include/spdlog/details/os.h create mode 100644 fpga/include/spdlog/details/pattern_formatter_impl.h create mode 100644 fpga/include/spdlog/details/registry.h create mode 100644 fpga/include/spdlog/details/spdlog_impl.h create mode 100644 fpga/include/spdlog/fmt/bundled/LICENSE.rst create mode 100644 fpga/include/spdlog/fmt/bundled/format.cc create mode 100644 fpga/include/spdlog/fmt/bundled/format.h create mode 100644 fpga/include/spdlog/fmt/bundled/ostream.cc create mode 100644 fpga/include/spdlog/fmt/bundled/ostream.h create mode 100644 fpga/include/spdlog/fmt/bundled/posix.cc create mode 100644 fpga/include/spdlog/fmt/bundled/posix.h create mode 100644 fpga/include/spdlog/fmt/bundled/printf.cc create mode 100644 fpga/include/spdlog/fmt/bundled/printf.h create mode 100644 fpga/include/spdlog/fmt/bundled/time.h create mode 100644 fpga/include/spdlog/fmt/fmt.h create mode 100644 fpga/include/spdlog/fmt/ostr.h create mode 100644 fpga/include/spdlog/formatter.h create mode 100644 fpga/include/spdlog/logger.h create mode 100644 fpga/include/spdlog/sinks/android_sink.h create mode 100644 fpga/include/spdlog/sinks/ansicolor_sink.h create mode 100644 fpga/include/spdlog/sinks/base_sink.h create mode 100644 fpga/include/spdlog/sinks/dist_sink.h create mode 100644 fpga/include/spdlog/sinks/file_sinks.h create mode 100644 fpga/include/spdlog/sinks/msvc_sink.h create mode 100644 fpga/include/spdlog/sinks/null_sink.h create mode 100644 fpga/include/spdlog/sinks/ostream_sink.h create mode 100644 fpga/include/spdlog/sinks/sink.h create mode 100644 fpga/include/spdlog/sinks/stdout_sinks.h create mode 100644 fpga/include/spdlog/sinks/syslog_sink.h create mode 100644 fpga/include/spdlog/sinks/wincolor_sink.h create mode 100644 fpga/include/spdlog/sinks/windebug_sink.h create mode 100644 fpga/include/spdlog/spdlog.h create mode 100644 fpga/include/spdlog/tweakme.h create mode 100644 fpga/tests/CMakeLists.txt create mode 100644 fpga/tests/Makefile create mode 100644 fpga/tests/catch.hpp create mode 100644 fpga/tests/catch.license create mode 100644 fpga/tests/errors.cpp create mode 100644 fpga/tests/file_helper.cpp create mode 100644 fpga/tests/file_log.cpp create mode 100644 fpga/tests/format.cpp create mode 100644 fpga/tests/includes.h create mode 100755 fpga/tests/install_libcxx.sh create mode 100644 fpga/tests/main.cpp create mode 100644 fpga/tests/registry.cpp create mode 100644 fpga/tests/test_macros.cpp create mode 100644 fpga/tests/tests.sln create mode 100644 fpga/tests/tests.vcxproj create mode 100644 fpga/tests/tests.vcxproj.filters create mode 100644 fpga/tests/utils.cpp create mode 100644 fpga/tests/utils.h diff --git a/fpga/.gitignore b/fpga/.gitignore new file mode 100644 index 000000000..2f3cb33de --- /dev/null +++ b/fpga/.gitignore @@ -0,0 +1,67 @@ +# Auto generated files +*.slo +*.lo +*.o +*.obj +*.suo +*.tlog +*.ilk +*.log +*.pdb +*.idb +*.iobj +*.ipdb +*.opensdf +*.sdf + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Codelite +.codelite + +# .orig files +*.orig + +# example files +example/* +!example/example.cpp +!example/bench.cpp +!example/utils.h +!example/Makefile* +!example/example.sln +!example/example.vcxproj +!example/CMakeLists.txt +!example/multisink.cpp +!example/jni + +# generated files +generated + +# Cmake +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt +/tests/tests.VC.VC.opendb +/tests/tests.VC.db +/tests/tests +/tests/logs/* + +# idea +.idea/ \ No newline at end of file diff --git a/fpga/.travis.yml b/fpga/.travis.yml new file mode 100644 index 000000000..852738442 --- /dev/null +++ b/fpga/.travis.yml @@ -0,0 +1,91 @@ +# Adapted from various sources, including: +# - Louis Dionne's Hana: https://github.com/ldionne/hana +# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit +# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3 +language: cpp + +# Test matrix: +# - Build matrix per compiler: C++11/C++14 + Debug/Release +# - Optionally: AddressSanitizer (ASAN) +# - Valgrind: all release builds are also tested with valgrind +# - clang 3.4, 3.5, 3.6, trunk +# - Note: 3.4 and trunk are tested with/without ASAN, +# the rest is only tested with ASAN=On. +# - gcc 4.9, 5.0 +# +matrix: + include: + +# Test gcc-4.8: C++11, Build=Debug/Release, ASAN=Off + - env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11 ASAN=Off LIBCXX=Off + os: linux + addons: &gcc48 + apt: + packages: + - g++-4.8 + - valgrind + sources: + - ubuntu-toolchain-r-test + + - env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11 ASAN=Off LIBCXX=Off + os: linux + addons: *gcc48 + + # Test gcc-4.9: C++11, Build=Debug/Release, ASAN=Off + - env: GCC_VERSION=4.9 BUILD_TYPE=Debug CPP=11 ASAN=Off LIBCXX=Off + os: linux + addons: &gcc49 + apt: + packages: + - g++-4.9 + - valgrind + sources: + - ubuntu-toolchain-r-test + + - env: GCC_VERSION=4.9 BUILD_TYPE=Release CPP=11 ASAN=Off LIBCXX=Off + os: linux + addons: *gcc49 + +# Install dependencies +before_install: + - export CHECKOUT_PATH=`pwd`; + - if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi + - if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi + - if [ "$CLANG_VERSION" == "3.4" ]; then export CXX="/usr/local/clang-3.4/bin/clang++" CC="/usr/local/clang-3.4/bin/clang"; fi + - which $CXX + - which $CC + - which valgrind + - if [ -n "$CLANG_VERSION" ]; then sudo CXX=$CXX CC=$CC ./tests/install_libcxx.sh; fi + +install: + - cd $CHECKOUT_PATH + + # Workaround for valgrind bug: https://bugs.kde.org/show_bug.cgi?id=326469. + # It is fixed in valgrind 3.10 so this won't be necessary if someone + # replaces the current valgrind (3.7) with valgrind-3.10 + - sed -i 's/march=native/msse4.2/' example/Makefile + + - if [ ! -d build ]; then mkdir build; fi + - export CXX_FLAGS="-I${CHECKOUT_PATH}/include" + - export CXX_LINKER_FLAGS="" + - if [ -z "$BUILD_TYPE" ]; then export BUILD_TYPE=Release; fi + - if [ "$ASAN" == "On"]; then export CXX_FLAGS="${CXX_FLAGS} -fsanitize=address,undefined,integer -fno-omit-frame-pointer -fno-sanitize=unsigned-integer-overflow"; fi + - if [ -n "$CLANG_VERSION" ]; then CXX_FLAGS="${CXX_FLAGS} -D__extern_always_inline=inline"; fi + - if [ "$LIBCXX" == "On" ]; then CXX_FLAGS="${CXX_FLAGS} -stdlib=libc++ -I/usr/include/c++/v1/"; fi + - if [ "$LIBCXX" == "On" ]; then CXX_LINKER_FLAGS="${CXX_FLAGS} -L/usr/lib/ -lc++"; fi + - CXX_FLAGS="${CXX_FLAGS} -std=c++${CPP}" + + # Build examples + - cd example + - if [ "$BUILD_TYPE" == "Release" ]; then make rebuild CXXFLAGS="${CXX_FLAGS} ${CXX_LINKER_FLAGS}" VERBOSE=1; export BIN=example; fi + - if [ "$BUILD_TYPE" == "Debug" ]; then make rebuild debug CXXFLAGS="${CXX_FLAGS} ${CXX_LINKER_FLAGS}" VERBOSE=1; export BIN=example-debug; fi + + +script: + - ./"${BIN}" + - valgrind --trace-children=yes --leak-check=full ./"${BIN}" + - cd $CHECKOUT_PATH/tests; make rebuild; ./tests + - cd $CHECKOUT_PATH/tests; STYLE=printf make rebuild; ./tests + +notifications: + email: false diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt new file mode 100644 index 000000000..52d29531e --- /dev/null +++ b/fpga/CMakeLists.txt @@ -0,0 +1,116 @@ +# +# Copyright(c) 2015 Ruslan Baratov. +# Distributed under the MIT License (http://opensource.org/licenses/MIT) +# + +cmake_minimum_required(VERSION 3.1) +project(spdlog VERSION 0.16.2) +include(CTest) +include(CMakeDependentOption) +include(GNUInstallDirs) + +#--------------------------------------------------------------------------------------- +# compiler config +#--------------------------------------------------------------------------------------- +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(CMAKE_CXX_FLAGS "-Wall ${CMAKE_CXX_FLAGS}") +endif() + +#--------------------------------------------------------------------------------------- +# spdlog target +#--------------------------------------------------------------------------------------- +add_library(spdlog INTERFACE) + +option(SPDLOG_BUILD_EXAMPLES "Build examples" OFF) +cmake_dependent_option(SPDLOG_BUILD_TESTING + "Build spdlog tests" ON + "BUILD_TESTING" OFF +) + +target_include_directories( + spdlog + INTERFACE + "$" + "$" +) + +set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include") + +if(SPDLOG_BUILD_EXAMPLES) + add_subdirectory(example) +endif() + +if(SPDLOG_BUILD_TESTING) + add_subdirectory(tests) +endif() + +#--------------------------------------------------------------------------------------- +# Install/export targets and files +#--------------------------------------------------------------------------------------- +# set files and directories +set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +set(include_install_dir "${CMAKE_INSTALL_INCLUDEDIR}") +set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +set(version_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake") +set(project_config "${PROJECT_NAME}Config.cmake") +set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc") +set(targets_export_name "${PROJECT_NAME}Targets") +set(namespace "${PROJECT_NAME}::") + +# generate package version file +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${version_config}" COMPATIBILITY SameMajorVersion +) + +# configure pkg config file +configure_file("cmake/spdlog.pc.in" "${pkg_config}" @ONLY) + +# install targets +install( + TARGETS spdlog + EXPORT "${targets_export_name}" + INCLUDES DESTINATION "${include_install_dir}" +) + +# install headers +install( + DIRECTORY "${HEADER_BASE}/${PROJECT_NAME}" + DESTINATION "${include_install_dir}" +) + +# install project version file +install( + FILES "${version_config}" + DESTINATION "${config_install_dir}" +) + +# install pkg config file +install( + FILES "${pkg_config}" + DESTINATION "${pkgconfig_install_dir}" +) + +# install project config file +install( + EXPORT "${targets_export_name}" + NAMESPACE "${namespace}" + DESTINATION "${config_install_dir}" + FILE ${project_config} +) + +# export build directory config file +export( + EXPORT ${targets_export_name} + NAMESPACE "${namespace}" + FILE ${project_config} +) + +# register project in CMake user registry +export(PACKAGE ${PROJECT_NAME}) + +file(GLOB_RECURSE spdlog_include_SRCS "${HEADER_BASE}/*.h") +add_custom_target(spdlog_headers_for_ide SOURCES ${spdlog_include_SRCS}) diff --git a/fpga/INSTALL b/fpga/INSTALL new file mode 100644 index 000000000..664509d2c --- /dev/null +++ b/fpga/INSTALL @@ -0,0 +1,13 @@ +spdlog is header only library. +Just copy the files to your build tree and use a C++11 compiler + +Tested on: +gcc 4.8.1 and above +clang 3.5 +Visual Studio 2013 + +gcc 4.8 flags: --std==c++11 -pthread -O3 -flto -Wl,--no-as-needed +gcc 4.9 flags: --std=c++11 -pthread -O3 -flto + + +see the makefile in the example folder diff --git a/fpga/LICENSE b/fpga/LICENSE new file mode 100644 index 000000000..4b43e0640 --- /dev/null +++ b/fpga/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Gabi Melman. + +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/fpga/README.md b/fpga/README.md new file mode 100644 index 000000000..733471390 --- /dev/null +++ b/fpga/README.md @@ -0,0 +1,221 @@ +# spdlog + +Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)  [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) + + +## Install +#### Just copy the headers: + +* Copy the source [folder](https://github.com/gabime/spdlog/tree/master/include/spdlog) to your build tree and use a C++11 compiler. + +#### Or use your favorite package manager: + +* Ubuntu: `apt-get install libspdlog-dev` +* Homebrew: `brew install spdlog` +* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean` +* Fedora: `yum install spdlog` +* Gentoo: `emerge dev-libs/spdlog` +* Arch Linux: `yaourt -S spdlog-git` +* vcpkg: `vcpkg install spdlog` + + +## Platforms + * Linux, FreeBSD, Solaris + * Windows (vc 2013+, cygwin) + * Mac OSX (clang 3.5+) + * Android + +## Features +* Very fast - performance is the primary goal (see [benchmarks](#benchmarks) below). +* Headers only, just copy and use. +* Feature rich [call style](#usage-example) using the excellent [fmt](https://github.com/fmtlib/fmt) library. +* Optional printf syntax support. +* Extremely fast asynchronous mode (optional) - using lockfree queues and other tricks to reach millions of calls/sec. +* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. +* Conditional Logging +* Multi/Single threaded loggers. +* Various log targets: + * Rotating log files. + * Daily log files. + * Console logging (colors supported). + * syslog. + * Windows debugger (```OutputDebugString(..)```) + * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). +* Severity based filtering - threshold levels can be modified in runtime as well as in compile time. + + + +## Benchmarks + +Below are some [benchmarks](bench) comparing popular log libraries under Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz + +#### Synchronous mode +Time needed to log 1,000,000 lines in synchronous mode (in seconds, the best of 3 runs): + +|threads|boost log 1.54|glog |easylogging |spdlog| +|-------|:-------:|:-----:|----------:|------:| +|1| 4.169s |1.066s |0.975s |0.302s| +|10| 6.180s |3.032s |2.857s |0.968s| +|100| 5.981s |1.139s |4.512s |0.497s| + + +#### Asynchronous mode +Time needed to log 1,000,000 lines in asynchronous mode, i.e. the time it takes to put them in the async queue (in seconds, the best of 3 runs): + +|threads|g2log async logger |spdlog async mode| +|:-------|:-----:|-------------------------:| +|1| 1.850s |0.216s | +|10| 0.943s |0.173s| +|100| 0.959s |0.202s| + + + + +## Usage Example +```c++ + +#include "spdlog/spdlog.h" + +#include +#include + +void async_example(); +void syslog_example(); +void user_defined_example(); +void err_handler_example(); + +namespace spd = spdlog; +int main(int, char*[]) +{ + try + { + // Console logger with color + auto console = spd::stdout_color_mt("console"); + console->info("Welcome to spdlog!"); + console->error("Some error message with arg{}..", 1); + + // Formatting examples + console->warn("Easy padding in numbers like {:08d}", 12); + console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + console->info("Support for floats {:03.2f}", 1.23456); + console->info("Positional args are {1} {0}..", "too", "supported"); + console->info("{:<30}", "left aligned"); + + // Use global registry to retrieve loggers + spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function"); + + // Create basic file logger (not rotated) + auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic.txt"); + my_logger->info("Some log message"); + + // Create a file rotating logger with 5mb size max and 3 rotated files + auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/mylogfile.txt", 1048576 * 5, 3); + for (int i = 0; i < 10; ++i) + rotating_logger->info("{} * {} equals {:>10}", i, i, i*i); + + // Create a daily logger - a new file is created every day on 2:30am + auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); + // trigger flush if the log severity is error or higher + daily_logger->flush_on(spd::level::err); + daily_logger->info(123.44); + + // Customize msg format for all messages + spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); + rotating_logger->info("This is another message with custom format"); + + + // Runtime log levels + spd::set_level(spd::level::info); //Set global log level to info + console->debug("This message should not be displayed!"); + console->set_level(spd::level::debug); // Set specific logger's log level + console->debug("This message should be displayed.."); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON + SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); + SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); + + // Asynchronous logging is very fast.. + // Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous.. + async_example(); + + // syslog example. linux/osx only + syslog_example(); + + // android example. compile with NDK + android_example(); + + // Log user-defined types example + user_defined_example(); + + // Change default log error handler + err_handler_example(); + + // Apply a function on all registered loggers + spd::apply_all([&](std::shared_ptr l) + { + l->info("End of example."); + }); + + // Release and close all loggers + spd::drop_all(); + } + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + catch (const spd::spdlog_ex& ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} + +void async_example() +{ + size_t q_size = 4096; //queue size must be power of 2 + spd::set_async_mode(q_size); + auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); + for (int i = 0; i < 100; ++i) + async_file->info("Async message #{}", i); +} + +//syslog example +void syslog_example() +{ +#ifdef SPDLOG_ENABLE_SYSLOG + std::string ident = "spdlog-example"; + auto syslog_logger = spd::syslog_logger("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog.."); +#endif +} + +// user defined types logging by implementing operator<< +struct my_type +{ + int i; + template + friend OStream& operator<<(OStream& os, const my_type &c) + { + return os << "[my_type i="< // must be included +void user_defined_example() +{ + spd::get("console")->info("user defined type: {}", my_type { 14 }); +} + +// +//custom error handler +// +void err_handler_example() +{ + spd::set_error_handler([](const std::string& msg) { + std::cerr << "my err handler: " << msg << std::endl; + }); + // (or logger->set_error_handler(..) to set for specific logger) +} + +``` + +## Documentation +Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. diff --git a/fpga/appveyor.yml b/fpga/appveyor.yml new file mode 100644 index 000000000..2a176c1be --- /dev/null +++ b/fpga/appveyor.yml @@ -0,0 +1,32 @@ +version: 1.0.{build} +image: Visual Studio 2015 +environment: + matrix: + - GENERATOR: '"MinGW Makefiles"' + BUILD_TYPE: Debug + - GENERATOR: '"MinGW Makefiles"' + BUILD_TYPE: Release + - GENERATOR: '"Visual Studio 14 2015"' + BUILD_TYPE: Debug + - GENERATOR: '"Visual Studio 14 2015"' + BUILD_TYPE: Release + - GENERATOR: '"Visual Studio 14 2015 Win64"' + BUILD_TYPE: Debug + - GENERATOR: '"Visual Studio 14 2015 Win64"' + BUILD_TYPE: Release +build_script: +- cmd: >- + set + + mkdir build + + cd build + + set PATH=%PATH:C:\Program Files\Git\usr\bin;=% + + set PATH=C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH% + + cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% + + cmake --build . --config %BUILD_TYPE% +test: off diff --git a/fpga/astyle.sh b/fpga/astyle.sh new file mode 100755 index 000000000..a7a90510b --- /dev/null +++ b/fpga/astyle.sh @@ -0,0 +1,5 @@ +#!/bin/bash +find . -name "*\.h" -o -name "*\.cpp"|xargs dos2unix +find . -name "*\.h" -o -name "*\.cpp"|xargs astyle -n -c -A1 + + diff --git a/fpga/bench/Makefile b/fpga/bench/Makefile new file mode 100644 index 000000000..418a5285f --- /dev/null +++ b/fpga/bench/Makefile @@ -0,0 +1,62 @@ +CXX ?= g++ +CXXFLAGS = -march=native -Wall -Wextra -pedantic -std=c++11 -pthread -I../include +CXX_RELEASE_FLAGS = -O3 -flto -DNDEBUG + + +binaries=spdlog-bench spdlog-bench-mt spdlog-async spdlog-null-async boost-bench boost-bench-mt glog-bench glog-bench-mt g2log-async easylogging-bench easylogging-bench-mt + +all: $(binaries) + +spdlog-bench: spdlog-bench.cpp + $(CXX) spdlog-bench.cpp -o spdlog-bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + +spdlog-bench-mt: spdlog-bench-mt.cpp + $(CXX) spdlog-bench-mt.cpp -o spdlog-bench-mt $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + +spdlog-async: spdlog-async.cpp + $(CXX) spdlog-async.cpp -o spdlog-async $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +spdlog-null-async: spdlog-null-async.cpp + $(CXX) spdlog-null-async.cpp -o spdlog-null-async $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + + +BOOST_FLAGS = -DBOOST_LOG_DYN_LINK -I/usr/include -lboost_log -lboost_log_setup -lboost_filesystem -lboost_system -lboost_thread -lboost_regex -lboost_date_time -lboost_chrono + +boost-bench: boost-bench.cpp + $(CXX) boost-bench.cpp -o boost-bench $(CXXFLAGS) $(BOOST_FLAGS) $(CXX_RELEASE_FLAGS) + +boost-bench-mt: boost-bench-mt.cpp + $(CXX) boost-bench-mt.cpp -o boost-bench-mt $(CXXFLAGS) $(BOOST_FLAGS) $(CXX_RELEASE_FLAGS) + + +GLOG_FLAGS = -lglog +glog-bench: glog-bench.cpp + $(CXX) glog-bench.cpp -o glog-bench $(CXXFLAGS) $(GLOG_FLAGS) $(CXX_RELEASE_FLAGS) + +glog-bench-mt: glog-bench-mt.cpp + $(CXX) glog-bench-mt.cpp -o glog-bench-mt $(CXXFLAGS) $(GLOG_FLAGS) $(CXX_RELEASE_FLAGS) + + +G2LOG_FLAGS = -I/home/gabi/devel/g2log/g2log/src -L/home/gabi/devel/g2log/g2log -llib_g2logger +g2log-async: g2log-async.cpp + $(CXX) g2log-async.cpp -o g2log-async $(CXXFLAGS) $(G2LOG_FLAGS) $(CXX_RELEASE_FLAGS) + + +EASYL_FLAGS = -I../../easylogging/src/ +easylogging-bench: easylogging-bench.cpp + $(CXX) easylogging-bench.cpp -o easylogging-bench $(CXXFLAGS) $(EASYL_FLAGS) $(CXX_RELEASE_FLAGS) +easylogging-bench-mt: easylogging-bench-mt.cpp + $(CXX) easylogging-bench-mt.cpp -o easylogging-bench-mt $(CXXFLAGS) $(EASYL_FLAGS) $(CXX_RELEASE_FLAGS) + +.PHONY: clean + +clean: + rm -f *.o logs/* $(binaries) + + +rebuild: clean all + + + diff --git a/fpga/bench/Makefile.mingw b/fpga/bench/Makefile.mingw new file mode 100644 index 000000000..b4357be4a --- /dev/null +++ b/fpga/bench/Makefile.mingw @@ -0,0 +1,57 @@ +CXX ?= g++ +CXXFLAGS = -D_WIN32_WINNT=0x600 -march=native -Wall -Wextra -pedantic -std=c++11 -pthread -Wl,--no-as-needed -I../include +CXX_RELEASE_FLAGS = -O3 -flto + + +binaries=spdlog-bench spdlog-bench-mt spdlog-async boost-bench boost-bench-mt glog-bench glog-bench-mt g2log-async easylogging-bench easylogging-bench-mt + +all: $(binaries) + +spdlog-bench: spdlog-bench.cpp + $(CXX) spdlog-bench.cpp -o spdlog-bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + +spdlog-bench-mt: spdlog-bench-mt.cpp + $(CXX) spdlog-bench-mt.cpp -o spdlog-bench-mt $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + +spdlog-async: spdlog-async.cpp + $(CXX) spdlog-async.cpp -o spdlog-async $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +BOOST_FLAGS = -DBOOST_LOG_DYN_LINK -I/home/gabi/devel/boost_1_56_0/ -L/home/gabi/devel/boost_1_56_0/stage/lib -lboost_log -lboost_log_setup -lboost_filesystem -lboost_system -lboost_thread -lboost_regex -lboost_date_time -lboost_chrono + +boost-bench: boost-bench.cpp + $(CXX) boost-bench.cpp -o boost-bench $(CXXFLAGS) $(BOOST_FLAGS) $(CXX_RELEASE_FLAGS) + +boost-bench-mt: boost-bench-mt.cpp + $(CXX) boost-bench-mt.cpp -o boost-bench-mt $(CXXFLAGS) $(BOOST_FLAGS) $(CXX_RELEASE_FLAGS) + + +GLOG_FLAGS = -lglog +glog-bench: glog-bench.cpp + $(CXX) glog-bench.cpp -o glog-bench $(CXXFLAGS) $(GLOG_FLAGS) $(CXX_RELEASE_FLAGS) + +glog-bench-mt: glog-bench-mt.cpp + $(CXX) glog-bench-mt.cpp -o glog-bench-mt $(CXXFLAGS) $(GLOG_FLAGS) $(CXX_RELEASE_FLAGS) + + +G2LOG_FLAGS = -I/home/gabi/devel/g2log/g2log/src -L/home/gabi/devel/g2log/g2log -llib_g2logger +g2log-async: g2log-async.cpp + $(CXX) g2log-async.cpp -o g2log-async $(CXXFLAGS) $(G2LOG_FLAGS) $(CXX_RELEASE_FLAGS) + + +EASYL_FLAGS = -I../../easylogging/src/ +easylogging-bench: easylogging-bench.cpp + $(CXX) easylogging-bench.cpp -o easylogging-bench $(CXXFLAGS) $(EASYL_FLAGS) $(CXX_RELEASE_FLAGS) +easylogging-bench-mt: easylogging-bench-mt.cpp + $(CXX) easylogging-bench-mt.cpp -o easylogging-bench-mt $(CXXFLAGS) $(EASYL_FLAGS) $(CXX_RELEASE_FLAGS) + +.PHONY: clean + +clean: + rm -f *.o logs/* $(binaries) + + +rebuild: clean all + + + diff --git a/fpga/bench/boost-bench-mt.cpp b/fpga/bench/boost-bench-mt.cpp new file mode 100644 index 000000000..d845fcecf --- /dev/null +++ b/fpga/bench/boost-bench-mt.cpp @@ -0,0 +1,84 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace logging = boost::log; +namespace src = boost::log::sources; +namespace sinks = boost::log::sinks; +namespace keywords = boost::log::keywords; + +void init() +{ + logging::add_file_log + ( + keywords::file_name = "logs/boost-sample_%N.log", /*< file name pattern >*/ + keywords::auto_flush = false, + keywords::format = "[%TimeStamp%]: %Message%" + ); + + logging::core::get()->set_filter + ( + logging::trivial::severity >= logging::trivial::info + ); +} + + + +using namespace std; + +int main(int argc, char* argv[]) +{ + int thread_count = 10; + if(argc > 1) + thread_count = atoi(argv[1]); + + int howmany = 1000000; + + + init(); + logging::add_common_attributes(); + + + using namespace logging::trivial; + + src::severity_logger_mt< severity_level > lg; + + std::atomic msg_counter {0}; + vector threads; + + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + BOOST_LOG_SEV(lg, info) << "boost message #" << counter << ": This is some text for your pleasure"; + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + + return 0; +} diff --git a/fpga/bench/boost-bench.cpp b/fpga/bench/boost-bench.cpp new file mode 100644 index 000000000..32c5b692a --- /dev/null +++ b/fpga/bench/boost-bench.cpp @@ -0,0 +1,47 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +#include +#include +#include +#include +#include +#include +#include +#include + +namespace logging = boost::log; +namespace src = boost::log::sources; +namespace sinks = boost::log::sinks; +namespace keywords = boost::log::keywords; + +void init() +{ + logging::add_file_log + ( + keywords::file_name = "logs/boost-sample_%N.log", /*< file name pattern >*/ + keywords::auto_flush = false, + keywords::format = "[%TimeStamp%]: %Message%" + ); + + logging::core::get()->set_filter + ( + logging::trivial::severity >= logging::trivial::info + ); +} + + +int main(int argc, char* []) +{ + int howmany = 1000000; + init(); + logging::add_common_attributes(); + + using namespace logging::trivial; + src::severity_logger_mt< severity_level > lg; + for(int i = 0 ; i < howmany; ++i) + BOOST_LOG_SEV(lg, info) << "boost message #" << i << ": This is some text for your pleasure"; + + return 0; +} diff --git a/fpga/bench/easyl.conf b/fpga/bench/easyl.conf new file mode 100644 index 000000000..3bfb5440c --- /dev/null +++ b/fpga/bench/easyl.conf @@ -0,0 +1,10 @@ +* GLOBAL: + FORMAT = "[%datetime]: %msg" + FILENAME = ./logs/easylogging.log + ENABLED = true + TO_FILE = true + TO_STANDARD_OUTPUT = false + MILLISECONDS_WIDTH = 3 + PERFORMANCE_TRACKING = false + MAX_LOG_FILE_SIZE = 10485760 + Log_Flush_Threshold = 10485760 diff --git a/fpga/bench/easylogging-bench-mt.cpp b/fpga/bench/easylogging-bench-mt.cpp new file mode 100644 index 000000000..98d1ae35c --- /dev/null +++ b/fpga/bench/easylogging-bench-mt.cpp @@ -0,0 +1,52 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include + +#define _ELPP_THREAD_SAFE +#include "easylogging++.h" +_INITIALIZE_EASYLOGGINGPP + +using namespace std; + +int main(int argc, char* argv[]) +{ + + int thread_count = 10; + if(argc > 1) + thread_count = atoi(argv[1]); + + int howmany = 1000000; + + // Load configuration from file + el::Configurations conf("easyl.conf"); + el::Loggers::reconfigureLogger("default", conf); + + std::atomic msg_counter {0}; + vector threads; + + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + LOG(INFO) << "easylog message #" << counter << ": This is some text for your pleasure"; + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + return 0; +} diff --git a/fpga/bench/easylogging-bench.cpp b/fpga/bench/easylogging-bench.cpp new file mode 100644 index 000000000..a952cbd53 --- /dev/null +++ b/fpga/bench/easylogging-bench.cpp @@ -0,0 +1,22 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + + +#include "easylogging++.h" + +_INITIALIZE_EASYLOGGINGPP + +int main(int, char* []) +{ + int howmany = 1000000; + + // Load configuration from file + el::Configurations conf("easyl.conf"); + el::Loggers::reconfigureLogger("default", conf); + + for(int i = 0 ; i < howmany; ++i) + LOG(INFO) << "easylog message #" << i << ": This is some text for your pleasure"; + return 0; +} diff --git a/fpga/bench/g2log-async.cpp b/fpga/bench/g2log-async.cpp new file mode 100644 index 000000000..9f9eb71e9 --- /dev/null +++ b/fpga/bench/g2log-async.cpp @@ -0,0 +1,62 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include +#include +#include + +#include "g2logworker.h" +#include "g2log.h" + +using namespace std; +template std::string format(const T& value); + +int main(int argc, char* argv[]) +{ + using namespace std::chrono; + using clock=steady_clock; + int thread_count = 10; + + if(argc > 1) + thread_count = atoi(argv[1]); + int howmany = 1000000; + + g2LogWorker g2log(argv[0], "logs"); + g2::initializeLogging(&g2log); + + + std::atomic msg_counter {0}; + vector threads; + auto start = clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + LOG(INFO) << "g2log message #" << counter << ": This is some text for your pleasure"; + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + duration delta = clock::now() - start; + float deltaf = delta.count(); + auto rate = howmany/deltaf; + + cout << "Total: " << howmany << std::endl; + cout << "Threads: " << thread_count << std::endl; + std::cout << "Delta = " << deltaf << " seconds" << std::endl; + std::cout << "Rate = " << rate << "/sec" << std::endl; +} diff --git a/fpga/bench/glog-bench-mt.cpp b/fpga/bench/glog-bench-mt.cpp new file mode 100644 index 000000000..db193aeb6 --- /dev/null +++ b/fpga/bench/glog-bench-mt.cpp @@ -0,0 +1,50 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include + +#include "glog/logging.h" + +using namespace std; + +int main(int argc, char* argv[]) +{ + + int thread_count = 10; + if(argc > 1) + thread_count = atoi(argv[1]); + + int howmany = 1000000; + + FLAGS_logtostderr = 0; + FLAGS_log_dir = "logs"; + google::InitGoogleLogging(argv[0]); + + std::atomic msg_counter {0}; + vector threads; + + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + LOG(INFO) << "glog message #" << counter << ": This is some text for your pleasure"; + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + return 0; +} diff --git a/fpga/bench/glog-bench.cpp b/fpga/bench/glog-bench.cpp new file mode 100644 index 000000000..cf7e70a2d --- /dev/null +++ b/fpga/bench/glog-bench.cpp @@ -0,0 +1,21 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include "glog/logging.h" + + +int main(int, char* argv[]) +{ + int howmany = 1000000; + + + FLAGS_logtostderr = 0; + FLAGS_log_dir = "logs"; + google::InitGoogleLogging(argv[0]); + for(int i = 0 ; i < howmany; ++i) + LOG(INFO) << "glog message # " << i << ": This is some text for your pleasure"; + + return 0; +} diff --git a/fpga/bench/latency/Makefile b/fpga/bench/latency/Makefile new file mode 100644 index 000000000..99b479a4a --- /dev/null +++ b/fpga/bench/latency/Makefile @@ -0,0 +1,32 @@ +CXX ?= g++ +CXXFLAGS = -march=native -Wall -std=c++11 -pthread +CXX_RELEASE_FLAGS = -O2 -DNDEBUG + + +binaries=spdlog-latency g3log-latency g3log-crush + +all: $(binaries) + +spdlog-latency: spdlog-latency.cpp + $(CXX) spdlog-latency.cpp -o spdlog-latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -I../../include + + + +g3log-latency: g3log-latency.cpp + $(CXX) g3log-latency.cpp -o g3log-latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -I../../../g3log/src -L. -lg3logger + + +g3log-crush: g3log-crush.cpp + $(CXX) g3log-crush.cpp -o g3log-crush $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -I../../../g3log/src -L. -lg3logger + + +.PHONY: clean + +clean: + rm -f *.o *.log $(binaries) + + +rebuild: clean all + + + diff --git a/fpga/bench/latency/compare.sh b/fpga/bench/latency/compare.sh new file mode 100755 index 000000000..0f0e4c97f --- /dev/null +++ b/fpga/bench/latency/compare.sh @@ -0,0 +1,13 @@ +#!/bin/bash +echo "running spdlog and g3log tests 10 time with ${1:-10} threads each (total 1,000,000 entries).." +rm -f *.log +for i in {1..10} + +do + echo + sleep 0.5 + ./spdlog-latency ${1:-10} 2>/dev/null || exit + sleep 0.5 + ./g3log-latency ${1:-10} 2>/dev/null || exit + +done diff --git a/fpga/bench/latency/g3log-crush.cpp b/fpga/bench/latency/g3log-crush.cpp new file mode 100644 index 000000000..417b014ca --- /dev/null +++ b/fpga/bench/latency/g3log-crush.cpp @@ -0,0 +1,37 @@ +#include + +#include +#include + +void CrusherLoop() +{ + size_t counter = 0; + while (true) + { + LOGF(INFO, "Some text to crush you machine. thread:"); + if(++counter % 1000000 == 0) + { + std::cout << "Wrote " << counter << " entries" << std::endl; + } + } +} + + +int main(int argc, char** argv) +{ + std::cout << "WARNING: This test will exaust all your machine memory and will crush it!" << std::endl; + std::cout << "Are you sure you want to continue ? " << std::endl; + char c; + std::cin >> c; + if (toupper( c ) != 'Y') + return 0; + + auto worker = g3::LogWorker::createLogWorker(); + auto handle= worker->addDefaultLogger(argv[0], "g3log.txt"); + g3::initializeLogging(worker.get()); + CrusherLoop(); + + return 0; +} + + diff --git a/fpga/bench/latency/g3log-latency.cpp b/fpga/bench/latency/g3log-latency.cpp new file mode 100644 index 000000000..e96e421b9 --- /dev/null +++ b/fpga/bench/latency/g3log-latency.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include +#include + + +namespace +{ +const uint64_t g_iterations = 1000000; + + +std::atomic g_counter = {0}; + + +void MeasurePeakDuringLogWrites(const size_t id, std::vector& result) +{ + + while (true) + { + const size_t value_now = ++g_counter; + if (value_now > g_iterations) + { + return; + } + + auto start_time = std::chrono::high_resolution_clock::now(); + LOGF(INFO, "Some text to log for thread: %ld", id); + auto stop_time = std::chrono::high_resolution_clock::now(); + uint64_t time_us = std::chrono::duration_cast(stop_time - start_time).count(); + result.push_back(time_us); + } +} + + + +void PrintResults(const std::map>& threads_result, size_t total_us) +{ + + std::vector all_measurements; + all_measurements.reserve(g_iterations); + for (auto& t_result : threads_result) + { + all_measurements.insert(all_measurements.end(), t_result.second.begin(), t_result.second.end()); + } + + // calc worst latenct + auto worst = *std::max_element(all_measurements.begin(), all_measurements.end()); + + // calc avg + auto total = accumulate(begin(all_measurements), end(all_measurements), 0, std::plus()); + auto avg = double(total)/all_measurements.size(); + + std::cout << "[g3log] worst: " << std::setw(10) << std::right << worst << "\tAvg: " << avg << "\tTotal: " << utils::format(total_us) << " us" << std::endl; + +} +}// anonymous + + +// The purpose of this test is NOT to see how fast +// each thread can possibly write. It is to see what +// the worst latency is for writing a log entry +// +// In the test 1 million log entries will be written +// an atomic counter is used to give each thread what +// it is to write next. The overhead of atomic +// synchronization between the threads are not counted in the worst case latency +int main(int argc, char** argv) +{ + size_t number_of_threads {0}; + if (argc == 2) + { + number_of_threads = atoi(argv[1]); + } + if (argc != 2 || number_of_threads == 0) + { + std::cerr << "USAGE is: " << argv[0] << " number_threads" << std::endl; + return 1; + } + + + std::vector threads(number_of_threads); + std::map> threads_result; + + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + // reserve to 1 million for all the result + // it's a test so let's not care about the wasted space + threads_result[idx].reserve(g_iterations); + } + + const std::string g_path = "./" ; + const std::string g_prefix_log_name = "g3log-performance-"; + const std::string g_measurement_dump = g_path + g_prefix_log_name + "_RESULT.txt"; + + auto worker = g3::LogWorker::createLogWorker(); + auto handle= worker->addDefaultLogger(argv[0], "g3log.txt"); + g3::initializeLogging(worker.get()); + + auto start_time_application_total = std::chrono::high_resolution_clock::now(); + for (uint64_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx] = std::thread(MeasurePeakDuringLogWrites, idx, std::ref(threads_result[idx])); + } + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx].join(); + } + auto stop_time_application_total = std::chrono::high_resolution_clock::now(); + + uint64_t total_time_in_us = std::chrono::duration_cast(stop_time_application_total - start_time_application_total).count(); + PrintResults(threads_result, total_time_in_us); + return 0; +} + + diff --git a/fpga/bench/latency/spdlog-latency.cpp b/fpga/bench/latency/spdlog-latency.cpp new file mode 100644 index 000000000..ed4966cc4 --- /dev/null +++ b/fpga/bench/latency/spdlog-latency.cpp @@ -0,0 +1,128 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include + +#include "spdlog/spdlog.h" + +namespace spd = spdlog; + +namespace +{ +const uint64_t g_iterations = 1000000; + + +std::atomic g_counter = {0}; + + +void MeasurePeakDuringLogWrites(const size_t id, std::vector& result) +{ + auto logger = spd::get("file_logger"); + while (true) + { + const size_t value_now = ++g_counter; + if (value_now > g_iterations) + { + return; + } + + auto start_time = std::chrono::high_resolution_clock::now(); + logger->info("Some text to log for thread: [somemore text...............................] {}", id); + auto stop_time = std::chrono::high_resolution_clock::now(); + uint64_t time_us = std::chrono::duration_cast(stop_time - start_time).count(); + result.push_back(time_us); + } +} + + +void PrintResults(const std::map>& threads_result, size_t total_us) +{ + + std::vector all_measurements; + all_measurements.reserve(g_iterations); + for (auto& t_result : threads_result) + { + all_measurements.insert(all_measurements.end(), t_result.second.begin(), t_result.second.end()); + } + + // calc worst latenct + auto worst = *std::max_element(all_measurements.begin(), all_measurements.end()); + + // calc avg + auto total = accumulate(begin(all_measurements), end(all_measurements), 0, std::plus()); + auto avg = double(total)/all_measurements.size(); + + std::cout << "[spdlog] worst: " << std::setw(10) << std::right << worst << "\tAvg: " << avg << "\tTotal: " << utils::format(total_us) << " us" << std::endl; + +} +}// anonymous + + +// The purpose of this test is NOT to see how fast +// each thread can possibly write. It is to see what +// the worst latency is for writing a log entry +// +// In the test 1 million log entries will be written +// an atomic counter is used to give each thread what +// it is to write next. The overhead of atomic +// synchronization between the threads are not counted in the worst case latency +int main(int argc, char** argv) +{ + size_t number_of_threads {0}; + if (argc == 2) + { + number_of_threads = atoi(argv[1]); + } + if (argc != 2 || number_of_threads == 0) + { + std::cerr << "usage: " << argv[0] << " number_threads" << std::endl; + return 1; + } + + + std::vector threads(number_of_threads); + std::map> threads_result; + + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + // reserve to 1 million for all the result + // it's a test so let's not care about the wasted space + threads_result[idx].reserve(g_iterations); + } + + int queue_size = 1048576; // 2 ^ 20 + spdlog::set_async_mode(queue_size); + auto logger = spdlog::create("file_logger", "spdlog.log", true); + + //force flush on every call to compare with g3log + auto s = (spd::sinks::simple_file_sink_mt*)logger->sinks()[0].get(); + s->set_force_flush(true); + + auto start_time_application_total = std::chrono::high_resolution_clock::now(); + for (uint64_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx] = std::thread(MeasurePeakDuringLogWrites, idx, std::ref(threads_result[idx])); + } + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx].join(); + } + auto stop_time_application_total = std::chrono::high_resolution_clock::now(); + + uint64_t total_time_in_us = std::chrono::duration_cast(stop_time_application_total - start_time_application_total).count(); + + PrintResults(threads_result, total_time_in_us); + return 0; +} + + diff --git a/fpga/bench/latency/utils.h b/fpga/bench/latency/utils.h new file mode 100644 index 000000000..b260f7249 --- /dev/null +++ b/fpga/bench/latency/utils.h @@ -0,0 +1,35 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils +{ + +template +inline std::string format(const T& value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template<> +inline std::string format(const double & value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} diff --git a/fpga/bench/logs/.gitignore b/fpga/bench/logs/.gitignore new file mode 100644 index 000000000..40637012b --- /dev/null +++ b/fpga/bench/logs/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fpga/bench/spdlog-async.cpp b/fpga/bench/spdlog-async.cpp new file mode 100644 index 000000000..f788e4dfb --- /dev/null +++ b/fpga/bench/spdlog-async.cpp @@ -0,0 +1,62 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include +#include +#include +#include +#include "spdlog/spdlog.h" + +using namespace std; + +int main(int argc, char* argv[]) +{ + + using namespace std::chrono; + using clock=steady_clock; + namespace spd = spdlog; + + int thread_count = 10; + if(argc > 1) + thread_count = ::atoi(argv[1]); + int howmany = 1000000; + + spd::set_async_mode(1048576); + auto logger = spdlog::create("file_logger", "logs/spd-bench-async.txt", false); + logger->set_pattern("[%Y-%b-%d %T.%e]: %v"); + + + std::atomic msg_counter {0}; + vector threads; + auto start = clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + logger->info("spdlog message #{}: This is some text for your pleasure", counter); + } + })); + } + + for(auto &t:threads) + { + t.join(); + }; + + duration delta = clock::now() - start; + float deltaf = delta.count(); + auto rate = howmany/deltaf; + + cout << "Total: " << howmany << std::endl; + cout << "Threads: " << thread_count << std::endl; + std::cout << "Delta = " << deltaf << " seconds" << std::endl; + std::cout << "Rate = " << rate << "/sec" << std::endl; +} diff --git a/fpga/bench/spdlog-bench-mt.cpp b/fpga/bench/spdlog-bench-mt.cpp new file mode 100644 index 000000000..e28e7bb83 --- /dev/null +++ b/fpga/bench/spdlog-bench-mt.cpp @@ -0,0 +1,55 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include +#include +#include "spdlog/spdlog.h" + + +using namespace std; + +int main(int argc, char* argv[]) +{ + + int thread_count = 10; + if(argc > 1) + thread_count = std::atoi(argv[1]); + + int howmany = 1000000; + + namespace spd = spdlog; + + auto logger = spdlog::create("file_logger", "logs/spd-bench-mt.txt", false); + + logger->set_pattern("[%Y-%b-%d %T.%e]: %v"); + + std::atomic msg_counter {0}; + std::vector threads; + + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + logger->info("spdlog message #{}: This is some text for your pleasure", counter); + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + + + return 0; +} diff --git a/fpga/bench/spdlog-bench.cpp b/fpga/bench/spdlog-bench.cpp new file mode 100644 index 000000000..4ac95f6af --- /dev/null +++ b/fpga/bench/spdlog-bench.cpp @@ -0,0 +1,20 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include "spdlog/spdlog.h" + + +int main(int, char* []) +{ + int howmany = 1000000; + namespace spd = spdlog; + ///Create a file rotating logger with 5mb size max and 3 rotated files + auto logger = spdlog::create("file_logger", "logs/spd-bench-st.txt", false); + + logger->set_pattern("[%Y-%b-%d %T.%e]: %v"); + for(int i = 0 ; i < howmany; ++i) + logger->info("spdlog message #{} : This is some text for your pleasure", i); + return 0; +} diff --git a/fpga/bench/spdlog-null-async.cpp b/fpga/bench/spdlog-null-async.cpp new file mode 100644 index 000000000..3874371a7 --- /dev/null +++ b/fpga/bench/spdlog-null-async.cpp @@ -0,0 +1,112 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// bench.cpp : spdlog benchmarks +// +#include +#include // EXIT_FAILURE +#include +#include +#include +#include +#include "spdlog/spdlog.h" +#include "spdlog/async_logger.h" +#include "spdlog/sinks/null_sink.h" +#include "utils.h" + + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +using namespace spdlog::sinks; +using namespace utils; + + + +size_t bench_as(int howmany, std::shared_ptr log, int thread_count); + +int main(int argc, char* argv[]) +{ + + int queue_size = 1048576; + int howmany = 1000000; + int threads = 10; + int iters = 10; + + try + { + + if(argc > 1) + howmany = atoi(argv[1]); + if (argc > 2) + threads = atoi(argv[2]); + if (argc > 3) + queue_size = atoi(argv[3]); + + + cout << "\n*******************************************************************************\n"; + cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " messages " << endl; + cout << "*******************************************************************************\n"; + + spdlog::set_async_mode(queue_size); + + size_t total_rate = 0; + + for(int i = 0; i < iters; ++i) + { + //auto as = spdlog::daily_logger_st("as", "logs/daily_async"); + auto as = spdlog::create("async(null-sink)"); + total_rate+= bench_as(howmany, as, threads); + spdlog::drop("async(null-sink)"); + } + std::cout << endl; + std::cout << "Avg rate: " << format(total_rate/iters) << "/sec" < log, int thread_count) +{ + cout << log->name() << "...\t\t" << flush; + std::atomic msg_counter {0}; + vector threads; + auto start = system_clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + for(;;) + { + int counter = ++msg_counter; + if (counter > howmany) break; + log->info("Hello logger: msg number {}", counter); + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + + auto delta = system_clock::now() - start; + auto delta_d = duration_cast> (delta).count(); + auto per_sec = size_t(howmany / delta_d); + cout << format(per_sec) << "/sec" << endl; + return per_sec; +} diff --git a/fpga/bench/utils.h b/fpga/bench/utils.h new file mode 100644 index 000000000..b260f7249 --- /dev/null +++ b/fpga/bench/utils.h @@ -0,0 +1,35 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils +{ + +template +inline std::string format(const T& value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template<> +inline std::string format(const double & value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} diff --git a/fpga/cmake/Config.cmake.in b/fpga/cmake/Config.cmake.in new file mode 100644 index 000000000..ba0b36f2b --- /dev/null +++ b/fpga/cmake/Config.cmake.in @@ -0,0 +1,24 @@ +# *************************************************************************/ +# * Copyright (c) 2015 Ruslan Baratov. */ +# * */ +# * 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. */ +# *************************************************************************/ + +include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") diff --git a/fpga/cmake/spdlog.pc.in b/fpga/cmake/spdlog.pc.in new file mode 100644 index 000000000..262248a74 --- /dev/null +++ b/fpga/cmake/spdlog.pc.in @@ -0,0 +1,6 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: Super fast C++ logging library. +Version: @PROJECT_VERSION@ diff --git a/fpga/example/CMakeLists.txt b/fpga/example/CMakeLists.txt new file mode 100644 index 000000000..7859e4d58 --- /dev/null +++ b/fpga/example/CMakeLists.txt @@ -0,0 +1,49 @@ +# *************************************************************************/ +# * Copyright (c) 2015 Ruslan Baratov. */ +# * */ +# * 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. */ +# *************************************************************************/ + +cmake_minimum_required(VERSION 3.0) +project(SpdlogExamples) + +if(TARGET spdlog) + # Part of the main project + add_library(spdlog::spdlog ALIAS spdlog) +else() + # Stand-alone build + find_package(spdlog CONFIG REQUIRED) +endif() + +find_package(Threads) + +add_executable(example example.cpp) +target_link_libraries(example spdlog::spdlog ${CMAKE_THREAD_LIBS_INIT}) + +add_executable(benchmark bench.cpp) +target_link_libraries(benchmark spdlog::spdlog ${CMAKE_THREAD_LIBS_INIT}) + +add_executable(multisink multisink.cpp) +target_link_libraries(multisink spdlog::spdlog ${CMAKE_THREAD_LIBS_INIT}) + +enable_testing() +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") +add_test(NAME RunExample COMMAND example) +add_test(NAME RunBenchmark COMMAND benchmark) diff --git a/fpga/example/Makefile b/fpga/example/Makefile new file mode 100644 index 000000000..6d169e41c --- /dev/null +++ b/fpga/example/Makefile @@ -0,0 +1,29 @@ +CXX ?= g++ +CXXFLAGS = +CXX_FLAGS = -Wall -Wshadow -Wextra -pedantic -std=c++11 -pthread -I../include +CXX_RELEASE_FLAGS = -O3 -march=native +CXX_DEBUG_FLAGS= -g + + +all: example bench +debug: example-debug bench-debug + +example: example.cpp + $(CXX) example.cpp -o example $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS) + +bench: bench.cpp + $(CXX) bench.cpp -o bench $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS) + + +example-debug: example.cpp + $(CXX) example.cpp -o example-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS) + +bench-debug: bench.cpp + $(CXX) bench.cpp -o bench-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS) + +clean: + rm -f *.o logs/*.txt example example-debug bench bench-debug + + +rebuild: clean all +rebuild-debug: clean debug diff --git a/fpga/example/Makefile.clang b/fpga/example/Makefile.clang new file mode 100644 index 000000000..0ed004d03 --- /dev/null +++ b/fpga/example/Makefile.clang @@ -0,0 +1,32 @@ +CXX ?= clang++ +CXXFLAGS = -march=native -Wall -Wextra -Wshadow -pedantic -std=c++11 -pthread -I../include +CXX_RELEASE_FLAGS = -O2 +CXX_DEBUG_FLAGS= -g + + +all: example bench +debug: example-debug bench-debug + +example: example.cpp + $(CXX) example.cpp -o example-clang $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + +bench: bench.cpp + $(CXX) bench.cpp -o bench-clang $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +example-debug: example.cpp + $(CXX) example.cpp -o example-clang-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) + +bench-debug: bench.cpp + $(CXX) bench.cpp -o bench-clang-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) + + + +clean: + rm -f *.o logs/*.txt example-clang example-clang-debug bench-clang bench-clang-debug + + +rebuild: clean all +rebuild-debug: clean debug + + diff --git a/fpga/example/Makefile.mingw b/fpga/example/Makefile.mingw new file mode 100644 index 000000000..5ee5ab6fc --- /dev/null +++ b/fpga/example/Makefile.mingw @@ -0,0 +1,32 @@ +CXX ?= g++ +CXXFLAGS = -D_WIN32_WINNT=0x600 -march=native -Wall -Wextra -Wshadow -pedantic -std=gnu++0x -pthread -Wl,--no-as-needed -I../include +CXX_RELEASE_FLAGS = -O3 +CXX_DEBUG_FLAGS= -g + + +all: example bench +debug: example-debug bench-debug + +example: example.cpp + $(CXX) example.cpp -o example $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + +bench: bench.cpp + $(CXX) bench.cpp -o bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +example-debug: example.cpp + $(CXX) example.cpp -o example-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) + +bench-debug: bench.cpp + $(CXX) bench.cpp -o bench-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) + + + +clean: + rm -f *.o logs/*.txt example example-debug bench bench-debug + + +rebuild: clean all +rebuild-debug: clean debug + + diff --git a/fpga/example/bench.cpp b/fpga/example/bench.cpp new file mode 100644 index 000000000..b21c44353 --- /dev/null +++ b/fpga/example/bench.cpp @@ -0,0 +1,144 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// bench.cpp : spdlog benchmarks +// +#include +#include // EXIT_FAILURE +#include +#include +#include +#include +#include "spdlog/spdlog.h" +#include "spdlog/async_logger.h" +#include "spdlog/sinks/file_sinks.h" +#include "spdlog/sinks/null_sink.h" +#include "utils.h" + + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +using namespace spdlog::sinks; +using namespace utils; + + +void bench(int howmany, std::shared_ptr log); +void bench_mt(int howmany, std::shared_ptr log, int thread_count); + +int main(int argc, char* argv[]) +{ + + int queue_size = 1048576; + int howmany = 1000000; + int threads = 10; + int file_size = 30 * 1024 * 1024; + int rotating_files = 5; + + try + { + + if(argc > 1) + howmany = atoi(argv[1]); + if (argc > 2) + threads = atoi(argv[2]); + if (argc > 3) + queue_size = atoi(argv[3]); + + + cout << "*******************************************************************************\n"; + cout << "Single thread, " << format(howmany) << " iterations" << endl; + cout << "*******************************************************************************\n"; + + auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st", file_size, rotating_files); + bench(howmany, rotating_st); + auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st"); + bench(howmany, daily_st); + bench(howmany, spdlog::create("null_st")); + + cout << "\n*******************************************************************************\n"; + cout << threads << " threads sharing same logger, " << format(howmany) << " iterations" << endl; + cout << "*******************************************************************************\n"; + + auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt", file_size, rotating_files); + bench_mt(howmany, rotating_mt, threads); + + + auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt"); + bench_mt(howmany, daily_mt, threads); + bench(howmany, spdlog::create("null_mt")); + + cout << "\n*******************************************************************************\n"; + cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " iterations " << endl; + cout << "*******************************************************************************\n"; + + + spdlog::set_async_mode(queue_size); + + for(int i = 0; i < 3; ++i) + { + auto as = spdlog::daily_logger_st("as", "logs/daily_async"); + bench_mt(howmany, as, threads); + spdlog::drop("as"); + } + } + catch (std::exception &ex) + { + std::cerr << "Error: " << ex.what() << std::endl; + perror("Last error"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + + +void bench(int howmany, std::shared_ptr log) +{ + cout << log->name() << "...\t\t" << flush; + auto start = system_clock::now(); + for (auto i = 0; i < howmany; ++i) + { + log->info("Hello logger: msg number {}", i); + } + + + auto delta = system_clock::now() - start; + auto delta_d = duration_cast> (delta).count(); + cout << format(int(howmany / delta_d)) << "/sec" << endl; +} + + +void bench_mt(int howmany, std::shared_ptr log, int thread_count) +{ + + cout << log->name() << "...\t\t" << flush; + std::atomic msg_counter {0}; + vector threads; + auto start = system_clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + for(;;) + { + int counter = ++msg_counter; + if (counter > howmany) break; + log->info("Hello logger: msg number {}", counter); + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + + auto delta = system_clock::now() - start; + auto delta_d = duration_cast> (delta).count(); + cout << format(int(howmany / delta_d)) << "/sec" << endl; +} diff --git a/fpga/example/example.cpp b/fpga/example/example.cpp new file mode 100644 index 000000000..c9aee7ad7 --- /dev/null +++ b/fpga/example/example.cpp @@ -0,0 +1,167 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// +// spdlog usage example +// +// + +#define SPDLOG_TRACE_ON +#define SPDLOG_DEBUG_ON + +#include "spdlog/spdlog.h" + +#include +#include + +void async_example(); +void syslog_example(); +void android_example(); +void user_defined_example(); +void err_handler_example(); + +namespace spd = spdlog; +int main(int, char*[]) +{ + try + { + // Console logger with color + auto console = spd::stdout_color_mt("console"); + console->info("Welcome to spdlog!"); + console->error("Some error message with arg{}..", 1); + + + // Formatting examples + console->warn("Easy padding in numbers like {:08d}", 12); + console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + console->info("Support for floats {:03.2f}", 1.23456); + console->info("Positional args are {1} {0}..", "too", "supported"); + console->info("{:<30}", "left aligned"); + + spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function"); + + + // Create basic file logger (not rotated) + auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic-log.txt"); + my_logger->info("Some log message"); + + // Create a file rotating logger with 5mb size max and 3 rotated files + auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); + for (int i = 0; i < 10; ++i) + rotating_logger->info("{} * {} equals {:>10}", i, i, i*i); + + // Create a daily logger - a new file is created every day on 2:30am + auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); + // trigger flush if the log severity is error or higher + daily_logger->flush_on(spd::level::err); + daily_logger->info(123.44); + + // Customize msg format for all messages + spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); + rotating_logger->info("This is another message with custom format"); + + + // Runtime log levels + spd::set_level(spd::level::info); //Set global log level to info + console->debug("This message shold not be displayed!"); + console->set_level(spd::level::debug); // Set specific logger's log level + console->debug("This message shold be displayed.."); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON + SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); + SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); + + + // Asynchronous logging is very fast.. + // Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous.. + async_example(); + + // syslog example. linux/osx only + syslog_example(); + + // android example. compile with NDK + android_example(); + + // Log user-defined types example + user_defined_example(); + + // Change default log error handler + err_handler_example(); + + // Apply a function on all registered loggers + spd::apply_all([&](std::shared_ptr l) + { + l->info("End of example."); + }); + + // Release and close all loggers + spdlog::drop_all(); + } + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + catch (const spd::spdlog_ex& ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} + +void async_example() +{ + size_t q_size = 4096; //queue size must be power of 2 + spdlog::set_async_mode(q_size); + auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); + for (int i = 0; i < 100; ++i) + async_file->info("Async message #{}", i); +} + +//syslog example (linux/osx/freebsd) +void syslog_example() +{ +#ifdef SPDLOG_ENABLE_SYSLOG + std::string ident = "spdlog-example"; + auto syslog_logger = spd::syslog_logger("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +#endif +} + +// Android example +void android_example() +{ +#if defined(__ANDROID__) + std::string tag = "spdlog-android"; + auto android_logger = spd::android_logger("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +#endif +} + +// user defined types logging by implementing operator<< +struct my_type +{ + int i; + template + friend OStream& operator<<(OStream& os, const my_type &c) + { + return os << "[my_type i="<info("user defined type: {}", my_type { 14 }); +} + +// +//custom error handler +// +void err_handler_example() +{ + //can be set globaly or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string& msg) + { + std::cerr << "my err handler: " << msg << std::endl; + }); + spd::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); +} diff --git a/fpga/example/example.sln b/fpga/example/example.sln new file mode 100644 index 000000000..81f45629a --- /dev/null +++ b/fpga/example/example.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|Win32.ActiveCfg = Debug|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|Win32.Build.0 = Debug|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|x64.ActiveCfg = Debug|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|Win32.ActiveCfg = Release|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|Win32.Build.0 = Release|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|x64.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/fpga/example/example.vcxproj b/fpga/example/example.vcxproj new file mode 100644 index 000000000..63db2b5dd --- /dev/null +++ b/fpga/example/example.vcxproj @@ -0,0 +1,126 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2} + Win32Proj + . + 8.1 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + ..\include;%(AdditionalIncludeDirectories) + + + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + ..\include;%(AdditionalIncludeDirectories) + + + + + Console + true + true + true + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/fpga/example/jni/Android.mk b/fpga/example/jni/Android.mk new file mode 100644 index 000000000..7accbad31 --- /dev/null +++ b/fpga/example/jni/Android.mk @@ -0,0 +1,15 @@ +# Setup a project +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := example +LOCAL_SRC_FILES := example.cpp +LOCAL_CPPFLAGS += -Wall -Wshadow -Wextra -pedantic -std=c++11 -fPIE -pie +LOCAL_LDFLAGS += -fPIE -pie + +# Add exception support and set path for spdlog's headers +LOCAL_CPPFLAGS += -fexceptions -I../include +# Use android's log library +LOCAL_LDFLAGS += -llog + +include $(BUILD_EXECUTABLE) diff --git a/fpga/example/jni/Application.mk b/fpga/example/jni/Application.mk new file mode 100644 index 000000000..dccd2a5a3 --- /dev/null +++ b/fpga/example/jni/Application.mk @@ -0,0 +1,2 @@ +# Exceptions are used in spdlog. Link to an exception-ready C++ runtime. +APP_STL = gnustl_static diff --git a/fpga/example/jni/example.cpp b/fpga/example/jni/example.cpp new file mode 120000 index 000000000..6170abce2 --- /dev/null +++ b/fpga/example/jni/example.cpp @@ -0,0 +1 @@ +../example.cpp \ No newline at end of file diff --git a/fpga/example/logs/.gitignore b/fpga/example/logs/.gitignore new file mode 100644 index 000000000..203251351 --- /dev/null +++ b/fpga/example/logs/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/fpga/example/multisink.cpp b/fpga/example/multisink.cpp new file mode 100644 index 000000000..fe6539b53 --- /dev/null +++ b/fpga/example/multisink.cpp @@ -0,0 +1,47 @@ +#include "spdlog/spdlog.h" + +#include +#include + +namespace spd = spdlog; +int main(int, char*[]) +{ + bool enable_debug = true; + try + { + // This other example use a single logger with multiple sinks. + // This means that the same log_msg is forwarded to multiple sinks; + // Each sink can have it's own log level and a message will be logged. + std::vector sinks; + sinks.push_back( std::make_shared() ); + sinks.push_back( std::make_shared("./log_regular_file.txt") ); + sinks.push_back( std::make_shared("./log_debug_file.txt") ); + + spdlog::logger console_multisink("multisink", sinks.begin(), sinks.end() ); + console_multisink.set_level( spdlog::level::warn); + + sinks[0]->set_level( spdlog::level::trace); // console. Allow everything. Default value + sinks[1]->set_level( spdlog::level::trace); // regular file. Allow everything. Default value + sinks[2]->set_level( spdlog::level::off); // regular file. Ignore everything. + + console_multisink.warn("warn: will print only on console and regular file"); + + if( enable_debug ) + { + console_multisink.set_level( spdlog::level::debug); // level of the logger + sinks[1]->set_level( spdlog::level::debug); // regular file + sinks[2]->set_level( spdlog::level::debug); // debug file + } + console_multisink.debug("Debug: you should see this on console and both files"); + + // Release and close all loggers + spdlog::drop_all(); + } + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + catch (const spd::spdlog_ex& ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} + diff --git a/fpga/example/utils.h b/fpga/example/utils.h new file mode 100644 index 000000000..b260f7249 --- /dev/null +++ b/fpga/example/utils.h @@ -0,0 +1,35 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils +{ + +template +inline std::string format(const T& value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template<> +inline std::string format(const double & value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} diff --git a/fpga/include/spdlog/async_logger.h b/fpga/include/spdlog/async_logger.h new file mode 100644 index 000000000..e9fcd5f40 --- /dev/null +++ b/fpga/include/spdlog/async_logger.h @@ -0,0 +1,82 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Very fast asynchronous logger (millions of logs per second on an average desktop) +// Uses pre allocated lockfree queue for maximum throughput even under large number of threads. +// Creates a single back thread to pop messages from the queue and log them. +// +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Push a new copy of the message to a queue (or block the caller until space is available in the queue) +// 3. will throw spdlog_ex upon log exceptions +// Upon destruction, logs all remaining messages in the queue before destructing.. + +#include "common.h" +#include "logger.h" + +#include +#include +#include +#include + +namespace spdlog +{ + +namespace details +{ +class async_log_helper; +} + +class async_logger SPDLOG_FINAL :public logger +{ +public: + template + async_logger(const std::string& name, + const It& begin, + const It& end, + size_t queue_size, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + async_logger(const std::string& logger_name, + sinks_init_list sinks, + size_t queue_size, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + async_logger(const std::string& logger_name, + sink_ptr single_sink, + size_t queue_size, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + //Wait for the queue to be empty, and flush synchronously + //Warning: this can potentially last forever as we wait it to complete + void flush() override; + + // Error handler + virtual void set_error_handler(log_err_handler) override; + virtual log_err_handler error_handler() override; + +protected: + void _sink_it(details::log_msg& msg) override; + void _set_formatter(spdlog::formatter_ptr msg_formatter) override; + void _set_pattern(const std::string& pattern, pattern_time_type pattern_time) override; + +private: + std::unique_ptr _async_log_helper; +}; +} + + +#include "details/async_logger_impl.h" diff --git a/fpga/include/spdlog/common.h b/fpga/include/spdlog/common.h new file mode 100644 index 000000000..ea0b05670 --- /dev/null +++ b/fpga/include/spdlog/common.h @@ -0,0 +1,161 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#include +#include +#endif + +#include "details/null_mutex.h" + +//visual studio upto 2013 does not support noexcept nor constexpr +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define SPDLOG_NOEXCEPT throw() +#define SPDLOG_CONSTEXPR +#else +#define SPDLOG_NOEXCEPT noexcept +#define SPDLOG_CONSTEXPR constexpr +#endif + +// final keyword support. On by default. See tweakme.h +#if defined(SPDLOG_NO_FINAL) +#define SPDLOG_FINAL +#else +#define SPDLOG_FINAL final +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SPDLOG_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SPDLOG_DEPRECATED __declspec(deprecated) +#else +#define SPDLOG_DEPRECATED +#endif + +#include "fmt/fmt.h" + +namespace spdlog +{ + +class formatter; + +namespace sinks +{ +class sink; +} + +using log_clock = std::chrono::system_clock; +using sink_ptr = std::shared_ptr < sinks::sink >; +using sinks_init_list = std::initializer_list < sink_ptr >; +using formatter_ptr = std::shared_ptr; +#if defined(SPDLOG_NO_ATOMIC_LEVELS) +using level_t = details::null_atomic_int; +#else +using level_t = std::atomic; +#endif + +using log_err_handler = std::function; + +//Log level enum +namespace level +{ +typedef enum +{ + trace = 0, + debug = 1, + info = 2, + warn = 3, + err = 4, + critical = 5, + off = 6 +} level_enum; + +#if !defined(SPDLOG_LEVEL_NAMES) +#define SPDLOG_LEVEL_NAMES { "trace", "debug", "info", "warning", "error", "critical", "off" } +#endif +static const char* level_names[] SPDLOG_LEVEL_NAMES; + +static const char* short_level_names[] { "T", "D", "I", "W", "E", "C", "O" }; + +inline const char* to_str(spdlog::level::level_enum l) +{ + return level_names[l]; +} + +inline const char* to_short_str(spdlog::level::level_enum l) +{ + return short_level_names[l]; +} +} //level + + +// +// Async overflow policy - block by default. +// +enum class async_overflow_policy +{ + block_retry, // Block / yield / sleep until message can be enqueued + discard_log_msg // Discard the message it enqueue fails +}; + +// +// Pattern time - specific time getting to use for pattern_formatter. +// local time by default +// +enum class pattern_time_type +{ + local, // log localtime + utc // log utc +}; + +// +// Log exception +// +namespace details +{ +namespace os +{ +std::string errno_str(int err_num); +} +} +class spdlog_ex: public std::exception +{ +public: + spdlog_ex(const std::string& msg):_msg(msg) + {} + spdlog_ex(const std::string& msg, int last_errno) + { + _msg = msg + ": " + details::os::errno_str(last_errno); + } + const char* what() const SPDLOG_NOEXCEPT override + { + return _msg.c_str(); + } +private: + std::string _msg; + +}; + +// +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +// +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +using filename_t = std::wstring; +#else +using filename_t = std::string; +#endif + + +} //spdlog diff --git a/fpga/include/spdlog/details/async_log_helper.h b/fpga/include/spdlog/details/async_log_helper.h new file mode 100644 index 000000000..7c3dcf44f --- /dev/null +++ b/fpga/include/spdlog/details/async_log_helper.h @@ -0,0 +1,399 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// async log helper : +// Process logs asynchronously using a back thread. +// +// If the internal queue of log messages reaches its max size, +// then the client call will block until there is more room. +// + +#pragma once + +#include "../common.h" +#include "../sinks/sink.h" +#include "../details/mpmc_bounded_q.h" +#include "../details/log_msg.h" +#include "../details/os.h" +#include "../formatter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ + +class async_log_helper +{ + // Async msg to move to/from the queue + // Movable only. should never be copied + enum class async_msg_type + { + log, + flush, + terminate + }; + struct async_msg + { + std::string logger_name; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + std::string txt; + async_msg_type msg_type; + size_t msg_id; + + async_msg() = default; + ~async_msg() = default; + + +async_msg(async_msg&& other) SPDLOG_NOEXCEPT: + logger_name(std::move(other.logger_name)), + level(std::move(other.level)), + time(std::move(other.time)), + thread_id(other.thread_id), + txt(std::move(other.txt)), + msg_type(std::move(other.msg_type)), + msg_id(other.msg_id) + {} + + async_msg(async_msg_type m_type): + level(level::info), + thread_id(0), + msg_type(m_type), + msg_id(0) + {} + + async_msg& operator=(async_msg&& other) SPDLOG_NOEXCEPT + { + logger_name = std::move(other.logger_name); + level = other.level; + time = std::move(other.time); + thread_id = other.thread_id; + txt = std::move(other.txt); + msg_type = other.msg_type; + msg_id = other.msg_id; + return *this; + } + + // never copy or assign. should only be moved.. + async_msg(const async_msg&) = delete; + async_msg& operator=(const async_msg& other) = delete; + + // construct from log_msg + async_msg(const details::log_msg& m): + level(m.level), + time(m.time), + thread_id(m.thread_id), + txt(m.raw.data(), m.raw.size()), + msg_type(async_msg_type::log), + msg_id(m.msg_id) + { +#ifndef SPDLOG_NO_NAME + logger_name = *m.logger_name; +#endif + } + + + // copy into log_msg + void fill_log_msg(log_msg &msg) + { + msg.logger_name = &logger_name; + msg.level = level; + msg.time = time; + msg.thread_id = thread_id; + msg.raw << txt; + msg.msg_id = msg_id; + } + }; + +public: + + using item_type = async_msg; + using q_type = details::mpmc_bounded_queue; + + using clock = std::chrono::steady_clock; + + + async_log_helper(formatter_ptr formatter, + const std::vector& sinks, + size_t queue_size, + const log_err_handler err_handler, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + void log(const details::log_msg& msg); + + // stop logging and join the back thread + ~async_log_helper(); + + void set_formatter(formatter_ptr); + + void flush(bool wait_for_q); + + void set_error_handler(spdlog::log_err_handler err_handler); + +private: + formatter_ptr _formatter; + std::vector> _sinks; + + // queue of messages to log + q_type _q; + + log_err_handler _err_handler; + + bool _flush_requested; + + bool _terminate_requested; + + + // overflow policy + const async_overflow_policy _overflow_policy; + + // worker thread warmup callback - one can set thread priority, affinity, etc + const std::function _worker_warmup_cb; + + // auto periodic sink flush parameter + const std::chrono::milliseconds _flush_interval_ms; + + // worker thread teardown callback + const std::function _worker_teardown_cb; + + // worker thread + std::thread _worker_thread; + + void push_msg(async_msg&& new_msg); + + // worker thread main loop + void worker_loop(); + + // pop next message from the queue and process it. will set the last_pop to the pop time + // return false if termination of the queue is required + bool process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush); + + void handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush); + + // sleep,yield or return immediately using the time passed since last message as a hint + static void sleep_or_yield(const spdlog::log_clock::time_point& now, const log_clock::time_point& last_op_time); + + // wait until the queue is empty + void wait_empty_q(); + +}; +} +} + +/////////////////////////////////////////////////////////////////////////////// +// async_sink class implementation +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::details::async_log_helper::async_log_helper( + formatter_ptr formatter, + const std::vector& sinks, + size_t queue_size, + log_err_handler err_handler, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb): + _formatter(formatter), + _sinks(sinks), + _q(queue_size), + _err_handler(err_handler), + _flush_requested(false), + _terminate_requested(false), + _overflow_policy(overflow_policy), + _worker_warmup_cb(worker_warmup_cb), + _flush_interval_ms(flush_interval_ms), + _worker_teardown_cb(worker_teardown_cb), + _worker_thread(&async_log_helper::worker_loop, this) +{} + +// Send to the worker thread termination message(level=off) +// and wait for it to finish gracefully +inline spdlog::details::async_log_helper::~async_log_helper() +{ + try + { + push_msg(async_msg(async_msg_type::terminate)); + _worker_thread.join(); + } + catch (...) // don't crash in destructor + { + } +} + + +//Try to push and block until succeeded (if the policy is not to discard when the queue is full) +inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) +{ + push_msg(async_msg(msg)); +} + +inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_msg) +{ + if (!_q.enqueue(std::move(new_msg)) && _overflow_policy != async_overflow_policy::discard_log_msg) + { + auto last_op_time = details::os::now(); + auto now = last_op_time; + do + { + now = details::os::now(); + sleep_or_yield(now, last_op_time); + } + while (!_q.enqueue(std::move(new_msg))); + } +} + +// optionally wait for the queue be empty and request flush from the sinks +inline void spdlog::details::async_log_helper::flush(bool wait_for_q) +{ + push_msg(async_msg(async_msg_type::flush)); + if (wait_for_q) + wait_empty_q(); //return when queue is empty +} + +inline void spdlog::details::async_log_helper::worker_loop() +{ + if (_worker_warmup_cb) _worker_warmup_cb(); + auto last_pop = details::os::now(); + auto last_flush = last_pop; + auto active = true; + while (active) + { + try + { + active = process_next_msg(last_pop, last_flush); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch(...) + { + _err_handler("Unknown exeption in async logger worker loop."); + } + } + if (_worker_teardown_cb) _worker_teardown_cb(); + + +} + +// process next message in the queue +// return true if this thread should still be active (while no terminate msg was received) +inline bool spdlog::details::async_log_helper::process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush) +{ + async_msg incoming_async_msg; + + if (_q.dequeue(incoming_async_msg)) + { + last_pop = details::os::now(); + switch (incoming_async_msg.msg_type) + { + case async_msg_type::flush: + _flush_requested = true; + break; + + case async_msg_type::terminate: + _flush_requested = true; + _terminate_requested = true; + break; + + default: + log_msg incoming_log_msg; + incoming_async_msg.fill_log_msg(incoming_log_msg); + _formatter->format(incoming_log_msg); + for (auto &s : _sinks) + { + if (s->should_log(incoming_log_msg.level)) + { + s->log(incoming_log_msg); + } + } + } + return true; + } + + // Handle empty queue.. + // This is the only place where the queue can terminate or flush to avoid losing messages already in the queue + else + { + auto now = details::os::now(); + handle_flush_interval(now, last_flush); + sleep_or_yield(now, last_pop); + return !_terminate_requested; + } +} + +// flush all sinks if _flush_interval_ms has expired +inline void spdlog::details::async_log_helper::handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush) +{ + auto should_flush = _flush_requested || (_flush_interval_ms != std::chrono::milliseconds::zero() && now - last_flush >= _flush_interval_ms); + if (should_flush) + { + for (auto &s : _sinks) + s->flush(); + now = last_flush = details::os::now(); + _flush_requested = false; + } +} + +inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; +} + + +// spin, yield or sleep. use the time passed since last message as a hint +inline void spdlog::details::async_log_helper::sleep_or_yield(const spdlog::log_clock::time_point& now, const spdlog::log_clock::time_point& last_op_time) +{ + using namespace std::this_thread; + using std::chrono::milliseconds; + using std::chrono::microseconds; + + auto time_since_op = now - last_op_time; + + // spin upto 50 micros + if (time_since_op <= microseconds(50)) + return; + + // yield upto 150 micros + if (time_since_op <= microseconds(100)) + return std::this_thread::yield(); + + // sleep for 20 ms upto 200 ms + if (time_since_op <= milliseconds(200)) + return sleep_for(milliseconds(20)); + + // sleep for 500 ms + return sleep_for(milliseconds(500)); +} + +// wait for the queue to be empty +inline void spdlog::details::async_log_helper::wait_empty_q() +{ + auto last_op = details::os::now(); + while (!_q.is_empty()) + { + sleep_or_yield(details::os::now(), last_op); + } +} + +inline void spdlog::details::async_log_helper::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; +} + + + diff --git a/fpga/include/spdlog/details/async_logger_impl.h b/fpga/include/spdlog/details/async_logger_impl.h new file mode 100644 index 000000000..837321176 --- /dev/null +++ b/fpga/include/spdlog/details/async_logger_impl.h @@ -0,0 +1,107 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Async Logger implementation +// Use an async_sink (queue per logger) to perform the logging in a worker thread + +#include "../details/async_log_helper.h" +#include "../async_logger.h" + +#include +#include +#include +#include + +template +inline spdlog::async_logger::async_logger(const std::string& logger_name, + const It& begin, + const It& end, + size_t queue_size, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb) : + logger(logger_name, begin, end), + _async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size, _err_handler, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb)) +{ +} + +inline spdlog::async_logger::async_logger(const std::string& logger_name, + sinks_init_list sinks_list, + size_t queue_size, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb) : + async_logger(logger_name, sinks_list.begin(), sinks_list.end(), queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} + +inline spdlog::async_logger::async_logger(const std::string& logger_name, + sink_ptr single_sink, + size_t queue_size, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb) : + async_logger(logger_name, +{ + single_sink +}, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} + + +inline void spdlog::async_logger::flush() +{ + _async_log_helper->flush(true); +} + +// Error handler +inline void spdlog::async_logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; + _async_log_helper->set_error_handler(err_handler); + +} +inline spdlog::log_err_handler spdlog::async_logger::error_handler() +{ + return _err_handler; +} + + +inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; + _async_log_helper->set_formatter(_formatter); +} + +inline void spdlog::async_logger::_set_pattern(const std::string& pattern, pattern_time_type pattern_time) +{ + _formatter = std::make_shared(pattern, pattern_time); + _async_log_helper->set_formatter(_formatter); +} + + +inline void spdlog::async_logger::_sink_it(details::log_msg& msg) +{ + try + { +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + _incr_msg_counter(msg); +#endif + _async_log_helper->log(msg); + if (_should_flush_on(msg)) + _async_log_helper->flush(false); // do async flush + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch(...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } + +} diff --git a/fpga/include/spdlog/details/file_helper.h b/fpga/include/spdlog/details/file_helper.h new file mode 100644 index 000000000..72fc09365 --- /dev/null +++ b/fpga/include/spdlog/details/file_helper.h @@ -0,0 +1,145 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Helper class for file sink +// When failing to open a file, retry several times(5) with small delay between the tries(10 ms) +// Throw spdlog_ex exception on errors + +#include "../details/os.h" +#include "../details/log_msg.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ + +class file_helper +{ + +public: + const int open_tries = 5; + const int open_interval = 10; + + explicit file_helper() : + _fd(nullptr) + {} + + file_helper(const file_helper&) = delete; + file_helper& operator=(const file_helper&) = delete; + + ~file_helper() + { + close(); + } + + + void open(const filename_t& fname, bool truncate = false) + { + + close(); + auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); + _filename = fname; + for (int tries = 0; tries < open_tries; ++tries) + { + if (!os::fopen_s(&_fd, fname, mode)) + return; + + std::this_thread::sleep_for(std::chrono::milliseconds(open_interval)); + } + + throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); + } + + void reopen(bool truncate) + { + if (_filename.empty()) + throw spdlog_ex("Failed re opening file - was not opened before"); + open(_filename, truncate); + + } + + void flush() + { + std::fflush(_fd); + } + + void close() + { + if (_fd) + { + std::fclose(_fd); + _fd = nullptr; + } + } + + void write(const log_msg& msg) + { + size_t msg_size = msg.formatted.size(); + auto data = msg.formatted.data(); + if (std::fwrite(data, 1, msg_size, _fd) != msg_size) + throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + } + + size_t size() const + { + if (!_fd) + throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); + return os::filesize(_fd); + } + + const filename_t& filename() const + { + return _filename; + } + + static bool file_exists(const filename_t& fname) + { + return os::file_exists(fname); + } + + // + // return file path and its extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // "mylog." => ("mylog.", "") + // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") + // + // the starting dot in filenames is ignored (hidden files): + // + // ".mylog" => (".mylog". "") + // "my_folder/.mylog" => ("my_folder/.mylog", "") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + static std::tuple split_by_extenstion(const spdlog::filename_t& fname) + { + auto ext_index = fname.rfind('.'); + + // no valid extension found - return whole path and empty string as extension + if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) + return std::make_tuple(fname, spdlog::filename_t()); + + // treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" + auto folder_index = fname.rfind(details::os::folder_sep); + if (folder_index != fname.npos && folder_index >= ext_index - 1) + return std::make_tuple(fname, spdlog::filename_t()); + + // finally - return a valid base and extension tuple + return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); + } +private: + FILE* _fd; + filename_t _filename; +}; +} +} diff --git a/fpga/include/spdlog/details/log_msg.h b/fpga/include/spdlog/details/log_msg.h new file mode 100644 index 000000000..a9fe92011 --- /dev/null +++ b/fpga/include/spdlog/details/log_msg.h @@ -0,0 +1,50 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../common.h" +#include "../details/os.h" + + +#include +#include + +namespace spdlog +{ +namespace details +{ +struct log_msg +{ + log_msg() = default; + log_msg(const std::string *loggers_name, level::level_enum lvl) : + logger_name(loggers_name), + level(lvl), + msg_id(0) + { +#ifndef SPDLOG_NO_DATETIME + time = os::now(); +#endif + +#ifndef SPDLOG_NO_THREAD_ID + thread_id = os::thread_id(); +#endif + } + + log_msg(const log_msg& other) = delete; + log_msg& operator=(log_msg&& other) = delete; + log_msg(log_msg&& other) = delete; + + + const std::string *logger_name; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::MemoryWriter raw; + fmt::MemoryWriter formatted; + size_t msg_id; +}; +} +} diff --git a/fpga/include/spdlog/details/logger_impl.h b/fpga/include/spdlog/details/logger_impl.h new file mode 100644 index 000000000..880447471 --- /dev/null +++ b/fpga/include/spdlog/details/logger_impl.h @@ -0,0 +1,373 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../logger.h" +#include "../sinks/stdout_sinks.h" + +#include +#include + +// create logger with given name, sinks and the default pattern formatter +// all other ctors will call this one +template +inline spdlog::logger::logger(const std::string& logger_name, const It& begin, const It& end): + _name(logger_name), + _sinks(begin, end), + _formatter(std::make_shared("%+")), + _level(level::info), + _flush_level(level::off), + _last_err_time(0), + _msg_counter(1) // message counter will start from 1. 0-message id will be reserved for controll messages +{ + _err_handler = [this](const std::string &msg) + { + this->_default_err_handler(msg); + }; +} + +// ctor with sinks as init list +inline spdlog::logger::logger(const std::string& logger_name, sinks_init_list sinks_list): + logger(logger_name, sinks_list.begin(), sinks_list.end()) +{} + + +// ctor with single sink +inline spdlog::logger::logger(const std::string& logger_name, spdlog::sink_ptr single_sink): + logger(logger_name, +{ + single_sink +}) +{} + + +inline spdlog::logger::~logger() = default; + + +inline void spdlog::logger::set_formatter(spdlog::formatter_ptr msg_formatter) +{ + _set_formatter(msg_formatter); +} + +inline void spdlog::logger::set_pattern(const std::string& pattern, pattern_time_type pattern_time) +{ + _set_pattern(pattern, pattern_time); +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const char* fmt, const Args&... args) +{ + if (!should_log(lvl)) return; + + try + { + details::log_msg log_msg(&_name, lvl); + +#if defined(SPDLOG_FMT_PRINTF) + fmt::printf(log_msg.raw, fmt, args...); +#else + log_msg.raw.write(fmt, args...); +#endif + _sink_it(log_msg); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch(...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const char* msg) +{ + if (!should_log(lvl)) return; + try + { + details::log_msg log_msg(&_name, lvl); + log_msg.raw << msg; + _sink_it(log_msg); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch (...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const T& msg) +{ + if (!should_log(lvl)) return; + try + { + details::log_msg log_msg(&_name, lvl); + log_msg.raw << msg; + _sink_it(log_msg); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch (...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } +} + + +template +inline void spdlog::logger::trace(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::trace, fmt, arg1, args...); +} + +template +inline void spdlog::logger::debug(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::debug, fmt, arg1, args...); +} + +template +inline void spdlog::logger::info(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::info, fmt, arg1, args...); +} + +template +inline void spdlog::logger::warn(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::warn, fmt, arg1, args...); +} + +template +inline void spdlog::logger::error(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::err, fmt, arg1, args...); +} + +template +inline void spdlog::logger::critical(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::critical, fmt, arg1, args...); +} + + +template +inline void spdlog::logger::trace(const T& msg) +{ + log(level::trace, msg); +} + +template +inline void spdlog::logger::debug(const T& msg) +{ + log(level::debug, msg); +} + + +template +inline void spdlog::logger::info(const T& msg) +{ + log(level::info, msg); +} + + +template +inline void spdlog::logger::warn(const T& msg) +{ + log(level::warn, msg); +} + +template +inline void spdlog::logger::error(const T& msg) +{ + log(level::err, msg); +} + +template +inline void spdlog::logger::critical(const T& msg) +{ + log(level::critical, msg); +} + + + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +#include +#include + +template +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t* msg) +{ + std::wstring_convert > conv; + + log(lvl, conv.to_bytes(msg)); +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t* fmt, const Args&... args) +{ + fmt::WMemoryWriter wWriter; + + wWriter.write(fmt, args...); + log(lvl, wWriter.c_str()); +} + +template +inline void spdlog::logger::trace(const wchar_t* fmt, const Args&... args) +{ + log(level::trace, fmt, args...); +} + +template +inline void spdlog::logger::debug(const wchar_t* fmt, const Args&... args) +{ + log(level::debug, fmt, args...); +} + +template +inline void spdlog::logger::info(const wchar_t* fmt, const Args&... args) +{ + log(level::info, fmt, args...); +} + + +template +inline void spdlog::logger::warn(const wchar_t* fmt, const Args&... args) +{ + log(level::warn, fmt, args...); +} + +template +inline void spdlog::logger::error(const wchar_t* fmt, const Args&... args) +{ + log(level::err, fmt, args...); +} + +template +inline void spdlog::logger::critical(const wchar_t* fmt, const Args&... args) +{ + log(level::critical, fmt, args...); +} + +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + + +// +// name and level +// +inline const std::string& spdlog::logger::name() const +{ + return _name; +} + +inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) +{ + _level.store(log_level); +} + +inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; +} + +inline spdlog::log_err_handler spdlog::logger::error_handler() +{ + return _err_handler; +} + + +inline void spdlog::logger::flush_on(level::level_enum log_level) +{ + _flush_level.store(log_level); +} + +inline spdlog::level::level_enum spdlog::logger::level() const +{ + return static_cast(_level.load(std::memory_order_relaxed)); +} + +inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const +{ + return msg_level >= _level.load(std::memory_order_relaxed); +} + +// +// protected virtual called at end of each user log call (if enabled) by the line_logger +// +inline void spdlog::logger::_sink_it(details::log_msg& msg) +{ +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + _incr_msg_counter(msg); +#endif + _formatter->format(msg); + for (auto &sink : _sinks) + { + if( sink->should_log( msg.level)) + { + sink->log(msg); + } + } + + if(_should_flush_on(msg)) + flush(); +} + +inline void spdlog::logger::_set_pattern(const std::string& pattern, pattern_time_type pattern_time) +{ + _formatter = std::make_shared(pattern, pattern_time); +} +inline void spdlog::logger::_set_formatter(formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; +} + +inline void spdlog::logger::flush() +{ + for (auto& sink : _sinks) + sink->flush(); +} + +inline void spdlog::logger::_default_err_handler(const std::string &msg) +{ + auto now = time(nullptr); + if (now - _last_err_time < 60) + return; + auto tm_time = details::os::localtime(now); + char date_buf[100]; + std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); + details::log_msg err_msg; + err_msg.formatted.write("[*** LOG ERROR ***] [{}] [{}] [{}]{}", name(), msg, date_buf, details::os::eol); + sinks::stderr_sink_mt::instance()->log(err_msg); + _last_err_time = now; +} + +inline bool spdlog::logger::_should_flush_on(const details::log_msg &msg) +{ + const auto flush_level = _flush_level.load(std::memory_order_relaxed); + return (msg.level >= flush_level) && (msg.level != level::off); +} + +inline void spdlog::logger::_incr_msg_counter(details::log_msg &msg) +{ + msg.msg_id = _msg_counter.fetch_add(1, std::memory_order_relaxed); +} + +inline const std::vector& spdlog::logger::sinks() const +{ + return _sinks; +} + diff --git a/fpga/include/spdlog/details/mpmc_bounded_q.h b/fpga/include/spdlog/details/mpmc_bounded_q.h new file mode 100644 index 000000000..102df8555 --- /dev/null +++ b/fpga/include/spdlog/details/mpmc_bounded_q.h @@ -0,0 +1,176 @@ +/* +A modified version of Bounded MPMC queue by Dmitry Vyukov. + +Original code from: +http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue + +licensed by Dmitry Vyukov under the terms below: + +Simplified BSD license + +Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list +of conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and +should not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov. +*/ + +/* +The code in its current form adds the license below: + +Copyright(c) 2015 Gabi Melman. +Distributed under the MIT License (http://opensource.org/licenses/MIT) + +*/ + +#pragma once + +#include "../common.h" + +#include +#include + +namespace spdlog +{ +namespace details +{ + +template +class mpmc_bounded_queue +{ +public: + + using item_type = T; + mpmc_bounded_queue(size_t buffer_size) + :max_size_(buffer_size), + buffer_(new cell_t[buffer_size]), + buffer_mask_(buffer_size - 1) + { + //queue size must be power of two + if (!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0))) + throw spdlog_ex("async logger queue size must be power of two"); + + for (size_t i = 0; i != buffer_size; i += 1) + buffer_[i].sequence_.store(i, std::memory_order_relaxed); + enqueue_pos_.store(0, std::memory_order_relaxed); + dequeue_pos_.store(0, std::memory_order_relaxed); + } + + ~mpmc_bounded_queue() + { + delete[] buffer_; + } + + + bool enqueue(T&& data) + { + cell_t* cell; + size_t pos = enqueue_pos_.load(std::memory_order_relaxed); + for (;;) + { + cell = &buffer_[pos & buffer_mask_]; + size_t seq = cell->sequence_.load(std::memory_order_acquire); + intptr_t dif = static_cast(seq) - static_cast(pos); + if (dif == 0) + { + if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) + break; + } + else if (dif < 0) + { + return false; + } + else + { + pos = enqueue_pos_.load(std::memory_order_relaxed); + } + } + cell->data_ = std::move(data); + cell->sequence_.store(pos + 1, std::memory_order_release); + return true; + } + + bool dequeue(T& data) + { + cell_t* cell; + size_t pos = dequeue_pos_.load(std::memory_order_relaxed); + for (;;) + { + cell = &buffer_[pos & buffer_mask_]; + size_t seq = + cell->sequence_.load(std::memory_order_acquire); + intptr_t dif = static_cast(seq) - static_cast(pos + 1); + if (dif == 0) + { + if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) + break; + } + else if (dif < 0) + return false; + else + pos = dequeue_pos_.load(std::memory_order_relaxed); + } + data = std::move(cell->data_); + cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release); + return true; + } + + bool is_empty() + { + size_t front, front1, back; + // try to take a consistent snapshot of front/tail. + do + { + front = enqueue_pos_.load(std::memory_order_acquire); + back = dequeue_pos_.load(std::memory_order_acquire); + front1 = enqueue_pos_.load(std::memory_order_relaxed); + } + while (front != front1); + return back == front; + } + +private: + struct cell_t + { + std::atomic sequence_; + T data_; + }; + + size_t const max_size_; + + static size_t const cacheline_size = 64; + typedef char cacheline_pad_t[cacheline_size]; + + cacheline_pad_t pad0_; + cell_t* const buffer_; + size_t const buffer_mask_; + cacheline_pad_t pad1_; + std::atomic enqueue_pos_; + cacheline_pad_t pad2_; + std::atomic dequeue_pos_; + cacheline_pad_t pad3_; + + mpmc_bounded_queue(mpmc_bounded_queue const&) = delete; + void operator= (mpmc_bounded_queue const&) = delete; +}; + +} // ns details +} // ns spdlog diff --git a/fpga/include/spdlog/details/null_mutex.h b/fpga/include/spdlog/details/null_mutex.h new file mode 100644 index 000000000..67b0aeee0 --- /dev/null +++ b/fpga/include/spdlog/details/null_mutex.h @@ -0,0 +1,45 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +// null, no cost dummy "mutex" and dummy "atomic" int + +namespace spdlog +{ +namespace details +{ +struct null_mutex +{ + void lock() {} + void unlock() {} + bool try_lock() + { + return true; + } +}; + +struct null_atomic_int +{ + int value; + null_atomic_int() = default; + + null_atomic_int(int val):value(val) + {} + + int load(std::memory_order) const + { + return value; + } + + void store(int val) + { + value = val; + } +}; + +} +} diff --git a/fpga/include/spdlog/details/os.h b/fpga/include/spdlog/details/os.h new file mode 100644 index 000000000..aaf949ee9 --- /dev/null +++ b/fpga/include/spdlog/details/os.h @@ -0,0 +1,479 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +#pragma once + +#include "../common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX //prevent windows redefining min/max +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include // _get_pid support +#include // _get_osfhandle and _isatty support + +#ifdef __MINGW32__ +#include +#endif + +#else // unix + +#include +#include + +#ifdef __linux__ +#include //Use gettid() syscall under linux to get thread id + +#elif __FreeBSD__ +#include //Use thr_self() syscall under FreeBSD to get thread id +#endif + +#endif //unix + +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + + +namespace spdlog +{ +namespace details +{ +namespace os +{ + +inline spdlog::log_clock::time_point now() +{ + +#if defined __linux__ && defined SPDLOG_CLOCK_COARSE + timespec ts; + ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); + return std::chrono::time_point( + std::chrono::duration_cast( + std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); + + +#else + return log_clock::now(); +#endif + +} +inline std::tm localtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + localtime_s(&tm, &time_tt); +#else + std::tm tm; + localtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm localtime() +{ + std::time_t now_t = time(nullptr); + return localtime(now_t); +} + + +inline std::tm gmtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + gmtime_s(&tm, &time_tt); +#else + std::tm tm; + gmtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm gmtime() +{ + std::time_t now_t = time(nullptr); + return gmtime(now_t); +} +inline bool operator==(const std::tm& tm1, const std::tm& tm2) +{ + return (tm1.tm_sec == tm2.tm_sec && + tm1.tm_min == tm2.tm_min && + tm1.tm_hour == tm2.tm_hour && + tm1.tm_mday == tm2.tm_mday && + tm1.tm_mon == tm2.tm_mon && + tm1.tm_year == tm2.tm_year && + tm1.tm_isdst == tm2.tm_isdst); +} + +inline bool operator!=(const std::tm& tm1, const std::tm& tm2) +{ + return !(tm1 == tm2); +} + +// eol definition +#if !defined (SPDLOG_EOL) +#ifdef _WIN32 +#define SPDLOG_EOL "\r\n" +#else +#define SPDLOG_EOL "\n" +#endif +#endif + +SPDLOG_CONSTEXPR static const char* eol = SPDLOG_EOL; +SPDLOG_CONSTEXPR static int eol_size = sizeof(SPDLOG_EOL) - 1; + + + +// folder separator +#ifdef _WIN32 +SPDLOG_CONSTEXPR static const char folder_sep = '\\'; +#else +SPDLOG_CONSTEXPR static const char folder_sep = '/'; +#endif + + +inline void prevent_child_fd(FILE *f) +{ +#ifdef _WIN32 + auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) + throw spdlog_ex("SetHandleInformation failed", errno); +#else + auto fd = fileno(f); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); +#endif +} + + +//fopen_s on non windows for writing +inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + *fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); +#else + *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); +#endif +#else //unix + *fp = fopen((filename.c_str()), mode.c_str()); +#endif + +#ifdef SPDLOG_PREVENT_CHILD_FD + if (*fp != nullptr) + prevent_child_fd(*fp); +#endif + return *fp == nullptr; +} + + +inline int remove(const filename_t &filename) +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wremove(filename.c_str()); +#else + return std::remove(filename.c_str()); +#endif +} + +inline int rename(const filename_t& filename1, const filename_t& filename2) +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wrename(filename1.c_str(), filename2.c_str()); +#else + return std::rename(filename1.c_str(), filename2.c_str()); +#endif +} + + +//Return if file exists +inline bool file_exists(const filename_t& filename) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + auto attribs = GetFileAttributesW(filename.c_str()); +#else + auto attribs = GetFileAttributesA(filename.c_str()); +#endif + return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); +#else //common linux/unix all have the stat system call + struct stat buffer; + return (stat(filename.c_str(), &buffer) == 0); +#endif +} + + + + +//Return file size according to open FILE* object +inline size_t filesize(FILE *f) +{ + if (f == nullptr) + throw spdlog_ex("Failed getting file size. fd is null"); +#if defined ( _WIN32) && !defined(__CYGWIN__) + int fd = _fileno(f); +#if _WIN64 //64 bits + struct _stat64 st; + if (_fstat64(fd, &st) == 0) + return st.st_size; + +#else //windows 32 bits + long ret = _filelength(fd); + if (ret >= 0) + return static_cast(ret); +#endif + +#else // unix + int fd = fileno(f); + //64 bits(but not in osx or cygwin, where fstat64 is deprecated) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) + struct stat64 st; + if (fstat64(fd, &st) == 0) + return static_cast(st.st_size); +#else // unix 32 bits or cygwin + struct stat st; + if (fstat(fd, &st) == 0) + return static_cast(st.st_size); +#endif +#endif + throw spdlog_ex("Failed getting file size from fd", errno); +} + + + + +//Return utc offset in minutes or throw spdlog_ex on failure +inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) +{ + +#ifdef _WIN32 +#if _WIN32_WINNT < _WIN32_WINNT_WS08 + TIME_ZONE_INFORMATION tzinfo; + auto rv = GetTimeZoneInformation(&tzinfo); +#else + DYNAMIC_TIME_ZONE_INFORMATION tzinfo; + auto rv = GetDynamicTimeZoneInformation(&tzinfo); +#endif + if (rv == TIME_ZONE_ID_INVALID) + throw spdlog::spdlog_ex("Failed getting timezone info. ", errno); + + int offset = -tzinfo.Bias; + if (tm.tm_isdst) + offset -= tzinfo.DaylightBias; + else + offset -= tzinfo.StandardBias; + return offset; +#else + +#if defined(sun) || defined(__sun) + // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris + struct helper + { + static long int calculate_gmt_offset(const std::tm & localtm = details::os::localtime(), const std::tm & gmtm = details::os::gmtime()) + { + int local_year = localtm.tm_year + (1900 - 1); + int gmt_year = gmtm.tm_year + (1900 - 1); + + long int days = ( + // difference in day of year + localtm.tm_yday - gmtm.tm_yday + + // + intervening leap days + + ((local_year >> 2) - (gmt_year >> 2)) + - (local_year / 100 - gmt_year / 100) + + ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) + + // + difference in years * 365 */ + + (long int)(local_year - gmt_year) * 365 + ); + + long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); + long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); + long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); + + return secs; + } + }; + + long int offset_seconds = helper::calculate_gmt_offset(tm); +#else + long int offset_seconds = tm.tm_gmtoff; +#endif + + return static_cast(offset_seconds / 60); +#endif +} + +//Return current thread id as size_t +//It exists because the std::this_thread::get_id() is much slower(especially under VS 2013) +inline size_t _thread_id() +{ +#ifdef _WIN32 + return static_cast(::GetCurrentThreadId()); +#elif __linux__ +# if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) +# define SYS_gettid __NR_gettid +# endif + return static_cast(syscall(SYS_gettid)); +#elif __FreeBSD__ + long tid; + thr_self(&tid); + return static_cast(tid); +#elif __APPLE__ + uint64_t tid; + pthread_threadid_np(nullptr, &tid); + return static_cast(tid); +#else //Default to standard C++11 (other Unix) + return static_cast(std::hash()(std::this_thread::get_id())); +#endif +} + +//Return current thread id as size_t (from thread local storage) +inline size_t thread_id() +{ +#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || (defined(__clang__) && !__has_feature(cxx_thread_local)) + return _thread_id(); +#else // cache thread id in tls + static thread_local const size_t tid = _thread_id(); + return tid; +#endif + + +} + + +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#define SPDLOG_FILENAME_T(s) L ## s +inline std::string filename_to_str(const filename_t& filename) +{ + std::wstring_convert, wchar_t> c; + return c.to_bytes(filename); +} +#else +#define SPDLOG_FILENAME_T(s) s +inline std::string filename_to_str(const filename_t& filename) +{ + return filename; +} +#endif + +inline std::string errno_to_string(char[256], char* res) +{ + return std::string(res); +} + +inline std::string errno_to_string(char buf[256], int res) +{ + if (res == 0) + { + return std::string(buf); + } + else + { + return "Unknown error"; + } +} + +// Return errno string (thread safe) +inline std::string errno_str(int err_num) +{ + char buf[256]; + SPDLOG_CONSTEXPR auto buf_size = sizeof(buf); + +#ifdef _WIN32 + if (strerror_s(buf, buf_size, err_num) == 0) + return std::string(buf); + else + return "Unknown error"; + +#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(ANDROID) || defined(__SUNPRO_CC) || \ + ((_POSIX_C_SOURCE >= 200112L) && ! defined(_GNU_SOURCE)) // posix version + + if (strerror_r(err_num, buf, buf_size) == 0) + return std::string(buf); + else + return "Unknown error"; + +#else // gnu version (might not use the given buf, so its retval pointer must be used) + auto err = strerror_r(err_num, buf, buf_size); // let compiler choose type + return errno_to_string(buf, err); // use overloading to select correct stringify function +#endif +} + +inline int pid() +{ + +#ifdef _WIN32 + return ::_getpid(); +#else + return static_cast(::getpid()); +#endif + +} + + +// Determine if the terminal supports colors +// Source: https://github.com/agauniyal/rang/ +inline bool is_color_terminal() +{ +#ifdef _WIN32 + return true; +#else + static constexpr const char* Terms[] = + { + "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", + "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm" + }; + + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) + { + return false; + } + + static const bool result = std::any_of( + std::begin(Terms), std::end(Terms), [&](const char* term) + { + return std::strstr(env_p, term) != nullptr; + }); + return result; +#endif +} + + +// Detrmine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +inline bool in_terminal(FILE* file) +{ + +#ifdef _WIN32 + return _isatty(_fileno(file)) ? true : false; +#else + return isatty(fileno(file)) ? true : false; +#endif +} +} //os +} //details +} //spdlog diff --git a/fpga/include/spdlog/details/pattern_formatter_impl.h b/fpga/include/spdlog/details/pattern_formatter_impl.h new file mode 100644 index 000000000..a73f5deab --- /dev/null +++ b/fpga/include/spdlog/details/pattern_formatter_impl.h @@ -0,0 +1,686 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../formatter.h" +#include "../details/log_msg.h" +#include "../details/os.h" +#include "../fmt/fmt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ +class flag_formatter +{ +public: + virtual ~flag_formatter() + {} + virtual void format(details::log_msg& msg, const std::tm& tm_time) = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appenders +/////////////////////////////////////////////////////////////////////// +namespace +{ +class name_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << *msg.logger_name; + } +}; +} + +// log level appender +class level_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << level::to_str(msg.level); + } +}; + +// short log level appender +class short_level_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << level::to_short_str(msg.level); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char* ampm(const tm& t) +{ + return t.tm_hour >= 12 ? "PM" : "AM"; +} + +static int to12h(const tm& t) +{ + return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; +} + +//Abbreviated weekday name +static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +class a_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << days[tm_time.tm_wday]; + } +}; + +//Full weekday name +static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; +class A_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << full_days[tm_time.tm_wday]; + } +}; + +//Abbreviated month +static const std::string months[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec" }; +class b_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << months[tm_time.tm_mon]; + } +}; + +//Full month name +static const std::string full_months[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; +class B_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << full_months[tm_time.tm_mon]; + } +}; + + +//write 2 ints separated by sep with padding of 2 +static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, char sep) +{ + w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0'); + return w; +} + +//write 3 ints separated by sep with padding of 2 +static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, int v3, char sep) +{ + w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0') << sep << fmt::pad(v3, 2, '0'); + return w; +} + + +//Date and time representation (Thu Aug 23 15:35:46 2014) +class c_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << days[tm_time.tm_wday] << ' ' << months[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' '; + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900; + } +}; + + +// year - 2 digit +class C_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_year % 100, 2, '0'); + } +}; + + + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +class D_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, tm_time.tm_mon + 1, tm_time.tm_mday, tm_time.tm_year % 100, '/'); + } +}; + + +// year - 4 digit +class Y_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << tm_time.tm_year + 1900; + } +}; + +// month 1-12 +class m_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_mon + 1, 2, '0'); + } +}; + +// day of month 1-31 +class d_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_mday, 2, '0'); + } +}; + +// hours in 24 format 0-23 +class H_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_hour, 2, '0'); + } +}; + +// hours in 12 format 1-12 +class I_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(to12h(tm_time), 2, '0'); + } +}; + +// minutes 0-59 +class M_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_min, 2, '0'); + } +}; + +// seconds 0-59 +class S_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_sec, 2, '0'); + } +}; + +// milliseconds +class e_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto millis = std::chrono::duration_cast(duration).count() % 1000; + msg.formatted << fmt::pad(static_cast(millis), 3, '0'); + } +}; + +// microseconds +class f_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto micros = std::chrono::duration_cast(duration).count() % 1000000; + msg.formatted << fmt::pad(static_cast(micros), 6, '0'); + } +}; + +// nanoseconds +class F_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto ns = std::chrono::duration_cast(duration).count() % 1000000000; + msg.formatted << fmt::pad(static_cast(ns), 9, '0'); + } +}; + +class E_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration).count(); + msg.formatted << seconds; + } +}; + +// AM/PM +class p_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << ampm(tm_time); + } +}; + + +// 12 hour clock 02:55:02 pm +class r_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, to12h(tm_time), tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << ampm(tm_time); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +class R_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, ':'); + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +class T_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':'); + } +}; + +// ISO 8601 offset from UTC in timezone (+-HH:MM) +class z_formatter SPDLOG_FINAL:public flag_formatter +{ +public: + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); + + z_formatter():_last_update(std::chrono::seconds(0)), _offset_minutes(0) + {} + z_formatter(const z_formatter&) = delete; + z_formatter& operator=(const z_formatter&) = delete; + + void format(details::log_msg& msg, const std::tm& tm_time) override + { +#ifdef _WIN32 + int total_minutes = get_cached_offset(msg, tm_time); +#else + // No need to chache under gcc, + // it is very fast (already stored in tm.tm_gmtoff) + int total_minutes = os::utc_minutes_offset(tm_time); +#endif + bool is_negative = total_minutes < 0; + char sign; + if (is_negative) + { + total_minutes = -total_minutes; + sign = '-'; + } + else + { + sign = '+'; + } + + int h = total_minutes / 60; + int m = total_minutes % 60; + msg.formatted << sign; + pad_n_join(msg.formatted, h, m, ':'); + } +private: + log_clock::time_point _last_update; + int _offset_minutes; + std::mutex _mutex; + + int get_cached_offset(const log_msg& msg, const std::tm& tm_time) + { + using namespace std::chrono; + std::lock_guard l(_mutex); + if (msg.time - _last_update >= cache_refresh) + { + _offset_minutes = os::utc_minutes_offset(tm_time); + _last_update = msg.time; + } + return _offset_minutes; + } +}; + + + +// Thread id +class t_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << msg.thread_id; + } +}; + +// Current pid +class pid_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << details::os::pid(); + } +}; + +// message counter formatter +class i_formatter SPDLOG_FINAL :public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << fmt::pad(msg.msg_id, 6, '0'); + } +}; + +class v_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); + } +}; + +class ch_formatter SPDLOG_FINAL:public flag_formatter +{ +public: + explicit ch_formatter(char ch): _ch(ch) + {} + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << _ch; + } +private: + char _ch; +}; + + +//aggregate user chars to display as is +class aggregate_formatter SPDLOG_FINAL:public flag_formatter +{ +public: + aggregate_formatter() + {} + void add_ch(char ch) + { + _str += ch; + } + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << _str; + } +private: + std::string _str; +}; + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v +class full_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { +#ifndef SPDLOG_NO_DATETIME + auto duration = msg.time.time_since_epoch(); + auto millis = std::chrono::duration_cast(duration).count() % 1000; + + /* Slower version(while still very fast - about 3.2 million lines/sec under 10 threads), + msg.formatted.write("[{:d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}] [{}] [{}] {} ", + tm_time.tm_year + 1900, + tm_time.tm_mon + 1, + tm_time.tm_mday, + tm_time.tm_hour, + tm_time.tm_min, + tm_time.tm_sec, + static_cast(millis), + msg.logger_name, + level::to_str(msg.level), + msg.raw.str());*/ + + + // Faster (albeit uglier) way to format the line (5.6 million lines/sec under 10 threads) + msg.formatted << '[' << static_cast(tm_time.tm_year + 1900) << '-' + << fmt::pad(static_cast(tm_time.tm_mon + 1), 2, '0') << '-' + << fmt::pad(static_cast(tm_time.tm_mday), 2, '0') << ' ' + << fmt::pad(static_cast(tm_time.tm_hour), 2, '0') << ':' + << fmt::pad(static_cast(tm_time.tm_min), 2, '0') << ':' + << fmt::pad(static_cast(tm_time.tm_sec), 2, '0') << '.' + << fmt::pad(static_cast(millis), 3, '0') << "] "; + + //no datetime needed +#else + (void)tm_time; +#endif + +#ifndef SPDLOG_NO_NAME + msg.formatted << '[' << *msg.logger_name << "] "; +#endif + + msg.formatted << '[' << level::to_str(msg.level) << "] "; + msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); + } +}; + + + +} +} +/////////////////////////////////////////////////////////////////////////////// +// pattern_formatter inline impl +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::pattern_formatter::pattern_formatter(const std::string& pattern, pattern_time_type pattern_time) + : _pattern_time(pattern_time) +{ + compile_pattern(pattern); +} + +inline void spdlog::pattern_formatter::compile_pattern(const std::string& pattern) +{ + auto end = pattern.end(); + std::unique_ptr user_chars; + for (auto it = pattern.begin(); it != end; ++it) + { + if (*it == '%') + { + if (user_chars) //append user chars found so far + _formatters.push_back(std::move(user_chars)); + + if (++it != end) + handle_flag(*it); + else + break; + } + else // chars not following the % sign should be displayed as is + { + if (!user_chars) + user_chars = std::unique_ptr(new details::aggregate_formatter()); + user_chars->add_ch(*it); + } + } + if (user_chars) //append raw chars found so far + { + _formatters.push_back(std::move(user_chars)); + } + +} +inline void spdlog::pattern_formatter::handle_flag(char flag) +{ + switch (flag) + { + // logger name + case 'n': + _formatters.push_back(std::unique_ptr(new details::name_formatter())); + break; + + case 'l': + _formatters.push_back(std::unique_ptr(new details::level_formatter())); + break; + + case 'L': + _formatters.push_back(std::unique_ptr(new details::short_level_formatter())); + break; + + case('t'): + _formatters.push_back(std::unique_ptr(new details::t_formatter())); + break; + + case('v'): + _formatters.push_back(std::unique_ptr(new details::v_formatter())); + break; + + case('a'): + _formatters.push_back(std::unique_ptr(new details::a_formatter())); + break; + + case('A'): + _formatters.push_back(std::unique_ptr(new details::A_formatter())); + break; + + case('b'): + case('h'): + _formatters.push_back(std::unique_ptr(new details::b_formatter())); + break; + + case('B'): + _formatters.push_back(std::unique_ptr(new details::B_formatter())); + break; + case('c'): + _formatters.push_back(std::unique_ptr(new details::c_formatter())); + break; + + case('C'): + _formatters.push_back(std::unique_ptr(new details::C_formatter())); + break; + + case('Y'): + _formatters.push_back(std::unique_ptr(new details::Y_formatter())); + break; + + case('D'): + case('x'): + + _formatters.push_back(std::unique_ptr(new details::D_formatter())); + break; + + case('m'): + _formatters.push_back(std::unique_ptr(new details::m_formatter())); + break; + + case('d'): + _formatters.push_back(std::unique_ptr(new details::d_formatter())); + break; + + case('H'): + _formatters.push_back(std::unique_ptr(new details::H_formatter())); + break; + + case('I'): + _formatters.push_back(std::unique_ptr(new details::I_formatter())); + break; + + case('M'): + _formatters.push_back(std::unique_ptr(new details::M_formatter())); + break; + + case('S'): + _formatters.push_back(std::unique_ptr(new details::S_formatter())); + break; + + case('e'): + _formatters.push_back(std::unique_ptr(new details::e_formatter())); + break; + + case('f'): + _formatters.push_back(std::unique_ptr(new details::f_formatter())); + break; + case('F'): + _formatters.push_back(std::unique_ptr(new details::F_formatter())); + break; + + case('E'): + _formatters.push_back(std::unique_ptr(new details::E_formatter())); + break; + + case('p'): + _formatters.push_back(std::unique_ptr(new details::p_formatter())); + break; + + case('r'): + _formatters.push_back(std::unique_ptr(new details::r_formatter())); + break; + + case('R'): + _formatters.push_back(std::unique_ptr(new details::R_formatter())); + break; + + case('T'): + case('X'): + _formatters.push_back(std::unique_ptr(new details::T_formatter())); + break; + + case('z'): + _formatters.push_back(std::unique_ptr(new details::z_formatter())); + break; + + case ('+'): + _formatters.push_back(std::unique_ptr(new details::full_formatter())); + break; + + case ('P'): + _formatters.push_back(std::unique_ptr(new details::pid_formatter())); + break; + + + case ('i'): + _formatters.push_back(std::unique_ptr(new details::i_formatter())); + break; + + default: //Unknown flag appears as is + _formatters.push_back(std::unique_ptr(new details::ch_formatter('%'))); + _formatters.push_back(std::unique_ptr(new details::ch_formatter(flag))); + break; + } +} + +inline std::tm spdlog::pattern_formatter::get_time(details::log_msg& msg) +{ + if (_pattern_time == pattern_time_type::local) + return details::os::localtime(log_clock::to_time_t(msg.time)); + else + return details::os::gmtime(log_clock::to_time_t(msg.time)); +} + +inline void spdlog::pattern_formatter::format(details::log_msg& msg) +{ + +#ifndef SPDLOG_NO_DATETIME + auto tm_time = get_time(msg); +#else + std::tm tm_time; +#endif + for (auto &f : _formatters) + { + f->format(msg, tm_time); + } + //write eol + msg.formatted.write(details::os::eol, details::os::eol_size); +} diff --git a/fpga/include/spdlog/details/registry.h b/fpga/include/spdlog/details/registry.h new file mode 100644 index 000000000..b68b9f5a3 --- /dev/null +++ b/fpga/include/spdlog/details/registry.h @@ -0,0 +1,225 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Loggers registy of unique name->logger pointer +// An attempt to create a logger with an already existing name will be ignored +// If user requests a non existing logger, nullptr will be returned +// This class is thread safe + +#include "../details/null_mutex.h" +#include "../logger.h" +#include "../async_logger.h" +#include "../common.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ +template class registry_t +{ +public: + + void register_logger(std::shared_ptr logger) + { + std::lock_guard lock(_mutex); + auto logger_name = logger->name(); + throw_if_exists(logger_name); + _loggers[logger_name] = logger; + } + + + std::shared_ptr get(const std::string& logger_name) + { + std::lock_guard lock(_mutex); + auto found = _loggers.find(logger_name); + return found == _loggers.end() ? nullptr : found->second; + } + + template + std::shared_ptr create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) + { + std::lock_guard lock(_mutex); + throw_if_exists(logger_name); + std::shared_ptr new_logger; + if (_async_mode) + new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms, _worker_teardown_cb); + else + new_logger = std::make_shared(logger_name, sinks_begin, sinks_end); + + if (_formatter) + new_logger->set_formatter(_formatter); + + if (_err_handler) + new_logger->set_error_handler(_err_handler); + + new_logger->set_level(_level); + new_logger->flush_on(_flush_level); + + + //Add to registry + _loggers[logger_name] = new_logger; + return new_logger; + } + + template + std::shared_ptr create_async(const std::string& logger_name, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb, const It& sinks_begin, const It& sinks_end) + { + std::lock_guard lock(_mutex); + throw_if_exists(logger_name); + auto new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb); + + if (_formatter) + new_logger->set_formatter(_formatter); + + if (_err_handler) + new_logger->set_error_handler(_err_handler); + + new_logger->set_level(_level); + new_logger->flush_on(_flush_level); + + //Add to registry + _loggers[logger_name] = new_logger; + return new_logger; + } + + void apply_all(std::function)> fun) + { + std::lock_guard lock(_mutex); + for (auto &l : _loggers) + fun(l.second); + } + + void drop(const std::string& logger_name) + { + std::lock_guard lock(_mutex); + _loggers.erase(logger_name); + } + + void drop_all() + { + std::lock_guard lock(_mutex); + _loggers.clear(); + } + std::shared_ptr create(const std::string& logger_name, sinks_init_list sinks) + { + return create(logger_name, sinks.begin(), sinks.end()); + } + + std::shared_ptr create(const std::string& logger_name, sink_ptr sink) + { + return create(logger_name, { sink }); + } + + std::shared_ptr create_async(const std::string& logger_name, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb, sinks_init_list sinks) + { + return create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sinks.begin(), sinks.end()); + } + + std::shared_ptr create_async(const std::string& logger_name, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb, sink_ptr sink) + { + return create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, { sink }); + } + + void formatter(formatter_ptr f) + { + std::lock_guard lock(_mutex); + _formatter = f; + for (auto& l : _loggers) + l.second->set_formatter(_formatter); + } + + void set_pattern(const std::string& pattern) + { + std::lock_guard lock(_mutex); + _formatter = std::make_shared(pattern); + for (auto& l : _loggers) + l.second->set_formatter(_formatter); + } + + void set_level(level::level_enum log_level) + { + std::lock_guard lock(_mutex); + for (auto& l : _loggers) + l.second->set_level(log_level); + _level = log_level; + } + + void flush_on(level::level_enum log_level) + { + std::lock_guard lock(_mutex); + for (auto& l : _loggers) + l.second->flush_on(log_level); + _flush_level = log_level; + } + + void set_error_handler(log_err_handler handler) + { + for (auto& l : _loggers) + l.second->set_error_handler(handler); + _err_handler = handler; + } + + void set_async_mode(size_t q_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) + { + std::lock_guard lock(_mutex); + _async_mode = true; + _async_q_size = q_size; + _overflow_policy = overflow_policy; + _worker_warmup_cb = worker_warmup_cb; + _flush_interval_ms = flush_interval_ms; + _worker_teardown_cb = worker_teardown_cb; + } + + void set_sync_mode() + { + std::lock_guard lock(_mutex); + _async_mode = false; + } + + static registry_t& instance() + { + static registry_t s_instance; + return s_instance; + } + +private: + registry_t() {} + registry_t(const registry_t&) = delete; + registry_t& operator=(const registry_t&) = delete; + + void throw_if_exists(const std::string &logger_name) + { + if (_loggers.find(logger_name) != _loggers.end()) + throw spdlog_ex("logger with name '" + logger_name + "' already exists"); + } + Mutex _mutex; + std::unordered_map > _loggers; + formatter_ptr _formatter; + level::level_enum _level = level::info; + level::level_enum _flush_level = level::off; + log_err_handler _err_handler; + bool _async_mode = false; + size_t _async_q_size = 0; + async_overflow_policy _overflow_policy = async_overflow_policy::block_retry; + std::function _worker_warmup_cb = nullptr; + std::chrono::milliseconds _flush_interval_ms; + std::function _worker_teardown_cb = nullptr; +}; +#ifdef SPDLOG_NO_REGISTRY_MUTEX +typedef registry_t registry; +#else +typedef registry_t registry; +#endif +} +} diff --git a/fpga/include/spdlog/details/spdlog_impl.h b/fpga/include/spdlog/details/spdlog_impl.h new file mode 100644 index 000000000..25c7b7e9a --- /dev/null +++ b/fpga/include/spdlog/details/spdlog_impl.h @@ -0,0 +1,268 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Global registry functions +// +#include "../spdlog.h" +#include "../details/registry.h" +#include "../sinks/file_sinks.h" +#include "../sinks/stdout_sinks.h" +#ifdef SPDLOG_ENABLE_SYSLOG +#include "../sinks/syslog_sink.h" +#endif + +#ifdef _WIN32 +#include "../sinks/wincolor_sink.h" +#else +#include "../sinks/ansicolor_sink.h" +#endif + + +#ifdef __ANDROID__ +#include "../sinks/android_sink.h" +#endif + +#include +#include +#include +#include + +inline void spdlog::register_logger(std::shared_ptr logger) +{ + return details::registry::instance().register_logger(logger); +} + +inline std::shared_ptr spdlog::get(const std::string& name) +{ + return details::registry::instance().get(name); +} + +inline void spdlog::drop(const std::string &name) +{ + details::registry::instance().drop(name); +} + +// Create multi/single threaded simple file logger +inline std::shared_ptr spdlog::basic_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate) +{ + return create(logger_name, filename, truncate); +} + +inline std::shared_ptr spdlog::basic_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate) +{ + return create(logger_name, filename, truncate); +} + +// Create multi/single threaded rotating file logger +inline std::shared_ptr spdlog::rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) +{ + return create(logger_name, filename, max_file_size, max_files); +} + +inline std::shared_ptr spdlog::rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) +{ + return create(logger_name, filename, max_file_size, max_files); +} + +// Create file logger which creates new file at midnight): +inline std::shared_ptr spdlog::daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour, int minute) +{ + return create(logger_name, filename, hour, minute); +} + +inline std::shared_ptr spdlog::daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour, int minute) +{ + return create(logger_name, filename, hour, minute); +} + + +// +// stdout/stderr loggers +// +inline std::shared_ptr spdlog::stdout_logger_mt(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_mt::instance()); +} + +inline std::shared_ptr spdlog::stdout_logger_st(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_st::instance()); +} + +inline std::shared_ptr spdlog::stderr_logger_mt(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_mt::instance()); +} + +inline std::shared_ptr spdlog::stderr_logger_st(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_st::instance()); +} + +// +// stdout/stderr color loggers +// +#ifdef _WIN32 +inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + + +inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +#else //ansi terminal colors + +inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} +#endif + +#ifdef SPDLOG_ENABLE_SYSLOG +// Create syslog logger +inline std::shared_ptr spdlog::syslog_logger(const std::string& logger_name, const std::string& syslog_ident, int syslog_option, int syslog_facility) +{ + return create(logger_name, syslog_ident, syslog_option, syslog_facility); +} +#endif + +#ifdef __ANDROID__ +inline std::shared_ptr spdlog::android_logger(const std::string& logger_name, const std::string& tag) +{ + return create(logger_name, tag); +} +#endif + +// Create and register a logger a single sink +inline std::shared_ptr spdlog::create(const std::string& logger_name, const spdlog::sink_ptr& sink) +{ + return details::registry::instance().create(logger_name, sink); +} + +//Create logger with multiple sinks + +inline std::shared_ptr spdlog::create(const std::string& logger_name, spdlog::sinks_init_list sinks) +{ + return details::registry::instance().create(logger_name, sinks); +} + + +template +inline std::shared_ptr spdlog::create(const std::string& logger_name, Args... args) +{ + sink_ptr sink = std::make_shared(args...); + return details::registry::instance().create(logger_name, { sink }); +} + + +template +inline std::shared_ptr spdlog::create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) +{ + return details::registry::instance().create(logger_name, sinks_begin, sinks_end); +} + +// Create and register an async logger with a single sink +inline std::shared_ptr spdlog::create_async(const std::string& logger_name, const sink_ptr& sink, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) +{ + return details::registry::instance().create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sink); +} + +// Create and register an async logger with multiple sinks +inline std::shared_ptr spdlog::create_async(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb ) +{ + return details::registry::instance().create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sinks); +} + +template +inline std::shared_ptr spdlog::create_async(const std::string& logger_name, const It& sinks_begin, const It& sinks_end, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) +{ + return details::registry::instance().create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sinks_begin, sinks_end); +} + +inline void spdlog::set_formatter(spdlog::formatter_ptr f) +{ + details::registry::instance().formatter(f); +} + +inline void spdlog::set_pattern(const std::string& format_string) +{ + return details::registry::instance().set_pattern(format_string); +} + +inline void spdlog::set_level(level::level_enum log_level) +{ + return details::registry::instance().set_level(log_level); +} + +inline void spdlog::flush_on(level::level_enum log_level) +{ + return details::registry::instance().flush_on(log_level); +} + +inline void spdlog::set_error_handler(log_err_handler handler) +{ + return details::registry::instance().set_error_handler(handler); +} + + +inline void spdlog::set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) +{ + details::registry::instance().set_async_mode(queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb); +} + +inline void spdlog::set_sync_mode() +{ + details::registry::instance().set_sync_mode(); +} + +inline void spdlog::apply_all(std::function)> fun) +{ + details::registry::instance().apply_all(fun); +} + +inline void spdlog::drop_all() +{ + details::registry::instance().drop_all(); +} diff --git a/fpga/include/spdlog/fmt/bundled/LICENSE.rst b/fpga/include/spdlog/fmt/bundled/LICENSE.rst new file mode 100644 index 000000000..eb6be6503 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/LICENSE.rst @@ -0,0 +1,23 @@ +Copyright (c) 2012 - 2016, Victor Zverovich + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/fpga/include/spdlog/fmt/bundled/format.cc b/fpga/include/spdlog/fmt/bundled/format.cc new file mode 100644 index 000000000..09d2ea9fd --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/format.cc @@ -0,0 +1,535 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "format.h" + +#include + +#include +#include +#include +#include +#include +#include // for std::ptrdiff_t + +#if defined(_WIN32) && defined(__MINGW32__) +# include +#endif + +#if FMT_USE_WINDOWS_H +# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +# include +# else +# define NOMINMAX +# include +# undef NOMINMAX +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +# pragma warning(disable: 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +static inline fmt::internal::Null<> strerror_r(int, char *, ...) { + return fmt::internal::Null<>(); +} +static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { + return fmt::internal::Null<>(); +} + +namespace fmt { + +FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {} +FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {} +FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {} + +namespace { + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +# define FMT_SWPRINTF snwprintf +#else +# define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +const char RESET_COLOR[] = "\x1b[0m"; + +typedef void (*FormatFunc)(Writer &, int, StringRef); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { + FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer"); + + class StrError { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const StrError &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::Null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? + ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::Null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + StrError(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { + // Suppress a warning about unused strerror_r. + strerror_r(0, FMT_NULL, ""); + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return StrError(error_code, buffer, buffer_size).run(); +} + +void format_error_code(Writer &out, int error_code, + StringRef message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential + // bad_alloc. + out.clear(); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(error_code); + if (internal::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) + out << message << SEP; + out << ERROR_STR << error_code; + assert(out.size() <= internal::INLINE_BUFFER_SIZE); +} + +void report_error(FormatFunc func, int error_code, + StringRef message) FMT_NOEXCEPT { + MemoryWriter full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +FMT_FUNC void SystemError::init( + int err_code, CStringRef format_str, ArgList args) { + error_code_ = err_code; + MemoryWriter w; + format_system_error(w, err_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +template +int internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, value) : + FMT_SNPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, width, value) : + FMT_SNPRINTF(buffer, size, format, width, precision, value); +} + +template +int internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, value) : + FMT_SWPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, width, value) : + FMT_SWPRINTF(buffer, size, format, width, precision, value); +} + +template +const char internal::BasicData::DIGITS[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 + +template +const uint32_t internal::BasicData::POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template +const uint64_t internal::BasicData::POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(ULongLong(1000000000)), + // Multiply several constants instead of using a single long long constant + // to avoid warnings about C++98 not supporting long long. + ULongLong(1000000000) * ULongLong(1000000000) * 10 +}; + +FMT_FUNC void internal::report_unknown_type(char code, const char *type) { + (void)type; + if (std::isprint(static_cast(code))) { + FMT_THROW(FormatError( + format("unknown format code '{}' for {}", code, type))); + } + FMT_THROW(FormatError( + format("unknown format code '\\x{:02x}' for {}", + static_cast(code), type))); +} + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) { + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast(s.size()); + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) { + if (int error_code = convert(s)) { + FMT_THROW(WindowsError(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) { + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast(s.size()); + int length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void WindowsError::init( + int err_code, CStringRef format_str, ArgList args) { + error_code_ = err_code; + MemoryWriter w; + internal::format_windows_error(w, err_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +FMT_FUNC void internal::format_windows_error( + Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { + FMT_TRY { + MemoryBuffer buffer; + buffer.resize(INLINE_BUFFER_SIZE); + for (;;) { + wchar_t *system_message = &buffer[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_message, static_cast(buffer.size()), FMT_NULL); + if (result != 0) { + UTF16ToUTF8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + out << message << ": " << utf8_message; + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error( + Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { + FMT_TRY { + internal::MemoryBuffer buffer; + buffer.resize(internal::INLINE_BUFFER_SIZE); + for (;;) { + char *system_message = &buffer[0]; + int result = safe_strerror(error_code, system_message, buffer.size()); + if (result == 0) { + out << message << ": " << system_message; + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. +} + +template +void internal::ArgMap::init(const ArgList &args) { + if (!map_.empty()) + return; + typedef internal::NamedArg NamedArg; + const NamedArg *named_arg = FMT_NULL; + bool use_values = + args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; + if (use_values) { + for (unsigned i = 0;/*nothing*/; ++i) { + internal::Arg::Type arg_type = args.type(i); + switch (arg_type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast(args.values_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } + return; + } + for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { + internal::Arg::Type arg_type = args.type(i); + if (arg_type == internal::Arg::NAMED_ARG) { + named_arg = static_cast(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + } + } + for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { + switch (args.args_[i].type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } +} + +template +void internal::FixedBuffer::grow(std::size_t) { + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg( + unsigned arg_index, const char *&error) { + internal::Arg arg = args_[arg_index]; + switch (arg.type) { + case internal::Arg::NONE: + error = "argument index out of range"; + break; + case internal::Arg::NAMED_ARG: + arg = *static_cast(arg.pointer); + break; + default: + /*nothing*/; + } + return arg; +} + +FMT_FUNC void report_system_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT { + // 'fmt::' is for bcc32. + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT { + // 'fmt::' is for bcc32. + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + std::fwrite(w.data(), 1, w.size(), f); +} + +FMT_FUNC void print(CStringRef format_str, ArgList args) { + print(stdout, format_str, args); +} + +FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) { + char escape[] = "\x1b[30m"; + escape[3] = static_cast('0' + c); + std::fputs(escape, stdout); + print(format, args); + std::fputs(RESET_COLOR, stdout); +} + +#ifndef FMT_HEADER_ONLY + +template struct internal::BasicData; + +// Explicit instantiations for char. + +template void internal::FixedBuffer::grow(std::size_t); + +template void internal::ArgMap::init(const ArgList &args); + +template FMT_API int internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, double value); + +template FMT_API int internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, long double value); + +// Explicit instantiations for wchar_t. + +template void internal::FixedBuffer::grow(std::size_t); + +template void internal::ArgMap::init(const ArgList &args); + +template FMT_API int internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, double value); + +template FMT_API int internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, long double value); + +#endif // FMT_HEADER_ONLY + +} // namespace fmt + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/fpga/include/spdlog/fmt/bundled/format.h b/fpga/include/spdlog/fmt/bundled/format.h new file mode 100644 index 000000000..91f434813 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/format.h @@ -0,0 +1,4659 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for std::pair + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 40000 + +#ifdef _SECURE_SCL +# define FMT_SECURE_SCL _SECURE_SCL +#else +# define FMT_SECURE_SCL 0 +#endif + +#if FMT_SECURE_SCL +# include +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +#else +# define FMT_MSC_VER 0 +#endif + +#if FMT_MSC_VER && FMT_MSC_VER <= 1500 +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef __int64 intmax_t; +#else +#include +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifdef __GNUC__ +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# define FMT_GCC_EXTENSION __extension__ +# if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic push +// Disable the warning about "long long" which is sometimes reported even +// when using __extension__. +# pragma GCC diagnostic ignored "-Wlong-long" +// Disable the warning about declaration shadowing because it affects too +// many valid cases. +# pragma GCC diagnostic ignored "-Wshadow" +// Disable the warning about implicit conversions that may change the sign of +// an integer; silencing it otherwise would require many explicit casts. +# pragma GCC diagnostic ignored "-Wsign-conversion" +# endif +# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__ +# define FMT_HAS_GXX_CXX11 1 +# endif +#else +# define FMT_GCC_EXTENSION +#endif + +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +# define FMT_ICC_VERSION __ICL +#endif + +#if defined(__clang__) && !defined(FMT_ICC_VERSION) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +# pragma clang diagnostic ignored "-Wpadded" +#endif + +#ifdef __GNUC_LIBSTD__ +# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#endif + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#ifndef FMT_USE_VARIADIC_TEMPLATES +// Variadic templates are available in GCC since version 4.4 +// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ +// since version 2013. +# define FMT_USE_VARIADIC_TEMPLATES \ + (FMT_HAS_FEATURE(cxx_variadic_templates) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800) +#endif + +#ifndef FMT_USE_RVALUE_REFERENCES +// Don't use rvalue references when compiling with clang and an old libstdc++ +// as the latter doesn't provide std::move. +# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 +# define FMT_USE_RVALUE_REFERENCES 0 +# else +# define FMT_USE_RVALUE_REFERENCES \ + (FMT_HAS_FEATURE(cxx_rvalue_references) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600) +# endif +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#endif +#if FMT_MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +# define FMT_EXCEPTIONS 1 +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# define FMT_THROW(x) throw x +# else +# define FMT_THROW(x) assert(false) +# endif +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +# define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_DETECTED_NOEXCEPT noexcept +#else +# define FMT_DETECTED_NOEXCEPT throw() +#endif + +#ifndef FMT_NOEXCEPT +# if FMT_EXCEPTIONS +# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +# else +# define FMT_NOEXCEPT +# endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_OVERRIDE +# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + +#ifndef FMT_NULL +# if FMT_HAS_FEATURE(cxx_nullptr) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1600 +# define FMT_NULL nullptr +# else +# define FMT_NULL NULL +# endif +#endif + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#ifndef FMT_USE_DELETED_FUNCTIONS +# define FMT_USE_DELETED_FUNCTIONS 0 +#endif + +#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 +# define FMT_DELETED_OR_UNDEFINED = delete +# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete +#else +# define FMT_DELETED_OR_UNDEFINED +# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + TypeName& operator=(const TypeName&) +#endif + +#ifndef FMT_USE_DEFAULTED_FUNCTIONS +# define FMT_USE_DEFAULTED_FUNCTIONS 0 +#endif + +#ifndef FMT_DEFAULTED_COPY_CTOR +# if FMT_USE_DEFAULTED_FUNCTIONS || FMT_HAS_FEATURE(cxx_defaulted_functions) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 +# define FMT_DEFAULTED_COPY_CTOR(TypeName) \ + TypeName(const TypeName&) = default; +# else +# define FMT_DEFAULTED_COPY_CTOR(TypeName) +# endif +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// All compilers which support UDLs also support variadic templates. This +// makes the fmt::literals implementation easier. However, an explicit check +// for variadic templates is added here just in case. +// For Intel's compiler both it and the system gcc/msc must support UDLs. +# define FMT_USE_USER_DEFINED_LITERALS \ + FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ + (FMT_HAS_FEATURE(cxx_user_literals) || \ + (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \ + (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500) +#endif + +#ifndef FMT_USE_EXTERN_TEMPLATES +# define FMT_USE_EXTERN_TEMPLATES \ + (FMT_CLANG_VERSION >= 209 || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) +#endif + +#ifdef FMT_HEADER_ONLY +// If header only do not use extern templates. +# undef FMT_USE_EXTERN_TEMPLATES +# define FMT_USE_EXTERN_TEMPLATES 0 +#endif + +#ifndef FMT_ASSERT +# define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#ifndef _MSC_VER +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif + +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or +// otherwise support __builtin_clz and __builtin_clzll, so +// only define FMT_BUILTIN_CLZ using the MSVC intrinsics +// if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) +# include // _BitScanReverse, _BitScanReverse64 + +namespace fmt +{ +namespace internal +{ +# pragma intrinsic(_BitScanReverse) +inline uint32_t clz(uint32_t x) +{ + unsigned long r = 0; + _BitScanReverse(&r, x); + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 31 - r; +} +# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) + +# ifdef _WIN64 +# pragma intrinsic(_BitScanReverse64) +# endif + +inline uint32_t clzll(uint64_t x) +{ + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +# endif + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 63 - r; +} +# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) +} +} +#endif + +namespace fmt +{ +namespace internal +{ +struct DummyInt +{ + int data[2]; + operator int() const + { + return 0; + } +}; +typedef std::numeric_limits FPUtil; + +// Dummy implementations of system functions such as signbit and ecvt called +// if the latter are not available. +inline DummyInt signbit(...) +{ + return DummyInt(); +} +inline DummyInt _ecvt_s(...) +{ + return DummyInt(); +} +inline DummyInt isinf(...) +{ + return DummyInt(); +} +inline DummyInt _finite(...) +{ + return DummyInt(); +} +inline DummyInt isnan(...) +{ + return DummyInt(); +} +inline DummyInt _isnan(...) +{ + return DummyInt(); +} + +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template +inline T const_check(T value) +{ + return value; +} +} +} // namespace fmt + +namespace std +{ +// Standard permits specialization of std::numeric_limits. This specialization +// is used to resolve ambiguity between isinf and std::isinf in glibc: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 +// and the same for isnan and signbit. +template <> +class numeric_limits : + public std::numeric_limits +{ +public: + // Portable version of isinf. + template + static bool isinfinity(T x) + { + using namespace fmt::internal; + // The resolution "priority" is: + // isinf macro > std::isinf > ::isinf > fmt::internal::isinf + if (const_check(sizeof(isinf(x)) == sizeof(bool) || + sizeof(isinf(x)) == sizeof(int))) + { + return isinf(x) != 0; + } + return !_finite(static_cast(x)); + } + + // Portable version of isnan. + template + static bool isnotanumber(T x) + { + using namespace fmt::internal; + if (const_check(sizeof(isnan(x)) == sizeof(bool) || + sizeof(isnan(x)) == sizeof(int))) + { + return isnan(x) != 0; + } + return _isnan(static_cast(x)) != 0; + } + + // Portable version of signbit. + static bool isnegative(double x) + { + using namespace fmt::internal; + if (const_check(sizeof(signbit(x)) == sizeof(bool) || + sizeof(signbit(x)) == sizeof(int))) + { + return signbit(x) != 0; + } + if (x < 0) return true; + if (!isnotanumber(x)) return false; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); + return sign != 0; + } +}; +} // namespace std + +namespace fmt +{ + +// Fix the warning about long long on older versions of GCC +// that don't support the diagnostic pragma. +FMT_GCC_EXTENSION typedef long long LongLong; +FMT_GCC_EXTENSION typedef unsigned long long ULongLong; + +#if FMT_USE_RVALUE_REFERENCES +using std::move; +#endif + +template +class BasicWriter; + +typedef BasicWriter Writer; +typedef BasicWriter WWriter; + +template +class ArgFormatter; + +struct FormatSpec; + +template +class BasicPrintfArgFormatter; + +template > +class BasicFormatter; + +/** + \rst + A string reference. It can be constructed from a C string or + ``std::basic_string``. + + You can use one of the following typedefs for common character types: + + +------------+-------------------------+ + | Type | Definition | + +============+=========================+ + | StringRef | BasicStringRef | + +------------+-------------------------+ + | WStringRef | BasicStringRef | + +------------+-------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template + std::string format(StringRef format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template +class BasicStringRef +{ +private: + const Char *data_; + std::size_t size_; + +public: + /** Constructs a string reference object from a C string and a size. */ + BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits::length``. + \endrst + */ + BasicStringRef(const Char *s) + : data_(s), size_(std::char_traits::length(s)) {} + + /** + \rst + Constructs a string reference from a ``std::basic_string`` object. + \endrst + */ + template + BasicStringRef( + const std::basic_string, Allocator> &s) + : data_(s.c_str()), size_(s.size()) {} + + /** + \rst + Converts a string reference to an ``std::string`` object. + \endrst + */ + std::basic_string to_string() const + { + return std::basic_string(data_, size_); + } + + /** Returns a pointer to the string data. */ + const Char *data() const + { + return data_; + } + + /** Returns the string size. */ + std::size_t size() const + { + return size_; + } + + // Lexicographically compare this string reference to other. + int compare(BasicStringRef other) const + { + std::size_t size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits::compare(data_, other.data_, size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) != 0; + } + friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) >= 0; + } +}; + +typedef BasicStringRef StringRef; +typedef BasicStringRef WStringRef; + +/** + \rst + A reference to a null terminated string. It can be constructed from a C + string or ``std::basic_string``. + + You can use one of the following typedefs for common character types: + + +-------------+--------------------------+ + | Type | Definition | + +=============+==========================+ + | CStringRef | BasicCStringRef | + +-------------+--------------------------+ + | WCStringRef | BasicCStringRef | + +-------------+--------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template + std::string format(CStringRef format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template +class BasicCStringRef +{ +private: + const Char *data_; + +public: + /** Constructs a string reference object from a C string. */ + BasicCStringRef(const Char *s) : data_(s) {} + + /** + \rst + Constructs a string reference from a ``std::basic_string`` object. + \endrst + */ + template + BasicCStringRef( + const std::basic_string, Allocator> &s) + : data_(s.c_str()) {} + + /** Returns the pointer to a C string. */ + const Char *c_str() const + { + return data_; + } +}; + +typedef BasicCStringRef CStringRef; +typedef BasicCStringRef WCStringRef; + +/** A formatting error such as invalid format string. */ +class FormatError : public std::runtime_error +{ +public: + explicit FormatError(CStringRef message) + : std::runtime_error(message.c_str()) {} + FormatError(const FormatError &ferr) : std::runtime_error(ferr) {} + FMT_API ~FormatError() FMT_DTOR_NOEXCEPT; +}; + +namespace internal +{ + +// MakeUnsigned::Type gives an unsigned type corresponding to integer type T. +template +struct MakeUnsigned +{ + typedef T Type; +}; + +#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ + template <> \ + struct MakeUnsigned { typedef U Type; } + +FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); +FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); +FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); +FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); + +// Casts nonnegative integer to unsigned. +template +inline typename MakeUnsigned::Type to_unsigned(Int value) +{ + FMT_ASSERT(value >= 0, "negative value"); + return static_cast::Type>(value); +} + +// The number of characters to store in the MemoryBuffer object itself +// to avoid dynamic memory allocation. +enum { INLINE_BUFFER_SIZE = 500 }; + +#if FMT_SECURE_SCL +// Use checked iterator to avoid warnings on MSVC. +template +inline stdext::checked_array_iterator make_ptr(T *ptr, std::size_t size) +{ + return stdext::checked_array_iterator(ptr, size); +} +#else +template +inline T *make_ptr(T *ptr, std::size_t) +{ + return ptr; +} +#endif +} // namespace internal + +/** + \rst + A buffer supporting a subset of ``std::vector``'s operations. + \endrst + */ +template +class Buffer +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); + +protected: + T *ptr_; + std::size_t size_; + std::size_t capacity_; + + Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0) + : ptr_(ptr), size_(0), capacity_(capacity) {} + + /** + \rst + Increases the buffer capacity to hold at least *size* elements updating + ``ptr_`` and ``capacity_``. + \endrst + */ + virtual void grow(std::size_t size) = 0; + +public: + virtual ~Buffer() {} + + /** Returns the size of this buffer. */ + std::size_t size() const + { + return size_; + } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const + { + return capacity_; + } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) + { + if (new_size > capacity_) + grow(new_size); + size_ = new_size; + } + + /** + \rst + Reserves space to store at least *capacity* elements. + \endrst + */ + void reserve(std::size_t capacity) + { + if (capacity > capacity_) + grow(capacity); + } + + void clear() FMT_NOEXCEPT { size_ = 0; } + + void push_back(const T &value) + { + if (size_ == capacity_) + grow(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) + { + return ptr_[index]; + } + const T &operator[](std::size_t index) const + { + return ptr_[index]; + } +}; + +template +template +void Buffer::append(const U *begin, const U *end) +{ + FMT_ASSERT(end >= begin, "negative value"); + std::size_t new_size = size_ + (end - begin); + if (new_size > capacity_) + grow(new_size); + std::uninitialized_copy(begin, end, + internal::make_ptr(ptr_, capacity_) + size_); + size_ = new_size; +} + +namespace internal +{ + +// A memory buffer for trivially copyable/constructible types with the first +// SIZE elements stored in the object itself. +template > +class MemoryBuffer : private Allocator, public Buffer +{ +private: + T data_[SIZE]; + + // Deallocate memory allocated by the buffer. + void deallocate() + { + if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_); + } + +protected: + void grow(std::size_t size) FMT_OVERRIDE; + +public: + explicit MemoryBuffer(const Allocator &alloc = Allocator()) + : Allocator(alloc), Buffer(data_, SIZE) {} + ~MemoryBuffer() + { + deallocate(); + } + +#if FMT_USE_RVALUE_REFERENCES +private: + // Move data from other to this buffer. + void move(MemoryBuffer &other) + { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + this->size_ = other.size_; + this->capacity_ = other.capacity_; + if (other.ptr_ == other.data_) + { + this->ptr_ = data_; + std::uninitialized_copy(other.data_, other.data_ + this->size_, + make_ptr(data_, this->capacity_)); + } + else + { + this->ptr_ = other.ptr_; + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.ptr_ = other.data_; + } + } + +public: + MemoryBuffer(MemoryBuffer &&other) + { + move(other); + } + + MemoryBuffer &operator=(MemoryBuffer &&other) + { + assert(this != &other); + deallocate(); + move(other); + return *this; + } +#endif + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const + { + return *this; + } +}; + +template +void MemoryBuffer::grow(std::size_t size) +{ + std::size_t new_capacity = this->capacity_ + this->capacity_ / 2; + if (size > new_capacity) + new_capacity = size; + T *new_ptr = this->allocate(new_capacity, FMT_NULL); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_, + make_ptr(new_ptr, new_capacity)); + std::size_t old_capacity = this->capacity_; + T *old_ptr = this->ptr_; + this->capacity_ = new_capacity; + this->ptr_ = new_ptr; + // deallocate may throw (at least in principle), but it doesn't matter since + // the buffer already uses the new storage and will deallocate it in case + // of exception. + if (old_ptr != data_) + Allocator::deallocate(old_ptr, old_capacity); +} + +// A fixed-size buffer. +template +class FixedBuffer : public fmt::Buffer +{ +public: + FixedBuffer(Char *array, std::size_t size) : fmt::Buffer(array, size) {} + +protected: + FMT_API void grow(std::size_t size) FMT_OVERRIDE; +}; + +template +class BasicCharTraits +{ +public: +#if FMT_SECURE_SCL + typedef stdext::checked_array_iterator CharPtr; +#else + typedef Char *CharPtr; +#endif + static Char cast(int value) + { + return static_cast(value); + } +}; + +template +class CharTraits; + +template <> +class CharTraits : public BasicCharTraits +{ +private: + // Conversion from wchar_t to char is not allowed. + static char convert(wchar_t); + +public: + static char convert(char value) + { + return value; + } + + // Formats a floating-point number. + template + FMT_API static int format_float(char *buffer, std::size_t size, + const char *format, unsigned width, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int CharTraits::format_float +(char *buffer, std::size_t size, + const char* format, unsigned width, int precision, double value); +extern template int CharTraits::format_float +(char *buffer, std::size_t size, + const char* format, unsigned width, int precision, long double value); +#endif + +template <> +class CharTraits : public BasicCharTraits +{ +public: + static wchar_t convert(char value) + { + return value; + } + static wchar_t convert(wchar_t value) + { + return value; + } + + template + FMT_API static int format_float(wchar_t *buffer, std::size_t size, + const wchar_t *format, unsigned width, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int CharTraits::format_float +(wchar_t *buffer, std::size_t size, + const wchar_t* format, unsigned width, int precision, double value); +extern template int CharTraits::format_float +(wchar_t *buffer, std::size_t size, + const wchar_t* format, unsigned width, int precision, long double value); +#endif + +// Checks if a number is negative - used to avoid warnings. +template +struct SignChecker +{ + template + static bool is_negative(T value) + { + return value < 0; + } +}; + +template <> +struct SignChecker +{ + template + static bool is_negative(T) + { + return false; + } +}; + +// Returns true if value is negative, false otherwise. +// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. +template +inline bool is_negative(T value) +{ + return SignChecker::is_signed>::is_negative(value); +} + +// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. +template +struct TypeSelector +{ + typedef uint32_t Type; +}; + +template <> +struct TypeSelector +{ + typedef uint64_t Type; +}; + +template +struct IntTraits +{ + // Smallest of uint32_t and uint64_t that is large enough to represent + // all values of T. + typedef typename + TypeSelector::digits <= 32>::Type MainType; +}; + +FMT_API void report_unknown_type(char code, const char *type); + +// Static data is placed in this class template to allow header-only +// configuration. +template +struct FMT_API BasicData +{ + static const uint32_t POWERS_OF_10_32[]; + static const uint64_t POWERS_OF_10_64[]; + static const char DIGITS[]; +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template struct BasicData; +#endif + +typedef BasicData<> Data; + +#ifdef FMT_BUILTIN_CLZLL +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +inline unsigned count_digits(uint64_t n) +{ + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1; +} +#else +// Fallback version of count_digits used when __builtin_clz is not available. +inline unsigned count_digits(uint64_t n) +{ + unsigned count = 1; + for (;;) + { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#endif + +#ifdef FMT_BUILTIN_CLZ +// Optional version of count_digits for better performance on 32-bit platforms. +inline unsigned count_digits(uint32_t n) +{ + int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1; +} +#endif + +// A functor that doesn't add a thousands separator. +struct NoThousandsSep +{ + template + void operator()(Char *) {} +}; + +// A functor that adds a thousands separator. +class ThousandsSep +{ +private: + fmt::StringRef sep_; + + // Index of a decimal digit with the least significant digit having index 0. + unsigned digit_index_; + +public: + explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {} + + template + void operator()(Char *&buffer) + { + if (++digit_index_ % 3 != 0) + return; + buffer -= sep_.size(); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), + internal::make_ptr(buffer, sep_.size())); + } +}; + +// Formats a decimal unsigned integer value writing into buffer. +// thousands_sep is a functor that is called after writing each char to +// add a thousands separator if necessary. +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, + ThousandsSep thousands_sep) +{ + buffer += num_digits; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast((value % 100) * 2); + value /= 100; + *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = Data::DIGITS[index]; + thousands_sep(buffer); + } + if (value < 10) + { + *--buffer = static_cast('0' + value); + return; + } + unsigned index = static_cast(value * 2); + *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = Data::DIGITS[index]; +} + +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) +{ + format_decimal(buffer, value, num_digits, NoThousandsSep()); + return; +} + +#ifndef _WIN32 +# define FMT_USE_WINDOWS_H 0 +#elif !defined(FMT_USE_WINDOWS_H) +# define FMT_USE_WINDOWS_H 1 +#endif + +// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. +// All the functionality that relies on it will be disabled too. +#if FMT_USE_WINDOWS_H +// A converter from UTF-8 to UTF-16. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF8ToUTF16 +{ +private: + MemoryBuffer buffer_; + +public: + FMT_API explicit UTF8ToUTF16(StringRef s); + operator WStringRef() const + { + return WStringRef(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const wchar_t *c_str() const + { + return &buffer_[0]; + } + std::wstring str() const + { + return std::wstring(&buffer_[0], size()); + } +}; + +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF16ToUTF8 +{ +private: + MemoryBuffer buffer_; + +public: + UTF16ToUTF8() {} + FMT_API explicit UTF16ToUTF8(WStringRef s); + operator StringRef() const + { + return StringRef(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const char *c_str() const + { + return &buffer_[0]; + } + std::string str() const + { + return std::string(&buffer_[0], size()); + } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(WStringRef s); +}; + +FMT_API void format_windows_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; +#endif + +// A formatting argument value. +struct Value +{ + template + struct StringValue + { + const Char *value; + std::size_t size; + }; + + typedef void (*FormatFunc)( + void *formatter, const void *arg, void *format_str_ptr); + + struct CustomValue + { + const void *value; + FormatFunc format; + }; + + union + { + int int_value; + unsigned uint_value; + LongLong long_long_value; + ULongLong ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + StringValue string; + StringValue sstring; + StringValue ustring; + StringValue wstring; + CustomValue custom; + }; + + enum Type + { + NONE, NAMED_ARG, + // Integer types should go first, + INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR, + // followed by floating-point types. + DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, + CSTRING, STRING, WSTRING, POINTER, CUSTOM + }; +}; + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in internal::MemoryBuffer. +struct Arg : Value +{ + Type type; +}; + +template +struct NamedArg; +template +struct NamedArgWithType; + +template +struct Null {}; + +// A helper class template to enable or disable overloads taking wide +// characters and strings in MakeValue. +template +struct WCharHelper +{ + typedef Null Supported; + typedef T Unsupported; +}; + +template +struct WCharHelper +{ + typedef T Supported; + typedef Null Unsupported; +}; + +typedef char Yes[1]; +typedef char No[2]; + +template +T &get(); + +// These are non-members to workaround an overload resolution bug in bcc32. +Yes &convert(fmt::ULongLong); +No &convert(...); + +template +struct ConvertToIntImpl +{ + enum { value = ENABLE_CONVERSION }; +}; + +template +struct ConvertToIntImpl2 +{ + enum { value = false }; +}; + +template +struct ConvertToIntImpl2 +{ + enum + { + // Don't convert numeric types. + value = ConvertToIntImpl::is_specialized>::value + }; +}; + +template +struct ConvertToInt +{ + enum + { + enable_conversion = sizeof(fmt::internal::convert(get())) == sizeof(Yes) + }; + enum { value = ConvertToIntImpl2::value }; +}; + +#define FMT_DISABLE_CONVERSION_TO_INT(Type) \ + template <> \ + struct ConvertToInt { enum { value = 0 }; } + +// Silence warnings about convering float to int. +FMT_DISABLE_CONVERSION_TO_INT(float); +FMT_DISABLE_CONVERSION_TO_INT(double); +FMT_DISABLE_CONVERSION_TO_INT(long double); + +template +struct EnableIf {}; + +template +struct EnableIf +{ + typedef T type; +}; + +template +struct Conditional +{ + typedef T type; +}; + +template +struct Conditional +{ + typedef F type; +}; + +// For bcc32 which doesn't understand ! in template arguments. +template +struct Not +{ + enum { value = 0 }; +}; + +template <> +struct Not +{ + enum { value = 1 }; +}; + +template +struct FalseType +{ + enum { value = 0 }; +}; + +template struct LConvCheck +{ + LConvCheck(int) {} +}; + +// Returns the thousands separator for the current locale. +// We check if ``lconv`` contains ``thousands_sep`` because on Android +// ``lconv`` is stubbed as an empty struct. +template +inline StringRef thousands_sep( + LConv *lc, LConvCheck = 0) +{ + return lc->thousands_sep; +} + +inline fmt::StringRef thousands_sep(...) +{ + return ""; +} + +#define FMT_CONCAT(a, b) a##b + +#if FMT_GCC_VERSION >= 303 +# define FMT_UNUSED __attribute__((unused)) +#else +# define FMT_UNUSED +#endif + +#ifndef FMT_USE_STATIC_ASSERT +# define FMT_USE_STATIC_ASSERT 0 +#endif + +#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 +# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) +#else +# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) +# define FMT_STATIC_ASSERT(cond, message) \ + typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED +#endif + +template +void format_arg(Formatter &, const Char *, const T &) +{ + FMT_STATIC_ASSERT(FalseType::value, + "Cannot format argument. To enable the use of ostream " + "operator<< include fmt/ostream.h. Otherwise provide " + "an overload of format_arg."); +} + +// Makes an Arg object from any type. +template +class MakeValue : public Arg +{ +public: + typedef typename Formatter::Char Char; + +private: + // The following two methods are private to disallow formatting of + // arbitrary pointers. If you want to output a pointer cast it to + // "void *" or "const void *". In particular, this forbids formatting + // of "[const] volatile char *" which is printed as bool by iostreams. + // Do not implement! + template + MakeValue(const T *value); + template + MakeValue(T *value); + + // The following methods are private to disallow formatting of wide + // characters and strings into narrow strings as in + // fmt::format("{}", L"test"); + // To fix this, use a wide format string: fmt::format(L"{}", L"test"). +#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED) + MakeValue(typename WCharHelper::Unsupported); +#endif + MakeValue(typename WCharHelper::Unsupported); + MakeValue(typename WCharHelper::Unsupported); + MakeValue(typename WCharHelper::Unsupported); + MakeValue(typename WCharHelper::Unsupported); + + void set_string(StringRef str) + { + string.value = str.data(); + string.size = str.size(); + } + + void set_string(WStringRef str) + { + wstring.value = str.data(); + wstring.size = str.size(); + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg( + void *formatter, const void *arg, void *format_str_ptr) + { + format_arg(*static_cast(formatter), + *static_cast(format_str_ptr), + *static_cast(arg)); + } + +public: + MakeValue() {} + +#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \ + MakeValue(Type value) { field = rhs; } \ + static uint64_t type(Type) { return Arg::TYPE; } + +#define FMT_MAKE_VALUE(Type, field, TYPE) \ + FMT_MAKE_VALUE_(Type, field, TYPE, value) + + FMT_MAKE_VALUE(bool, int_value, BOOL) + FMT_MAKE_VALUE(short, int_value, INT) + FMT_MAKE_VALUE(unsigned short, uint_value, UINT) + FMT_MAKE_VALUE(int, int_value, INT) + FMT_MAKE_VALUE(unsigned, uint_value, UINT) + + MakeValue(long value) + { + // To minimize the number of types we need to deal with, long is + // translated either to int or to long long depending on its size. + if (const_check(sizeof(long) == sizeof(int))) + int_value = static_cast(value); + else + long_long_value = value; + } + static uint64_t type(long) + { + return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG; + } + + MakeValue(unsigned long value) + { + if (const_check(sizeof(unsigned long) == sizeof(unsigned))) + uint_value = static_cast(value); + else + ulong_long_value = value; + } + static uint64_t type(unsigned long) + { + return sizeof(unsigned long) == sizeof(unsigned) ? + Arg::UINT : Arg::ULONG_LONG; + } + + FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG) + FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG) + FMT_MAKE_VALUE(float, double_value, DOUBLE) + FMT_MAKE_VALUE(double, double_value, DOUBLE) + FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE) + FMT_MAKE_VALUE(signed char, int_value, INT) + FMT_MAKE_VALUE(unsigned char, uint_value, UINT) + FMT_MAKE_VALUE(char, int_value, CHAR) + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) + MakeValue(typename WCharHelper::Supported value) + { + int_value = value; + } + static uint64_t type(wchar_t) + { + return Arg::CHAR; + } +#endif + +#define FMT_MAKE_STR_VALUE(Type, TYPE) \ + MakeValue(Type value) { set_string(value); } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_VALUE(char *, string.value, CSTRING) + FMT_MAKE_VALUE(const char *, string.value, CSTRING) + FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING) + FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) + FMT_MAKE_STR_VALUE(const std::string &, STRING) + FMT_MAKE_STR_VALUE(StringRef, STRING) + FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str()) + +#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \ + MakeValue(typename WCharHelper::Supported value) { \ + set_string(value); \ + } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING) + FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING) + FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING) + FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING) + + FMT_MAKE_VALUE(void *, pointer, POINTER) + FMT_MAKE_VALUE(const void *, pointer, POINTER) + + template + MakeValue(const T &value, + typename EnableIf::value>::value, int>::type = 0) + { + custom.value = &value; + custom.format = &format_custom_arg; + } + + template + static typename EnableIf::value>::value, uint64_t>::type + type(const T &) + { + return Arg::CUSTOM; + } + + // Additional template param `Char_` is needed here because make_type always + // uses char. + template + MakeValue(const NamedArg &value) + { + pointer = &value; + } + template + MakeValue(const NamedArgWithType &value) + { + pointer = &value; + } + + template + static uint64_t type(const NamedArg &) + { + return Arg::NAMED_ARG; + } + template + static uint64_t type(const NamedArgWithType &) + { + return Arg::NAMED_ARG; + } +}; + +template +class MakeArg : public Arg +{ +public: + MakeArg() + { + type = Arg::NONE; + } + + template + MakeArg(const T &value) + : Arg(MakeValue(value)) + { + type = static_cast(MakeValue::type(value)); + } +}; + +template +struct NamedArg : Arg +{ + BasicStringRef name; + + template + NamedArg(BasicStringRef argname, const T &value) + : Arg(MakeArg< BasicFormatter >(value)), name(argname) {} +}; + +template +struct NamedArgWithType : NamedArg +{ + NamedArgWithType(BasicStringRef argname, const T &value) + : NamedArg(argname, value) {} +}; + +class RuntimeError : public std::runtime_error +{ +protected: + RuntimeError() : std::runtime_error("") {} + RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {} + FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT; +}; + +template +class ArgMap; +} // namespace internal + +/** An argument list. */ +class ArgList +{ +private: + // To reduce compiled code size per formatting function call, types of first + // MAX_PACKED_ARGS arguments are passed in the types_ field. + uint64_t types_; + union + { + // If the number of arguments is less than MAX_PACKED_ARGS, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::Value *values_; + const internal::Arg *args_; + }; + + internal::Arg::Type type(unsigned index) const + { + return type(types_, index); + } + + template + friend class internal::ArgMap; + +public: + // Maximum number of arguments with packed types. + enum { MAX_PACKED_ARGS = 16 }; + + ArgList() : types_(0) {} + + ArgList(ULongLong types, const internal::Value *values) + : types_(types), values_(values) {} + ArgList(ULongLong types, const internal::Arg *args) + : types_(types), args_(args) {} + + uint64_t types() const + { + return types_; + } + + /** Returns the argument at specified index. */ + internal::Arg operator[](unsigned index) const + { + using internal::Arg; + Arg arg; + bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE; + if (index < MAX_PACKED_ARGS) + { + Arg::Type arg_type = type(index); + internal::Value &val = arg; + if (arg_type != Arg::NONE) + val = use_values ? values_[index] : args_[index]; + arg.type = arg_type; + return arg; + } + if (use_values) + { + // The index is greater than the number of arguments that can be stored + // in values, so return a "none" argument. + arg.type = Arg::NONE; + return arg; + } + for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) + { + if (args_[i].type == Arg::NONE) + return args_[i]; + } + return args_[index]; + } + + static internal::Arg::Type type(uint64_t types, unsigned index) + { + unsigned shift = index * 4; + uint64_t mask = 0xf; + return static_cast( + (types & (mask << shift)) >> shift); + } +}; + +#define FMT_DISPATCH(call) static_cast(this)->call + +/** + \rst + An argument visitor based on the `curiously recurring template pattern + `_. + + To use `~fmt::ArgVisitor` define a subclass that implements some or all of the + visit methods with the same signatures as the methods in `~fmt::ArgVisitor`, + for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. Then calling + `~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::ArgVisitor` will be called. + + **Example**:: + + class MyArgVisitor : public fmt::ArgVisitor { + public: + void visit_int(int value) { fmt::print("{}", value); } + void visit_double(double value) { fmt::print("{}", value ); } + }; + \endrst + */ +template +class ArgVisitor +{ +private: + typedef internal::Arg Arg; + +public: + void report_unhandled_arg() {} + + Result visit_unhandled_arg() + { + FMT_DISPATCH(report_unhandled_arg()); + return Result(); + } + + /** Visits an ``int`` argument. **/ + Result visit_int(int value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``long long`` argument. **/ + Result visit_long_long(LongLong value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an ``unsigned`` argument. **/ + Result visit_uint(unsigned value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an ``unsigned long long`` argument. **/ + Result visit_ulong_long(ULongLong value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``bool`` argument. **/ + Result visit_bool(bool value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``char`` or ``wchar_t`` argument. **/ + Result visit_char(int value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an argument of any integral type. **/ + template + Result visit_any_int(T) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a ``double`` argument. **/ + Result visit_double(double value) + { + return FMT_DISPATCH(visit_any_double(value)); + } + + /** Visits a ``long double`` argument. **/ + Result visit_long_double(long double value) + { + return FMT_DISPATCH(visit_any_double(value)); + } + + /** Visits a ``double`` or ``long double`` argument. **/ + template + Result visit_any_double(T) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a null-terminated C string (``const char *``) argument. **/ + Result visit_cstring(const char *) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a string argument. **/ + Result visit_string(Arg::StringValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a wide string argument. **/ + Result visit_wstring(Arg::StringValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a pointer argument. **/ + Result visit_pointer(const void *) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits an argument of a custom (user-defined) type. **/ + Result visit_custom(Arg::CustomValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be + called. + \endrst + */ + Result visit(const Arg &arg) + { + switch (arg.type) + { + case Arg::NONE: + case Arg::NAMED_ARG: + FMT_ASSERT(false, "invalid argument type"); + break; + case Arg::INT: + return FMT_DISPATCH(visit_int(arg.int_value)); + case Arg::UINT: + return FMT_DISPATCH(visit_uint(arg.uint_value)); + case Arg::LONG_LONG: + return FMT_DISPATCH(visit_long_long(arg.long_long_value)); + case Arg::ULONG_LONG: + return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); + case Arg::BOOL: + return FMT_DISPATCH(visit_bool(arg.int_value != 0)); + case Arg::CHAR: + return FMT_DISPATCH(visit_char(arg.int_value)); + case Arg::DOUBLE: + return FMT_DISPATCH(visit_double(arg.double_value)); + case Arg::LONG_DOUBLE: + return FMT_DISPATCH(visit_long_double(arg.long_double_value)); + case Arg::CSTRING: + return FMT_DISPATCH(visit_cstring(arg.string.value)); + case Arg::STRING: + return FMT_DISPATCH(visit_string(arg.string)); + case Arg::WSTRING: + return FMT_DISPATCH(visit_wstring(arg.wstring)); + case Arg::POINTER: + return FMT_DISPATCH(visit_pointer(arg.pointer)); + case Arg::CUSTOM: + return FMT_DISPATCH(visit_custom(arg.custom)); + } + return Result(); + } +}; + +enum Alignment +{ + ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC +}; + +// Flags. +enum +{ + SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, + CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. +}; + +// An empty format specifier. +struct EmptySpec {}; + +// A type specifier. +template +struct TypeSpec : EmptySpec +{ + Alignment align() const + { + return ALIGN_DEFAULT; + } + unsigned width() const + { + return 0; + } + int precision() const + { + return -1; + } + bool flag(unsigned) const + { + return false; + } + char type() const + { + return TYPE; + } + char type_prefix() const + { + return TYPE; + } + char fill() const + { + return ' '; + } +}; + +// A width specifier. +struct WidthSpec +{ + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of WidthSpec and its subclasses. + wchar_t fill_; + + WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {} + + unsigned width() const + { + return width_; + } + wchar_t fill() const + { + return fill_; + } +}; + +// An alignment specifier. +struct AlignSpec : WidthSpec +{ + Alignment align_; + + AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) + : WidthSpec(width, fill), align_(align) {} + + Alignment align() const + { + return align_; + } + + int precision() const + { + return -1; + } +}; + +// An alignment and type specifier. +template +struct AlignTypeSpec : AlignSpec +{ + AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {} + + bool flag(unsigned) const + { + return false; + } + char type() const + { + return TYPE; + } + char type_prefix() const + { + return TYPE; + } +}; + +// A full format specifier. +struct FormatSpec : AlignSpec +{ + unsigned flags_; + int precision_; + char type_; + + FormatSpec( + unsigned width = 0, char type = 0, wchar_t fill = ' ') + : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {} + + bool flag(unsigned f) const + { + return (flags_ & f) != 0; + } + int precision() const + { + return precision_; + } + char type() const + { + return type_; + } + char type_prefix() const + { + return type_; + } +}; + +// An integer format specifier. +template , typename Char = char> +class IntFormatSpec : public SpecT +{ +private: + T value_; + +public: + IntFormatSpec(T val, const SpecT &spec = SpecT()) + : SpecT(spec), value_(val) {} + + T value() const + { + return value_; + } +}; + +// A string format specifier. +template +class StrFormatSpec : public AlignSpec +{ +private: + const Char *str_; + +public: + template + StrFormatSpec(const Char *str, unsigned width, FillChar fill) + : AlignSpec(width, fill), str_(str) + { + internal::CharTraits::convert(FillChar()); + } + + const Char *str() const + { + return str_; + } +}; + +/** + Returns an integer format specifier to format the value in base 2. + */ +IntFormatSpec > bin(int value); + +/** + Returns an integer format specifier to format the value in base 8. + */ +IntFormatSpec > oct(int value); + +/** + Returns an integer format specifier to format the value in base 16 using + lower-case letters for the digits above 9. + */ +IntFormatSpec > hex(int value); + +/** + Returns an integer formatter format specifier to format in base 16 using + upper-case letters for the digits above 9. + */ +IntFormatSpec > hexu(int value); + +/** + \rst + Returns an integer format specifier to pad the formatted argument with the + fill character to the specified width using the default (right) numeric + alignment. + + **Example**:: + + MemoryWriter out; + out << pad(hex(0xcafe), 8, '0'); + // out.str() == "0000cafe" + + \endrst + */ +template +IntFormatSpec, Char> pad( + int value, unsigned width, Char fill = ' '); + +#define FMT_DEFINE_INT_FORMATTERS(TYPE) \ +inline IntFormatSpec > bin(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'b'>()); \ +} \ + \ +inline IntFormatSpec > oct(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'o'>()); \ +} \ + \ +inline IntFormatSpec > hex(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'x'>()); \ +} \ + \ +inline IntFormatSpec > hexu(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'X'>()); \ +} \ + \ +template \ +inline IntFormatSpec > pad( \ + IntFormatSpec > f, unsigned width) { \ + return IntFormatSpec >( \ + f.value(), AlignTypeSpec(width, ' ')); \ +} \ + \ +/* For compatibility with older compilers we provide two overloads for pad, */ \ +/* one that takes a fill character and one that doesn't. In the future this */ \ +/* can be replaced with one overload making the template argument Char */ \ +/* default to char (C++11). */ \ +template \ +inline IntFormatSpec, Char> pad( \ + IntFormatSpec, Char> f, \ + unsigned width, Char fill) { \ + return IntFormatSpec, Char>( \ + f.value(), AlignTypeSpec(width, fill)); \ +} \ + \ +inline IntFormatSpec > pad( \ + TYPE value, unsigned width) { \ + return IntFormatSpec >( \ + value, AlignTypeSpec<0>(width, ' ')); \ +} \ + \ +template \ +inline IntFormatSpec, Char> pad( \ + TYPE value, unsigned width, Char fill) { \ + return IntFormatSpec, Char>( \ + value, AlignTypeSpec<0>(width, fill)); \ +} + +FMT_DEFINE_INT_FORMATTERS(int) +FMT_DEFINE_INT_FORMATTERS(long) +FMT_DEFINE_INT_FORMATTERS(unsigned) +FMT_DEFINE_INT_FORMATTERS(unsigned long) +FMT_DEFINE_INT_FORMATTERS(LongLong) +FMT_DEFINE_INT_FORMATTERS(ULongLong) + +/** + \rst + Returns a string formatter that pads the formatted argument with the fill + character to the specified width using the default (left) string alignment. + + **Example**:: + + std::string s = str(MemoryWriter() << pad("abc", 8)); + // s == "abc " + + \endrst + */ +template +inline StrFormatSpec pad( + const Char *str, unsigned width, Char fill = ' ') +{ + return StrFormatSpec(str, width, fill); +} + +inline StrFormatSpec pad( + const wchar_t *str, unsigned width, char fill = ' ') +{ + return StrFormatSpec(str, width, fill); +} + +namespace internal +{ + +template +class ArgMap +{ +private: + typedef std::vector< + std::pair, internal::Arg> > MapType; + typedef typename MapType::value_type Pair; + + MapType map_; + +public: + FMT_API void init(const ArgList &args); + + const internal::Arg *find(const fmt::BasicStringRef &name) const + { + // The list is unsorted, so just return the first matching name. + for (typename MapType::const_iterator it = map_.begin(), end = map_.end(); + it != end; ++it) + { + if (it->first == name) + return &it->second; + } + return FMT_NULL; + } +}; + +template +class ArgFormatterBase : public ArgVisitor +{ +private: + BasicWriter &writer_; + Spec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase); + + void write_pointer(const void *p) + { + spec_.flags_ = HASH_FLAG; + spec_.type_ = 'x'; + writer_.write_int(reinterpret_cast(p), spec_); + } + + // workaround MSVC two-phase lookup issue + typedef internal::Arg Arg; + +protected: + BasicWriter &writer() + { + return writer_; + } + Spec &spec() + { + return spec_; + } + + void write(bool value) + { + const char *str_value = value ? "true" : "false"; + Arg::StringValue str = { str_value, std::strlen(str_value) }; + writer_.write_str(str, spec_); + } + + void write(const char *value) + { + Arg::StringValue str = {value, value ? std::strlen(value) : 0}; + writer_.write_str(str, spec_); + } + +public: + typedef Spec SpecType; + + ArgFormatterBase(BasicWriter &w, Spec &s) + : writer_(w), spec_(s) {} + + template + void visit_any_int(T value) + { + writer_.write_int(value, spec_); + } + + template + void visit_any_double(T value) + { + writer_.write_double(value, spec_); + } + + void visit_bool(bool value) + { + if (spec_.type_) + { + visit_any_int(value); + return; + } + write(value); + } + + void visit_char(int value) + { + if (spec_.type_ && spec_.type_ != 'c') + { + spec_.flags_ |= CHAR_FLAG; + writer_.write_int(value, spec_); + return; + } + if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) + FMT_THROW(FormatError("invalid format specifier for char")); + typedef typename BasicWriter::CharPtr CharPtr; + Char fill = internal::CharTraits::cast(spec_.fill()); + CharPtr out = CharPtr(); + const unsigned CHAR_SIZE = 1; + if (spec_.width_ > CHAR_SIZE) + { + out = writer_.grow_buffer(spec_.width_); + if (spec_.align_ == ALIGN_RIGHT) + { + std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill); + out += spec_.width_ - CHAR_SIZE; + } + else if (spec_.align_ == ALIGN_CENTER) + { + out = writer_.fill_padding(out, spec_.width_, + internal::const_check(CHAR_SIZE), fill); + } + else + { + std::uninitialized_fill_n(out + CHAR_SIZE, + spec_.width_ - CHAR_SIZE, fill); + } + } + else + { + out = writer_.grow_buffer(CHAR_SIZE); + } + *out = internal::CharTraits::cast(value); + } + + void visit_cstring(const char *value) + { + if (spec_.type_ == 'p') + return write_pointer(value); + write(value); + } + + // Qualification with "internal" here and below is a workaround for nvcc. + void visit_string(internal::Arg::StringValue value) + { + writer_.write_str(value, spec_); + } + + using ArgVisitor::visit_wstring; + + void visit_wstring(internal::Arg::StringValue value) + { + writer_.write_str(value, spec_); + } + + void visit_pointer(const void *value) + { + if (spec_.type_ && spec_.type_ != 'p') + report_unknown_type(spec_.type_, "pointer"); + write_pointer(value); + } +}; + +class FormatterBase +{ +private: + ArgList args_; + int next_arg_index_; + + // Returns the argument with specified index. + FMT_API Arg do_get_arg(unsigned arg_index, const char *&error); + +protected: + const ArgList &args() const + { + return args_; + } + + explicit FormatterBase(const ArgList &args) + { + args_ = args; + next_arg_index_ = 0; + } + + // Returns the next argument. + Arg next_arg(const char *&error) + { + if (next_arg_index_ >= 0) + return do_get_arg(internal::to_unsigned(next_arg_index_++), error); + error = "cannot switch from manual to automatic argument indexing"; + return Arg(); + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + Arg get_arg(unsigned arg_index, const char *&error) + { + return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg(); + } + + bool check_no_auto_index(const char *&error) + { + if (next_arg_index_ > 0) + { + error = "cannot switch from automatic to manual argument indexing"; + return false; + } + next_arg_index_ = -1; + return true; + } + + template + void write(BasicWriter &w, const Char *start, const Char *end) + { + if (start != end) + w << BasicStringRef(start, internal::to_unsigned(end - start)); + } +}; +} // namespace internal + +/** + \rst + An argument formatter based on the `curiously recurring template pattern + `_. + + To use `~fmt::BasicArgFormatter` define a subclass that implements some or + all of the visit methods with the same signatures as the methods in + `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. When a formatting + function processes an argument, it will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::BasicArgFormatter` or its superclass + will be called. + \endrst + */ +template +class BasicArgFormatter : public internal::ArgFormatterBase +{ +private: + BasicFormatter &formatter_; + const Char *format_; + +public: + /** + \rst + Constructs an argument formatter object. + *formatter* is a reference to the main formatter object, *spec* contains + format specifier information for standard argument types, and *fmt* points + to the part of the format string being parsed for custom argument types. + \endrst + */ + BasicArgFormatter(BasicFormatter &formatter, + Spec &spec, const Char *fmt) + : internal::ArgFormatterBase(formatter.writer(), spec), + formatter_(formatter), format_(fmt) {} + + /** Formats an argument of a custom (user-defined) type. */ + void visit_custom(internal::Arg::CustomValue c) + { + c.format(&formatter_, c.value, &format_); + } +}; + +/** The default argument formatter. */ +template +class ArgFormatter : + public BasicArgFormatter, Char, FormatSpec> +{ +public: + /** Constructs an argument formatter object. */ + ArgFormatter(BasicFormatter &formatter, + FormatSpec &spec, const Char *fmt) + : BasicArgFormatter, + Char, FormatSpec>(formatter, spec, fmt) {} +}; + +/** This template formats data and writes the output to a writer. */ +template +class BasicFormatter : private internal::FormatterBase +{ +public: + /** The character type for the output. */ + typedef CharType Char; + +private: + BasicWriter &writer_; + internal::ArgMap map_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter); + + using internal::FormatterBase::get_arg; + + // Checks if manual indexing is used and returns the argument with + // specified name. + internal::Arg get_arg(BasicStringRef arg_name, const char *&error); + + // Parses argument index and returns corresponding argument. + internal::Arg parse_arg_index(const Char *&s); + + // Parses argument name and returns corresponding argument. + internal::Arg parse_arg_name(const Char *&s); + +public: + /** + \rst + Constructs a ``BasicFormatter`` object. References to the arguments and + the writer are stored in the formatter object so make sure they have + appropriate lifetimes. + \endrst + */ + BasicFormatter(const ArgList &args, BasicWriter &w) + : internal::FormatterBase(args), writer_(w) {} + + /** Returns a reference to the writer associated with this formatter. */ + BasicWriter &writer() + { + return writer_; + } + + /** Formats stored arguments and writes the output to the writer. */ + void format(BasicCStringRef format_str); + + // Formats a single argument and advances format_str, a format string pointer. + const Char *format(const Char *&format_str, const internal::Arg &arg); +}; + +// Generates a comma-separated list with results of applying f to +// numbers 0..n-1. +# define FMT_GEN(n, f) FMT_GEN##n(f) +# define FMT_GEN1(f) f(0) +# define FMT_GEN2(f) FMT_GEN1(f), f(1) +# define FMT_GEN3(f) FMT_GEN2(f), f(2) +# define FMT_GEN4(f) FMT_GEN3(f), f(3) +# define FMT_GEN5(f) FMT_GEN4(f), f(4) +# define FMT_GEN6(f) FMT_GEN5(f), f(5) +# define FMT_GEN7(f) FMT_GEN6(f), f(6) +# define FMT_GEN8(f) FMT_GEN7(f), f(7) +# define FMT_GEN9(f) FMT_GEN8(f), f(8) +# define FMT_GEN10(f) FMT_GEN9(f), f(9) +# define FMT_GEN11(f) FMT_GEN10(f), f(10) +# define FMT_GEN12(f) FMT_GEN11(f), f(11) +# define FMT_GEN13(f) FMT_GEN12(f), f(12) +# define FMT_GEN14(f) FMT_GEN13(f), f(13) +# define FMT_GEN15(f) FMT_GEN14(f), f(14) + +namespace internal +{ +inline uint64_t make_type() +{ + return 0; +} + +template +inline uint64_t make_type(const T &arg) +{ + return MakeValue< BasicFormatter >::type(arg); +} + +template + struct ArgArray; + +template +struct ArgArray +{ + typedef Value Type[N > 0 ? N : 1]; + +template +static Value make(const T &value) +{ +#ifdef __clang__ + Value result = MakeValue(value); + // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang: + // https://github.com/fmtlib/fmt/issues/276 + (void)result.custom.format; + return result; +#else + return MakeValue(value); +#endif +} + }; + +template +struct ArgArray +{ + typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE + + template + static Arg make(const T &value) + { + return MakeArg(value); + } +}; + +#if FMT_USE_VARIADIC_TEMPLATES +template +inline uint64_t make_type(const Arg &first, const Args & ... tail) +{ + return make_type(first) | (make_type(tail...) << 4); +} + +#else + +struct ArgType +{ + uint64_t type; + + ArgType() : type(0) {} + + template + ArgType(const T &arg) : type(make_type(arg)) {} +}; + +# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType() + +inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) +{ + return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) | + (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) | + (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) | + (t12.type << 48) | (t13.type << 52) | (t14.type << 56); +} +#endif +} // namespace internal + +# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n +# define FMT_MAKE_ARG_TYPE(n) T##n +# define FMT_MAKE_ARG(n) const T##n &v##n +# define FMT_ASSIGN_char(n) \ + arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) +# define FMT_ASSIGN_wchar_t(n) \ + arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) + +#if FMT_USE_VARIADIC_TEMPLATES +// Defines a variadic function returning void. +# define FMT_VARIADIC_VOID(func, arg_type) \ + template \ + void func(arg_type arg0, const Args & ... args) { \ + typedef fmt::internal::ArgArray ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make >(args)...}; \ + func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } + +// Defines a variadic constructor. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + template \ + ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ + typedef fmt::internal::ArgArray ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make >(args)...}; \ + func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } + +#else + +# define FMT_MAKE_REF(n) \ + fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) +# define FMT_MAKE_REF2(n) v##n + +// Defines a wrapper for a function taking one argument of type arg_type +// and n additional arguments of arbitrary types. +# define FMT_WRAP1(func, arg_type, n) \ + template \ + inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::ArgArray::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ + } + +// Emulates a variadic function returning void on a pre-C++11 compiler. +# define FMT_VARIADIC_VOID(func, arg_type) \ + inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \ + FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ + FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ + FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ + FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ + FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) + +# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ + template \ + ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::ArgArray::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg0, arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ + } + +// Emulates a variadic constructor on a pre-C++11 compiler. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) +#endif + +// Generates a comma-separated list with results of applying f to pairs +// (argument, index). +#define FMT_FOR_EACH1(f, x0) f(x0, 0) +#define FMT_FOR_EACH2(f, x0, x1) \ + FMT_FOR_EACH1(f, x0), f(x1, 1) +#define FMT_FOR_EACH3(f, x0, x1, x2) \ + FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) +#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ + FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) +#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ + FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) +#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ + FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) +#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ + FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) +#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ + FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) +#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ + FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) +#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ + FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) + +/** + An error returned by an operating system or a language runtime, + for example a file opening error. +*/ +class SystemError : public internal::RuntimeError +{ +private: + FMT_API void init(int err_code, CStringRef format_str, ArgList args); + +protected: + int error_code_; + + typedef char Char; // For FMT_VARIADIC_CTOR. + + SystemError() {} + +public: + /** + \rst + Constructs a :class:`fmt::SystemError` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a SystemError with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::SystemError(errno, "cannot open file '{}'", filename); + \endrst + */ + SystemError(int error_code, CStringRef message) + { + init(error_code, message, ArgList()); + } + FMT_DEFAULTED_COPY_CTOR(SystemError) + FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef) + + FMT_API ~SystemError() FMT_DTOR_NOEXCEPT; + + int error_code() const + { + return error_code_; + } +}; + +/** + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + **: ** + + where ** is the passed message and ** is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; + +/** + \rst + This template provides operations for formatting and writing data into + a character stream. The output is stored in a buffer provided by a subclass + such as :class:`fmt::BasicMemoryWriter`. + + You can use one of the following typedefs for common character types: + + +---------+----------------------+ + | Type | Definition | + +=========+======================+ + | Writer | BasicWriter | + +---------+----------------------+ + | WWriter | BasicWriter | + +---------+----------------------+ + + \endrst + */ +template +class BasicWriter +{ +private: + // Output buffer. + Buffer &buffer_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); + + typedef typename internal::CharTraits::CharPtr CharPtr; + +#if FMT_SECURE_SCL + // Returns pointer value. + static Char *get(CharPtr p) + { + return p.base(); + } +#else + static Char *get(Char *p) + { + return p; + } +#endif + + // Fills the padding around the content and returns the pointer to the + // content area. + static CharPtr fill_padding(CharPtr buffer, + unsigned total_size, std::size_t content_size, wchar_t fill); + + // Grows the buffer by n characters and returns a pointer to the newly + // allocated area. + CharPtr grow_buffer(std::size_t n) + { + std::size_t size = buffer_.size(); + buffer_.resize(size + n); + return internal::make_ptr(&buffer_[size], n); + } + + // Writes an unsigned decimal integer. + template + Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) + { + unsigned num_digits = internal::count_digits(value); + Char *ptr = get(grow_buffer(prefix_size + num_digits)); + internal::format_decimal(ptr + prefix_size, value, num_digits); + return ptr; + } + + // Writes a decimal integer. + template + void write_decimal(Int value) + { + typedef typename internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(value); + if (internal::is_negative(value)) + { + abs_value = 0 - abs_value; + *write_unsigned_decimal(abs_value, 1) = '-'; + } + else + { + write_unsigned_decimal(abs_value, 0); + } + } + + // Prepare a buffer for integer formatting. + CharPtr prepare_int_buffer(unsigned num_digits, + const EmptySpec &, const char *prefix, unsigned prefix_size) + { + unsigned size = prefix_size + num_digits; + CharPtr p = grow_buffer(size); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + + template + CharPtr prepare_int_buffer(unsigned num_digits, + const Spec &spec, const char *prefix, unsigned prefix_size); + + // Formats an integer. + template + void write_int(T value, Spec spec); + + // Formats a floating-point number (double or long double). + template + void write_double(T value, const Spec &spec); + + // Writes a formatted string. + template + CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec); + + template + void write_str(const internal::Arg::StringValue &str, + const Spec &spec); + + // This following methods are private to disallow writing wide characters + // and strings to a char stream. If you want to print a wide string as a + // pointer as std::ostream does, cast it to const void*. + // Do not implement! + void operator<<(typename internal::WCharHelper::Unsupported); + void operator<<( + typename internal::WCharHelper::Unsupported); + + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(Char *&format_ptr, long double) + { + *format_ptr++ = 'L'; + } + + template + void append_float_length(Char *&, T) {} + + template + friend class internal::ArgFormatterBase; + + template + friend class BasicPrintfArgFormatter; + +protected: + /** + Constructs a ``BasicWriter`` object. + */ + explicit BasicWriter(Buffer &b) : buffer_(b) {} + +public: + /** + \rst + Destroys a ``BasicWriter`` object. + \endrst + */ + virtual ~BasicWriter() {} + + /** + Returns the total number of characters written. + */ + std::size_t size() const + { + return buffer_.size(); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const Char *data() const FMT_NOEXCEPT + { + return &buffer_[0]; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const Char *c_str() const + { + std::size_t size = buffer_.size(); + buffer_.reserve(size + 1); + buffer_[size] = '\0'; + return &buffer_[0]; + } + + /** + \rst + Returns the content of the output buffer as an `std::string`. + \endrst + */ + std::basic_string str() const + { + return std::basic_string(&buffer_[0], buffer_.size()); + } + + /** + \rst + Writes formatted data. + + *args* is an argument list representing arbitrary arguments. + + **Example**:: + + MemoryWriter out; + out.write("Current point:\n"); + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + Current point: + (-3.140000, +3.140000) + + The output can be accessed using :func:`data()`, :func:`c_str` or + :func:`str` methods. + + See also :ref:`syntax`. + \endrst + */ + void write(BasicCStringRef format, ArgList args) + { + BasicFormatter(args, *this).format(format); + } + FMT_VARIADIC_VOID(write, BasicCStringRef) + + BasicWriter &operator<<(int value) + { + write_decimal(value); + return *this; + } + BasicWriter &operator<<(unsigned value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(long value) + { + write_decimal(value); + return *this; + } + BasicWriter &operator<<(unsigned long value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(LongLong value) + { + write_decimal(value); + return *this; + } + + /** + \rst + Formats *value* and writes it to the stream. + \endrst + */ + BasicWriter &operator<<(ULongLong value) + { + return *this << IntFormatSpec(value); + } + + BasicWriter &operator<<(double value) + { + write_double(value, FormatSpec()); + return *this; + } + + /** + \rst + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the stream. + \endrst + */ + BasicWriter &operator<<(long double value) + { + write_double(value, FormatSpec()); + return *this; + } + + /** + Writes a character to the stream. + */ + BasicWriter &operator<<(char value) + { + buffer_.push_back(value); + return *this; + } + + BasicWriter &operator<<( + typename internal::WCharHelper::Supported value) + { + buffer_.push_back(value); + return *this; + } + + /** + \rst + Writes *value* to the stream. + \endrst + */ + BasicWriter &operator<<(fmt::BasicStringRef value) + { + const Char *str = value.data(); + buffer_.append(str, str + value.size()); + return *this; + } + + BasicWriter &operator<<( + typename internal::WCharHelper::Supported value) + { + const char *str = value.data(); + buffer_.append(str, str + value.size()); + return *this; + } + + template + BasicWriter &operator<<(IntFormatSpec spec) + { + internal::CharTraits::convert(FillChar()); + write_int(spec.value(), spec); + return *this; + } + + template + BasicWriter &operator<<(const StrFormatSpec &spec) + { + const StrChar *s = spec.str(); + write_str(s, std::char_traits::length(s), spec); + return *this; + } + + void clear() FMT_NOEXCEPT { buffer_.clear(); } + + Buffer &buffer() FMT_NOEXCEPT { return buffer_; } +}; + +template +template +typename BasicWriter::CharPtr BasicWriter::write_str( + const StrChar *s, std::size_t size, const AlignSpec &spec) +{ + CharPtr out = CharPtr(); + if (spec.width() > size) + { + out = grow_buffer(spec.width()); + Char fill = internal::CharTraits::cast(spec.fill()); + if (spec.align() == ALIGN_RIGHT) + { + std::uninitialized_fill_n(out, spec.width() - size, fill); + out += spec.width() - size; + } + else if (spec.align() == ALIGN_CENTER) + { + out = fill_padding(out, spec.width(), size, fill); + } + else + { + std::uninitialized_fill_n(out + size, spec.width() - size, fill); + } + } + else + { + out = grow_buffer(size); + } + std::uninitialized_copy(s, s + size, out); + return out; +} + +template +template +void BasicWriter::write_str( + const internal::Arg::StringValue &s, const Spec &spec) +{ + // Check if StrChar is convertible to Char. + internal::CharTraits::convert(StrChar()); + if (spec.type_ && spec.type_ != 's') + internal::report_unknown_type(spec.type_, "string"); + const StrChar *str_value = s.value; + std::size_t str_size = s.size; + if (str_size == 0) + { + if (!str_value) + { + FMT_THROW(FormatError("string pointer is null")); + } + } + std::size_t precision = static_cast(spec.precision_); + if (spec.precision_ >= 0 && precision < str_size) + str_size = precision; + write_str(str_value, str_size, spec); +} + +template +typename BasicWriter::CharPtr +BasicWriter::fill_padding( + CharPtr buffer, unsigned total_size, + std::size_t content_size, wchar_t fill) +{ + std::size_t padding = total_size - content_size; + std::size_t left_padding = padding / 2; + Char fill_char = internal::CharTraits::cast(fill); + std::uninitialized_fill_n(buffer, left_padding, fill_char); + buffer += left_padding; + CharPtr content = buffer; + std::uninitialized_fill_n(buffer + content_size, + padding - left_padding, fill_char); + return content; +} + +template +template +typename BasicWriter::CharPtr +BasicWriter::prepare_int_buffer( + unsigned num_digits, const Spec &spec, + const char *prefix, unsigned prefix_size) +{ + unsigned width = spec.width(); + Alignment align = spec.align(); + Char fill = internal::CharTraits::cast(spec.fill()); + if (spec.precision() > static_cast(num_digits)) + { + // Octal prefix '0' is counted as a digit, so ignore it if precision + // is specified. + if (prefix_size > 0 && prefix[prefix_size - 1] == '0') + --prefix_size; + unsigned number_size = + prefix_size + internal::to_unsigned(spec.precision()); + AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); + if (number_size >= width) + return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); + buffer_.reserve(width); + unsigned fill_size = width - number_size; + if (align != ALIGN_LEFT) + { + CharPtr p = grow_buffer(fill_size); + std::uninitialized_fill(p, p + fill_size, fill); + } + CharPtr result = prepare_int_buffer( + num_digits, subspec, prefix, prefix_size); + if (align == ALIGN_LEFT) + { + CharPtr p = grow_buffer(fill_size); + std::uninitialized_fill(p, p + fill_size, fill); + } + return result; + } + unsigned size = prefix_size + num_digits; + if (width <= size) + { + CharPtr p = grow_buffer(size); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + CharPtr p = grow_buffer(width); + CharPtr end = p + width; + if (align == ALIGN_LEFT) + { + std::uninitialized_copy(prefix, prefix + prefix_size, p); + p += size; + std::uninitialized_fill(p, end, fill); + } + else if (align == ALIGN_CENTER) + { + p = fill_padding(p, width, size, fill); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + p += size; + } + else + { + if (align == ALIGN_NUMERIC) + { + if (prefix_size != 0) + { + p = std::uninitialized_copy(prefix, prefix + prefix_size, p); + size -= prefix_size; + } + } + else + { + std::uninitialized_copy(prefix, prefix + prefix_size, end - size); + } + std::uninitialized_fill(p, end - size, fill); + p = end; + } + return p - 1; +} + +template +template +void BasicWriter::write_int(T value, Spec spec) +{ + unsigned prefix_size = 0; + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType abs_value = static_cast(value); + char prefix[4] = ""; + if (internal::is_negative(value)) + { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } + else if (spec.flag(SIGN_FLAG)) + { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + switch (spec.type()) + { + case 0: + case 'd': + { + unsigned num_digits = internal::count_digits(abs_value); + CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0); + break; + } + case 'x': + case 'X': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type_prefix(); + } + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 4) != 0); + Char *p = get(prepare_int_buffer( + num_digits, spec, prefix, prefix_size)); + n = abs_value; + const char *digits = spec.type() == 'x' ? + "0123456789abcdef" : "0123456789ABCDEF"; + do + { + *p-- = digits[n & 0xf]; + } + while ((n >>= 4) != 0); + break; + } + case 'b': + case 'B': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type_prefix(); + } + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 1) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do + { + *p-- = static_cast('0' + (n & 1)); + } + while ((n >>= 1) != 0); + break; + } + case 'o': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + prefix[prefix_size++] = '0'; + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 3) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do + { + *p-- = static_cast('0' + (n & 7)); + } + while ((n >>= 3) != 0); + break; + } + case 'n': + { + unsigned num_digits = internal::count_digits(abs_value); + fmt::StringRef sep = ""; +#if !(defined(ANDROID) || defined(__ANDROID__)) + sep = internal::thousands_sep(std::localeconv()); +#endif + unsigned size = static_cast( + num_digits + sep.size() * ((num_digits - 1) / 3)); + CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep)); + break; + } + default: + internal::report_unknown_type( + spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); + break; + } +} + +template +template +void BasicWriter::write_double(T value, const Spec &spec) +{ + // Check type. + char type = spec.type(); + bool upper = false; + switch (type) + { + case 0: + type = 'g'; + break; + case 'e': + case 'f': + case 'g': + case 'a': + break; + case 'F': +#if FMT_MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + // Fall through. + case 'E': + case 'G': + case 'A': + upper = true; + break; + default: + internal::report_unknown_type(type, "double"); + break; + } + + char sign = 0; + // Use isnegative instead of value < 0 because the latter is always + // false for NaN. + if (internal::FPUtil::isnegative(static_cast(value))) + { + sign = '-'; + value = -value; + } + else if (spec.flag(SIGN_FLAG)) + { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + if (internal::FPUtil::isnotanumber(value)) + { + // Format NaN ourselves because sprintf's output is not consistent + // across platforms. + std::size_t nan_size = 4; + const char *nan = upper ? " NAN" : " nan"; + if (!sign) + { + --nan_size; + ++nan; + } + CharPtr out = write_str(nan, nan_size, spec); + if (sign) + *out = sign; + return; + } + + if (internal::FPUtil::isinfinity(value)) + { + // Format infinity ourselves because sprintf's output is not consistent + // across platforms. + std::size_t inf_size = 4; + const char *inf = upper ? " INF" : " inf"; + if (!sign) + { + --inf_size; + ++inf; + } + CharPtr out = write_str(inf, inf_size, spec); + if (sign) + *out = sign; + return; + } + + std::size_t offset = buffer_.size(); + unsigned width = spec.width(); + if (sign) + { + buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u)); + if (width > 0) + --width; + ++offset; + } + + // Build format string. + enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg + Char format[MAX_FORMAT_SIZE]; + Char *format_ptr = format; + *format_ptr++ = '%'; + unsigned width_for_sprintf = width; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.align() == ALIGN_CENTER) + { + width_for_sprintf = 0; + } + else + { + if (spec.align() == ALIGN_LEFT) + *format_ptr++ = '-'; + if (width != 0) + *format_ptr++ = '*'; + } + if (spec.precision() >= 0) + { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + + append_float_length(format_ptr, value); + *format_ptr++ = type; + *format_ptr = '\0'; + + // Format using snprintf. + Char fill = internal::CharTraits::cast(spec.fill()); + unsigned n = 0; + Char *start = FMT_NULL; + for (;;) + { + std::size_t buffer_size = buffer_.capacity() - offset; +#if FMT_MSC_VER + // MSVC's vsnprintf_s doesn't work with zero size, so reserve + // space for at least one extra character to make the size non-zero. + // Note that the buffer's capacity will increase by more than 1. + if (buffer_size == 0) + { + buffer_.reserve(offset + 1); + buffer_size = buffer_.capacity() - offset; + } +#endif + start = &buffer_[offset]; + int result = internal::CharTraits::format_float( + start, buffer_size, format, width_for_sprintf, spec.precision(), value); + if (result >= 0) + { + n = internal::to_unsigned(result); + if (offset + n < buffer_.capacity()) + break; // The buffer is large enough - continue with formatting. + buffer_.reserve(offset + n + 1); + } + else + { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer_.reserve(buffer_.capacity() + 1); + } + } + if (sign) + { + if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || + *start != ' ') + { + *(start - 1) = sign; + sign = 0; + } + else + { + *(start - 1) = fill; + } + ++n; + } + if (spec.align() == ALIGN_CENTER && spec.width() > n) + { + width = spec.width(); + CharPtr p = grow_buffer(width); + std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char)); + fill_padding(p, spec.width(), n, fill); + return; + } + if (spec.fill() != ' ' || sign) + { + while (*start == ' ') + *start++ = fill; + if (sign) + *(start - 1) = sign; + } + grow_buffer(n); +} + +/** + \rst + This class template provides operations for formatting and writing data + into a character stream. The output is stored in a memory buffer that grows + dynamically. + + You can use one of the following typedefs for common character types + and the standard allocator: + + +---------------+-----------------------------------------------------+ + | Type | Definition | + +===============+=====================================================+ + | MemoryWriter | BasicMemoryWriter> | + +---------------+-----------------------------------------------------+ + | WMemoryWriter | BasicMemoryWriter> | + +---------------+-----------------------------------------------------+ + + **Example**:: + + MemoryWriter out; + out << "The answer is " << 42 << "\n"; + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42 + (-3.140000, +3.140000) + + The output can be converted to an ``std::string`` with ``out.str()`` or + accessed as a C string with ``out.c_str()``. + \endrst + */ +template > +class BasicMemoryWriter : public BasicWriter +{ +private: + internal::MemoryBuffer buffer_; + +public: + explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) + : BasicWriter(buffer_), buffer_(alloc) {} + +#if FMT_USE_RVALUE_REFERENCES + /** + \rst + Constructs a :class:`fmt::BasicMemoryWriter` object moving the content + of the other object to it. + \endrst + */ + BasicMemoryWriter(BasicMemoryWriter &&other) + : BasicWriter(buffer_), buffer_(std::move(other.buffer_)) + { + } + + /** + \rst + Moves the content of the other ``BasicMemoryWriter`` object to this one. + \endrst + */ + BasicMemoryWriter &operator=(BasicMemoryWriter &&other) + { + buffer_ = std::move(other.buffer_); + return *this; + } +#endif +}; + +typedef BasicMemoryWriter MemoryWriter; +typedef BasicMemoryWriter WMemoryWriter; + +/** + \rst + This class template provides operations for formatting and writing data + into a fixed-size array. For writing into a dynamically growing buffer + use :class:`fmt::BasicMemoryWriter`. + + Any write method will throw ``std::runtime_error`` if the output doesn't fit + into the array. + + You can use one of the following typedefs for common character types: + + +--------------+---------------------------+ + | Type | Definition | + +==============+===========================+ + | ArrayWriter | BasicArrayWriter | + +--------------+---------------------------+ + | WArrayWriter | BasicArrayWriter | + +--------------+---------------------------+ + \endrst + */ +template +class BasicArrayWriter : public BasicWriter +{ +private: + internal::FixedBuffer buffer_; + +public: + /** + \rst + Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the + given size. + \endrst + */ + BasicArrayWriter(Char *array, std::size_t size) + : BasicWriter(buffer_), buffer_(array, size) {} + + /** + \rst + Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the + size known at compile time. + \endrst + */ + template + explicit BasicArrayWriter(Char (&array)[SIZE]) + : BasicWriter(buffer_), buffer_(array, SIZE) {} +}; + +typedef BasicArrayWriter ArrayWriter; +typedef BasicArrayWriter WArrayWriter; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, + StringRef message) FMT_NOEXCEPT; + +#if FMT_USE_WINDOWS_H + +/** A Windows error. */ +class WindowsError : public SystemError +{ +private: + FMT_API void init(int error_code, CStringRef format_str, ArgList args); + +public: + /** + \rst + Constructs a :class:`fmt::WindowsError` object with the description + of the form + + .. parsed-literal:: + **: ** + + where ** is the formatted message and ** is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a WindowsError with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::WindowsError(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + WindowsError(int error_code, CStringRef message) + { + init(error_code, message, ArgList()); + } + FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef) +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, + StringRef message) FMT_NOEXCEPT; + +#endif + +enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; + +/** + Formats a string and prints it to stdout using ANSI escape sequences + to specify color (experimental). + Example: + print_colored(fmt::RED, "Elapsed time: {0:.2f} seconds", 1.23); + */ +FMT_API void print_colored(Color c, CStringRef format, ArgList args); + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = format("The answer is {}", 42); + \endrst +*/ +inline std::string format(CStringRef format_str, ArgList args) +{ + MemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +inline std::wstring format(WCStringRef format_str, ArgList args) +{ + WMemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + print(stderr, "Don't {}!", "panic"); + \endrst + */ +FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +FMT_API void print(CStringRef format_str, ArgList args); + +/** + Fast integer formatter. + */ +class FormatInt +{ +private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum {BUFFER_SIZE = std::numeric_limits::digits10 + 3}; + mutable char buffer_[BUFFER_SIZE]; + char *str_; + + // Formats value in reverse and returns the number of digits. + char *format_decimal(ULongLong value) + { + char *buffer_end = buffer_ + BUFFER_SIZE - 1; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast((value % 100) * 2); + value /= 100; + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + } + if (value < 10) + { + *--buffer_end = static_cast('0' + value); + return buffer_end; + } + unsigned index = static_cast(value * 2); + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + return buffer_end; + } + + void FormatSigned(LongLong value) + { + ULongLong abs_value = static_cast(value); + bool negative = value < 0; + if (negative) + abs_value = 0 - abs_value; + str_ = format_decimal(abs_value); + if (negative) + *--str_ = '-'; + } + +public: + explicit FormatInt(int value) + { + FormatSigned(value); + } + explicit FormatInt(long value) + { + FormatSigned(value); + } + explicit FormatInt(LongLong value) + { + FormatSigned(value); + } + explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} + explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} + explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {} + + /** Returns the number of characters written to the output buffer. */ + std::size_t size() const + { + return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char *data() const + { + return str_; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char *c_str() const + { + buffer_[BUFFER_SIZE - 1] = '\0'; + return str_; + } + + /** + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ + std::string str() const + { + return std::string(str_, size()); + } +}; + +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template +inline void format_decimal(char *&buffer, T value) +{ + typedef typename internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(value); + if (internal::is_negative(value)) + { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + if (abs_value < 100) + { + if (abs_value < 10) + { + *buffer++ = static_cast('0' + abs_value); + return; + } + unsigned index = static_cast(abs_value * 2); + *buffer++ = internal::Data::DIGITS[index]; + *buffer++ = internal::Data::DIGITS[index + 1]; + return; + } + unsigned num_digits = internal::count_digits(abs_value); + internal::format_decimal(buffer, abs_value, num_digits); + buffer += num_digits; +} + +/** + \rst + Returns a named argument for formatting functions. + + **Example**:: + + print("Elapsed time: {s:.2f} seconds", arg("s", 1.23)); + + \endrst + */ +template +inline internal::NamedArgWithType arg(StringRef name, const T &arg) +{ + return internal::NamedArgWithType(name, arg); +} + +template +inline internal::NamedArgWithType arg(WStringRef name, const T &arg) +{ + return internal::NamedArgWithType(name, arg); +} + +// The following two functions are deleted intentionally to disable +// nested named arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template +void arg(StringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; +template +void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; +} + +#if FMT_GCC_VERSION +// Use the system_header pragma to suppress warnings about variadic macros +// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't +// work. It is used at the end because we want to suppress as little warnings +// as possible. +# pragma GCC system_header +#endif + +// This is used to work around VC++ bugs in handling variadic macros. +#define FMT_EXPAND(args) args + +// Returns the number of arguments. +// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. +#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) +#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) +#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define FMT_FOR_EACH_(N, f, ...) \ + FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) +#define FMT_FOR_EACH(f, ...) \ + FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) + +#define FMT_ADD_ARG_NAME(type, index) type arg##index +#define FMT_GET_ARG_NAME(type, index) arg##index + +#if FMT_USE_VARIADIC_TEMPLATES +# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ + template \ + ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + const Args & ... args) { \ + typedef fmt::internal::ArgArray ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make >(args)...}; \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ + fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } +#else +// Defines a wrapper for a function taking __VA_ARGS__ arguments +// and n additional arguments of arbitrary types. +# define FMT_WRAP(Char, ReturnType, func, call, n, ...) \ + template \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + FMT_GEN(n, FMT_MAKE_ARG)) { \ + fmt::internal::ArgArray::Type arr; \ + FMT_GEN(n, FMT_ASSIGN_##Char); \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \ + } + +# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) { \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ + } \ + FMT_WRAP(Char, ReturnType, func, call, 1, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 2, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 3, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 4, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 5, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 6, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 7, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 8, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 9, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 10, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 11, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 12, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 13, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 14, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 15, __VA_ARGS__) +#endif // FMT_USE_VARIADIC_TEMPLATES + +/** + \rst + Defines a variadic function with the specified return type, function name + and argument types passed as variable arguments to this macro. + + **Example**:: + + void print_error(const char *file, int line, const char *format, + fmt::ArgList args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args); + } + FMT_VARIADIC(void, print_error, const char *, int, const char *) + + ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that + don't implement variadic templates. You don't have to use this macro if + you don't need legacy compiler support and can use variadic templates + directly:: + + template + void print_error(const char *file, int line, const char *format, + const Args & ... args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args...); + } + \endrst + */ +#define FMT_VARIADIC(ReturnType, func, ...) \ + FMT_VARIADIC_(char, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_W(ReturnType, func, ...) \ + FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) + +#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id) + +/** + \rst + Convenient macro to capture the arguments' names and values into several + ``fmt::arg(name, value)``. + + **Example**:: + + int x = 1, y = 2; + print("point: ({x}, {y})", FMT_CAPTURE(x, y)); + // same as: + // print("point: ({x}, {y})", arg("x", x), arg("y", y)); + + \endrst + */ +#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__) + +#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__) + +namespace fmt +{ +FMT_VARIADIC(std::string, format, CStringRef) +FMT_VARIADIC_W(std::wstring, format, WCStringRef) +FMT_VARIADIC(void, print, CStringRef) +FMT_VARIADIC(void, print, std::FILE *, CStringRef) +FMT_VARIADIC(void, print_colored, Color, CStringRef) + +namespace internal +{ +template +inline bool is_name_start(Char c) +{ + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +// Parses an unsigned integer advancing s to the end of the parsed input. +// This function assumes that the first character of s is a digit. +template +unsigned parse_nonnegative_int(const Char *&s) +{ + assert('0' <= *s && *s <= '9'); + unsigned value = 0; + do + { + unsigned new_value = value * 10 + (*s++ - '0'); + // Check if value wrapped around. + if (new_value < value) + { + value = (std::numeric_limits::max)(); + break; + } + value = new_value; + } + while ('0' <= *s && *s <= '9'); + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits::max)(); + if (value > max_int) + FMT_THROW(FormatError("number is too big")); + return value; +} + +inline void require_numeric_argument(const Arg &arg, char spec) +{ + if (arg.type > Arg::LAST_NUMERIC_TYPE) + { + std::string message = + fmt::format("format specifier '{}' requires numeric argument", spec); + FMT_THROW(fmt::FormatError(message)); + } +} + +template +void check_sign(const Char *&s, const Arg &arg) +{ + char sign = static_cast(*s); + require_numeric_argument(arg, sign); + if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) + { + FMT_THROW(FormatError(fmt::format( + "format specifier '{}' requires signed argument", sign))); + } + ++s; +} +} // namespace internal + +template +inline internal::Arg BasicFormatter::get_arg( + BasicStringRef arg_name, const char *&error) +{ + if (check_no_auto_index(error)) + { + map_.init(args()); + const internal::Arg *arg = map_.find(arg_name); + if (arg) + return *arg; + error = "argument not found"; + } + return internal::Arg(); +} + +template +inline internal::Arg BasicFormatter::parse_arg_index(const Char *&s) +{ + const char *error = FMT_NULL; + internal::Arg arg = *s < '0' || *s > '9' ? + next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error); + if (error) + { + FMT_THROW(FormatError( + *s != '}' && *s != ':' ? "invalid format string" : error)); + } + return arg; +} + +template +inline internal::Arg BasicFormatter::parse_arg_name(const Char *&s) +{ + assert(internal::is_name_start(*s)); + const Char *start = s; + Char c; + do + { + c = *++s; + } + while (internal::is_name_start(c) || ('0' <= c && c <= '9')); + const char *error = FMT_NULL; + internal::Arg arg = get_arg(BasicStringRef(start, s - start), error); + if (error) + FMT_THROW(FormatError(error)); + return arg; +} + +template +const Char *BasicFormatter::format( + const Char *&format_str, const internal::Arg &arg) +{ + using internal::Arg; + const Char *s = format_str; + typename ArgFormatter::SpecType spec; + if (*s == ':') + { + if (arg.type == Arg::CUSTOM) + { + arg.custom.format(this, arg.custom.value, &s); + return s; + } + ++s; + // Parse fill and alignment. + if (Char c = *s) + { + const Char *p = s + 1; + spec.align_ = ALIGN_DEFAULT; + do + { + switch (*p) + { + case '<': + spec.align_ = ALIGN_LEFT; + break; + case '>': + spec.align_ = ALIGN_RIGHT; + break; + case '=': + spec.align_ = ALIGN_NUMERIC; + break; + case '^': + spec.align_ = ALIGN_CENTER; + break; + } + if (spec.align_ != ALIGN_DEFAULT) + { + if (p != s) + { + if (c == '}') break; + if (c == '{') + FMT_THROW(FormatError("invalid fill character '{'")); + s += 2; + spec.fill_ = c; + } + else ++s; + if (spec.align_ == ALIGN_NUMERIC) + require_numeric_argument(arg, '='); + break; + } + } + while (--p >= s); + } + + // Parse sign. + switch (*s) + { + case '+': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '-': + check_sign(s, arg); + spec.flags_ |= MINUS_FLAG; + break; + case ' ': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG; + break; + } + + if (*s == '#') + { + require_numeric_argument(arg, '#'); + spec.flags_ |= HASH_FLAG; + ++s; + } + + // Parse zero flag. + if (*s == '0') + { + require_numeric_argument(arg, '0'); + spec.align_ = ALIGN_NUMERIC; + spec.fill_ = '0'; + ++s; + } + + // Parse width. + if ('0' <= *s && *s <= '9') + { + spec.width_ = internal::parse_nonnegative_int(s); + } + else if (*s == '{') + { + ++s; + Arg width_arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (width_arg.type) + { + case Arg::INT: + if (width_arg.int_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.int_value; + break; + case Arg::UINT: + value = width_arg.uint_value; + break; + case Arg::LONG_LONG: + if (width_arg.long_long_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = width_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("width is not integer")); + } + if (value > (std::numeric_limits::max)()) + FMT_THROW(FormatError("number is too big")); + spec.width_ = static_cast(value); + } + + // Parse precision. + if (*s == '.') + { + ++s; + spec.precision_ = 0; + if ('0' <= *s && *s <= '9') + { + spec.precision_ = internal::parse_nonnegative_int(s); + } + else if (*s == '{') + { + ++s; + Arg precision_arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (precision_arg.type) + { + case Arg::INT: + if (precision_arg.int_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.int_value; + break; + case Arg::UINT: + value = precision_arg.uint_value; + break; + case Arg::LONG_LONG: + if (precision_arg.long_long_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = precision_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("precision is not integer")); + } + if (value > (std::numeric_limits::max)()) + FMT_THROW(FormatError("number is too big")); + spec.precision_ = static_cast(value); + } + else + { + FMT_THROW(FormatError("missing precision specifier")); + } + if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) + { + FMT_THROW(FormatError( + fmt::format("precision not allowed in {} format specifier", + arg.type == Arg::POINTER ? "pointer" : "integer"))); + } + } + + // Parse type. + if (*s != '}' && *s) + spec.type_ = static_cast(*s++); + } + + if (*s++ != '}') + FMT_THROW(FormatError("missing '}' in format string")); + + // Format argument. + ArgFormatter(*this, spec, s - 1).visit(arg); + return s; +} + +template +void BasicFormatter::format(BasicCStringRef format_str) +{ + const Char *s = format_str.c_str(); + const Char *start = s; + while (*s) + { + Char c = *s++; + if (c != '{' && c != '}') continue; + if (*s == c) + { + write(writer_, start, s); + start = ++s; + continue; + } + if (c == '}') + FMT_THROW(FormatError("unmatched '}' in format string")); + write(writer_, start, s - 1); + internal::Arg arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + start = s = format(s, arg); + } + write(writer_, start, s); +} + +template +struct ArgJoin +{ + It first; + It last; + BasicCStringRef sep; + + ArgJoin(It first, It last, const BasicCStringRef& sep) : + first(first), + last(last), + sep(sep) {} +}; + +template +ArgJoin join(It first, It last, const BasicCStringRef& sep) +{ + return ArgJoin(first, last, sep); +} + +template +ArgJoin join(It first, It last, const BasicCStringRef& sep) +{ + return ArgJoin(first, last, sep); +} + +#if FMT_HAS_GXX_CXX11 +template +auto join(const Range& range, const BasicCStringRef& sep) +-> ArgJoin +{ + return join(std::begin(range), std::end(range), sep); +} + +template +auto join(const Range& range, const BasicCStringRef& sep) +-> ArgJoin +{ + return join(std::begin(range), std::end(range), sep); +} +#endif + +template +void format_arg(fmt::BasicFormatter &f, + const Char *&format_str, const ArgJoin& e) +{ + const Char* end = format_str; + if (*end == ':') + ++end; + while (*end && *end != '}') + ++end; + if (*end != '}') + FMT_THROW(FormatError("missing '}' in format string")); + + It it = e.first; + if (it != e.last) + { + const Char* save = format_str; + f.format(format_str, internal::MakeArg >(*it++)); + while (it != e.last) + { + f.writer().write(e.sep); + format_str = save; + f.format(format_str, internal::MakeArg >(*it++)); + } + } + format_str = end + 1; +} +} // namespace fmt + +#if FMT_USE_USER_DEFINED_LITERALS +namespace fmt +{ +namespace internal +{ + +template +struct UdlFormat +{ + const Char *str; + + template + auto operator()(Args && ... args) const + -> decltype(format(str, std::forward(args)...)) + { + return format(str, std::forward(args)...); + } +}; + +template +struct UdlArg +{ + const Char *str; + + template + NamedArgWithType operator=(T &&value) const + { + return {str, std::forward(value)}; + } +}; + +} // namespace internal + +inline namespace literals +{ + +/** + \rst + C++11 literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::UdlFormat +operator"" _format(const char *s, std::size_t) +{ + return {s}; +} +inline internal::UdlFormat +operator"" _format(const wchar_t *s, std::size_t) +{ + return {s}; +} + +/** + \rst + C++11 literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::UdlArg +operator"" _a(const char *s, std::size_t) +{ + return {s}; +} +inline internal::UdlArg +operator"" _a(const wchar_t *s, std::size_t) +{ + return {s}; +} + +} // inline namespace literals +} // namespace fmt +#endif // FMT_USE_USER_DEFINED_LITERALS + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic pop +#endif + +#if defined(__clang__) && !defined(FMT_ICC_VERSION) +# pragma clang diagnostic pop +#endif + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format.cc" +#else +# define FMT_FUNC +#endif + +#endif // FMT_FORMAT_H_ diff --git a/fpga/include/spdlog/fmt/bundled/ostream.cc b/fpga/include/spdlog/fmt/bundled/ostream.cc new file mode 100644 index 000000000..2d443f730 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/ostream.cc @@ -0,0 +1,35 @@ +/* + Formatting library for C++ - std::ostream support + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#include "ostream.h" + +namespace fmt { + +namespace internal { +FMT_FUNC void write(std::ostream &os, Writer &w) { + const char *data = w.data(); + typedef internal::MakeUnsigned::Type UnsignedStreamSize; + UnsignedStreamSize size = w.size(); + UnsignedStreamSize max_size = + internal::to_unsigned((std::numeric_limits::max)()); + do { + UnsignedStreamSize n = size <= max_size ? size : max_size; + os.write(data, static_cast(n)); + data += n; + size -= n; + } while (size != 0); +} +} + +FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + internal::write(os, w); +} +} // namespace fmt diff --git a/fpga/include/spdlog/fmt/bundled/ostream.h b/fpga/include/spdlog/fmt/bundled/ostream.h new file mode 100644 index 000000000..cfb8e0355 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/ostream.h @@ -0,0 +1,114 @@ +/* + Formatting library for C++ - std::ostream support + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#include "format.h" +#include + +namespace fmt +{ + +namespace internal +{ + +template +class FormatBuf : public std::basic_streambuf +{ +private: + typedef typename std::basic_streambuf::int_type int_type; + typedef typename std::basic_streambuf::traits_type traits_type; + + Buffer &buffer_; + +public: + FormatBuf(Buffer &buffer) : buffer_(buffer) {} + +protected: + // The put-area is actually always empty. This makes the implementation + // simpler and has the advantage that the streambuf and the buffer are always + // in sync and sputc never writes into uninitialized memory. The obvious + // disadvantage is that each call to sputc always results in a (virtual) call + // to overflow. There is no disadvantage here for sputn since this always + // results in a call to xsputn. + + int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE + { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE + { + buffer_.append(s, s + count); + return count; + } +}; + +Yes &convert(std::ostream &); + +struct DummyStream : std::ostream +{ + DummyStream(); // Suppress a bogus warning in MSVC. + // Hide all operator<< overloads from std::ostream. + void operator<<(Null<>); +}; + +No &operator<<(std::ostream &, int); + +template +struct ConvertToIntImpl +{ + // Convert to int only if T doesn't have an overloaded operator<<. + enum + { + value = sizeof(convert(get() << get())) == sizeof(No) + }; +}; + +// Write the content of w to os. +FMT_API void write(std::ostream &os, Writer &w); +} // namespace internal + +// Formats a value. +template +void format_arg(BasicFormatter &f, + const Char *&format_str, const T &value) +{ + internal::MemoryBuffer buffer; + + internal::FormatBuf format_buf(buffer); + std::basic_ostream output(&format_buf); + output << value; + + BasicStringRef str(&buffer[0], buffer.size()); + typedef internal::MakeArg< BasicFormatter > MakeArg; + format_str = f.format(format_str, MakeArg(str)); +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + print(cerr, "Don't {}!", "panic"); + \endrst + */ +FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args); +FMT_VARIADIC(void, print, std::ostream &, CStringRef) +} // namespace fmt + +#ifdef FMT_HEADER_ONLY +# include "ostream.cc" +#endif + +#endif // FMT_OSTREAM_H_ diff --git a/fpga/include/spdlog/fmt/bundled/posix.cc b/fpga/include/spdlog/fmt/bundled/posix.cc new file mode 100644 index 000000000..356668c13 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/posix.cc @@ -0,0 +1,241 @@ +/* + A C++ interface to POSIX functions. + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +// Disable bogus MSVC warnings. +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "posix.h" + +#include +#include +#include + +#ifndef _WIN32 +# include +#else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include + +# define O_CREAT _O_CREAT +# define O_TRUNC _O_TRUNC + +# ifndef S_IRUSR +# define S_IRUSR _S_IREAD +# endif + +# ifndef S_IWUSR +# define S_IWUSR _S_IWRITE +# endif + +# ifdef __MINGW32__ +# define _SH_DENYNO 0x40 +# endif + +#endif // _WIN32 + +#ifdef fileno +# undef fileno +#endif + +namespace { +#ifdef _WIN32 +// Return type of read and write functions. +typedef int RWResult; + +// On Windows the count argument to read and write is unsigned, so convert +// it from size_t preventing integer overflow. +inline unsigned convert_rwcount(std::size_t count) { + return count <= UINT_MAX ? static_cast(count) : UINT_MAX; +} +#else +// Return type of read and write functions. +typedef ssize_t RWResult; + +inline std::size_t convert_rwcount(std::size_t count) { return count; } +#endif +} + +fmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT { + if (file_ && FMT_SYSTEM(fclose(file_)) != 0) + fmt::report_system_error(errno, "cannot close file"); +} + +fmt::BufferedFile::BufferedFile( + fmt::CStringRef filename, fmt::CStringRef mode) { + FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0); + if (!file_) + FMT_THROW(SystemError(errno, "cannot open file {}", filename)); +} + +void fmt::BufferedFile::close() { + if (!file_) + return; + int result = FMT_SYSTEM(fclose(file_)); + file_ = FMT_NULL; + if (result != 0) + FMT_THROW(SystemError(errno, "cannot close file")); +} + +// A macro used to prevent expansion of fileno on broken versions of MinGW. +#define FMT_ARGS + +int fmt::BufferedFile::fileno() const { + int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_)); + if (fd == -1) + FMT_THROW(SystemError(errno, "cannot get file descriptor")); + return fd; +} + +fmt::File::File(fmt::CStringRef path, int oflag) { + int mode = S_IRUSR | S_IWUSR; +#if defined(_WIN32) && !defined(__MINGW32__) + fd_ = -1; + FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); +#else + FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); +#endif + if (fd_ == -1) + FMT_THROW(SystemError(errno, "cannot open file {}", path)); +} + +fmt::File::~File() FMT_NOEXCEPT { + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) + fmt::report_system_error(errno, "cannot close file"); +} + +void fmt::File::close() { + if (fd_ == -1) + return; + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + int result = FMT_POSIX_CALL(close(fd_)); + fd_ = -1; + if (result != 0) + FMT_THROW(SystemError(errno, "cannot close file")); +} + +fmt::LongLong fmt::File::size() const { +#ifdef _WIN32 + // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT + // is less than 0x0500 as is the case with some default MinGW builds. + // Both functions support large file sizes. + DWORD size_upper = 0; + HANDLE handle = reinterpret_cast(_get_osfhandle(fd_)); + DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper)); + if (size_lower == INVALID_FILE_SIZE) { + DWORD error = GetLastError(); + if (error != NO_ERROR) + FMT_THROW(WindowsError(GetLastError(), "cannot get file size")); + } + fmt::ULongLong long_size = size_upper; + return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; +#else + typedef struct stat Stat; + Stat file_stat = Stat(); + if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) + FMT_THROW(SystemError(errno, "cannot get file attributes")); + FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size), + "return type of File::size is not large enough"); + return file_stat.st_size; +#endif +} + +std::size_t fmt::File::read(void *buffer, std::size_t count) { + RWResult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(SystemError(errno, "cannot read from file")); + return internal::to_unsigned(result); +} + +std::size_t fmt::File::write(const void *buffer, std::size_t count) { + RWResult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(SystemError(errno, "cannot write to file")); + return internal::to_unsigned(result); +} + +fmt::File fmt::File::dup(int fd) { + // Don't retry as dup doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html + int new_fd = FMT_POSIX_CALL(dup(fd)); + if (new_fd == -1) + FMT_THROW(SystemError(errno, "cannot duplicate file descriptor {}", fd)); + return File(new_fd); +} + +void fmt::File::dup2(int fd) { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) { + FMT_THROW(SystemError(errno, + "cannot duplicate file descriptor {} to {}", fd_, fd)); + } +} + +void fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) + ec = ErrorCode(errno); +} + +void fmt::File::pipe(File &read_end, File &write_end) { + // Close the descriptors first to make sure that assignments don't throw + // and there are no leaks. + read_end.close(); + write_end.close(); + int fds[2] = {}; +#ifdef _WIN32 + // Make the default pipe capacity same as on Linux 2.6.11+. + enum { DEFAULT_CAPACITY = 65536 }; + int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); +#else + // Don't retry as the pipe function doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html + int result = FMT_POSIX_CALL(pipe(fds)); +#endif + if (result != 0) + FMT_THROW(SystemError(errno, "cannot create pipe")); + // The following assignments don't throw because read_fd and write_fd + // are closed. + read_end = File(fds[0]); + write_end = File(fds[1]); +} + +fmt::BufferedFile fmt::File::fdopen(const char *mode) { + // Don't retry as fdopen doesn't return EINTR. + FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode)); + if (!f) + FMT_THROW(SystemError(errno, "cannot associate stream with file descriptor")); + BufferedFile file(f); + fd_ = -1; + return file; +} + +long fmt::getpagesize() { +#ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); + if (size < 0) + FMT_THROW(SystemError(errno, "cannot get memory page size")); + return size; +#endif +} diff --git a/fpga/include/spdlog/fmt/bundled/posix.h b/fpga/include/spdlog/fmt/bundled/posix.h new file mode 100644 index 000000000..693272500 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/posix.h @@ -0,0 +1,424 @@ +/* + A C++ interface to POSIX functions. + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_POSIX_H_ +#define FMT_POSIX_H_ + +#if defined(__MINGW32__) || defined(__CYGWIN__) +// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. +# undef __STRICT_ANSI__ +#endif + +#include +#include // for O_RDONLY +#include // for locale_t +#include +#include // for strtod_l + +#include + +#if defined __APPLE__ || defined(__FreeBSD__) +# include // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +#ifndef FMT_POSIX +# if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +# define FMT_POSIX(call) _##call +# else +# define FMT_POSIX(call) call +# endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +# define FMT_SYSTEM(call) call +# ifdef _WIN32 +// Fix warnings about deprecated symbols. +# define FMT_POSIX_CALL(call) ::_##call +# else +# define FMT_POSIX_CALL(call) ::call +# endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +# define FMT_RETRY_VAL(result, expression, error_result) \ + do { \ + result = (expression); \ + } while (result == error_result && errno == EINTR) +#else +# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +namespace fmt +{ + +// An error code. +class ErrorCode +{ +private: + int value_; + +public: +explicit ErrorCode(int value = 0) FMT_NOEXCEPT : + value_(value) {} + + int get() const FMT_NOEXCEPT + { + return value_; + } +}; + +// A buffered file. +class BufferedFile +{ +private: + FILE *file_; + + friend class File; + + explicit BufferedFile(FILE *f) : file_(f) {} + +public: + // Constructs a BufferedFile object which doesn't represent any file. +BufferedFile() FMT_NOEXCEPT : + file_(FMT_NULL) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~BufferedFile() FMT_NOEXCEPT; + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + FILE *file; + }; + +public: + // A "move constructor" for moving from a temporary. +BufferedFile(Proxy p) FMT_NOEXCEPT : + file_(p.file) {} + + // A "move constructor" for moving from an lvalue. +BufferedFile(BufferedFile &f) FMT_NOEXCEPT : + file_(f.file_) + { + f.file_ = FMT_NULL; + } + + // A "move assignment operator" for moving from a temporary. + BufferedFile &operator=(Proxy p) + { + close(); + file_ = p.file; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + BufferedFile &operator=(BufferedFile &other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // BufferedFile file = BufferedFile(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {file_}; + file_ = FMT_NULL; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile); + +public: +BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : + file_(other.file_) + { + other.file_ = FMT_NULL; + } + + BufferedFile& operator=(BufferedFile &&other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } +#endif + + // Opens a file. + FMT_API BufferedFile(CStringRef filename, CStringRef mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + FILE *get() const FMT_NOEXCEPT + { + return file_; + } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + FMT_API int (fileno)() const; + + void print(CStringRef format_str, const ArgList &args) + { + fmt::print(file_, format_str, args); + } + FMT_VARIADIC(void, print, CStringRef) +}; + +// A file. Closed file is represented by a File object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::SystemError in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class File +{ +private: + int fd_; // File descriptor. + + // Constructs a File object with a given descriptor. + explicit File(int fd) : fd_(fd) {} + +public: + // Possible values for the oflag argument to the constructor. + enum + { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + }; + + // Constructs a File object which doesn't represent any file. +File() FMT_NOEXCEPT : + fd_(-1) {} + + // Opens a file and constructs a File object representing this file. + FMT_API File(CStringRef path, int oflag); + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + int fd; + }; + +public: + // A "move constructor" for moving from a temporary. +File(Proxy p) FMT_NOEXCEPT : + fd_(p.fd) {} + + // A "move constructor" for moving from an lvalue. +File(File &other) FMT_NOEXCEPT : + fd_(other.fd_) + { + other.fd_ = -1; + } + + // A "move assignment operator" for moving from a temporary. + File &operator=(Proxy p) + { + close(); + fd_ = p.fd; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + File &operator=(File &other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // File file = File(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {fd_}; + fd_ = -1; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(File); + +public: +File(File &&other) FMT_NOEXCEPT : + fd_(other.fd_) + { + other.fd_ = -1; + } + + File& operator=(File &&other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } +#endif + + // Destroys the object closing the file it represents if any. + FMT_API ~File() FMT_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT + { + return fd_; + } + + // Closes the file. + FMT_API void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + FMT_API LongLong size() const; + + // Attempts to read count bytes from the file into the specified buffer. + FMT_API std::size_t read(void *buffer, std::size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + FMT_API std::size_t write(const void *buffer, std::size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + FMT_API static File dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + FMT_API static void pipe(File &read_end, File &write_end); + + // Creates a BufferedFile object associated with this file and detaches + // this File object from the file. + FMT_API BufferedFile fdopen(const char *mode); +}; + +// Returns the memory page size. +long getpagesize(); + +#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \ + !defined(__ANDROID__) && !defined(__CYGWIN__) +# define FMT_LOCALE +#endif + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class Locale +{ +private: +# ifdef _MSC_VER + typedef _locale_t locale_t; + + enum { LC_NUMERIC_MASK = LC_NUMERIC }; + + static locale_t newlocale(int category_mask, const char *locale, locale_t) + { + return _create_locale(category_mask, locale); + } + + static void freelocale(locale_t locale) + { + _free_locale(locale); + } + + static double strtod_l(const char *nptr, char **endptr, _locale_t locale) + { + return _strtod_l(nptr, endptr, locale); + } +# endif + + locale_t locale_; + + FMT_DISALLOW_COPY_AND_ASSIGN(Locale); + +public: + typedef locale_t Type; + + Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) + { + if (!locale_) + FMT_THROW(fmt::SystemError(errno, "cannot create locale")); + } + ~Locale() + { + freelocale(locale_); + } + + Type get() const + { + return locale_; + } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char *&str) const + { + char *end = FMT_NULL; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +#endif // FMT_LOCALE +} // namespace fmt + +#if !FMT_USE_RVALUE_REFERENCES +namespace std +{ +// For compatibility with C++98. +inline fmt::BufferedFile &move(fmt::BufferedFile &f) +{ + return f; +} +inline fmt::File &move(fmt::File &f) +{ + return f; +} +} +#endif + +#endif // FMT_POSIX_H_ diff --git a/fpga/include/spdlog/fmt/bundled/printf.cc b/fpga/include/spdlog/fmt/bundled/printf.cc new file mode 100644 index 000000000..95d7a36ab --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/printf.cc @@ -0,0 +1,32 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#include "format.h" +#include "printf.h" + +namespace fmt { + +template +void printf(BasicWriter &w, BasicCStringRef format, ArgList args); + +FMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + std::size_t size = w.size(); + return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); +} + +#ifndef FMT_HEADER_ONLY + +template void PrintfFormatter::format(CStringRef format); +template void PrintfFormatter::format(WCStringRef format); + +#endif // FMT_HEADER_ONLY + +} // namespace fmt diff --git a/fpga/include/spdlog/fmt/bundled/printf.h b/fpga/include/spdlog/fmt/bundled/printf.h new file mode 100644 index 000000000..7861b460e --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/printf.h @@ -0,0 +1,712 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#include // std::fill_n +#include // std::numeric_limits + +#include "ostream.h" + +namespace fmt +{ +namespace internal +{ + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template +struct IntChecker +{ + template + static bool fits_in_int(T value) + { + unsigned max = std::numeric_limits::max(); + return value <= max; + } + static bool fits_in_int(bool) + { + return true; + } +}; + +template <> +struct IntChecker +{ + template + static bool fits_in_int(T value) + { + return value >= std::numeric_limits::min() && + value <= std::numeric_limits::max(); + } + static bool fits_in_int(int) + { + return true; + } +}; + +class PrecisionHandler : public ArgVisitor +{ +public: + void report_unhandled_arg() + { + FMT_THROW(FormatError("precision is not integer")); + } + + template + int visit_any_int(T value) + { + if (!IntChecker::is_signed>::fits_in_int(value)) + FMT_THROW(FormatError("number is too big")); + return static_cast(value); + } +}; + +// IsZeroInt::visit(arg) returns true iff arg is a zero integer. +class IsZeroInt : public ArgVisitor +{ +public: + template + bool visit_any_int(T value) + { + return value == 0; + } +}; + +// returns the default type for format specific "%s" +class DefaultType : public ArgVisitor +{ +public: + char visit_char(int) + { + return 'c'; + } + + char visit_bool(bool) + { + return 's'; + } + + char visit_pointer(const void *) + { + return 'p'; + } + + template + char visit_any_int(T) + { + return 'd'; + } + + template + char visit_any_double(T) + { + return 'g'; + } + + char visit_unhandled_arg() + { + return 's'; + } +}; + +template +struct is_same +{ + enum { value = 0 }; +}; + +template +struct is_same +{ + enum { value = 1 }; +}; + +// An argument visitor that converts an integer argument to T for printf, +// if T is an integral type. If T is void, the argument is converted to +// corresponding signed or unsigned type depending on the type specifier: +// 'd' and 'i' - signed, other - unsigned) +template +class ArgConverter : public ArgVisitor, void> +{ +private: + internal::Arg &arg_; + wchar_t type_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); + +public: + ArgConverter(internal::Arg &arg, wchar_t type) + : arg_(arg), type_(type) {} + + void visit_bool(bool value) + { + if (type_ != 's') + visit_any_int(value); + } + + void visit_char(char value) + { + if (type_ != 's') + visit_any_int(value); + } + + template + void visit_any_int(U value) + { + bool is_signed = type_ == 'd' || type_ == 'i'; + if (type_ == 's') + { + is_signed = std::numeric_limits::is_signed; + } + + using internal::Arg; + typedef typename internal::Conditional< + is_same::value, U, T>::type TargetType; + if (sizeof(TargetType) <= sizeof(int)) + { + // Extra casts are used to silence warnings. + if (is_signed) + { + arg_.type = Arg::INT; + arg_.int_value = static_cast(static_cast(value)); + } + else + { + arg_.type = Arg::UINT; + typedef typename internal::MakeUnsigned::Type Unsigned; + arg_.uint_value = static_cast(static_cast(value)); + } + } + else + { + if (is_signed) + { + arg_.type = Arg::LONG_LONG; + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_.long_long_value = static_cast(value); + } + else + { + arg_.type = Arg::ULONG_LONG; + arg_.ulong_long_value = + static_cast::Type>(value); + } + } + } +}; + +// Converts an integer argument to char for printf. +class CharConverter : public ArgVisitor +{ +private: + internal::Arg &arg_; + + FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); + +public: + explicit CharConverter(internal::Arg &arg) : arg_(arg) {} + + template + void visit_any_int(T value) + { + arg_.type = internal::Arg::CHAR; + arg_.int_value = static_cast(value); + } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class WidthHandler : public ArgVisitor +{ +private: + FormatSpec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); + +public: + explicit WidthHandler(FormatSpec &spec) : spec_(spec) {} + + void report_unhandled_arg() + { + FMT_THROW(FormatError("width is not integer")); + } + + template + unsigned visit_any_int(T value) + { + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType width = static_cast(value); + if (internal::is_negative(value)) + { + spec_.align_ = ALIGN_LEFT; + width = 0 - width; + } + unsigned int_max = std::numeric_limits::max(); + if (width > int_max) + FMT_THROW(FormatError("number is too big")); + return static_cast(width); + } +}; +} // namespace internal + +/** + \rst + A ``printf`` argument formatter based on the `curiously recurring template + pattern `_. + + To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some + or all of the visit methods with the same signatures as the methods in + `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. When a formatting + function processes an argument, it will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its + superclass will be called. + \endrst + */ +template +class BasicPrintfArgFormatter : + public internal::ArgFormatterBase +{ +private: + void write_null_pointer() + { + this->spec().type_ = 0; + this->write("(nil)"); + } + + typedef internal::ArgFormatterBase Base; + +public: + /** + \rst + Constructs an argument formatter object. + *writer* is a reference to the output writer and *spec* contains format + specifier information for standard argument types. + \endrst + */ + BasicPrintfArgFormatter(BasicWriter &w, Spec &s) + : internal::ArgFormatterBase(w, s) {} + + /** Formats an argument of type ``bool``. */ + void visit_bool(bool value) + { + Spec &fmt_spec = this->spec(); + if (fmt_spec.type_ != 's') + return this->visit_any_int(value); + fmt_spec.type_ = 0; + this->write(value); + } + + /** Formats a character. */ + void visit_char(int value) + { + const Spec &fmt_spec = this->spec(); + BasicWriter &w = this->writer(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + w.write_int(value, fmt_spec); + typedef typename BasicWriter::CharPtr CharPtr; + CharPtr out = CharPtr(); + if (fmt_spec.width_ > 1) + { + Char fill = ' '; + out = w.grow_buffer(fmt_spec.width_); + if (fmt_spec.align_ != ALIGN_LEFT) + { + std::fill_n(out, fmt_spec.width_ - 1, fill); + out += fmt_spec.width_ - 1; + } + else + { + std::fill_n(out + 1, fmt_spec.width_ - 1, fill); + } + } + else + { + out = w.grow_buffer(1); + } + *out = static_cast(value); + } + + /** Formats a null-terminated C string. */ + void visit_cstring(const char *value) + { + if (value) + Base::visit_cstring(value); + else if (this->spec().type_ == 'p') + write_null_pointer(); + else + this->write("(null)"); + } + + /** Formats a pointer. */ + void visit_pointer(const void *value) + { + if (value) + return Base::visit_pointer(value); + this->spec().type_ = 0; + write_null_pointer(); + } + + /** Formats an argument of a custom (user-defined) type. */ + void visit_custom(internal::Arg::CustomValue c) + { + BasicFormatter formatter(ArgList(), this->writer()); + const Char format_str[] = {'}', 0}; + const Char *format = format_str; + c.format(&formatter, c.value, &format); + } +}; + +/** The default printf argument formatter. */ +template +class PrintfArgFormatter : + public BasicPrintfArgFormatter, Char, FormatSpec> +{ +public: + /** Constructs an argument formatter object. */ + PrintfArgFormatter(BasicWriter &w, FormatSpec &s) + : BasicPrintfArgFormatter, Char, FormatSpec>(w, s) {} +}; + +/** This template formats data and writes the output to a writer. */ +template > +class PrintfFormatter : private internal::FormatterBase +{ +private: + BasicWriter &writer_; + + void parse_flags(FormatSpec &spec, const Char *&s); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + internal::Arg get_arg( + const Char *s, + unsigned arg_index = (std::numeric_limits::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(const Char *&s, FormatSpec &spec); + +public: + /** + \rst + Constructs a ``PrintfFormatter`` object. References to the arguments and + the writer are stored in the formatter object so make sure they have + appropriate lifetimes. + \endrst + */ + explicit PrintfFormatter(const ArgList &al, BasicWriter &w) + : FormatterBase(al), writer_(w) {} + + /** Formats stored arguments and writes the output to the writer. */ + void format(BasicCStringRef format_str); +}; + +template +void PrintfFormatter::parse_flags(FormatSpec &spec, const Char *&s) +{ + for (;;) + { + switch (*s++) + { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --s; + return; + } + } +} + +template +internal::Arg PrintfFormatter::get_arg(const Char *s, + unsigned arg_index) +{ + (void)s; + const char *error = FMT_NULL; + internal::Arg arg = arg_index == std::numeric_limits::max() ? + next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); + if (error) + FMT_THROW(FormatError(!*s ? "invalid format string" : error)); + return arg; +} + +template +unsigned PrintfFormatter::parse_header( + const Char *&s, FormatSpec &spec) +{ + unsigned arg_index = std::numeric_limits::max(); + Char c = *s; + if (c >= '0' && c <= '9') + { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + unsigned value = internal::parse_nonnegative_int(s); + if (*s == '$') // value is an argument index + { + ++s; + arg_index = value; + } + else + { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) + { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, s); + // Parse width. + if (*s >= '0' && *s <= '9') + { + spec.width_ = internal::parse_nonnegative_int(s); + } + else if (*s == '*') + { + ++s; + spec.width_ = internal::WidthHandler(spec).visit(get_arg(s)); + } + return arg_index; +} + +template +void PrintfFormatter::format(BasicCStringRef format_str) +{ + const Char *start = format_str.c_str(); + const Char *s = start; + while (*s) + { + Char c = *s++; + if (c != '%') continue; + if (*s == c) + { + write(writer_, start, s); + start = ++s; + continue; + } + write(writer_, start, s - 1); + + FormatSpec spec; + spec.align_ = ALIGN_RIGHT; + + // Parse argument index, flags and width. + unsigned arg_index = parse_header(s, spec); + + // Parse precision. + if (*s == '.') + { + ++s; + if ('0' <= *s && *s <= '9') + { + spec.precision_ = static_cast(internal::parse_nonnegative_int(s)); + } + else if (*s == '*') + { + ++s; + spec.precision_ = internal::PrecisionHandler().visit(get_arg(s)); + } + else + { + spec.precision_ = 0; + } + } + + using internal::Arg; + Arg arg = get_arg(s, arg_index); + if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg)) + spec.flags_ &= ~internal::to_unsigned(HASH_FLAG); + if (spec.fill_ == '0') + { + if (arg.type <= Arg::LAST_NUMERIC_TYPE) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + using internal::ArgConverter; + switch (*s++) + { + case 'h': + if (*s == 'h') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'l': + if (*s == 'l') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'j': + ArgConverter(arg, *s).visit(arg); + break; + case 'z': + ArgConverter(arg, *s).visit(arg); + break; + case 't': + ArgConverter(arg, *s).visit(arg); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --s; + ArgConverter(arg, *s).visit(arg); + } + + // Parse type. + if (!*s) + FMT_THROW(FormatError("invalid format string")); + spec.type_ = static_cast(*s++); + + if (spec.type_ == 's') + { + // set the format type to the default if 's' is specified + spec.type_ = internal::DefaultType().visit(arg); + } + + if (arg.type <= Arg::LAST_INTEGER_TYPE) + { + // Normalize type. + switch (spec.type_) + { + case 'i': + case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t + internal::CharConverter(arg).visit(arg); + break; + } + } + + start = s; + + // Format argument. + AF(writer_, spec).visit(arg); + } + write(writer_, start, s); +} + +inline void printf(Writer &w, CStringRef format, ArgList args) +{ + PrintfFormatter(args, w).format(format); +} +FMT_VARIADIC(void, printf, Writer &, CStringRef) + +inline void printf(WWriter &w, WCStringRef format, ArgList args) +{ + PrintfFormatter(args, w).format(format); +} +FMT_VARIADIC(void, printf, WWriter &, WCStringRef) + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst +*/ +inline std::string sprintf(CStringRef format, ArgList args) +{ + MemoryWriter w; + printf(w, format, args); + return w.str(); +} +FMT_VARIADIC(std::string, sprintf, CStringRef) + +inline std::wstring sprintf(WCStringRef format, ArgList args) +{ + WMemoryWriter w; + printf(w, format, args); + return w.str(); +} +FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); +FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +inline int printf(CStringRef format, ArgList args) +{ + return fprintf(stdout, format, args); +} +FMT_VARIADIC(int, printf, CStringRef) + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +inline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) +{ + MemoryWriter w; + printf(w, format_str, args); + internal::write(os, w); + return static_cast(w.size()); +} +FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) +} // namespace fmt + +#ifdef FMT_HEADER_ONLY +# include "printf.cc" +#endif + +#endif // FMT_PRINTF_H_ diff --git a/fpga/include/spdlog/fmt/bundled/time.h b/fpga/include/spdlog/fmt/bundled/time.h new file mode 100644 index 000000000..206d09209 --- /dev/null +++ b/fpga/include/spdlog/fmt/bundled/time.h @@ -0,0 +1,183 @@ +/* + Formatting library for C++ - time formatting + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_TIME_H_ +#define FMT_TIME_H_ + +#include "format.h" +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // "deprecated" functions +#endif + +namespace fmt +{ +template +void format_arg(BasicFormatter &f, + const char *&format_str, const std::tm &tm) +{ + if (*format_str == ':') + ++format_str; + const char *end = format_str; + while (*end && *end != '}') + ++end; + if (*end != '}') + FMT_THROW(FormatError("missing '}' in format string")); + internal::MemoryBuffer format; + format.append(format_str, end + 1); + format[format.size() - 1] = '\0'; + Buffer &buffer = f.writer().buffer(); + std::size_t start = buffer.size(); + for (;;) + { + std::size_t size = buffer.capacity() - start; + std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm); + if (count != 0) + { + buffer.resize(start + count); + break; + } + if (size >= format.size() * 256) + { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + format_str = end + 1; +} + +namespace internal +{ +inline Null<> localtime_r(...) +{ + return Null<>(); +} +inline Null<> localtime_s(...) +{ + return Null<>(); +} +inline Null<> gmtime_r(...) +{ + return Null<>(); +} +inline Null<> gmtime_s(...) +{ + return Null<>(); +} +} + +// Thread-safe replacement for std::localtime +inline std::tm localtime(std::time_t time) +{ + struct LocalTime + { + std::time_t time_; + std::tm tm_; + + LocalTime(std::time_t t): time_(t) {} + + bool run() + { + using namespace fmt::internal; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::Null<>) + { + using namespace fmt::internal; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::Null<>) + { + using namespace fmt::internal; + std::tm *tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != FMT_NULL; + } + }; + LocalTime lt(time); + if (lt.run()) + return lt.tm_; + // Too big time values may be unsupported. + FMT_THROW(fmt::FormatError("time_t value out of range")); + return std::tm(); +} + +// Thread-safe replacement for std::gmtime +inline std::tm gmtime(std::time_t time) +{ + struct GMTime + { + std::time_t time_; + std::tm tm_; + + GMTime(std::time_t t): time_(t) {} + + bool run() + { + using namespace fmt::internal; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::Null<>) + { + using namespace fmt::internal; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::Null<>) + { + std::tm *tm = std::gmtime(&time_); + if (tm != FMT_NULL) tm_ = *tm; + return tm != FMT_NULL; + } + }; + GMTime gt(time); + if (gt.run()) + return gt.tm_; + // Too big time values may be unsupported. + FMT_THROW(fmt::FormatError("time_t value out of range")); + return std::tm(); +} +} //namespace fmt + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif // FMT_TIME_H_ diff --git a/fpga/include/spdlog/fmt/fmt.h b/fpga/include/spdlog/fmt/fmt.h new file mode 100644 index 000000000..92ca4e50f --- /dev/null +++ b/fpga/include/spdlog/fmt/fmt.h @@ -0,0 +1,34 @@ +// +// Copyright(c) 2016 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Include a bundled header-only copy of fmtlib or an external one. +// By default spdlog include its own copy. +// + +#if !defined(SPDLOG_FMT_EXTERNAL) + +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#ifndef FMT_USE_WINDOWS_H +#define FMT_USE_WINDOWS_H 0 +#endif +#include "bundled/format.h" +#if defined(SPDLOG_FMT_PRINTF) +#include "bundled/printf.h" +#endif + +#else //external fmtlib + +#include +#if defined(SPDLOG_FMT_PRINTF) +#include +#endif + +#endif + diff --git a/fpga/include/spdlog/fmt/ostr.h b/fpga/include/spdlog/fmt/ostr.h new file mode 100644 index 000000000..5cdd5cd00 --- /dev/null +++ b/fpga/include/spdlog/fmt/ostr.h @@ -0,0 +1,17 @@ +// +// Copyright(c) 2016 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// include external or bundled copy of fmtlib's ostream support +// +#if !defined(SPDLOG_FMT_EXTERNAL) +#include "fmt.h" +#include "bundled/ostream.h" +#else +#include +#endif + + diff --git a/fpga/include/spdlog/formatter.h b/fpga/include/spdlog/formatter.h new file mode 100644 index 000000000..8bf0f43f8 --- /dev/null +++ b/fpga/include/spdlog/formatter.h @@ -0,0 +1,47 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "details/log_msg.h" + +#include +#include +#include + +namespace spdlog +{ +namespace details +{ +class flag_formatter; +} + +class formatter +{ +public: + virtual ~formatter() {} + virtual void format(details::log_msg& msg) = 0; +}; + +class pattern_formatter SPDLOG_FINAL : public formatter +{ + +public: + explicit pattern_formatter(const std::string& pattern, pattern_time_type pattern_time = pattern_time_type::local); + pattern_formatter(const pattern_formatter&) = delete; + pattern_formatter& operator=(const pattern_formatter&) = delete; + void format(details::log_msg& msg) override; +private: + const std::string _pattern; + const pattern_time_type _pattern_time; + std::vector> _formatters; + std::tm get_time(details::log_msg& msg); + void handle_flag(char flag); + void compile_pattern(const std::string& pattern); +}; +} + +#include "details/pattern_formatter_impl.h" + diff --git a/fpga/include/spdlog/logger.h b/fpga/include/spdlog/logger.h new file mode 100644 index 000000000..742f667f1 --- /dev/null +++ b/fpga/include/spdlog/logger.h @@ -0,0 +1,110 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Thread safe logger (except for set_pattern(..), set_formatter(..) and set_error_handler()) +// Has name, log level, vector of std::shared sink pointers and formatter +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Format the message using the formatter function +// 3. Pass the formatted message to its sinks to performa the actual logging + +#include "sinks/base_sink.h" +#include "common.h" + +#include +#include +#include + +namespace spdlog +{ + +class logger +{ +public: + logger(const std::string& logger_name, sink_ptr single_sink); + logger(const std::string& name, sinks_init_list); + template + logger(const std::string& name, const It& begin, const It& end); + + virtual ~logger(); + logger(const logger&) = delete; + logger& operator=(const logger&) = delete; + + + template void log(level::level_enum lvl, const char* fmt, const Args&... args); + template void log(level::level_enum lvl, const char* msg); + template void trace(const char* fmt, const Arg1&, const Args&... args); + template void debug(const char* fmt, const Arg1&, const Args&... args); + template void info(const char* fmt, const Arg1&, const Args&... args); + template void warn(const char* fmt, const Arg1&, const Args&... args); + template void error(const char* fmt, const Arg1&, const Args&... args); + template void critical(const char* fmt, const Arg1&, const Args&... args); + + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + template void log(level::level_enum lvl, const wchar_t* msg); + template void log(level::level_enum lvl, const wchar_t* fmt, const Args&... args); + template void trace(const wchar_t* fmt, const Args&... args); + template void debug(const wchar_t* fmt, const Args&... args); + template void info(const wchar_t* fmt, const Args&... args); + template void warn(const wchar_t* fmt, const Args&... args); + template void error(const wchar_t* fmt, const Args&... args); + template void critical(const wchar_t* fmt, const Args&... args); +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + template void log(level::level_enum lvl, const T&); + template void trace(const T&); + template void debug(const T&); + template void info(const T&); + template void warn(const T&); + template void error(const T&); + template void critical(const T&); + + bool should_log(level::level_enum) const; + void set_level(level::level_enum); + level::level_enum level() const; + const std::string& name() const; + void set_pattern(const std::string&, pattern_time_type = pattern_time_type::local); + void set_formatter(formatter_ptr); + + // automatically call flush() if message level >= log_level + void flush_on(level::level_enum log_level); + + virtual void flush(); + + const std::vector& sinks() const; + + // error handler + virtual void set_error_handler(log_err_handler); + virtual log_err_handler error_handler(); + +protected: + virtual void _sink_it(details::log_msg&); + virtual void _set_pattern(const std::string&, pattern_time_type); + virtual void _set_formatter(formatter_ptr); + + // default error handler: print the error to stderr with the max rate of 1 message/minute + virtual void _default_err_handler(const std::string &msg); + + // return true if the given message level should trigger a flush + bool _should_flush_on(const details::log_msg&); + + // increment the message count (only if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) + void _incr_msg_counter(details::log_msg &msg); + + const std::string _name; + std::vector _sinks; + formatter_ptr _formatter; + spdlog::level_t _level; + spdlog::level_t _flush_level; + log_err_handler _err_handler; + std::atomic _last_err_time; + std::atomic _msg_counter; +}; +} + +#include "details/logger_impl.h" diff --git a/fpga/include/spdlog/sinks/android_sink.h b/fpga/include/spdlog/sinks/android_sink.h new file mode 100644 index 000000000..63f6d8745 --- /dev/null +++ b/fpga/include/spdlog/sinks/android_sink.h @@ -0,0 +1,90 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(__ANDROID__) + +#include "sink.h" + +#include +#include +#include +#include +#include + +#if !defined(SPDLOG_ANDROID_RETRIES) +#define SPDLOG_ANDROID_RETRIES 2 +#endif + +namespace spdlog +{ +namespace sinks +{ + +/* +* Android sink (logging using __android_log_write) +* __android_log_write is thread-safe. No lock is needed. +*/ +class android_sink : public sink +{ +public: + explicit android_sink(const std::string& tag = "spdlog", bool use_raw_msg = false): _tag(tag), _use_raw_msg(use_raw_msg) {} + + void log(const details::log_msg& msg) override + { + const android_LogPriority priority = convert_to_android(msg.level); + const char *msg_output = (_use_raw_msg ? msg.raw.c_str() : msg.formatted.c_str()); + + // See system/core/liblog/logger_write.c for explanation of return value + int ret = __android_log_write(priority, _tag.c_str(), msg_output); + int retry_count = 0; + while ((ret == -11/*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) + { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + ret = __android_log_write(priority, _tag.c_str(), msg_output); + retry_count++; + } + + if (ret < 0) + { + throw spdlog_ex("__android_log_write() failed", ret); + } + } + + void flush() override + { + } + +private: + static android_LogPriority convert_to_android(spdlog::level::level_enum level) + { + switch(level) + { + case spdlog::level::trace: + return ANDROID_LOG_VERBOSE; + case spdlog::level::debug: + return ANDROID_LOG_DEBUG; + case spdlog::level::info: + return ANDROID_LOG_INFO; + case spdlog::level::warn: + return ANDROID_LOG_WARN; + case spdlog::level::err: + return ANDROID_LOG_ERROR; + case spdlog::level::critical: + return ANDROID_LOG_FATAL; + default: + return ANDROID_LOG_DEFAULT; + } + } + + std::string _tag; + bool _use_raw_msg; +}; + +} +} + +#endif diff --git a/fpga/include/spdlog/sinks/ansicolor_sink.h b/fpga/include/spdlog/sinks/ansicolor_sink.h new file mode 100644 index 000000000..290973133 --- /dev/null +++ b/fpga/include/spdlog/sinks/ansicolor_sink.h @@ -0,0 +1,133 @@ +// +// Copyright(c) 2017 spdlog authors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../common.h" +#include "../details/os.h" + +#include +#include + +namespace spdlog +{ +namespace sinks +{ + +/** + * This sink prefixes the output with an ANSI escape sequence color code depending on the severity + * of the message. + * If no color terminal detected, omit the escape codes. + */ +template +class ansicolor_sink: public base_sink +{ +public: + ansicolor_sink(FILE* file): target_file_(file) + { + should_do_colors_ = details::os::in_terminal(file) && details::os::is_color_terminal(); + colors_[level::trace] = cyan; + colors_[level::debug] = cyan; + colors_[level::info] = reset; + colors_[level::warn] = yellow + bold; + colors_[level::err] = red + bold; + colors_[level::critical] = bold + on_red; + colors_[level::off] = reset; + } + virtual ~ansicolor_sink() + { + _flush(); + } + + void set_color(level::level_enum color_level, const std::string& color) + { + std::lock_guard lock(base_sink::_mutex); + colors_[color_level] = color; + } + + /// Formatting codes + const std::string reset = "\033[00m"; + const std::string bold = "\033[1m"; + const std::string dark = "\033[2m"; + const std::string underline = "\033[4m"; + const std::string blink = "\033[5m"; + const std::string reverse = "\033[7m"; + const std::string concealed = "\033[8m"; + + // Foreground colors + const std::string grey = "\033[30m"; + const std::string red = "\033[31m"; + const std::string green = "\033[32m"; + const std::string yellow = "\033[33m"; + const std::string blue = "\033[34m"; + const std::string magenta = "\033[35m"; + const std::string cyan = "\033[36m"; + const std::string white = "\033[37m"; + + /// Background colors + const std::string on_grey = "\033[40m"; + const std::string on_red = "\033[41m"; + const std::string on_green = "\033[42m"; + const std::string on_yellow = "\033[43m"; + const std::string on_blue = "\033[44m"; + const std::string on_magenta = "\033[45m"; + const std::string on_cyan = "\033[46m"; + const std::string on_white = "\033[47m"; + +protected: + virtual void _sink_it(const details::log_msg& msg) override + { + // Wrap the originally formatted message in color codes. + // If color is not supported in the terminal, log as is instead. + if (should_do_colors_) + { + const std::string& prefix = colors_[msg.level]; + fwrite(prefix.data(), sizeof(char), prefix.size(), target_file_); + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), target_file_); + fwrite(reset.data(), sizeof(char), reset.size(), target_file_); + } + else + { + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), target_file_); + } + _flush(); + } + + void _flush() override + { + fflush(target_file_); + } + FILE* target_file_; + bool should_do_colors_; + std::map colors_; +}; + + +template +class ansicolor_stdout_sink: public ansicolor_sink +{ +public: + ansicolor_stdout_sink(): ansicolor_sink(stdout) + {} +}; + +template +class ansicolor_stderr_sink: public ansicolor_sink +{ +public: + ansicolor_stderr_sink(): ansicolor_sink(stderr) + {} +}; + +typedef ansicolor_stdout_sink ansicolor_stdout_sink_mt; +typedef ansicolor_stdout_sink ansicolor_stdout_sink_st; + +typedef ansicolor_stderr_sink ansicolor_stderr_sink_mt; +typedef ansicolor_stderr_sink ansicolor_stderr_sink_st; + +} // namespace sinks +} // namespace spdlog + diff --git a/fpga/include/spdlog/sinks/base_sink.h b/fpga/include/spdlog/sinks/base_sink.h new file mode 100644 index 000000000..23c856530 --- /dev/null +++ b/fpga/include/spdlog/sinks/base_sink.h @@ -0,0 +1,51 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +// +// base sink templated over a mutex (either dummy or real) +// concrete implementation should only override the _sink_it method. +// all locking is taken care of here so no locking needed by the implementers.. +// + +#include "sink.h" +#include "../formatter.h" +#include "../common.h" +#include "../details/log_msg.h" + +#include + +namespace spdlog +{ +namespace sinks +{ +template +class base_sink:public sink +{ +public: + base_sink():_mutex() {} + virtual ~base_sink() = default; + + base_sink(const base_sink&) = delete; + base_sink& operator=(const base_sink&) = delete; + + void log(const details::log_msg& msg) SPDLOG_FINAL override + { + std::lock_guard lock(_mutex); + _sink_it(msg); + } + void flush() SPDLOG_FINAL override + { + std::lock_guard lock(_mutex); + _flush(); + } + +protected: + virtual void _sink_it(const details::log_msg& msg) = 0; + virtual void _flush() = 0; + Mutex _mutex; +}; +} +} diff --git a/fpga/include/spdlog/sinks/dist_sink.h b/fpga/include/spdlog/sinks/dist_sink.h new file mode 100644 index 000000000..537efe1d1 --- /dev/null +++ b/fpga/include/spdlog/sinks/dist_sink.h @@ -0,0 +1,72 @@ +// +// Copyright (c) 2015 David Schury, Gabi Melman +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../details/log_msg.h" +#include "../details/null_mutex.h" +#include "base_sink.h" +#include "sink.h" + +#include +#include +#include +#include + +// Distribution sink (mux). Stores a vector of sinks which get called when log is called + +namespace spdlog +{ +namespace sinks +{ +template +class dist_sink: public base_sink +{ +public: + explicit dist_sink() :_sinks() {} + dist_sink(const dist_sink&) = delete; + dist_sink& operator=(const dist_sink&) = delete; + virtual ~dist_sink() = default; + +protected: + std::vector> _sinks; + + void _sink_it(const details::log_msg& msg) override + { + for (auto &sink : _sinks) + { + if( sink->should_log( msg.level)) + { + sink->log(msg); + } + } + } + + void _flush() override + { + for (auto &sink : _sinks) + sink->flush(); + } + +public: + + + void add_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::_mutex); + _sinks.push_back(sink); + } + + void remove_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::_mutex); + _sinks.erase(std::remove(_sinks.begin(), _sinks.end(), sink), _sinks.end()); + } +}; + +typedef dist_sink dist_sink_mt; +typedef dist_sink dist_sink_st; +} +} diff --git a/fpga/include/spdlog/sinks/file_sinks.h b/fpga/include/spdlog/sinks/file_sinks.h new file mode 100644 index 000000000..e78561722 --- /dev/null +++ b/fpga/include/spdlog/sinks/file_sinks.h @@ -0,0 +1,253 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../details/null_mutex.h" +#include "../details/file_helper.h" +#include "../fmt/fmt.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* + * Trivial file sink with single file as target + */ +template +class simple_file_sink SPDLOG_FINAL : public base_sink < Mutex > +{ +public: + explicit simple_file_sink(const filename_t &filename, bool truncate = false):_force_flush(false) + { + _file_helper.open(filename, truncate); + } + + void set_force_flush(bool force_flush) + { + _force_flush = force_flush; + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + _file_helper.write(msg); + if(_force_flush) + _file_helper.flush(); + } + void _flush() override + { + _file_helper.flush(); + } +private: + details::file_helper _file_helper; + bool _force_flush; +}; + +typedef simple_file_sink simple_file_sink_mt; +typedef simple_file_sink simple_file_sink_st; + +/* + * Rotating file sink based on size + */ +template +class rotating_file_sink SPDLOG_FINAL : public base_sink < Mutex > +{ +public: + rotating_file_sink(const filename_t &base_filename, + std::size_t max_size, std::size_t max_files) : + _base_filename(base_filename), + _max_size(max_size), + _max_files(max_files), + _current_size(0), + _file_helper() + { + _file_helper.open(calc_filename(_base_filename, 0)); + _current_size = _file_helper.size(); //expensive. called only once + } + + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t& filename, std::size_t index) + { + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + if (index) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + w.write(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + w.write(SPDLOG_FILENAME_T("{}"), filename); + } + return w.str(); + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + _current_size += msg.formatted.size(); + if (_current_size > _max_size) + { + _rotate(); + _current_size = msg.formatted.size(); + } + _file_helper.write(msg); + } + + void _flush() override + { + _file_helper.flush(); + } + + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void _rotate() + { + using details::os::filename_to_str; + _file_helper.close(); + for (auto i = _max_files; i > 0; --i) + { + filename_t src = calc_filename(_base_filename, i - 1); + filename_t target = calc_filename(_base_filename, i); + + if (details::file_helper::file_exists(target)) + { + if (details::os::remove(target) != 0) + { + throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); + } + } + if (details::file_helper::file_exists(src) && details::os::rename(src, target)) + { + throw spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + _file_helper.reopen(true); + } + filename_t _base_filename; + std::size_t _max_size; + std::size_t _max_files; + std::size_t _current_size; + details::file_helper _file_helper; +}; + +typedef rotating_file_sink rotating_file_sink_mt; +typedef rotating_file_sinkrotating_file_sink_st; + +/* + * Default generator of daily log file names. + */ +struct default_daily_file_name_calculator +{ + // Create filename for the form filename.YYYY-MM-DD_hh-mm.ext + static filename_t calc_filename(const filename_t& filename) + { + std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, ext); + return w.str(); + } +}; + +/* + * Generator of daily log file names in format basename.YYYY-MM-DD.ext + */ +struct dateonly_daily_file_name_calculator +{ + // Create filename for the form basename.YYYY-MM-DD + static filename_t calc_filename(const filename_t& filename) + { + std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, ext); + return w.str(); + } +}; + +/* + * Rotating file sink based on date. rotates at midnight + */ +template +class daily_file_sink SPDLOG_FINAL :public base_sink < Mutex > +{ +public: + //create daily file sink which rotates on given time + daily_file_sink( + const filename_t& base_filename, + int rotation_hour, + int rotation_minute) : _base_filename(base_filename), + _rotation_h(rotation_hour), + _rotation_m(rotation_minute) + { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) + throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + _rotation_tp = _next_rotation_tp(); + _file_helper.open(FileNameCalc::calc_filename(_base_filename)); + } + + +protected: + void _sink_it(const details::log_msg& msg) override + { + if (std::chrono::system_clock::now() >= _rotation_tp) + { + _file_helper.open(FileNameCalc::calc_filename(_base_filename)); + _rotation_tp = _next_rotation_tp(); + } + _file_helper.write(msg); + } + + void _flush() override + { + _file_helper.flush(); + } + +private: + std::chrono::system_clock::time_point _next_rotation_tp() + { + auto now = std::chrono::system_clock::now(); + time_t tnow = std::chrono::system_clock::to_time_t(now); + tm date = spdlog::details::os::localtime(tnow); + date.tm_hour = _rotation_h; + date.tm_min = _rotation_m; + date.tm_sec = 0; + auto rotation_time = std::chrono::system_clock::from_time_t(std::mktime(&date)); + if (rotation_time > now) + return rotation_time; + else + return std::chrono::system_clock::time_point(rotation_time + std::chrono::hours(24)); + } + + filename_t _base_filename; + int _rotation_h; + int _rotation_m; + std::chrono::system_clock::time_point _rotation_tp; + details::file_helper _file_helper; +}; + +typedef daily_file_sink daily_file_sink_mt; +typedef daily_file_sink daily_file_sink_st; +} +} diff --git a/fpga/include/spdlog/sinks/msvc_sink.h b/fpga/include/spdlog/sinks/msvc_sink.h new file mode 100644 index 000000000..22b52c8fe --- /dev/null +++ b/fpga/include/spdlog/sinks/msvc_sink.h @@ -0,0 +1,51 @@ +// +// Copyright(c) 2016 Alexander Dalshov. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(_WIN32) + +#include "base_sink.h" +#include "../details/null_mutex.h" + +#include + +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* +* MSVC sink (logging using OutputDebugStringA) +*/ +template +class msvc_sink : public base_sink < Mutex > +{ +public: + explicit msvc_sink() + { + } + + + +protected: + void _sink_it(const details::log_msg& msg) override + { + OutputDebugStringA(msg.formatted.c_str()); + } + + void _flush() override + {} +}; + +typedef msvc_sink msvc_sink_mt; +typedef msvc_sink msvc_sink_st; + +} +} + +#endif diff --git a/fpga/include/spdlog/sinks/null_sink.h b/fpga/include/spdlog/sinks/null_sink.h new file mode 100644 index 000000000..7605ac68c --- /dev/null +++ b/fpga/include/spdlog/sinks/null_sink.h @@ -0,0 +1,34 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../details/null_mutex.h" + +#include + +namespace spdlog +{ +namespace sinks +{ + +template +class null_sink : public base_sink < Mutex > +{ +protected: + void _sink_it(const details::log_msg&) override + {} + + void _flush() override + {} + +}; +typedef null_sink null_sink_st; +typedef null_sink null_sink_mt; + +} +} + diff --git a/fpga/include/spdlog/sinks/ostream_sink.h b/fpga/include/spdlog/sinks/ostream_sink.h new file mode 100644 index 000000000..1e5b261c0 --- /dev/null +++ b/fpga/include/spdlog/sinks/ostream_sink.h @@ -0,0 +1,47 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../details/null_mutex.h" +#include "base_sink.h" + +#include +#include + +namespace spdlog +{ +namespace sinks +{ +template +class ostream_sink: public base_sink +{ +public: + explicit ostream_sink(std::ostream& os, bool force_flush=false) :_ostream(os), _force_flush(force_flush) {} + ostream_sink(const ostream_sink&) = delete; + ostream_sink& operator=(const ostream_sink&) = delete; + virtual ~ostream_sink() = default; + +protected: + void _sink_it(const details::log_msg& msg) override + { + _ostream.write(msg.formatted.data(), msg.formatted.size()); + if (_force_flush) + _ostream.flush(); + } + + void _flush() override + { + _ostream.flush(); + } + + std::ostream& _ostream; + bool _force_flush; +}; + +typedef ostream_sink ostream_sink_mt; +typedef ostream_sink ostream_sink_st; +} +} diff --git a/fpga/include/spdlog/sinks/sink.h b/fpga/include/spdlog/sinks/sink.h new file mode 100644 index 000000000..af61b54c6 --- /dev/null +++ b/fpga/include/spdlog/sinks/sink.h @@ -0,0 +1,53 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + + +#pragma once + +#include "../details/log_msg.h" + +namespace spdlog +{ +namespace sinks +{ +class sink +{ +public: + sink() + { + _level = level::trace; + } + + virtual ~sink() {} + virtual void log(const details::log_msg& msg) = 0; + virtual void flush() = 0; + + bool should_log(level::level_enum msg_level) const; + void set_level(level::level_enum log_level); + level::level_enum level() const; + +private: + level_t _level; + +}; + +inline bool sink::should_log(level::level_enum msg_level) const +{ + return msg_level >= _level.load(std::memory_order_relaxed); +} + +inline void sink::set_level(level::level_enum log_level) +{ + _level.store(log_level); +} + +inline level::level_enum sink::level() const +{ + return static_cast(_level.load(std::memory_order_relaxed)); +} + +} +} + diff --git a/fpga/include/spdlog/sinks/stdout_sinks.h b/fpga/include/spdlog/sinks/stdout_sinks.h new file mode 100644 index 000000000..dfbfccd51 --- /dev/null +++ b/fpga/include/spdlog/sinks/stdout_sinks.h @@ -0,0 +1,77 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../details/null_mutex.h" +#include "base_sink.h" + +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ + +template +class stdout_sink SPDLOG_FINAL : public base_sink +{ + using MyType = stdout_sink; +public: + stdout_sink() + {} + static std::shared_ptr instance() + { + static std::shared_ptr instance = std::make_shared(); + return instance; + } +protected: + void _sink_it(const details::log_msg& msg) override + { + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), stdout); + _flush(); + } + + void _flush() override + { + fflush(stdout); + } +}; + +typedef stdout_sink stdout_sink_st; +typedef stdout_sink stdout_sink_mt; + + +template +class stderr_sink SPDLOG_FINAL : public base_sink +{ + using MyType = stderr_sink; +public: + stderr_sink() + {} + static std::shared_ptr instance() + { + static std::shared_ptr instance = std::make_shared(); + return instance; + } +protected: + void _sink_it(const details::log_msg& msg) override + { + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), stderr); + _flush(); + } + + void _flush() override + { + fflush(stderr); + } +}; + +typedef stderr_sink stderr_sink_mt; +typedef stderr_sink stderr_sink_st; +} +} diff --git a/fpga/include/spdlog/sinks/syslog_sink.h b/fpga/include/spdlog/sinks/syslog_sink.h new file mode 100644 index 000000000..c4a726a8f --- /dev/null +++ b/fpga/include/spdlog/sinks/syslog_sink.h @@ -0,0 +1,81 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../common.h" + +#ifdef SPDLOG_ENABLE_SYSLOG + +#include "sink.h" +#include "../details/log_msg.h" + +#include +#include +#include + + +namespace spdlog +{ +namespace sinks +{ +/** + * Sink that write to syslog using the `syscall()` library call. + * + * Locking is not needed, as `syslog()` itself is thread-safe. + */ +class syslog_sink : public sink +{ +public: + // + syslog_sink(const std::string& ident = "", int syslog_option=0, int syslog_facility=LOG_USER): + _ident(ident) + { + _priorities[static_cast(level::trace)] = LOG_DEBUG; + _priorities[static_cast(level::debug)] = LOG_DEBUG; + _priorities[static_cast(level::info)] = LOG_INFO; + _priorities[static_cast(level::warn)] = LOG_WARNING; + _priorities[static_cast(level::err)] = LOG_ERR; + _priorities[static_cast(level::critical)] = LOG_CRIT; + _priorities[static_cast(level::off)] = LOG_INFO; + + //set ident to be program name if empty + ::openlog(_ident.empty()? nullptr:_ident.c_str(), syslog_option, syslog_facility); + } + ~syslog_sink() + { + ::closelog(); + } + + syslog_sink(const syslog_sink&) = delete; + syslog_sink& operator=(const syslog_sink&) = delete; + + void log(const details::log_msg &msg) override + { + ::syslog(syslog_prio_from_level(msg), "%s", msg.raw.str().c_str()); + } + + void flush() override + { + } + + +private: + std::array _priorities; + //must store the ident because the man says openlog might use the pointer as is and not a string copy + const std::string _ident; + + // + // Simply maps spdlog's log level to syslog priority level. + // + int syslog_prio_from_level(const details::log_msg &msg) const + { + return _priorities[static_cast(msg.level)]; + } +}; +} +} + +#endif diff --git a/fpga/include/spdlog/sinks/wincolor_sink.h b/fpga/include/spdlog/sinks/wincolor_sink.h new file mode 100644 index 000000000..8ee3d894c --- /dev/null +++ b/fpga/include/spdlog/sinks/wincolor_sink.h @@ -0,0 +1,121 @@ +// +// Copyright(c) 2016 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../details/null_mutex.h" +#include "../common.h" + +#include +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* + * Windows color console sink. Uses WriteConsoleA to write to the console with colors + */ +template +class wincolor_sink: public base_sink +{ +public: + const WORD BOLD = FOREGROUND_INTENSITY; + const WORD RED = FOREGROUND_RED; + const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; + + wincolor_sink(HANDLE std_handle): out_handle_(std_handle) + { + colors_[level::trace] = CYAN; + colors_[level::debug] = CYAN; + colors_[level::info] = WHITE | BOLD; + colors_[level::warn] = YELLOW | BOLD; + colors_[level::err] = RED | BOLD; // red bold + colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background + colors_[level::off] = 0; + } + + virtual ~wincolor_sink() + { + this->flush(); + } + + wincolor_sink(const wincolor_sink& other) = delete; + wincolor_sink& operator=(const wincolor_sink& other) = delete; + +protected: + virtual void _sink_it(const details::log_msg& msg) override + { + auto color = colors_[msg.level]; + auto orig_attribs = set_console_attribs(color); + WriteConsoleA(out_handle_, msg.formatted.data(), static_cast(msg.formatted.size()), nullptr, nullptr); + SetConsoleTextAttribute(out_handle_, orig_attribs); //reset to orig colors + } + + virtual void _flush() override + { + // windows console always flushed? + } + + // change the color for the given level + void set_color(level::level_enum level, WORD color) + { + std::lock_guard lock(base_sink::_mutex); + colors_[level] = color; + } + +private: + HANDLE out_handle_; + std::map colors_; + + // set color and return the orig console attributes (for resetting later) + WORD set_console_attribs(WORD attribs) + { + CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; + GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); + WORD back_color = orig_buffer_info.wAttributes; + // retrieve the current background color + back_color &= static_cast(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); + // keep the background color unchanged + SetConsoleTextAttribute(out_handle_, attribs | back_color); + return orig_buffer_info.wAttributes; //return orig attribs + } +}; + +// +// windows color console to stdout +// +template +class wincolor_stdout_sink: public wincolor_sink +{ +public: + wincolor_stdout_sink() : wincolor_sink(GetStdHandle(STD_OUTPUT_HANDLE)) + {} +}; + +typedef wincolor_stdout_sink wincolor_stdout_sink_mt; +typedef wincolor_stdout_sink wincolor_stdout_sink_st; + +// +// windows color console to stderr +// +template +class wincolor_stderr_sink: public wincolor_sink +{ +public: + wincolor_stderr_sink() : wincolor_sink(GetStdHandle(STD_ERROR_HANDLE)) + {} +}; + +typedef wincolor_stderr_sink wincolor_stderr_sink_mt; +typedef wincolor_stderr_sink wincolor_stderr_sink_st; + +} +} diff --git a/fpga/include/spdlog/sinks/windebug_sink.h b/fpga/include/spdlog/sinks/windebug_sink.h new file mode 100644 index 000000000..c22e9522e --- /dev/null +++ b/fpga/include/spdlog/sinks/windebug_sink.h @@ -0,0 +1,29 @@ +// +// Copyright(c) 2017 Alexander Dalshov. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(_WIN32) + +#include "msvc_sink.h" + +namespace spdlog +{ +namespace sinks +{ + +/* +* Windows debug sink (logging using OutputDebugStringA, synonym for msvc_sink) +*/ +template +using windebug_sink = msvc_sink; + +typedef msvc_sink_mt windebug_sink_mt; +typedef msvc_sink_st windebug_sink_st; + +} +} + +#endif diff --git a/fpga/include/spdlog/spdlog.h b/fpga/include/spdlog/spdlog.h new file mode 100644 index 000000000..c7af2c795 --- /dev/null +++ b/fpga/include/spdlog/spdlog.h @@ -0,0 +1,192 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// spdlog main header file. +// see example.cpp for usage example + +#pragma once + +#define SPDLOG_VERSION "0.16.2" + +#include "tweakme.h" +#include "common.h" +#include "logger.h" + +#include +#include +#include +#include + +namespace spdlog +{ + +// +// Return an existing logger or nullptr if a logger with such name doesn't exist. +// example: spdlog::get("my_logger")->info("hello {}", "world"); +// +std::shared_ptr get(const std::string& name); + + +// +// Set global formatting +// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); +// +void set_pattern(const std::string& format_string); +void set_formatter(formatter_ptr f); + +// +// Set global logging level +// +void set_level(level::level_enum log_level); + +// +// Set global flush level +// +void flush_on(level::level_enum log_level); + +// +// Set global error handler +// +void set_error_handler(log_err_handler); + +// +// Turn on async mode (off by default) and set the queue size for each async_logger. +// effective only for loggers created after this call. +// queue_size: size of queue (must be power of 2): +// Each logger will pre-allocate a dedicated queue with queue_size entries upon construction. +// +// async_overflow_policy (optional, block_retry by default): +// async_overflow_policy::block_retry - if queue is full, block until queue has room for the new log entry. +// async_overflow_policy::discard_log_msg - never block and discard any new messages when queue overflows. +// +// worker_warmup_cb (optional): +// callback function that will be called in worker thread upon start (can be used to init stuff like thread affinity) +// +// worker_teardown_cb (optional): +// callback function that will be called in worker thread upon exit +// +void set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); + +// Turn off async mode +void set_sync_mode(); + + +// +// Create and register multi/single threaded basic file logger. +// Basic logger simply writes to given file without any limitations or rotations. +// +std::shared_ptr basic_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate = false); +std::shared_ptr basic_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate = false); + +// +// Create and register multi/single threaded rotating file logger +// +std::shared_ptr rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files); +std::shared_ptr rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files); + +// +// Create file logger which creates new file on the given time (default in midnight): +// +std::shared_ptr daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour=0, int minute=0); +std::shared_ptr daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour=0, int minute=0); + +// +// Create and register stdout/stderr loggers +// +std::shared_ptr stdout_logger_mt(const std::string& logger_name); +std::shared_ptr stdout_logger_st(const std::string& logger_name); +std::shared_ptr stderr_logger_mt(const std::string& logger_name); +std::shared_ptr stderr_logger_st(const std::string& logger_name); +// +// Create and register colored stdout/stderr loggers +// +std::shared_ptr stdout_color_mt(const std::string& logger_name); +std::shared_ptr stdout_color_st(const std::string& logger_name); +std::shared_ptr stderr_color_mt(const std::string& logger_name); +std::shared_ptr stderr_color_st(const std::string& logger_name); + + +// +// Create and register a syslog logger +// +#ifdef SPDLOG_ENABLE_SYSLOG +std::shared_ptr syslog_logger(const std::string& logger_name, const std::string& ident = "", int syslog_option = 0, int syslog_facilty = (1<<3)); +#endif + +#if defined(__ANDROID__) +std::shared_ptr android_logger(const std::string& logger_name, const std::string& tag = "spdlog"); +#endif + +// Create and register a logger with a single sink +std::shared_ptr create(const std::string& logger_name, const sink_ptr& sink); + +// Create and register a logger with multiple sinks +std::shared_ptr create(const std::string& logger_name, sinks_init_list sinks); +template +std::shared_ptr create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end); + + +// Create and register a logger with templated sink type +// Example: +// spdlog::create("mylog", "dailylog_filename"); +template +std::shared_ptr create(const std::string& logger_name, Args...); + +// Create and register an async logger with a single sink +std::shared_ptr create_async(const std::string& logger_name, const sink_ptr& sink, size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); + +// Create and register an async logger with multiple sinks +std::shared_ptr create_async(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); +template +std::shared_ptr create_async(const std::string& logger_name, const It& sinks_begin, const It& sinks_end, size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); + +// Register the given logger with the given name +void register_logger(std::shared_ptr logger); + +// Apply a user defined function on all registered loggers +// Example: +// spdlog::apply_all([&](std::shared_ptr l) {l->flush();}); +void apply_all(std::function)> fun); + +// Drop the reference to the given logger +void drop(const std::string &name); + +// Drop all references from the registry +void drop_all(); + + +/////////////////////////////////////////////////////////////////////////////// +// +// Trace & Debug can be switched on/off at compile time for zero cost debug statements. +// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. +// SPDLOG_TRACE(..) will also print current file and line. +// +// Example: +// spdlog::set_level(spdlog::level::trace); +// SPDLOG_TRACE(my_logger, "some trace message"); +// SPDLOG_TRACE(my_logger, "another trace message {} {}", 1, 2); +// SPDLOG_DEBUG(my_logger, "some debug message {} {}", 3, 4); +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SPDLOG_TRACE_ON +# define SPDLOG_STR_H(x) #x +# define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) +# ifdef _MSC_VER +# define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ "(" SPDLOG_STR_HELPER(__LINE__) ") ] " __VA_ARGS__) +# else +# define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) " ] " __VA_ARGS__) +# endif +#else +# define SPDLOG_TRACE(logger, ...) (void)0 +#endif + +#ifdef SPDLOG_DEBUG_ON +# define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) +#else +# define SPDLOG_DEBUG(logger, ...) (void)0 +#endif + +} + +#include "details/spdlog_impl.h" diff --git a/fpga/include/spdlog/tweakme.h b/fpga/include/spdlog/tweakme.h new file mode 100644 index 000000000..ad01a09c0 --- /dev/null +++ b/fpga/include/spdlog/tweakme.h @@ -0,0 +1,160 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +// +// Edit this file to squeeze more performance, and to customize supported features +// +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. +// This clock is less accurate - can be off by dozens of millis - depending on the kernel HZ. +// Uncomment to use it instead of the regular clock. +// +// #define SPDLOG_CLOCK_COARSE +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if date/time logging is not needed and never appear in the log pattern. +// This will prevent spdlog from querying the clock on each log call. +// +// WARNING: If the log pattern contains any date/time while this flag is on, the result is undefined. +// You must set new pattern(spdlog::set_pattern(..") without any date/time in it +// +// #define SPDLOG_NO_DATETIME +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). +// This will prevent spdlog from querying the thread id on each log call. +// +// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is on, the result is undefined. +// +// #define SPDLOG_NO_THREAD_ID +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent spdlog from caching thread ids in thread local storage. +// By default spdlog saves thread ids in tls to gain a few micros for each call. +// +// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined thread ids in the children logs. +// +// #define SPDLOG_DISABLE_TID_CACHING +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if logger name logging is not needed. +// This will prevent spdlog from copying the logger name on each log call. +// +// #define SPDLOG_NO_NAME +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros. +// +// #define SPDLOG_DEBUG_ON +// #define SPDLOG_TRACE_ON +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid locking in the registry operations (spdlog::get(), spdlog::drop() spdlog::register()). +// Use only if your code never modifies concurrently the registry. +// Note that upon creating a logger the registry is modified by spdlog.. +// +// #define SPDLOG_NO_REGISTRY_MUTEX +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid spdlog's usage of atomic log levels +// Use only if your code never modifies a logger's log levels concurrently by different threads. +// +// #define SPDLOG_NO_ATOMIC_LEVELS +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable usage of wchar_t for file names on Windows. +// +// #define SPDLOG_WCHAR_FILENAMES +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) +// +// #define SPDLOG_EOL ";-)\n" +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use your own copy of the fmt library instead of spdlog's copy. +// In this case spdlog will try to include so set your -I flag accordingly. +// +// #define SPDLOG_FMT_EXTERNAL +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use printf-style messages in your logs instead of the usual +// format-style used by default. +// +// #define SPDLOG_FMT_PRINTF +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable syslog (disabled by default) +// +// #define SPDLOG_ENABLE_SYSLOG +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable wchar_t support (convert to utf8) +// +// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent child processes from inheriting log file descriptors +// +// #define SPDLOG_PREVENT_CHILD_FD +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if your compiler doesn't support the "final" keyword. +// The final keyword allows more optimizations in release +// mode with recent compilers. See GCC's documentation for -Wsuggest-final-types +// for instance. +// +// #define SPDLOG_NO_FINAL +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable message counting feature. +// Use the %i in the logger pattern to display log message sequence id. +// +// #define SPDLOG_ENABLE_MESSAGE_COUNTER +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize level names (e.g. "MT TRACE") +// +// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", "MY ERROR", "MY CRITICAL", "OFF" } +/////////////////////////////////////////////////////////////////////////////// diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt new file mode 100644 index 000000000..22329b4e6 --- /dev/null +++ b/fpga/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Tests +# + +enable_testing() + +find_package(Threads) + +# Build Catch unit tests +add_library(catch INTERFACE) +target_include_directories(catch INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +file(GLOB catch_tests LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp *.h *.hpp) + +add_executable(catch_tests ${catch_tests}) +target_link_libraries(catch_tests spdlog ${CMAKE_THREAD_LIBS_INIT}) +add_test(NAME catch_tests COMMAND catch_tests) +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") + diff --git a/fpga/tests/Makefile b/fpga/tests/Makefile new file mode 100644 index 000000000..b1935e751 --- /dev/null +++ b/fpga/tests/Makefile @@ -0,0 +1,28 @@ +CXX ?= g++ +ifeq ($(STYLE),printf) + $(info *** PRINTF STYLE ***) + CXXFLAGS = -DSPDLOG_FMT_PRINTF -Wall -pedantic -std=c++11 -pthread -O2 -I../include +else + $(info *** FORMAT STYLE ***) + CXXFLAGS = -Wall -pedantic -std=c++11 -pthread -O2 -I../include +endif +LDPFALGS = -pthread + +CPP_FILES := $(wildcard *.cpp) +OBJ_FILES := $(addprefix ./,$(notdir $(CPP_FILES:.cpp=.o))) + + +tests: $(OBJ_FILES) + $(CXX) $(CXXFLAGS) $(LDPFALGS) -o $@ $^ + mkdir -p logs + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +clean: + rm -f tests *.o logs/*.txt + +rebuild: clean tests + + + diff --git a/fpga/tests/catch.hpp b/fpga/tests/catch.hpp new file mode 100644 index 000000000..925c6bffa --- /dev/null +++ b/fpga/tests/catch.hpp @@ -0,0 +1,9427 @@ +/* + * CATCH v1.1 build 1 (master branch) + * Generated: 2015-03-27 18:00:16.346230 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +// #included from: internal/catch_suppress_warnings.h + +#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Much of the following code is based on Boost (1.53) + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#if (__BORLANDC__ > 0x582 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#if (__EDG_VERSION__ > 238 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#if (__DMC__ > 0x840 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ < 3 + +#if (__GNUC_MINOR__ >= 96 ) +//#define CATCH_CONFIG_SFINAE +#endif + +#elif __GNUC__ >= 3 + +// #define CATCH_CONFIG_SFINAE // Taking this out completely for now + +#endif // __GNUC__ < 3 + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) + +#define CATCH_CONFIG_CPP11_NULLPTR +#endif + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CATCH_CONFIG_CPP11_NULLPTR +#endif + +#if (_MSC_VER >= 1310 ) // (VC++ 7.0+) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // _MSC_VER + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS +#define CATCH_CONFIG_VARIADIC_MACROS +#endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// detect language version: +#if (__cplusplus == 201103L) +# define CATCH_CPP11 +# define CATCH_CPP11_OR_GREATER +#elif (__cplusplus >= 201103L) +# define CATCH_CPP11_OR_GREATER +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { +#ifdef CATCH_CPP11_OR_GREATER + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CPP11_OR_GREATER + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() { return m_p; } + const T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( ... ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x00, + + ContinueOnFailure = 0x01, // Failures fail test, but execution continues + FalseTest = 0x02, // Prefix expression with ! + SuppressFail = 0x04 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CPP11_OR_GREATER + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator->* ( T const& operand ); + ExpressionLhs operator->* ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( NULL, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +// #included from: catch_sfinae.hpp +#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED + +// Try to detect if the current compiler supports SFINAE + +namespace Catch { + + struct TrueType { + static const bool value = true; + typedef void Enable; + char sizer[1]; + }; + struct FalseType { + static const bool value = false; + typedef void Disable; + char sizer[2]; + }; + +#ifdef CATCH_CONFIG_SFINAE + + template struct NotABooleanExpression; + + template struct If : NotABooleanExpression {}; + template<> struct If : TrueType {}; + template<> struct If : FalseType {}; + + template struct SizedIf; + template<> struct SizedIf : TrueType {}; + template<> struct SizedIf : FalseType {}; + +#endif // CATCH_CONFIG_SFINAE + +} // end namespace Catch + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CPP11_OR_GREATER +#include +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern std::string unprintableString; + +// SFINAE is currently disabled by default for all compilers. +// If the non SFINAE version of IsStreamInsertable is ambiguous for you +// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE +#ifdef CATCH_CONFIG_SFINAE + + template + class IsStreamInsertableHelper { + template struct TrueIfSizeable : TrueType {}; + + template + static TrueIfSizeable dummy(T2*); + static FalseType dummy(...); + + public: + typedef SizedIf type; + }; + + template + struct IsStreamInsertable : IsStreamInsertableHelper::type {}; + +#else + + struct BorgType { + template BorgType( T const& ); + }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#endif + +#if defined(CATCH_CPP11_OR_GREATER) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CPP11_OR_GREATER) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CPP11_OR_GREATER + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CPP11_OR_GREATER + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CPP11_OR_GREATER + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult->*expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct Equals : MatcherImpl { + Equals( std::string const& str ) : m_str( str ){} + Equals( Equals const& other ) : m_str( other.m_str ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_str == expr; + } + virtual std::string toString() const { + return "equals: \"" + m_str + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr ) : m_substr( substr ){} + StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr ) : m_substr( substr ){} + EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == expr.size() - m_substr.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + inline Impl::StdString::Equals Equals( const char* str ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); + } + inline Impl::StdString::Contains Contains( std::string const& substr ) { + return Impl::StdString::Contains( substr ); + } + inline Impl::StdString::Contains Contains( const char* substr ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { + if( startsWith( m_name, "*" ) ) { + m_name = m_name.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_name, "*" ) ) { + m_name = m_name.substr( 0, m_name.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_name == toLower( testCase.name ); + case WildcardAtStart: + return endsWith( toLower( testCase.name ), m_name ); + case WildcardAtEnd: + return startsWith( toLower( testCase.name ), m_name ); + case WildcardAtBothEnds: + return contains( toLower( testCase.name ), m_name ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual bool forceColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; + + std::ostream& cout(); + std::ostream& cerr(); +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + forceColour( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool forceColour; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( Catch::cout().rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( Catch::cout().rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( Catch::cout().rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + void setStreamBuf( std::streambuf* buf ) { + m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); + } + + void useStream( std::string const& streamName ) { + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; + } + + std::string getReporterName() const { return m_data.reporterName; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_os; } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual bool forceColour() const { return m_data.forceColour; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +# ifdef CATCH_CPP11_OR_GREATER + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // NOTE: std::auto_ptr is deprecated in c++11/c++0x +#if defined(__cplusplus) && __cplusplus > 199711L + typedef std::unique_ptr ArgAutoPtr; +#else + typedef std::auto_ptr ArgAutoPtr; +#endif + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output" ) + .bind( &ConfigData::forceColour ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CPP11_OR_GREATER + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CPP11_OR_GREATER + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CPP11_OR_GREATER + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + TrackedSection* acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + void enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + void leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + + }; + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + bool enterSection( std::string const& name ) { + TrackedSection* child = m_currentSection->acquireChild( name ); + if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) + return false; + + m_currentSection = child; + m_currentSection->enter(); + return true; + } + void leaveSection() { + m_currentSection->leave(); + m_currentSection = m_currentSection->getParent(); + assert( m_currentSection != NULL ); + m_completedASectionThisRun = true; + } + + bool currentSectionHasChildren() const { + return m_currentSection->hasChildren(); + } + bool isCompleted() const { + return m_testCase.runState() == TrackedSection::Completed; + } + + class Guard { + public: + Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { + m_tracker.enterTestCase(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + private: + Guard( Guard const& ); + void operator = ( Guard const& ); + TestCaseTracker& m_tracker; + }; + + private: + void enterTestCase() { + m_currentSection = &m_testCase; + m_completedASectionThisRun = false; + m_testCase.enter(); + } + void leaveTestCase() { + m_testCase.leave(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + m_context.setRunner( m_prevRunner ); + m_context.setConfig( NULL ); + m_context.setResultCapture( m_prevResultCapture ); + m_context.setConfig( m_prevConfig ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + m_unfinishedSections.clear(); + } + + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + Ptr m_prevConfig; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _buildNumber, + char const* const _branchName ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + buildNumber( _buildNumber ), + branchName( _branchName ) + {} + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const buildNumber; + char const* const branchName; + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + + TestSpec testSpec = m_config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector testCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + + int testsRunForGroup = 0; + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) { + testsRunForGroup++; + if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + + if( context.aborting() ) + break; + + totals += context.runTest( *it ); + m_testsAlreadyRun.insert( *it ); + } + } + std::vector skippedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + + for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); + it != itEnd; + ++it ) + m_reporter->skipTest( *it ); + + context.testGroupEnded( "all tests", totals, 1, 1 ); + return totals; + } + + private: + void openStream() { + // Open output file, if specified + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + m_config->setStreamBuf( m_ofs.rdbuf() ); + } + } + void makeReporter() { + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); + + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); + if( !m_reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + } + + private: + Ptr m_config; + std::ofstream m_ofs; + Ptr m_reporter; + std::set m_testsAlreadyRun; + }; + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " build " + << libraryVersion.buildNumber; + if( libraryVersion.branchName != std::string( "master" ) ) + Catch::cout() << " (" << libraryVersion.branchName << " branch)"; + Catch::cout() << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + std::srand( m_configData.rngSeed ); + + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector const& getAllNonHiddenTests() const { + return m_nonHiddenFunctions; + } + + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { + + for( std::vector::const_iterator it = m_functionsInOrder.begin(), + itEnd = m_functionsInOrder.end(); + it != itEnd; + ++it ) { + bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); + if( includeTest != negated ) + matchingTestCases.push_back( *it ); + } + sortTests( config, matchingTestCases ); + } + + private: + + static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + RandomNumberGenerator rng; + std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + } + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + std::string tryTranslators( std::vector::const_iterator it ) const { + if( it == m_translators.end() ) + return "Unknown exception"; + + try { + return (*it)->translate(); + } + catch(...) { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + Stream createStream( std::string const& streamName ) { + if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); + if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); + if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + void cleanUpContext() { + delete currentContext; + currentContext = NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + return (config && config->forceColour()) || isatty(STDOUT_FILENO) + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = isDebuggerActive() + ? NoColourImpl::instance() + : platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + tags( _tags ), + lineInfo( _lineInfo ), + properties( None ) + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); + lcaseTags.insert( lcaseTag ); + } + tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + // These numbers are maintained by a script + Version libraryVersion( 1, 1, 1, "master" ); +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + uint64_t t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + std::string unprintableString = "{?}"; + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + +//# ifndef CATCH_CPP11_OR_GREATER +// XmlWriter& operator = ( XmlWriter const& other ) { +// XmlWriter temp( other ); +// swap( temp ); +// return *this; +// } +//# else +// XmlWriter( XmlWriter const& ) = default; +// XmlWriter( XmlWriter && ) = default; +// XmlWriter& operator = ( XmlWriter const& ) = default; +// XmlWriter& operator = ( XmlWriter && ) = default; +//# endif +// +// void swap( XmlWriter& other ) { +// std::swap( m_tagIsOpen, other.m_tagIsOpen ); +// std::swap( m_needsNewline, other.m_needsNewline ); +// std::swap( m_tags, other.m_tags ); +// std::swap( m_indent, other.m_indent ); +// std::swap( m_os, other.m_os ); +// } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + void writeEncodedText( std::string const& text ) { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + {} + + virtual ~XmlReporter(); + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " b" + << libraryVersion.buildNumber; + if( libraryVersion.branchName != std::string( "master" ) ) + stream << " (" << libraryVersion.branchName << ")"; + stream << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/fpga/tests/catch.license b/fpga/tests/catch.license new file mode 100644 index 000000000..127a5bc39 --- /dev/null +++ b/fpga/tests/catch.license @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/fpga/tests/errors.cpp b/fpga/tests/errors.cpp new file mode 100644 index 000000000..c8281d632 --- /dev/null +++ b/fpga/tests/errors.cpp @@ -0,0 +1,126 @@ +/* +* This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE +*/ +#include "includes.h" + +#include + + + + +class failing_sink: public spdlog::sinks::sink +{ + void log(const spdlog::details::log_msg& msg) override + { + throw std::runtime_error("some error happened during log"); + } + + void flush() override + {} +}; + +TEST_CASE("default_error_handler", "[errors]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log.txt"; + + auto logger = spdlog::create("logger", filename, true); + logger->set_pattern("%v"); +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {} {}", 1); + logger->info("Test message {}", 2); +#else + logger->info("Test message %d %d", 1); + logger->info("Test message %d", 2); +#endif + logger->flush(); + + REQUIRE(file_contents(filename) == std::string("Test message 2\n")); + REQUIRE(count_lines(filename) == 1); +} + + + + +struct custom_ex +{}; +TEST_CASE("custom_error_handler", "[errors]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log.txt"; + auto logger = spdlog::create("logger", filename, true); + logger->flush_on(spdlog::level::info); + logger->set_error_handler([=](const std::string& msg) + { + throw custom_ex(); + }); + logger->info("Good message #1"); +#if !defined(SPDLOG_FMT_PRINTF) + REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex); +#else + REQUIRE_THROWS_AS(logger->info("Bad format msg %s %s", "xxx"), custom_ex); +#endif + logger->info("Good message #2"); + REQUIRE(count_lines(filename) == 2); +} + +TEST_CASE("default_error_handler2", "[errors]]") +{ + + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string& msg) + { + throw custom_ex(); + }); + REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex); +} + +TEST_CASE("async_error_handler", "[errors]]") +{ + prepare_logdir(); + std::string err_msg("log failed with some msg"); + spdlog::set_async_mode(128); + std::string filename = "logs/simple_async_log.txt"; + { + auto logger = spdlog::create("logger", filename, true); + logger->set_error_handler([=](const std::string& msg) + { + std::ofstream ofs("logs/custom_err.txt"); + if (!ofs) throw std::runtime_error("Failed open logs/custom_err.txt"); + ofs << err_msg; + }); + logger->info("Good message #1"); +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Bad format msg {} {}", "xxx"); +#else + logger->info("Bad format msg %s %s", "xxx"); +#endif + logger->info("Good message #2"); + spdlog::drop("logger"); //force logger to drain the queue and shutdown + spdlog::set_sync_mode(); + } + REQUIRE(count_lines(filename) == 2); + REQUIRE(file_contents("logs/custom_err.txt") == err_msg); +} + +// Make sure async error handler is executed +TEST_CASE("async_error_handler2", "[errors]]") +{ + prepare_logdir(); + std::string err_msg("This is async handler error message"); + spdlog::set_async_mode(128); + { + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string& msg) + { + std::ofstream ofs("logs/custom_err2.txt"); + if (!ofs) throw std::runtime_error("Failed open logs/custom_err2.txt"); + ofs << err_msg; + }); + logger->info("Hello failure"); + spdlog::drop("failed_logger"); //force logger to drain the queue and shutdown + spdlog::set_sync_mode(); + } + + REQUIRE(file_contents("logs/custom_err2.txt") == err_msg); +} diff --git a/fpga/tests/file_helper.cpp b/fpga/tests/file_helper.cpp new file mode 100644 index 000000000..d127c8f06 --- /dev/null +++ b/fpga/tests/file_helper.cpp @@ -0,0 +1,118 @@ +/* +* This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE +*/ +#include "includes.h" + +using namespace spdlog::details; + +static const std::string target_filename = "logs/file_helper_test.txt"; + +static void write_with_helper(file_helper &helper, size_t howmany) +{ + log_msg msg; + msg.formatted << std::string(howmany, '1'); + helper.write(msg); + helper.flush(); +} + + +TEST_CASE("file_helper_filename", "[file_helper::filename()]]") +{ + prepare_logdir(); + + file_helper helper; + helper.open(target_filename); + REQUIRE(helper.filename() == target_filename); +} + + + +TEST_CASE("file_helper_size", "[file_helper::size()]]") +{ + prepare_logdir(); + size_t expected_size = 123; + { + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(static_cast(helper.size()) == expected_size); + } + REQUIRE(get_filesize(target_filename) == expected_size); +} + + +TEST_CASE("file_helper_exists", "[file_helper::file_exists()]]") +{ + prepare_logdir(); + REQUIRE(!file_helper::file_exists(target_filename)); + file_helper helper; + helper.open(target_filename); + REQUIRE(file_helper::file_exists(target_filename)); +} + +TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]") +{ + prepare_logdir(); + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, 12); + REQUIRE(helper.size() == 12); + helper.reopen(true); + REQUIRE(helper.size() == 0); +} + +TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]") +{ + prepare_logdir(); + size_t expected_size = 14; + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(helper.size() == expected_size); + helper.reopen(false); + REQUIRE(helper.size() == expected_size); +} + + + +static void test_split_ext(const char* fname, const char* expect_base, const char* expect_ext) +{ + spdlog::filename_t filename(fname); + spdlog::filename_t expected_base(expect_base); + spdlog::filename_t expected_ext(expect_ext); + +#ifdef _WIN32 // replace folder sep + std::replace(filename.begin(), filename.end(), '/', '\\'); + std::replace(expected_base.begin(), expected_base.end(), '/', '\\'); +#endif + spdlog::filename_t basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion(filename); + REQUIRE(basename == expected_base); + REQUIRE(ext == expected_ext); +} + + +TEST_CASE("file_helper_split_by_extenstion", "[file_helper::split_by_extenstion()]]") +{ + test_split_ext("mylog.txt", "mylog", ".txt"); + test_split_ext(".mylog.txt", ".mylog", ".txt"); + test_split_ext(".mylog", ".mylog", ""); + test_split_ext("/aaa/bb.d/mylog", "/aaa/bb.d/mylog", ""); + test_split_ext("/aaa/bb.d/mylog.txt", "/aaa/bb.d/mylog", ".txt"); + test_split_ext("aaa/bbb/ccc/mylog.txt", "aaa/bbb/ccc/mylog", ".txt"); + test_split_ext("aaa/bbb/ccc/mylog.", "aaa/bbb/ccc/mylog.", ""); + test_split_ext("aaa/bbb/ccc/.mylog.txt", "aaa/bbb/ccc/.mylog", ".txt"); + test_split_ext("/aaa/bbb/ccc/mylog.txt", "/aaa/bbb/ccc/mylog", ".txt"); + test_split_ext("/aaa/bbb/ccc/.mylog", "/aaa/bbb/ccc/.mylog", ""); + test_split_ext("../mylog.txt", "../mylog", ".txt"); + test_split_ext(".././mylog.txt", ".././mylog", ".txt"); + test_split_ext(".././mylog.txt/xxx", ".././mylog.txt/xxx", ""); + test_split_ext("/mylog.txt", "/mylog", ".txt"); + test_split_ext("//mylog.txt", "//mylog", ".txt"); + test_split_ext("", "", ""); + test_split_ext(".", ".", ""); + test_split_ext("..txt", ".", ".txt"); +} + + + diff --git a/fpga/tests/file_log.cpp b/fpga/tests/file_log.cpp new file mode 100644 index 000000000..086bd5ed4 --- /dev/null +++ b/fpga/tests/file_log.cpp @@ -0,0 +1,248 @@ +/* + * This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + + +TEST_CASE("simple_file_logger", "[simple_logger]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); +#else + logger->info("Test message %d", 1); + logger->info("Test message %d", 2); +#endif + logger->flush(); + REQUIRE(file_contents(filename) == std::string("Test message 1\nTest message 2\n")); + REQUIRE(count_lines(filename) == 2); +} + + +TEST_CASE("flush_on", "[flush_on]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::info); + logger->trace("Should not be flushed"); + REQUIRE(count_lines(filename) == 0); + +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); +#else + logger->info("Test message %d", 1); + logger->info("Test message %d", 2); +#endif + logger->flush(); + REQUIRE(file_contents(filename) == std::string("Should not be flushed\nTest message 1\nTest message 2\n")); + REQUIRE(count_lines(filename) == 3); +} + +TEST_CASE("rotating_file_logger1", "[rotating_logger]]") +{ + prepare_logdir(); + std::string basename = "logs/rotating_log"; + auto logger = spdlog::rotating_logger_mt("logger", basename, 1024, 0); + + for (int i = 0; i < 10; ++i) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + + logger->flush(); + auto filename = basename; + REQUIRE(count_lines(filename) == 10); +} + + +TEST_CASE("rotating_file_logger2", "[rotating_logger]]") +{ + prepare_logdir(); + std::string basename = "logs/rotating_log"; + auto logger = spdlog::rotating_logger_mt("logger", basename, 1024, 1); + for (int i = 0; i < 10; ++i) + logger->info("Test message {}", i); + + logger->flush(); + auto filename = basename; + REQUIRE(count_lines(filename) == 10); + for (int i = 0; i < 1000; i++) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + + logger->flush(); + REQUIRE(get_filesize(filename) <= 1024); + auto filename1 = basename + ".1"; + REQUIRE(get_filesize(filename1) <= 1024); +} + + +TEST_CASE("daily_logger", "[daily_logger]]") +{ + prepare_logdir(); + //calculate filename (time based) + std::string basename = "logs/daily_log"; + std::tm tm = spdlog::details::os::localtime(); + fmt::MemoryWriter w; + w.write("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); + + auto logger = spdlog::daily_logger_mt("logger", basename, 0, 0); + logger->flush_on(spdlog::level::info); + for (int i = 0; i < 10; ++i) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + + auto filename = w.str(); + REQUIRE(count_lines(filename) == 10); +} + + +TEST_CASE("daily_logger with dateonly calculator", "[daily_logger_dateonly]]") +{ + using sink_type = spdlog::sinks::daily_file_sink< + std::mutex, + spdlog::sinks::dateonly_daily_file_name_calculator>; + + prepare_logdir(); + //calculate filename (time based) + std::string basename = "logs/daily_dateonly"; + std::tm tm = spdlog::details::os::localtime(); + fmt::MemoryWriter w; + w.write("{}_{:04d}-{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + auto logger = spdlog::create("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + logger->flush(); + auto filename = w.str(); + REQUIRE(count_lines(filename) == 10); +} + +struct custom_daily_file_name_calculator +{ + static spdlog::filename_t calc_filename(const spdlog::filename_t& basename) + { + std::tm tm = spdlog::details::os::localtime(); + fmt::MemoryWriter w; + w.write("{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + return w.str(); + } +}; + +TEST_CASE("daily_logger with custom calculator", "[daily_logger_custom]]") +{ + using sink_type = spdlog::sinks::daily_file_sink< + std::mutex, + custom_daily_file_name_calculator>; + + prepare_logdir(); + //calculate filename (time based) + std::string basename = "logs/daily_dateonly"; + std::tm tm = spdlog::details::os::localtime(); + fmt::MemoryWriter w; + w.write("{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + auto logger = spdlog::create("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + + logger->flush(); + auto filename = w.str(); + REQUIRE(count_lines(filename) == 10); +} + + +/* + * File name calculations + */ + +TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 3); + REQUIRE(filename == "rotated.3.txt"); +} + +TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated", 3); + REQUIRE(filename == "rotated.3"); +} + +TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 0); + REQUIRE(filename == "rotated.txt"); +} + + + + + +// regex supported only from gcc 4.9 and above +#if defined (_MSC_VER) || !(__GNUC__ <= 4 && __GNUC_MINOR__ < 9) +#include +TEST_CASE("daily_file_sink::default_daily_file_name_calculator1", "[daily_file_sink]]") +{ + // daily_YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::default_daily_file_name_calculator::calc_filename("daily.txt"); + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])_\d\d-[0-5][0-9].txt$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} + +TEST_CASE("daily_file_sink::default_daily_file_name_calculator2", "[daily_file_sink]]") +{ + // daily_YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::default_daily_file_name_calculator::calc_filename("daily"); + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])_\d\d-[0-5][0-9]$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} + +TEST_CASE("daily_file_sink::dateonly_daily_file_name_calculator", "[daily_file_sink]]") +{ + // daily_YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::dateonly_daily_file_name_calculator::calc_filename("daily.txt"); + // date regex based on https://www.regular-expressions.info/dates.html + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} +#endif diff --git a/fpga/tests/format.cpp b/fpga/tests/format.cpp new file mode 100644 index 000000000..adb8ba8be --- /dev/null +++ b/fpga/tests/format.cpp @@ -0,0 +1,56 @@ + +#include "includes.h" + +template +std::string log_info(const T& what, spdlog::level::level_enum logger_level = spdlog::level::info) +{ + + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + + spdlog::logger oss_logger("oss", oss_sink); + oss_logger.set_level(logger_level); + oss_logger.set_pattern("%v"); + oss_logger.info(what); + + return oss.str().substr(0, oss.str().length() - spdlog::details::os::eol_size); +} + + + + + + +TEST_CASE("basic_logging ", "[basic_logging]") +{ + //const char + REQUIRE(log_info("Hello") == "Hello"); + REQUIRE(log_info("") == ""); + + //std::string + REQUIRE(log_info(std::string("Hello")) == "Hello"); + REQUIRE(log_info(std::string()) == std::string()); + + //Numbers + REQUIRE(log_info(5) == "5"); + REQUIRE(log_info(5.6) == "5.6"); + + //User defined class + //REQUIRE(log_info(some_logged_class("some_val")) == "some_val"); +} + + +TEST_CASE("log_levels", "[log_levels]") +{ + REQUIRE(log_info("Hello", spdlog::level::err) == ""); + REQUIRE(log_info("Hello", spdlog::level::critical) == ""); + REQUIRE(log_info("Hello", spdlog::level::info) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::debug) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello"); +} + + + + + + diff --git a/fpga/tests/includes.h b/fpga/tests/includes.h new file mode 100644 index 000000000..8e70efad9 --- /dev/null +++ b/fpga/tests/includes.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "catch.hpp" +#include "utils.h" + +#define SPDLOG_TRACE_ON +#define SPDLOG_DEBUG_ON + +#include "../include/spdlog/spdlog.h" +#include "../include/spdlog/sinks/null_sink.h" +#include "../include/spdlog/sinks/ostream_sink.h" + diff --git a/fpga/tests/install_libcxx.sh b/fpga/tests/install_libcxx.sh new file mode 100755 index 000000000..cee976928 --- /dev/null +++ b/fpga/tests/install_libcxx.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Install libc++ under travis + +svn --quiet co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx +mkdir libcxx/build +(cd libcxx/build && cmake .. -DLIBCXX_CXX_ABI=libstdc++ -DLIBCXX_CXX_ABI_INCLUDE_PATHS="/usr/include/c++/4.6;/usr/include/c++/4.6/x86_64-linux-gnu") +make -C libcxx/build cxx -j2 +sudo cp libcxx/build/lib/libc++.so.1.0 /usr/lib/ +sudo cp -r libcxx/build/include/c++/v1 /usr/include/c++/v1/ +sudo ln -sf /usr/lib/libc++.so.1.0 /usr/lib/libc++.so +sudo ln -sf /usr/lib/libc++.so.1.0 /usr/lib/libc++.so.1 diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp new file mode 100644 index 000000000..063e87874 --- /dev/null +++ b/fpga/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" \ No newline at end of file diff --git a/fpga/tests/registry.cpp b/fpga/tests/registry.cpp new file mode 100644 index 000000000..1936932ae --- /dev/null +++ b/fpga/tests/registry.cpp @@ -0,0 +1,84 @@ +#include "includes.h" + +static const char *tested_logger_name = "null_logger"; +static const char *tested_logger_name2 = "null_logger2"; + +TEST_CASE("register_drop", "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + REQUIRE(spdlog::get(tested_logger_name)!=nullptr); + //Throw if registring existing name + REQUIRE_THROWS_AS(spdlog::create(tested_logger_name), spdlog::spdlog_ex); +} + + +TEST_CASE("explicit register" "[registry]") +{ + spdlog::drop_all(); + auto logger = std::make_shared(tested_logger_name, std::make_shared()); + spdlog::register_logger(logger); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + //Throw if registring existing name + REQUIRE_THROWS_AS(spdlog::create(tested_logger_name), spdlog::spdlog_ex); +} + +TEST_CASE("apply_all" "[registry]") +{ + spdlog::drop_all(); + auto logger = std::make_shared(tested_logger_name, std::make_shared()); + spdlog::register_logger(logger); + auto logger2 = std::make_shared(tested_logger_name2, std::make_shared()); + spdlog::register_logger(logger2); + + int counter = 0; + spdlog::apply_all([&counter](std::shared_ptr l) + { + counter++; + }); + REQUIRE(counter == 2); + + counter = 0; + spdlog::drop(tested_logger_name2); + spdlog::apply_all([&counter](std::shared_ptr l) + { + REQUIRE(l->name() == tested_logger_name); + counter++; + } + ); + REQUIRE(counter == 1); +} + + + +TEST_CASE("drop" "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::drop(tested_logger_name); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop_all" "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::create(tested_logger_name2); + spdlog::drop_all(); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + + +TEST_CASE("drop non existing" "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::drop("some_name"); + REQUIRE_FALSE(spdlog::get("some_name")); + REQUIRE(spdlog::get(tested_logger_name)); + spdlog::drop_all(); +} + + + diff --git a/fpga/tests/test_macros.cpp b/fpga/tests/test_macros.cpp new file mode 100644 index 000000000..8ad7bd417 --- /dev/null +++ b/fpga/tests/test_macros.cpp @@ -0,0 +1,50 @@ +/* +* This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE +*/ + +#include "includes.h" + +TEST_CASE("debug and trace w/o format string", "[macros]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + + SPDLOG_TRACE(logger, "Test message 1"); + //SPDLOG_DEBUG(logger, "Test message 2"); + SPDLOG_DEBUG(logger, "Test message 2"); + logger->flush(); + + REQUIRE(ends_with(file_contents(filename), "Test message 2\n")); + REQUIRE(count_lines(filename) == 2); +} + + +TEST_CASE("debug and trace with format strings", "[macros]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + +#if !defined(SPDLOG_FMT_PRINTF) + SPDLOG_TRACE(logger, "Test message {}", 1); + //SPDLOG_DEBUG(logger, "Test message 2"); + SPDLOG_DEBUG(logger, "Test message {}", 222); +#else + SPDLOG_TRACE(logger, "Test message %d", 1); + //SPDLOG_DEBUG(logger, "Test message 2"); + SPDLOG_DEBUG(logger, "Test message %d", 222); +#endif + + logger->flush(); + + REQUIRE(ends_with(file_contents(filename), "Test message 222\n")); + REQUIRE(count_lines(filename) == 2); +} + diff --git a/fpga/tests/tests.sln b/fpga/tests/tests.sln new file mode 100644 index 000000000..d224d204f --- /dev/null +++ b/fpga/tests/tests.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2015 +VisualStudioVersion = 14.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests.vcxproj", "{59A07559-5F38-4DD6-A7FA-DB4153690B42}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|Win32.ActiveCfg = Debug|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|Win32.Build.0 = Debug|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|x64.ActiveCfg = Debug|x64 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|x64.Build.0 = Debug|x64 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|Win32.ActiveCfg = Release|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|Win32.Build.0 = Release|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|x64.ActiveCfg = Release|x64 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/fpga/tests/tests.vcxproj b/fpga/tests/tests.vcxproj new file mode 100644 index 000000000..ef9d78f93 --- /dev/null +++ b/fpga/tests/tests.vcxproj @@ -0,0 +1,145 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {59A07559-5F38-4DD6-A7FA-DB4153690B42} + tests + + + + Application + true + v140 + MultiByte + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + Application + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(SolutionDir)\..\include;%(AdditionalIncludeDirectories) + + + true + Console + + + + + Level3 + Disabled + true + _MBCS;%(PreprocessorDefinitions) + $(SolutionDir)..\include;%(AdditionalIncludeDirectories) + + + true + Console + + + + + Level4 + MaxSpeed + true + true + true + $(SolutionDir)\..\include;%(AdditionalIncludeDirectories) + + + true + true + true + Console + + + + + Level4 + MaxSpeed + true + true + true + _MBCS;%(PreprocessorDefinitions) + $(SolutionDir)..\include;%(AdditionalIncludeDirectories) + + + true + true + true + Console + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fpga/tests/tests.vcxproj.filters b/fpga/tests/tests.vcxproj.filters new file mode 100644 index 000000000..adc86e7b7 --- /dev/null +++ b/fpga/tests/tests.vcxproj.filters @@ -0,0 +1,54 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/fpga/tests/utils.cpp b/fpga/tests/utils.cpp new file mode 100644 index 000000000..e179147b1 --- /dev/null +++ b/fpga/tests/utils.cpp @@ -0,0 +1,56 @@ +#include "includes.h" + + +void prepare_logdir() +{ + spdlog::drop_all(); +#ifdef _WIN32 + system("if not exist logs mkdir logs"); + system("del /F /Q logs\\*"); +#else + auto rv = system("mkdir -p logs"); + rv = system("rm -f logs/*"); + (void)rv; +#endif +} + + +std::string file_contents(const std::string& filename) +{ + std::ifstream ifs(filename); + if (!ifs) + throw std::runtime_error("Failed open file "); + return std::string((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + +} + +std::size_t count_lines(const std::string& filename) +{ + std::ifstream ifs(filename); + if (!ifs) + throw std::runtime_error("Failed open file "); + + std::string line; + size_t counter = 0; + while(std::getline(ifs, line)) + counter++; + return counter; +} + +std::size_t get_filesize(const std::string& filename) +{ + std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); + if (!ifs) + throw std::runtime_error("Failed open file "); + + return static_cast(ifs.tellg()); +} + + +// source: https://stackoverflow.com/a/2072890/192001 +bool ends_with(std::string const & value, std::string const & ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} diff --git a/fpga/tests/utils.h b/fpga/tests/utils.h new file mode 100644 index 000000000..819bcee61 --- /dev/null +++ b/fpga/tests/utils.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +std::size_t count_lines(const std::string& filename); + +void prepare_logdir(); + +std::string file_contents(const std::string& filename); + +std::size_t count_lines(const std::string& filename); + +std::size_t get_filesize(const std::string& filename); + +bool ends_with(std::string const & value, std::string const & ending); \ No newline at end of file From f2a5e7af22e6949f652036ec56ce0ee6b9cb79ed Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 10 Jan 2018 15:41:27 +0100 Subject: [PATCH 061/560] spdlog: patch name formatter to implement fixed width names in format --- .../include/spdlog/details/pattern_formatter_impl.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h b/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h index a73f5deab..de0f0f2d5 100644 --- a/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h +++ b/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h @@ -39,9 +39,21 @@ namespace { class name_formatter:public flag_formatter { + std::string center(std::string input, int width) { + const auto whitespace = width - input.length(); + return std::string(whitespace / 2, ' ') + + input + + std::string(whitespace / 2, ' ') + + ((whitespace % 2 == 0) ? "" : " "); + } + void format(details::log_msg& msg, const std::tm&) override { +#ifdef SPDLOG_NAME_WIDTH + msg.formatted << center(*msg.logger_name, SPDLOG_NAME_WIDTH); +#else msg.formatted << *msg.logger_name; +#endif } }; } From 3cf50db98d2343472c5e2a68e6b152df414a8006 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 10 Jan 2018 15:26:52 +0100 Subject: [PATCH 062/560] logging: use new spdlog library in favor of Logger --- fpga/CMakeLists.txt | 2 + fpga/include/villas/dependency_graph_impl.hpp | 13 +- fpga/include/villas/fpga/card.hpp | 9 ++ fpga/include/villas/fpga/ip.hpp | 14 ++- fpga/include/villas/log.hpp | 19 +++ fpga/include/villas/plugin.hpp | 6 + fpga/lib/card.cpp | 21 ++-- fpga/lib/ip.cpp | 112 ++++++++---------- fpga/lib/ip_node.cpp | 21 ++-- fpga/lib/ips/fifo.cpp | 6 +- fpga/lib/ips/switch.cpp | 24 ++-- fpga/lib/plugin.cpp | 11 +- fpga/tests/fifo.cpp | 24 ++-- fpga/tests/main.cpp | 8 +- 14 files changed, 182 insertions(+), 108 deletions(-) diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index af9f08dc1..540ccee44 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) set (CMAKE_CXX_STANDARD 17) +include_directories(thirdparty/spdlog/include) + add_subdirectory(lib) add_subdirectory(tests) add_subdirectory(src) diff --git a/fpga/include/villas/dependency_graph_impl.hpp b/fpga/include/villas/dependency_graph_impl.hpp index f5d5b32d7..0b4be0869 100644 --- a/fpga/include/villas/dependency_graph_impl.hpp +++ b/fpga/include/villas/dependency_graph_impl.hpp @@ -2,8 +2,12 @@ #error "Do not include this file directly, please include depedency_graph.hpp" #endif -#include "dependency_graph.hpp" #include +#include "dependency_graph.hpp" + +#include "log.hpp" + +static auto logger = loggerGetOrCreate("DependencyGraph"); namespace villas { namespace utils { @@ -53,7 +57,7 @@ DependencyGraph::dump() { for(auto& dep : node.second) { ss << dep << " "; } - cpp_debug << node.first << ": " << ss.str(); + logger->info("{}: {}", node.first, ss.str()); } } @@ -91,10 +95,9 @@ DependencyGraph::getEvaluationOrder() const // if a round doesn't add any elements and is not the last, then // there is a circular dependency if(added == 0 and graph.size() > 0) { - cpp_error << "Circular dependency detected! IPs not available:"; - Logger::Indenter indent = cpp_debug.indent(); + logger->error("Circular dependency detected! IPs not available:"); for(auto& [key, value] : graph) { - cpp_error << key; + logger->error(" {}", key); } break; } diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index d3a79a4fc..a65590432 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -93,6 +93,11 @@ public: size_t maplen; size_t dmalen; + +protected: + SpdLogger + getLogger() const + { return loggerGetOrCreate(name); } }; using CardList = std::list>; @@ -109,6 +114,10 @@ public: static PCIeCard* create(); + + static SpdLogger + getStaticLogger() + { return loggerGetOrCreate("PCIeCardFactory"); } }; } // namespace fpga diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index d78edb850..2071a6bb4 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -60,7 +60,7 @@ public: friend std::ostream& operator<< (std::ostream& stream, const IpIdentifier& id) - { return stream << "Name: " << TXT_BOLD(id.name) << " (VLNV: " << id.vlnv << ")"; } + { return stream << TXT_BOLD(id.name) << " vlnv=" << id.vlnv; } Vlnv vlnv; std::string name; @@ -120,6 +120,9 @@ protected: uintptr_t getAddrMapped(uintptr_t address) const; + SpdLogger + getLogger() { return loggerGetOrCreate(id.name); } + struct IrqPort { int num; std::string controllerName; @@ -149,6 +152,10 @@ public: static IpCoreList make(PCIeCard* card, json_t *json_ips); +protected: + SpdLogger + getLogger() { return loggerGetOrCreate(getName()); } + private: /// Create a concrete IP instance virtual IpCore* create() = 0; @@ -157,11 +164,16 @@ private: virtual bool configureJson(IpCore& ip, json_t *json) { return true; } + virtual Vlnv getCompatibleVlnv() const = 0; virtual std::string getName() const = 0; virtual std::string getDescription() const = 0; virtual std::list getDependencies() const { return {}; } +protected: + static SpdLogger + getStaticLogger() { return loggerGetOrCreate("IpCoreFactory"); } + private: static IpCoreFactory* lookup(const Vlnv& vlnv); diff --git a/fpga/include/villas/log.hpp b/fpga/include/villas/log.hpp index 13ee951f3..83244395f 100644 --- a/fpga/include/villas/log.hpp +++ b/fpga/include/villas/log.hpp @@ -3,9 +3,28 @@ #include #include +#define SPDLOG_LEVEL_NAMES { "trace", "debug", "info ", "warn ", "error", "crit ", "off " } +#define SPDLOG_NAME_WIDTH 17 + +#include +#include + #define _ESCAPE "\x1b" #define TXT_BOLD(s) _ESCAPE "[1m" + std::string(s) + _ESCAPE "[0m" +using SpdLogger = std::shared_ptr; + +inline SpdLogger loggerGetOrCreate(const std::string& logger_name) +{ + auto logger = spdlog::get(logger_name); + if(not logger) { + logger = spdlog::stdout_color_mt(logger_name); + } + return logger; +} + + + class LoggerIndent; class Logger { diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index 26a220fd2..97de10d4d 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -28,6 +28,7 @@ #include #include +#include "log.hpp" #include "utils.h" namespace villas { @@ -77,6 +78,11 @@ public: void *handle; enum state state; +protected: + static SpdLogger + getStaticLogger() + { return loggerGetOrCreate("Plugin"); } + private: /* Just using a standard std::list<> to hold plugins is problematic, because we want to push Plugins to the list from within each Plugin's constructor diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 96da37c74..99dca69f5 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -56,12 +56,12 @@ CardList fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) { CardList cards; + auto logger = getStaticLogger(); const char *card_name; json_t *json_card; json_object_foreach(json, card_name, json_card) { - cpp_info << "Found config for FPGA card " << card_name; - Logger::Indenter indent = cpp_debug.indent(); + logger->info("Found config for FPGA card {}", card_name); json_t* json_ips = nullptr; const char* pci_slot = nullptr; @@ -77,7 +77,7 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) "id", &pci_id); if(ret != 0) { - cpp_warn << "Cannot parse JSON config"; + logger->warn("Cannot parse JSON config"); continue; } @@ -93,29 +93,28 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) const char* error; if (pci_slot != nullptr and pci_device_parse_slot(&card->filter, pci_slot, &error) != 0) { - cpp_warn << "Failed to parse PCI slot: " << error; + logger->warn("Failed to parse PCI slot: {}", error); } if (pci_id != nullptr and pci_device_parse_id(&card->filter, pci_id, &error) != 0) { - cpp_warn << "Failed to parse PCI ID: " << error; + logger->warn("Failed to parse PCI ID: {}", error); } // TODO: currently fails, fix and remove comment // if(not card->start()) { -// cpp_warn << "Cannot start FPGA card " << card_name; -// delete card; +// logger->warn("Cannot start FPGA card {}", card_name); // continue; // } card->ips = ip::IpCoreFactory::make(card.get(), json_ips); if(card->ips.empty()) { - cpp_error << "Cannot initialize IPs"; + logger->error("Cannot initialize IPs of FPGA card {}", card_name); continue; } if(not card->check()) { - cpp_warn << "Checking failed, destroying ..."; + logger->warn("Checking of FPGA card {} failed", card_name); continue; } @@ -148,6 +147,8 @@ bool fpga::PCIeCard::start() int ret; struct pci_device *pdev; + auto logger = getLogger(); + /* Search for FPGA card */ pdev = pci_lookup_device(pci, &filter); if (!pdev) @@ -176,7 +177,7 @@ bool fpga::PCIeCard::start() serror("Failed to reset PCI device"); if(not reset()) { - cpp_debug << "Failed to reset FGPA card"; + logger->error("Failed to reset FGPA card"); return false; } } diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 0fdb365fe..460e0ddb1 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -48,10 +48,11 @@ static bool buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::string name) { -// cpp_debug << "preparse " << name; - const bool nodeExists = dependencyGraph.addNode(name); + // HACK: just get the right logger + auto logger = loggerGetOrCreate("IpCoreFactory"); + // do not add IP multiple times // this happens if more than 1 IP depends on a certain other IP if(nodeExists) { @@ -60,22 +61,22 @@ buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::st json_t* json_ip = json_object_get(json_ips, name.c_str()); if(json_ip == nullptr) { - cpp_error << "IP " << name << " not found in config"; + logger->error("IP {} not found in config", name); return false; } for(auto& dependencyToken : dependencyTokens) { json_t* json_dependency = json_object_get(json_ip, dependencyToken.c_str()); if(json_dependency == nullptr) { - cpp_debug << "Property " << dependencyToken << " of " << TXT_BOLD(name) - << " not present"; + logger->debug("Property {} of {} is not present", + dependencyToken, TXT_BOLD(name)); continue; } const char* value = json_string_value(json_dependency); if(value == nullptr) { - cpp_warn << "Property " << dependencyToken << " of " << TXT_BOLD(name) - << " is invalid"; + logger->warn("Property {} of {} is invalid", + dependencyToken, TXT_BOLD(name)); continue; } @@ -83,15 +84,15 @@ buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::st if(mapping.size() != 2) { - cpp_error << "Invalid " << dependencyToken << " mapping" - << " of " << TXT_BOLD(name); + logger->error("Invalid {} mapping of {}", + dependencyToken, TXT_BOLD(name)); dependencyGraph.removeNode(name); return false; } if(name == mapping[0]) { - cpp_error << "IP " << TXT_BOLD(name)<< " cannot depend on itself"; + logger->error("IP {} cannot depend on itself", TXT_BOLD(name)); dependencyGraph.removeNode(name); return false; @@ -102,8 +103,8 @@ buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::st dependencyGraph.addDependency(name, mapping[0]); if(not buildDependencyGraph(dependencyGraph, json_ips, mapping[0])) { - cpp_error << "Dependency " << mapping[0] << " of " << TXT_BOLD(name) - << " not satified"; + logger->error("Dependency {} of {} not satisfied", + mapping[0], TXT_BOLD(name)); dependencyGraph.removeNode(mapping[0]); return false; @@ -118,14 +119,12 @@ namespace villas { namespace fpga { namespace ip { - void IpCore::dump() { - cpp_info << id; - { - Logger::Indenter indent = cpp_info.indent(); - cpp_info << " Baseaddr: 0x" << std::hex << baseaddr << std::dec; -// cpp_info << " IRQ: " << irq; -// cpp_info << " Port: " << port; + auto logger = getLogger(); + + logger->info("Base address = {:08x}", baseaddr); + for(auto& [num, irq] : irqs) { + logger->info("IRQ {}: {}:{}", num, irq.controllerName, irq.num); } } @@ -155,40 +154,32 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) { DependencyGraph dependencyGraph; IpCoreList initializedIps; + auto loggerStatic = getStaticLogger(); - { - Logger::Indenter indent = cpp_debug.indent(); - cpp_debug << "Parsing IP dependency graph:"; - void* iter = json_object_iter(json_ips); - while(iter != nullptr) { - buildDependencyGraph(dependencyGraph, json_ips, json_object_iter_key(iter)); - iter = json_object_iter_next(json_ips, iter); - } - } - - { - Logger::Indenter indent = cpp_debug.indent(); - cpp_debug << "IP initialization order:"; - - for(auto& ipName : dependencyGraph.getEvaluationOrder()) { - cpp_debug << TXT_BOLD(ipName); - } + loggerStatic->debug("Parsing IP dependency graph:"); + void* iter = json_object_iter(json_ips); + while(iter != nullptr) { + buildDependencyGraph(dependencyGraph, json_ips, json_object_iter_key(iter)); + iter = json_object_iter_next(json_ips, iter); } - - cpp_info << "Initializing IP cores"; - - Logger::Indenter indent = cpp_info.indent(); + loggerStatic->debug("IP initialization order:"); for(auto& ipName : dependencyGraph.getEvaluationOrder()) { - cpp_debug << TXT_BOLD(ipName); + loggerStatic->debug(" {}", TXT_BOLD(ipName)); + } + + + for(auto& ipName : dependencyGraph.getEvaluationOrder()) { + loggerStatic->info("Initializing {}", TXT_BOLD(ipName)); + json_t* json_ip = json_object_get(json_ips, ipName.c_str()); // extract VLNV from JSON const char* vlnv; if(json_unpack(json_ip, "{ s: s }", "vlnv", &vlnv) != 0) { - cpp_warn << "IP " << ipName << " has no entry 'vlnv'"; + loggerStatic->warn("IP {} has no entry 'vlnv'", ipName); continue; } @@ -202,13 +193,15 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) IpCoreFactory* ipCoreFactory = lookup(id.vlnv); if(ipCoreFactory == nullptr) { - cpp_warn << "No plugin found to handle " << vlnv; + loggerStatic->warn("No plugin found to handle {}", vlnv); continue; } else { - cpp_debug << "Using " << ipCoreFactory->getName() - << " for IP " << vlnv; + loggerStatic->debug("Using {} for IP {}", + ipCoreFactory->getName(), vlnv); } + auto logger = ipCoreFactory->getLogger(); + // Create new IP instance. Since this function is virtual, it will // construct the right, specialized type without knowing it here // because we have already picked the right factory. @@ -218,8 +211,8 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) auto ip = std::unique_ptr(ipCoreFactory->create()); if(ip == nullptr) { - cpp_warn << "Cannot create an instance of " - << ipCoreFactory->getName(); + logger->warn("Cannot create an instance of {}", + ipCoreFactory->getName()); continue; } @@ -229,8 +222,8 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) // extract base address if it has one if(json_unpack(json_ip, "{ s?: i }", "baseaddr", &ip->baseaddr) != 0) { - cpp_warn << "Problem while parsing base address of IP " - << TXT_BOLD(ipName); + logger->warn("Problem while parsing base address of IP {}", + TXT_BOLD(ipName)); continue; } @@ -242,8 +235,8 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) const char* irq = json_string_value(json_irq); auto tokens = utils::tokenize(irq, ":"); if(tokens.size() != 2) { - cpp_warn << "Cannot parse IRQ '" << irq << "' of" - << TXT_BOLD(ipName); + logger->warn("Cannot parse IRQ '{}' of {}", + irq, TXT_BOLD(ipName)); continue; } @@ -251,11 +244,11 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) try { num = std::stoi(tokens[1]); } catch(const std::invalid_argument&) { - cpp_warn << "IRQ number is not an integer: '" << irq << "'"; + logger->warn("IRQ number is not an integer: '{}'", irq); continue; } - ip->irqs[index] = {num, tokens[0]}; + ip->irqs[index] = {num, tokens[0], ""}; } } @@ -270,14 +263,13 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) }); if(iter == initializedIps.end()) { - cpp_error << "Cannot find '" << depName << "' dependency " - << depVlnv.toString() - << "of " << TXT_BOLD(ipName); + logger->error("Cannot find '{}' dependency {} of {}", + depName, depVlnv, TXT_BOLD(ipName)); dependenciesOk = false; break; } - cpp_debug << "Found dependency IP " << (*iter)->id; + logger->debug("Found dependency IP {}", (*iter)->id); ip->dependencies[depName] = (*iter).get(); } @@ -287,18 +279,18 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) // IP-specific setup via JSON config if(not ipCoreFactory->configureJson(*ip, json_ip)) { - cpp_warn << "Cannot configure IP from JSON"; + logger->warn("Cannot configure IP from JSON"); continue; } // TODO: currently fails, fix and remove comment // if(not ip->start()) { -// cpp_error << "Cannot start IP" << ip->id.name; +// logger->error("Cannot start IP {}", ip->id.name); // continue; // } if(not ip->check()) { - cpp_error << "Checking IP " << ip->id.name << " failed"; + logger->error("Checking of IP {} failed", ip->id.name); continue; } diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index 47539c674..6c2b5df04 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -16,10 +16,11 @@ bool IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) { auto& ipNode = reinterpret_cast(ip); + auto logger = getLogger(); json_t* json_ports = json_object_get(json_ip, "ports"); if(json_ports == nullptr) { - cpp_error << "IpNode " << ip << " has no ports property"; + logger->error("IpNode {} has no ports property", ip); return false; } @@ -30,7 +31,7 @@ IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) const bool hasSlavePorts = json_is_array(json_slave); if( (not hasMasterPorts) and (not hasSlavePorts)) { - cpp_error << "IpNode " << ip << " has no ports"; + logger->error("IpNode {} has no ports", ip); return false; } @@ -50,6 +51,8 @@ IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) bool IpNodeFactory::populatePorts(std::map& portMap, json_t* json) { + auto logger = getLogger(); + size_t index; json_t* json_port; json_array_foreach(json, index, json_port) { @@ -60,19 +63,19 @@ IpNodeFactory::populatePorts(std::map& portMap, json_t* "num", &myPortNum, "to", &to); if(ret != 0) { - cpp_error << "Port definition required field 'num'"; + logger->error("Port definition required field 'num'"); return false; } if(to == nullptr) { - cpp_warn << "Nothing connected to port " << myPortNum; + logger->debug("Nothing connected to port {}", myPortNum); portMap[myPortNum] = {}; continue; } const auto tokens = utils::tokenize(to, ":"); if(tokens.size() != 2) { - cpp_error << "Too many tokens in property 'other'"; + logger->error("Too many tokens in property 'other'"); return false; } @@ -80,11 +83,11 @@ IpNodeFactory::populatePorts(std::map& portMap, json_t* try { otherPortNum = std::stoi(tokens[1]); } catch(const std::invalid_argument&) { - cpp_error << "Other port number is not an integral number"; + logger->error("Other port number is not an integral number"); return false; } - cpp_debug << "Adding port mapping: " << myPortNum << ":" << to; + logger->debug("Adding port mapping: {}:{}", myPortNum, to); portMap[myPortNum] = { otherPortNum, tokens[0] }; } @@ -116,6 +119,8 @@ IpNode::loopbackPossible() const bool IpNode::connectLoopback() { + auto logger = getLogger(); + auto ports = getLoopbackPorts(); const auto& portMaster = portsMaster[ports.first]; const auto& portSlave = portsSlave[ports.second]; @@ -125,7 +130,7 @@ IpNode::connectLoopback() card->lookupIp(portMaster.nodeName)); if(axiStreamSwitch == nullptr) { - cpp_error << "Cannot find IP " << *axiStreamSwitch; + logger->error("Cannot find IP {}", *axiStreamSwitch); return false; } diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 410e745ac..2dd1276b9 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -43,14 +43,16 @@ static FifoFactory factory; bool FifoFactory::configureJson(IpCore &ip, json_t *json_ip) { + auto logger = getLogger(); + if(not IpNodeFactory::configureJson(ip, json_ip)) { - cpp_error << "Configuring IpNode failed"; + logger->error("Configuring IpNode failed"); return false; } auto& fifo = reinterpret_cast(ip); if(json_unpack(json_ip, "{ s: i }", "baseaddr_axi4", &fifo.baseaddr_axi4) != 0) { - cpp_warn << "Cannot parse property 'baseaddr_axi4' of " << ip; + logger->warn("Cannot parse property 'baseaddr_axi4'"); return false; } diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index 51148a895..f60c552fb 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -43,7 +43,6 @@ AxiStreamSwitch::start() sw_cfg.MaxNumSI = portsSlave.size(); if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseaddr()) != XST_SUCCESS) { - cpp_error << "Cannot start " << *this; return false; } @@ -64,15 +63,18 @@ AxiStreamSwitch::start() bool AxiStreamSwitch::connect(int portSlave, int portMaster) { + auto logger = getLogger(); + if(portMapping[portMaster] == portSlave) { - cpp_debug << "Ports already connected"; + logger->debug("Ports already connected"); return true; } for(auto [master, slave] : portMapping) { if(slave == portSlave) { - cpp_warn << "Slave " << slave << " has already been connected to " - << "master " << master << ". Disabling master " << master; + logger->warn("Slave {} has already been connected to master {}. " + "Disabling master {}.", + slave, master, master); XAxisScr_RegUpdateDisable(&xSwitch); XAxisScr_MiPortDisable(&xSwitch, master); @@ -85,7 +87,7 @@ AxiStreamSwitch::connect(int portSlave, int portMaster) XAxisScr_MiPortEnable(&xSwitch, portMaster, portSlave); XAxisScr_RegUpdateEnable(&xSwitch); - cpp_debug << "Connect slave " << portSlave << " to master " << portMaster; + logger->debug("Connect slave {} to master {}", portSlave, portMaster); return true; } @@ -93,8 +95,10 @@ AxiStreamSwitch::connect(int portSlave, int portMaster) bool AxiStreamSwitch::disconnectMaster(int port) { - cpp_debug << "Disconnect slave " << portMapping[port] - << " from master " << port; + auto logger = getLogger(); + + logger->debug("Disconnect slave {} from master {}", + portMapping[port], port); XAxisScr_MiPortDisable(&xSwitch, port); portMapping[port] = PORT_DISABLED; @@ -104,15 +108,17 @@ AxiStreamSwitch::disconnectMaster(int port) bool AxiStreamSwitch::disconnectSlave(int port) { + auto logger = getLogger(); + for(auto [master, slave] : portMapping) { if(slave == port) { - cpp_debug << "Disconnect slave " << slave << " from master " << master; + logger->debug("Disconnect slave {} from master {}", slave, master); XAxisScr_MiPortDisable(&xSwitch, master); return true; } } - cpp_debug << "Slave " << port << " hasn't been connected to any master"; + logger->debug("Slave {} hasn't been connected to any master", port); return true; } diff --git a/fpga/lib/plugin.cpp b/fpga/lib/plugin.cpp index b0e2cbd6a..82d358a24 100644 --- a/fpga/lib/plugin.cpp +++ b/fpga/lib/plugin.cpp @@ -30,7 +30,7 @@ #include #include "plugin.hpp" - +#include "log.hpp" namespace villas { @@ -117,15 +117,18 @@ Plugin::unload() void Plugin::dump() { - std::cout << " - " << this->name << ": " << this->description << std::endl; + auto logger = getStaticLogger(); + logger->info("Name: '{}' Description: '{}'", name, description); } void Plugin::dumpList() { - std::cout << "Registered plugins:" << std::endl; + auto logger = getStaticLogger(); + + logger->info("Registered plugins:"); for(auto& p : pluginList) { - std::cout << " - " << p->name << std::endl; + logger->info(" - {}", p->name); } } diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index 409ee06e7..1d5fc146b 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -34,6 +34,8 @@ #include #include +#include "log.hpp" + extern villas::fpga::PCIeCard* fpga; Test(fpga, fifo, .description = "FIFO") @@ -43,6 +45,8 @@ Test(fpga, fifo, .description = "FIFO") char src[255], dst[255]; struct fpga_ip *fifo; + auto logger = loggerGetOrCreate("unittest:fifo"); + for(auto& ip : fpga->ips) { // skip non-fifo IPs if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_fifo_mm_s:")) @@ -51,7 +55,7 @@ Test(fpga, fifo, .description = "FIFO") auto fifo = reinterpret_cast(*ip); if(not fifo.loopbackPossible()) { - cpp_info << "Loopback test not possible for " << *ip; + logger->info("Loopback test not possible for {}", *ip); continue; } @@ -60,16 +64,22 @@ Test(fpga, fifo, .description = "FIFO") /* Get some random data to compare */ memset(dst, 0, sizeof(dst)); len = read_random((char *) src, sizeof(src)); - if (len != sizeof(src)) - error("Failed to get random data"); + if (len != sizeof(src)) { + logger->error("Failed to get random data"); + continue; + } len = fifo.write(src, sizeof(src)); - if (len != sizeof(src)) - cpp_error << "Failed to send to FIFO"; + if (len != sizeof(src)) { + logger->error("Failed to send to FIFO"); + continue; + } len = fifo.read(dst, sizeof(dst)); - if (len != sizeof(dst)) - cpp_error << "Failed to read from FIFO"; + if (len != sizeof(dst)) { + logger->error("Failed to read from FIFO"); + continue; + } /* Compare data */ cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 3c9c52bd1..46c539926 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -33,6 +33,8 @@ #include #include +#include + #define FPGA_CARD "vc707" #define TEST_CONFIG "../etc/fpga.json" #define TEST_LEN 0x1000 @@ -57,7 +59,9 @@ static void init() villas::Plugin::dumpList(); - Logger::setLogLevel(Logger::LogLevel::Debug); + auto logger = loggerGetOrCreate("unittest"); + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); ret = pci_init(&pci); cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); @@ -87,7 +91,7 @@ static void init() fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); if(fpgaCards.size() == 0) { - cpp_error << "No FPGA cards found!"; + logger->error("No FPGA cards found!"); } else { fpga = fpgaCards.front().get(); } From 09534e83a6297654ad9fd8b0adb2d4b8b65051ad Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 10 Jan 2018 15:42:33 +0100 Subject: [PATCH 063/560] lib/plugin: make type an argument of constructor --- fpga/include/villas/fpga/card.hpp | 3 +-- fpga/include/villas/fpga/ip.hpp | 4 ++-- fpga/include/villas/plugin.hpp | 2 +- fpga/lib/plugin.cpp | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index a65590432..2ac4ce514 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -106,8 +106,7 @@ class PCIeCardFactory : public Plugin { public: PCIeCardFactory() : - Plugin("FPGA Card plugin") - { pluginType = Plugin::Type::FpgaCard; } + Plugin(Plugin::Type::FpgaCard, "FPGA Card plugin") {} static CardList make(json_t *json, struct pci* pci, ::vfio_container* vc); diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 2071a6bb4..bdcd78bcf 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -145,8 +145,8 @@ using IpCoreList = std::list>; class IpCoreFactory : public Plugin { public: IpCoreFactory(std::string concreteName) : - Plugin(std::string("IpCore - ") + concreteName) - { pluginType = Plugin::Type::FpgaIp; } + Plugin(Plugin::Type::FpgaIp, std::string("IpCore - ") + concreteName) + {} /// Returns a running and checked FPGA IP static IpCoreList diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index 97de10d4d..c07501e83 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -42,7 +42,7 @@ public: FpgaCard, }; - Plugin(std::string name); + Plugin(Type type, const std::string& name); virtual ~Plugin(); // copying a plugin doesn't make sense, so explicitly deny it diff --git a/fpga/lib/plugin.cpp b/fpga/lib/plugin.cpp index 82d358a24..1b987c254 100644 --- a/fpga/lib/plugin.cpp +++ b/fpga/lib/plugin.cpp @@ -45,8 +45,8 @@ Plugin::pluginListBuffer; int Plugin::pluginListNiftyCounter; -Plugin::Plugin(std::string name) : - pluginType(Plugin::Type::Unknown), +Plugin::Plugin(Type type, const std::string& name) : + pluginType(type), name(name), description(""), path(""), From 44e78643ea08fbf683b857234191236609b757f7 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 10 Jan 2018 15:43:36 +0100 Subject: [PATCH 064/560] lib/log: purge remaining of old logger --- fpga/include/villas/log.hpp | 107 ------------------------------------ fpga/lib/CMakeLists.txt | 1 - fpga/lib/log.cpp | 26 --------- 3 files changed, 134 deletions(-) delete mode 100644 fpga/lib/log.cpp diff --git a/fpga/include/villas/log.hpp b/fpga/include/villas/log.hpp index 83244395f..8a7a84bad 100644 --- a/fpga/include/villas/log.hpp +++ b/fpga/include/villas/log.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #define SPDLOG_LEVEL_NAMES { "trace", "debug", "info ", "warn ", "error", "crit ", "off " } @@ -22,109 +21,3 @@ inline SpdLogger loggerGetOrCreate(const std::string& logger_name) } return logger; } - - - -class LoggerIndent; - -class Logger { - friend LoggerIndent; -public: - - enum class LogLevel : int { - Debug, - Info, - Warning, - Error, - Disabled - }; - - class LoggerNewline { - public: - LoggerNewline(bool enabled = true) : enabled(enabled) {} - ~LoggerNewline() { - if(enabled) - std::cout << std::endl; - } - template - LoggerNewline& operator<< (T const& value) { - if(enabled) - std::cout << value; - return *this; - } - bool enabled; - }; - - class Indenter { - public: - Indenter(Logger* l) : logger(l) - { logger->increaseIndention(); } - - ~Indenter() - { logger->decreaseIndention(); } - private: - Logger* logger; - }; - - Logger(LogLevel level, std::string prefix = "") : level(level), prefix(prefix) {} - - Indenter indent() - { return Indenter(this); } - - static std::string - getPadding() - { - std::string out = ""; - for(int i = 0; i < depthCurrent; i++) out.append("\u2551 "); - return out; - } - - template - LoggerNewline operator<< (T const& value) { - if(level >= global_level) { - - if(depth > depthCurrent) { - std::cout << Logger::getPadding() << "\u255f\u2500\u2556" << std::endl; - depthCurrent++; - } - std::cout << Logger::getPadding() << "\u255f " << prefix << value; - return LoggerNewline(); - } else { - return LoggerNewline(false); - } - } - - - void - increaseIndention() - { - depth++; - } - - void - decreaseIndention() - { - if(depth == depthCurrent) - std::cout << Logger::getPadding() << std::endl; - - depthCurrent = --depth; - } - - static - void - setLogLevel(LogLevel level) - { global_level = level; } - -private: - LogLevel level; - std::string prefix; - static int depth; - static LogLevel global_level; - static int depthCurrent; -}; - - -extern Logger cpp_debug; -extern Logger cpp_info; -extern Logger cpp_warn; -extern Logger cpp_error; diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index db8b7f023..62cff3113 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -33,7 +33,6 @@ set(SOURCES log.c log_config.c log_helper.c - log.cpp ) include(FindPkgConfig) diff --git a/fpga/lib/log.cpp b/fpga/lib/log.cpp deleted file mode 100644 index a16ebc375..000000000 --- a/fpga/lib/log.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "log.hpp" -#include "utils.h" - -int Logger::depth; -int Logger::depthCurrent; -Logger::LogLevel Logger::global_level = Logger::LogLevel::Info; - -Logger cpp_debug(Logger::LogLevel::Debug, "" CLR_BLU(" Debug ") "| "); -Logger cpp_info(Logger::LogLevel::Info); -Logger cpp_warn(Logger::LogLevel::Warning, "" CLR_YEL("Warning") "| "); -Logger cpp_error(Logger::LogLevel::Error, "" CLR_RED(" Error ") "| "); - -void test() -{ - cpp_debug << "Hello"; - { - Logger::Indenter indent = cpp_debug.indent(); - cpp_debug << "indented"; - { - Logger::Indenter indent = cpp_debug.indent(); - cpp_debug << "indented"; - } - } - - cpp_debug << "and root again"; -} From 687f1e5bba46bb7c2b908429af807f14ca9496bc Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 10 Jan 2018 15:45:24 +0100 Subject: [PATCH 065/560] lib/ips/fifo: use std:min in favor of old macro --- fpga/lib/ips/fifo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 2dd1276b9..a8378dc37 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -108,7 +108,7 @@ size_t Fifo::write(const void *buf, size_t len) size_t Fifo::read(void *buf, size_t len) { size_t nextlen = 0; - uint32_t rxlen; + size_t rxlen; auto intc = reinterpret_cast(dependencies["intc"]); @@ -119,7 +119,7 @@ size_t Fifo::read(void *buf, size_t len) /* Get length of next frame */ rxlen = XLlFifo_RxGetLen(&xFifo); - nextlen = MIN(rxlen, len); + nextlen = std::min(rxlen, len); /* Read from FIFO */ XLlFifo_Read(&xFifo, buf, nextlen); From f987c29d7107a4ae27c369feb2160cfa841b4470 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 14:46:00 +0100 Subject: [PATCH 066/560] lib/ip: change interface: start() -> init() --- fpga/include/villas/fpga/card.hpp | 2 +- fpga/include/villas/fpga/ip.hpp | 2 +- fpga/include/villas/fpga/ips/fifo.hpp | 2 +- fpga/include/villas/fpga/ips/intc.hpp | 2 +- fpga/include/villas/fpga/ips/switch.hpp | 2 +- fpga/include/villas/fpga/ips/timer.hpp | 2 +- fpga/lib/card.cpp | 10 +++++----- fpga/lib/ip.cpp | 8 ++++---- fpga/lib/ips/fifo.cpp | 2 +- fpga/lib/ips/intc.cpp | 2 +- fpga/lib/ips/switch.cpp | 2 +- fpga/lib/ips/timer.cpp | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 2ac4ce514..729745ab8 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -67,7 +67,7 @@ public: PCIeCard() : filter(PCI_FILTER_DEFAULT_FPGA) {} - bool start(); + bool init(); bool stop() { return true; } bool check() { return true; } bool reset() { return true; } diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index bdcd78bcf..258bfaade 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -81,7 +81,7 @@ public: // IPs can implement this interface virtual bool check() { return true; } - virtual bool start() { return true; } + virtual bool init() { return true; } virtual bool stop() { return true; } virtual bool reset() { return true; } virtual void dump(); diff --git a/fpga/include/villas/fpga/ips/fifo.hpp b/fpga/include/villas/fpga/ips/fifo.hpp index bf26089b3..5d9113448 100644 --- a/fpga/include/villas/fpga/ips/fifo.hpp +++ b/fpga/include/villas/fpga/ips/fifo.hpp @@ -43,7 +43,7 @@ class Fifo : public IpNode public: friend class FifoFactory; - bool start(); + bool init(); bool stop(); size_t write(const void* buf, size_t len); diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index d4ec73e66..5961378a5 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -46,7 +46,7 @@ public: ~InterruptController(); - bool start(); + bool init(); int enableInterrupt(IrqMaskType mask, bool polling); int enableInterrupt(IrqPort irq, bool polling) diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index 489e61920..64767346b 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -47,7 +47,7 @@ class AxiStreamSwitch : public IpNode { public: friend class AxiStreamSwitchFactory; - bool start(); + bool init(); bool connect(int portSlave, int portMaster); bool disconnectMaster(int port); diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp index dd1147f89..49354052d 100644 --- a/fpga/include/villas/fpga/ips/timer.hpp +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -40,7 +40,7 @@ namespace ip { class Timer : public IpCore { public: - bool start(); + bool init(); private: XTmrCtr xTmr; diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 99dca69f5..8c3603095 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -102,10 +102,10 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) // TODO: currently fails, fix and remove comment -// if(not card->start()) { -// logger->warn("Cannot start FPGA card {}", card_name); -// continue; -// } + if(not card->init()) { + logger->warn("Cannot start FPGA card {}", card_name); + continue; + } card->ips = ip::IpCoreFactory::make(card.get(), json_ips); if(card->ips.empty()) { @@ -142,7 +142,7 @@ PCIeCard::lookupIp(std::string name) const { } -bool fpga::PCIeCard::start() +bool fpga::PCIeCard::init() { int ret; struct pci_device *pdev; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 460e0ddb1..bd3dcda95 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -284,10 +284,10 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) } // TODO: currently fails, fix and remove comment -// if(not ip->start()) { -// logger->error("Cannot start IP {}", ip->id.name); -// continue; -// } + if(not ip->init()) { + logger->error("Cannot start IP {}", ip->id.name); + continue; + } if(not ip->check()) { logger->error("Checking of IP {} failed", ip->id.name); diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index a8378dc37..eb7dc4669 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -60,7 +60,7 @@ FifoFactory::configureJson(IpCore &ip, json_t *json_ip) } -bool Fifo::start() +bool Fifo::init() { XLlFifo_Config fifo_cfg; diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index e7ae502d5..950718fe1 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -45,7 +45,7 @@ InterruptController::~InterruptController() vfio_pci_msi_deinit(&card->vfio_device , this->efds); } -bool InterruptController::start() +bool InterruptController::init() { const uintptr_t base = getBaseaddr(); diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index f60c552fb..81110a7f7 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -35,7 +35,7 @@ namespace ip { static AxiStreamSwitchFactory factory; bool -AxiStreamSwitch::start() +AxiStreamSwitch::init() { /* Setup AXI-stream switch */ XAxis_Switch_Config sw_cfg; diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index 8a67f515b..fbd0219fd 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -37,7 +37,7 @@ namespace ip { // instantiate factory to make available to plugin infrastructure static TimerFactory factory; -bool Timer::start() +bool Timer::init() { XTmrCtr_Config xtmr_cfg; xtmr_cfg.SysClockFreqHz = FPGA_AXI_HZ; From 82204990cd6947c220ea3f95037c1844ad3e7fcb Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 14:46:59 +0100 Subject: [PATCH 067/560] lib/card: remove unused C code --- fpga/lib/card.cpp | 300 +--------------------------------------------- 1 file changed, 1 insertion(+), 299 deletions(-) diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 8c3603095..3dca60dcb 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -182,306 +182,8 @@ bool fpga::PCIeCard::init() } } - /* Initialize IP cores */ -// for (size_t j = 0; j < list_length(&ips); j++) { -// struct fpga_ip *i = (struct fpga_ip *) list_at(&ips, j); - -// ret = fpga_ip_start(i); -// if (ret) -// error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); -// } - - return 0; + return true; } - -#if 0 - -int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) -{ - assert(c->state == STATE_DESTROYED); - - c->vfio_container = vc; - c->pci = pci; - - list_init(&c->ips); - - /* Default values */ - c->filter.id.vendor = FPGA_PCI_VID_XILINX; - c->filter.id.device = FPGA_PCI_PID_VFPGA; - - c->affinity = 0; - c->do_reset = 0; - - c->state = STATE_INITIALIZED; - - return 0; -} - -int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name) -{ - int ret; - - json_t *json_ips; - json_t *json_slot = NULL; - json_t *json_id = NULL; - json_error_t err; - - c->name = strdup(name); - - ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: b, s?: o, s?: o, s: o }", - "affinity", &c->affinity, - "do_reset", &c->do_reset, - "slot", &json_slot, - "id", &json_id, - "ips", &json_ips - ); - if (ret) - jerror(&err, "Failed to parse FPGA vard configuration"); - - if (json_slot) { - const char *err, *slot; - - slot = json_string_value(json_slot); - if (slot) { - ret = pci_device_parse_slot(&c->filter, slot, &err); - if (ret) - error("Failed to parse PCI slot: %s", err); - } - else - error("PCI slot must be a string"); - } - - if (json_id) { - const char *err, *id; - - id = json_string_value(json_id); - if (id) { - ret = pci_device_parse_id(&c->filter, (char*) id, &err); - if (ret) - error("Failed to parse PCI id: %s", err); - } - else - error("PCI ID must be a string"); - } - - if (!json_is_object(json_ips)) - error("FPGA card IPs section must be an object"); - - const char *name_ip; - json_t *json_ip; - json_object_foreach(json_ips, name_ip, json_ip) { - const char *vlnv; - - struct fpga_ip_type *vt; - struct fpga_ip *ip = (struct fpga_ip *) alloc(sizeof(struct fpga_ip)); - - ip->card = c; - - ret = json_unpack_ex(json_ip, &err, 0, "{ s: s }", "vlnv", &vlnv); - if (ret) - error("Failed to parse FPGA IP '%s' of card '%s'", name_ip, name); - - vt = fpga_ip_type_lookup(vlnv); - if (!vt) - error("FPGA IP core VLNV identifier '%s' is invalid", vlnv); - - ret = fpga_ip_init(ip, vt); - if (ret) - error("Failed to initalize FPGA IP core"); - - ret = fpga_ip_parse(ip, json_ip, name_ip); - if (ret) - error("Failed to parse FPGA IP core"); - - list_push(&c->ips, ip); - } - - c->state = STATE_PARSED; - - return 0; -} - -int fpga_card_parse_list(struct list *cards, json_t *cfg) -{ - int ret; - - if (!json_is_object(cfg)) - error("FPGA card configuration section must be a JSON object"); - - const char *name; - json_t *json_fpga; - json_object_foreach(cfg, name, json_fpga) { - struct fpga_card *c = (struct fpga_card *) alloc(sizeof(struct fpga_card)); - - ret = ::fpga_card_parse(c, json_fpga, name); - if (ret) - error("Failed to parse FPGA card configuration"); - - list_push(cards, c); - } - - return 0; -} - -int fpga_card_start(struct fpga_card *c) -{ - int ret; - - struct pci_device *pdev; - - assert(c->state == STATE_INITIALIZED); - - /* Search for FPGA card */ - pdev = pci_lookup_device(c->pci, &c->filter); - if (!pdev) - error("Failed to find PCI device"); - - /* Attach PCIe card to VFIO container */ - ret = vfio_pci_attach(&c->vfio_device, c->vfio_container, pdev); - if (ret) - error("Failed to attach VFIO device"); - - /* Map PCIe BAR */ - c->map = (char*) vfio_map_region(&c->vfio_device, VFIO_PCI_BAR0_REGION_INDEX); - if (c->map == MAP_FAILED) - serror("Failed to mmap() BAR0"); - - /* Enable memory access and PCI bus mastering for DMA */ - ret = vfio_pci_enable(&c->vfio_device); - if (ret) - serror("Failed to enable PCI device"); - - /* Reset system? */ - if (c->do_reset) { - /* Reset / detect PCI device */ - ret = vfio_pci_reset(&c->vfio_device); - if (ret) - serror("Failed to reset PCI device"); - - ret = fpga_card_reset(c); - if (ret) - error("Failed to reset FGPA card"); - } - - /* Initialize IP cores */ - for (size_t j = 0; j < list_length(&c->ips); j++) { - struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); - - ret = fpga_ip_start(i); - if (ret) - error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); - } - - c->state = STATE_STARTED; - - return 0; -} - -int fpga_card_stop(struct fpga_card *c) -{ - int ret; - - assert(c->state == STATE_STOPPED); - - for (size_t j = 0; j < list_length(&c->ips); j++) { - struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); - - ret = fpga_ip_stop(i); - if (ret) - error("Failed to stop FPGA IP core: %s (%u)", i->name, ret); - } - - c->state = STATE_STOPPED; - - return 0; -} - -void fpga_card_dump(struct fpga_card *c) -{ - info("VILLASfpga card:"); - { INDENT - info("Slot: %04x:%02x:%02x.%d", c->vfio_device.pci_device->slot.domain, c->vfio_device.pci_device->slot.bus, c->vfio_device.pci_device->slot.device, c->vfio_device.pci_device->slot.function); - info("Vendor ID: %04x", c->vfio_device.pci_device->id.vendor); - info("Device ID: %04x", c->vfio_device.pci_device->id.device); - info("Class ID: %04x", c->vfio_device.pci_device->id.class_code); - - info("BAR0 mapped at %p", c->map); - - info("IP blocks:"); - for (size_t j = 0; j < list_length(&c->ips); j++) { INDENT - struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); - - fpga_ip_dump(i); - } - } - - vfio_dump(c->vfio_device.group->container); -} - -int fpga_card_check(struct fpga_card *c) -{ - assert(c->state == STATE_PARSED); - - - /* Check FPGA configuration */ - struct fpga_vlnv vlnv_reset = { "xilinx.com", "ip", "axi_gpio", NULL }; - c->reset = fpga_vlnv_lookup(&c->ips, &vlnv_reset); - if (!c->reset) - error("FPGA is missing a reset controller"); - - struct fpga_vlnv vlnv_intc = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }; - c->intc = fpga_vlnv_lookup(&c->ips, &vlnv_intc); - if (!c->intc) - error("FPGA is missing a interrupt controller"); - - struct fpga_vlnv vlnv_sw = { "xilinx.com", "ip", "axis_interconnect", NULL }; - c->sw = fpga_vlnv_lookup(&c->ips, &vlnv_sw); - if (!c->sw) - warn("FPGA is missing an AXI4-Stream switch"); - - return 0; -} - -int fpga_card_destroy(struct fpga_card *c) -{ - list_destroy(&c->ips, (dtor_cb_t) fpga_ip_destroy, true); - - return 0; -} - -int fpga_card_reset(struct fpga_card *c) -{ - int ret; - char state[4096]; - - /* Save current state of PCI configuration space */ - ret = pread(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); - if (ret != sizeof(state)) - return -1; - - uint32_t *rst_reg = (uint32_t *) (c->map + c->reset->baseaddr); - - debug(3, "FPGA: reset"); - rst_reg[0] = 1; - - usleep(100000); - - /* Restore previous state of PCI configuration space */ - ret = pwrite(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); - if (ret != sizeof(state)) - return -1; - - /* After reset the value should be zero again */ - if (rst_reg[0]) - return -2; - - c->state = STATE_INITIALIZED; - - return 0; -} - -#endif - } // namespace fpga } // namespace villas From 80bc9af0e71a4492db76f9cf7ab064b67c9b8db9 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 14:47:53 +0100 Subject: [PATCH 068/560] lib/ips/intc: change waitForInterrupt() interface Return -1 on failure, change type to `int`. int/2 bits should still be enough to track overflowed interrupts. --- fpga/include/villas/fpga/ips/intc.hpp | 4 +++- fpga/lib/ips/intc.cpp | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 5961378a5..54e76a6e0 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -56,7 +56,9 @@ public: int disableInterrupt(IrqPort irq) { return disableInterrupt(1 << irq.num); } - uint64_t waitForInterrupt(int irq); + int waitForInterrupt(int irq); + int waitForInterrupt(IrqPort irq) + { return waitForInterrupt(irq.num); } private: struct Interrupt { diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 950718fe1..0c4022ff9 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -124,7 +124,7 @@ InterruptController::disableInterrupt(InterruptController::IrqMaskType mask) return true; } -uint64_t InterruptController::waitForInterrupt(int irq) +int InterruptController::waitForInterrupt(int irq) { assert(irq < maxIrqs); @@ -134,21 +134,26 @@ uint64_t InterruptController::waitForInterrupt(int irq) uint32_t isr, mask = 1 << irq; do { + // poll status register isr = XIntc_In32(base + XIN_ISR_OFFSET); pthread_testcancel(); } while ((isr & mask) != mask); + // acknowledge interrupt XIntc_Out32(base + XIN_IAR_OFFSET, mask); + // we can only tell that there has been (at least) one interrupt return 1; } else { - uint64_t cnt; - ssize_t ret = read(efds[irq], &cnt, sizeof(cnt)); - if (ret != sizeof(cnt)) - return 0; + uint64_t count; - return cnt; + // block until there has been an interrupt, read number of interrupts + ssize_t ret = read(efds[irq], &count, sizeof(count)); + if (ret != sizeof(count)) + return -1; + + return static_cast(count); } } From 77135feebc86e24b15fe3c0809697eee167a2270 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 14:50:59 +0100 Subject: [PATCH 069/560] lib/ips/intc: use new logger --- fpga/lib/ips/intc.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 0c4022ff9..c2426cb1f 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -48,6 +48,7 @@ InterruptController::~InterruptController() bool InterruptController::init() { const uintptr_t base = getBaseaddr(); + auto logger = getLogger(); num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); if (num_irqs < 0) @@ -59,8 +60,10 @@ bool InterruptController::init() /* For each IRQ */ for (int i = 0; i < num_irqs; i++) { /* Pin to core */ - if(kernel_irq_setaffinity(nos[i], card->affinity, NULL) !=0) - serror("Failed to change affinity of VFIO-MSI interrupt"); + if(kernel_irq_setaffinity(nos[i], card->affinity, nullptr) != 0) { + logger->error("Failed to change affinity of VFIO-MSI interrupt"); + return false; + } /* Setup vector */ XIntc_Out32(base + XIN_IVAR_OFFSET + i * 4, i); @@ -72,7 +75,7 @@ bool InterruptController::init() XIntc_Out32(base + XIN_IER_OFFSET, 0x00000000); /* Disable all IRQs by default */ XIntc_Out32(base + XIN_MER_OFFSET, XIN_INT_HARDWARE_ENABLE_MASK | XIN_INT_MASTER_ENABLE_MASK); - debug(4, "FPGA: enabled interrupts"); + logger->debug("enabled interrupts");; return true; } @@ -80,12 +83,12 @@ bool InterruptController::init() int InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool polling) { - uint32_t ier, imr; + auto logger = getLogger(); const uintptr_t base = getBaseaddr(); /* Current state of INTC */ - ier = XIntc_In32(base + XIN_IER_OFFSET); - imr = XIntc_In32(base + XIN_IMR_OFFSET); + const uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); + const uint32_t imr = XIntc_In32(base + XIN_IMR_OFFSET); /* Clear pending IRQs */ XIntc_Out32(base + XIN_IAR_OFFSET, mask); @@ -104,11 +107,10 @@ InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool XIntc_Out32(base + XIN_IMR_OFFSET, imr | mask); } - debug(3, "New ier = %#x", XIntc_In32(base + XIN_IER_OFFSET)); - debug(3, "New imr = %#x", XIntc_In32(base + XIN_IMR_OFFSET)); - debug(3, "New isr = %#x", XIntc_In32(base + XIN_ISR_OFFSET)); - - debug(8, "FPGA: Interupts enabled: mask=%#x polling=%d", mask, polling); + logger->debug("New ier = {:x}", XIntc_In32(base + XIN_IER_OFFSET)); + logger->debug("New imr = {:x}", XIntc_In32(base + XIN_IMR_OFFSET)); + logger->debug("New isr = {:x}", XIntc_In32(base + XIN_ISR_OFFSET)); + logger->debug("Interupts enabled: mask={:x} polling={:d}", mask, polling); return true; } From 05131258d4c2e76eadb622a2ebdd312a8927756b Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 14:52:09 +0100 Subject: [PATCH 070/560] lib/card: use new logger --- fpga/lib/card.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 3dca60dcb..da0e2d830 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -151,30 +151,40 @@ bool fpga::PCIeCard::init() /* Search for FPGA card */ pdev = pci_lookup_device(pci, &filter); - if (!pdev) - error("Failed to find PCI device"); + if (!pdev) { + logger->error("Failed to find PCI device"); + return false; + } /* Attach PCIe card to VFIO container */ ret = ::vfio_pci_attach(&vfio_device, vfio_container, pdev); - if (ret) - error("Failed to attach VFIO device"); + if (ret) { + logger->error("Failed to attach VFIO device"); + return false; + } /* Map PCIe BAR */ map = (char*) vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX); - if (map == MAP_FAILED) - serror("Failed to mmap() BAR0"); + if (map == MAP_FAILED) { + logger->error("Failed to mmap() BAR0"); + return false; + } /* Enable memory access and PCI bus mastering for DMA */ ret = vfio_pci_enable(&vfio_device); - if (ret) - serror("Failed to enable PCI device"); + if (ret) { + logger->error("Failed to enable PCI device"); + return false; + } /* Reset system? */ if (do_reset) { /* Reset / detect PCI device */ ret = vfio_pci_reset(&vfio_device); - if (ret) - serror("Failed to reset PCI device"); + if (ret) { + logger->error("Failed to reset PCI device"); + return false; + } if(not reset()) { logger->error("Failed to reset FGPA card"); From e05ff515c7034073538213af3539abfe44340a20 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 15:05:37 +0100 Subject: [PATCH 071/560] lib/ips/timer: implement basic functionality --- fpga/include/villas/fpga/ips/timer.hpp | 22 ++++++++++++++++- fpga/lib/ips/timer.cpp | 33 +++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp index 49354052d..771d2723b 100644 --- a/fpga/include/villas/fpga/ips/timer.hpp +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -29,9 +29,13 @@ #pragma once -#include "fpga/ip.hpp" +#include #include +#include "config.h" +#include "fpga/ip.hpp" +#include "fpga/ips/intc.hpp" + namespace villas { namespace fpga { namespace ip { @@ -42,8 +46,24 @@ class Timer : public IpCore public: bool init(); + + bool start(uint32_t ticks); + bool wait(); + uint32_t remaining(); + + inline bool isRunning() + { return remaining() != 0; } + + inline bool isFinished() + { return remaining() == 0; } + + static constexpr uint32_t + getFrequency() + { return FPGA_AXI_HZ; } + private: XTmrCtr xTmr; + InterruptController* intc; }; diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index fbd0219fd..5e0c56e4c 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -23,11 +23,11 @@ * along with this program. If not, see . *********************************************************************************/ - -#include "config.h" +#include #include "log.hpp" #include "fpga/ips/timer.hpp" +#include "fpga/ips/intc.hpp" namespace villas { namespace fpga { @@ -40,14 +40,41 @@ static TimerFactory factory; bool Timer::init() { XTmrCtr_Config xtmr_cfg; - xtmr_cfg.SysClockFreqHz = FPGA_AXI_HZ; + xtmr_cfg.SysClockFreqHz = getFrequency(); XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseaddr()); XTmrCtr_InitHw(&xTmr); + intc = reinterpret_cast(dependencies["intc"]); + intc->disableInterrupt(irqs[0]); + return true; } +bool Timer::start(uint32_t ticks) +{ + intc->enableInterrupt(irqs[0], false); + + XTmrCtr_SetOptions(&xTmr, 0, XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION); + XTmrCtr_SetResetValue(&xTmr, 0, ticks); + XTmrCtr_Start(&xTmr, 0); + + return true; +} + +bool Timer::wait() +{ + int count = intc->waitForInterrupt(irqs[0]); + intc->disableInterrupt(irqs[0]); + + return (count == 1); +} + +uint32_t Timer::remaining() +{ + return XTmrCtr_GetValue(&xTmr, 0); +} + } // namespace ip } // namespace fpga From fc98aaecb424620234eea0be98322c68dc6ea1eb Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 15:07:21 +0100 Subject: [PATCH 072/560] lib/ips/intc: C++-ify interface --- fpga/include/villas/fpga/ips/intc.hpp | 8 ++++---- fpga/lib/ips/intc.cpp | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 54e76a6e0..68819371d 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -48,12 +48,12 @@ public: bool init(); - int enableInterrupt(IrqMaskType mask, bool polling); - int enableInterrupt(IrqPort irq, bool polling) + bool enableInterrupt(IrqMaskType mask, bool polling); + bool enableInterrupt(IrqPort irq, bool polling) { return enableInterrupt(1 << irq.num, polling); } - int disableInterrupt(IrqMaskType mask); - int disableInterrupt(IrqPort irq) + bool disableInterrupt(IrqMaskType mask); + bool disableInterrupt(IrqPort irq) { return disableInterrupt(1 << irq.num); } int waitForInterrupt(int irq); diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index c2426cb1f..e7716c1ff 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -45,7 +45,8 @@ InterruptController::~InterruptController() vfio_pci_msi_deinit(&card->vfio_device , this->efds); } -bool InterruptController::init() +bool +InterruptController::init() { const uintptr_t base = getBaseaddr(); auto logger = getLogger(); @@ -80,7 +81,7 @@ bool InterruptController::init() return true; } -int +bool InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool polling) { auto logger = getLogger(); @@ -115,7 +116,7 @@ InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool return true; } -int +bool InterruptController::disableInterrupt(InterruptController::IrqMaskType mask) { const uintptr_t base = getBaseaddr(); @@ -126,7 +127,8 @@ InterruptController::disableInterrupt(InterruptController::IrqMaskType mask) return true; } -int InterruptController::waitForInterrupt(int irq) +int +InterruptController::waitForInterrupt(int irq) { assert(irq < maxIrqs); From e626abfb5244628473d17594cad80eda383e02cb Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 15:08:12 +0100 Subject: [PATCH 073/560] tests/timer: add basic timer test --- fpga/tests/CMakeLists.txt | 2 +- fpga/tests/timer.cpp | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 fpga/tests/timer.cpp diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index 103abb6c4..bb9988ba2 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -5,7 +5,7 @@ set(SOURCES # hls.c # intc.c # rtds_rtt.c -# tmrctr.c + timer.cpp # xsg.c ) diff --git a/fpga/tests/timer.cpp b/fpga/tests/timer.cpp new file mode 100644 index 000000000..b25935c13 --- /dev/null +++ b/fpga/tests/timer.cpp @@ -0,0 +1,55 @@ +/** Timer/Counter unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" + +#include +#include +#include + +extern villas::fpga::PCIeCard* fpga; + +Test(fpga, timer, .description = "Timer Counter") +{ + auto logger = loggerGetOrCreate("unittest:timer"); + + for(auto& ip : fpga->ips) { + // skip non-timer IPs + if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_timer:")) { + continue; + } + + logger->info("Testing {}", *ip); + + auto timer = reinterpret_cast(*ip); + + timer.start(timer.getFrequency() / 10); + cr_assert(timer.wait(), "Timer failed"); + + logger->info("Timer passed: {}", timer); + } + + return; +} From 16455bdd13abcd3b6e2273c72d48bd5febf0d660 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 15:08:27 +0100 Subject: [PATCH 074/560] tests/fifo: cleanup --- fpga/tests/fifo.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index 1d5fc146b..db66d262e 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -23,27 +23,17 @@ #include -#include - -#include -#include -#include - -#include - +#include #include #include -#include "log.hpp" extern villas::fpga::PCIeCard* fpga; Test(fpga, fifo, .description = "FIFO") { - int ret; ssize_t len; char src[255], dst[255]; - struct fpga_ip *fifo; auto logger = loggerGetOrCreate("unittest:fifo"); @@ -52,6 +42,8 @@ Test(fpga, fifo, .description = "FIFO") if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_fifo_mm_s:")) continue; + logger->info("Testing {}", *ip); + auto fifo = reinterpret_cast(*ip); if(not fifo.loopbackPossible()) { @@ -83,5 +75,7 @@ Test(fpga, fifo, .description = "FIFO") /* Compare data */ cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); + + logger->info("All good for {}", *ip); } } From 61de103c9ebd28bca164c7b1ee79d8126e1c102f Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 15:08:56 +0100 Subject: [PATCH 075/560] tests/main: assert that there's an fpga --- fpga/tests/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 46c539926..680af8ab6 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -96,6 +96,8 @@ static void init() fpga = fpgaCards.front().get(); } + cr_assert_not_null(fpga, "No FPGA card available"); + json_decref(json); } From 21d1dd0a71d723a0e74ea0152ee4c8512180d32c Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 16 Jan 2018 15:26:19 +0100 Subject: [PATCH 076/560] tests/timer: test absolute timing --- fpga/tests/timer.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/fpga/tests/timer.cpp b/fpga/tests/timer.cpp index b25935c13..f6dd0b009 100644 --- a/fpga/tests/timer.cpp +++ b/fpga/tests/timer.cpp @@ -21,14 +21,14 @@ * along with this program. If not, see . *********************************************************************************/ +#include #include - -#include "config.h" - #include #include #include +#include "config.h" + extern villas::fpga::PCIeCard* fpga; Test(fpga, timer, .description = "Timer Counter") @@ -45,10 +45,26 @@ Test(fpga, timer, .description = "Timer Counter") auto timer = reinterpret_cast(*ip); + logger->info("Test simple waiting"); timer.start(timer.getFrequency() / 10); - cr_assert(timer.wait(), "Timer failed"); + cr_assert(timer.wait(), "Waiting failed"); + logger->info("-> passed"); - logger->info("Timer passed: {}", timer); + logger->info("Measure waiting time (1s)"); + timer.start(timer.getFrequency()); + + const auto start = std::chrono::high_resolution_clock::now(); + timer.wait(); + const auto stop = std::chrono::high_resolution_clock::now(); + + const int oneSecondInUs = 1000000; + const auto duration = stop - start; + const auto durationUs = + std::chrono::duration_cast(duration).count(); + logger->info("-> time passed: {} us", durationUs); + + cr_assert(std::abs(durationUs - oneSecondInUs) < 0.01 * oneSecondInUs, + "Timer deviation > 1%%"); } return; From f5a3c8c7129a65ec1fa9074fcf11e3a226130b48 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 17 Jan 2018 16:31:47 +0100 Subject: [PATCH 077/560] scripts/hwdef-parse: only set irqs and ports if there are any --- fpga/scripts/hwdef-parse.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index f114d3a34..a1ca66923 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -119,9 +119,7 @@ for module in modules: continue ips[instance] = { - 'vlnv' : vlnv, - 'irqs' : { }, - 'ports' : { } + 'vlnv' : vlnv } # find PCI-e module to extract memory map @@ -151,9 +149,9 @@ for busif in busifs: port = int(m.group(2)) ep = bus_trace(root, busname, opponent[type], whitelist) - if ep in ips: - ips[ep]['ports'][type.lower()] = port + ports = ips[ep].setdefault('ports', {}) + ports[type.lower()] = port # find Interrupt assignments intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') @@ -182,7 +180,8 @@ for port in ports: irqname = port.get('NAME') if instance in ips: - ips[instance]['irqs'][irqname] = irq + irqs = ips[instance].setdefault('irqs', {}) + irqs[irqname] = irq # Find BRAM storage depths (size) brams = root.xpath('.//MODULE[@MODTYPE="axi_bram_ctrl"]') From 4db0a980829890c48d15afa5a1ecdfb3495a1d4f Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 17 Jan 2018 16:32:14 +0100 Subject: [PATCH 078/560] scripts/hwdef-parse: add memory view for each instance --- fpga/scripts/hwdef-parse.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index a1ca66923..24708112c 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -86,6 +86,9 @@ def vlnv_match(vlnv, whitelist): return False +def remove_prefix(text, prefix): + return text[text.startswith(prefix) and len(prefix):] + if len(sys.argv) < 2: print('Usage: {} path/to/*.hwdef'.format(sys.argv[0])) print(' {} path/to/*.xml'.format(sys.argv[0])) @@ -122,6 +125,23 @@ for module in modules: 'vlnv' : vlnv } + # populate memory view + mmap = module.find('.//MEMORYMAP') + if not mmap: + continue + + mem = ips[instance].setdefault('memory-view', {}) + for mrange in mmap: + mem_interface = remove_prefix(mrange.get('MASTERBUSINTERFACE'), 'M_AXI_') + mem_instance = mrange.get('INSTANCE') + + entry = mem.setdefault(mem_interface, {}).setdefault(mem_instance, {}) + + entry['baseaddr'] = int(mrange.get('BASEVALUE'), 16); + entry['highaddr'] = int(mrange.get('HIGHVALUE'), 16); + + + # find PCI-e module to extract memory map pcie = root.find('.//MODULE[@MODTYPE="axi_pcie"]') mmap = pcie.find('.//MEMORYMAP') From 935fa847aa943b0dde5fd8e9d06e3f3f605e424f Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 17 Jan 2018 16:49:33 +0100 Subject: [PATCH 079/560] scripts/hwdef-parse: update ports format --- fpga/scripts/hwdef-parse.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 24708112c..8b94ee797 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -170,8 +170,12 @@ for busif in busifs: ep = bus_trace(root, busname, opponent[type], whitelist) if ep in ips: - ports = ips[ep].setdefault('ports', {}) - ports[type.lower()] = port + ports = ips[ep].setdefault('ports', []) + ports.append({ + 'role': type.lower(), + 'target': switch.get('INSTANCE'), + 'port': port + }) # find Interrupt assignments intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') From df93004720f8bf9ebdbac34a69013c076142ba75 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Wed, 17 Jan 2018 17:00:00 +0100 Subject: [PATCH 080/560] scripts/hwdef-parse: include intc instance name in irq ports --- fpga/scripts/hwdef-parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 8b94ee797..db1178136 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -205,7 +205,7 @@ for port in ports: if instance in ips: irqs = ips[instance].setdefault('irqs', {}) - irqs[irqname] = irq + irqs[irqname] = '{}:{}'.format(intc.get('INSTANCE'), irq) # Find BRAM storage depths (size) brams = root.xpath('.//MODULE[@MODTYPE="axi_bram_ctrl"]') From 28a7f2a3eef1f43e170ccdfa2ca5d9ad2b3b5faa Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 10:09:06 +0100 Subject: [PATCH 081/560] spdlog: fix handling of too long logger names `whitespace` overflows because the result implicitly is an unsigned value. --- .../spdlog/include/spdlog/details/pattern_formatter_impl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h b/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h index de0f0f2d5..6457d1034 100644 --- a/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h +++ b/fpga/thirdparty/spdlog/include/spdlog/details/pattern_formatter_impl.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace spdlog { @@ -40,7 +41,7 @@ namespace class name_formatter:public flag_formatter { std::string center(std::string input, int width) { - const auto whitespace = width - input.length(); + const int whitespace = std::max(int(width - input.length()), 0); return std::string(whitespace / 2, ' ') + input + std::string(whitespace / 2, ' ') From 02ea98dd97df5c50651d417e6e0b2e1b6cfb8fa2 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 12:30:54 +0100 Subject: [PATCH 082/560] hwdef-parse: add port name --- fpga/scripts/hwdef-parse.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index db1178136..a2f60296b 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -68,7 +68,7 @@ def bus_trace(root, busname, type, whitelist): instance = module[0].get('INSTANCE') if vlnv_match(vlnv, whitelist): - return instance + return instance, busname elif vlnv_match(vlnv, axi_converter_whitelist): next_bus = module[0].xpath('.//BUSINTERFACE[@TYPE="{}" or @TYPE="{}"]'.format(opponent[type[0]][0], opponent[type[0]][1])) next_busname = next_bus[0].get('BUSNAME') @@ -89,6 +89,14 @@ def vlnv_match(vlnv, whitelist): def remove_prefix(text, prefix): return text[text.startswith(prefix) and len(prefix):] +def sanitize_name(name): + name = remove_prefix(name, 'S_') + name = remove_prefix(name, 'M_') + name = remove_prefix(name, 'AXI_') + name = remove_prefix(name, 'AXIS_') + + return name + if len(sys.argv) < 2: print('Usage: {} path/to/*.hwdef'.format(sys.argv[0])) print(' {} path/to/*.xml'.format(sys.argv[0])) @@ -168,15 +176,21 @@ for busif in busifs: port = int(m.group(2)) - ep = bus_trace(root, busname, opponent[type], whitelist) + ep, busname_ep = bus_trace(root, busname, opponent[type], whitelist) if ep in ips: + ports = ips[ep].setdefault('ports', []) ports.append({ - 'role': type.lower(), - 'target': switch.get('INSTANCE'), - 'port': port + 'role': opponent[type][0].lower(), + 'target': '{}:{}'.format(switch.get('INSTANCE'), port) }) + module_ep = root.find('.//MODULE[@INSTANCE="{}"]'.format(ep)) + busif_ep = module_ep.find('.//BUSINTERFACE[@BUSNAME="{}"]'.format(busname_ep)) + if busif_ep: + ports[-1]['name'] = sanitize_name(busif_ep.get('NAME')) + + # find Interrupt assignments intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') intr = intc.xpath('.//PORT[@NAME="intr" and @DIR="I"]')[0] From fb37253623a638f2e7776116627433f89ae86e0c Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:38:12 +0100 Subject: [PATCH 083/560] hwdef-parse: populate all memory ranges based on name This used to overwrite earlier memory ranges because the same was used ('baseaddr', 'highaddr'). Now, deduce name from BASENAME and remove prefix `C_`. --- fpga/scripts/hwdef-parse.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index a2f60296b..98b569355 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -157,8 +157,11 @@ for mrange in mmap: instance = mrange.get('INSTANCE') if instance in ips: - ips[instance]['baseaddr'] = int(mrange.get('BASEVALUE'), 16); - ips[instance]['highaddr'] = int(mrange.get('HIGHVALUE'), 16); + base_name = remove_prefix(mrange.get('BASENAME'), 'C_').lower() + high_name = remove_prefix(mrange.get('HIGHNAME'), 'C_').lower() + + ips[instance][base_name] = int(mrange.get('BASEVALUE'), 16); + ips[instance][high_name] = int(mrange.get('HIGHVALUE'), 16); # find AXI-Stream switch port mapping switch = root.find('.//MODULE[@MODTYPE="axis_switch"]') From bbff2c9a88a021596df2166b7473db7c2f180809 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:41:31 +0100 Subject: [PATCH 084/560] hwdef-parse: count total switch ports and populate property --- fpga/scripts/hwdef-parse.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 98b569355..123ca3f9c 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -166,10 +166,13 @@ for mrange in mmap: # find AXI-Stream switch port mapping switch = root.find('.//MODULE[@MODTYPE="axis_switch"]') busifs = switch.find('.//BUSINTERFACES') +switch_ports = 0 for busif in busifs: if busif.get('VLNV') != 'xilinx.com:interface:axis:1.0': continue + switch_ports += 1 + busname = busif.get('BUSNAME') name = busif.get('NAME') type = busif.get('TYPE') @@ -193,6 +196,9 @@ for busif in busifs: if busif_ep: ports[-1]['name'] = sanitize_name(busif_ep.get('NAME')) +# set number of master/slave port pairs for switch +ips[switch.get('INSTANCE')]['num_ports'] = switch_ports / 2 + # find Interrupt assignments intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') From f642fa64289b6860fc96c66e97466d3720ead535 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:42:26 +0100 Subject: [PATCH 085/560] log: provide more macros for text colors --- fpga/include/villas/log.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fpga/include/villas/log.hpp b/fpga/include/villas/log.hpp index 8a7a84bad..656d24313 100644 --- a/fpga/include/villas/log.hpp +++ b/fpga/include/villas/log.hpp @@ -9,7 +9,16 @@ #include #define _ESCAPE "\x1b" -#define TXT_BOLD(s) _ESCAPE "[1m" + std::string(s) + _ESCAPE "[0m" +#define TXT_RESET_ALL _ESCAPE "[0m" + +#define TXT_RESET_BOLD _ESCAPE "[21m" +#define TXT_BOLD(s) _ESCAPE "[1m" + std::string(s) + TXT_RESET_BOLD + +#define TXT_RESET_COLOR _ESCAPE "[39m" +#define TXT_RED(s) _ESCAPE "[31m" + std::string(s) + TXT_RESET_COLOR +#define TXT_GREEN(s) _ESCAPE "[32m" + std::string(s) + TXT_RESET_COLOR +#define TXT_YELLOW(s) _ESCAPE "[33m" + std::string(s) + TXT_RESET_COLOR +#define TXT_BLUE(s) _ESCAPE "[34m" + std::string(s) + TXT_RESET_COLOR using SpdLogger = std::shared_ptr; From 62e1a7d9627f92b73abe30c51a642123a3119371 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:43:06 +0100 Subject: [PATCH 086/560] tests/fifo: fail if connecting loopback doesn't work --- fpga/tests/fifo.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index db66d262e..6e97d5904 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -51,7 +51,9 @@ Test(fpga, fifo, .description = "FIFO") continue; } - fifo.connectLoopback(); + if(not fifo.connectLoopback()) { + continue; + } /* Get some random data to compare */ memset(dst, 0, sizeof(dst)); From e46720d23bd4776232be2bf1f0caf6a729d0994f Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:43:30 +0100 Subject: [PATCH 087/560] tests: improve logging --- fpga/tests/fifo.cpp | 4 ++-- fpga/tests/timer.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index 6e97d5904..14c2e918a 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -76,8 +76,8 @@ Test(fpga, fifo, .description = "FIFO") } /* Compare data */ - cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); + cr_assert_eq(memcmp(src, dst, sizeof(src)), 0, "Data not equal"); - logger->info("All good for {}", *ip); + logger->info(TXT_GREEN("Passed")); } } diff --git a/fpga/tests/timer.cpp b/fpga/tests/timer.cpp index f6dd0b009..1f7013cf9 100644 --- a/fpga/tests/timer.cpp +++ b/fpga/tests/timer.cpp @@ -65,6 +65,8 @@ Test(fpga, timer, .description = "Timer Counter") cr_assert(std::abs(durationUs - oneSecondInUs) < 0.01 * oneSecondInUs, "Timer deviation > 1%%"); + + logger->info(TXT_GREEN("Passed")); } return; From 92aea92f194eff271420091afe16ea852cd4a192 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:43:53 +0100 Subject: [PATCH 088/560] etc: update fpga.json with output of hwdef-parse --- fpga/etc/fpga.json | 299 +++++++++++++++++++++++++++++++-------------- 1 file changed, 206 insertions(+), 93 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index de9e93980..275cfbb73 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -26,112 +26,225 @@ "slot": "03:00.0", "do_reset": true, "ips": { - "axi_reset_0": { - "vlnv": "xilinx.com:ip:axi_gpio:2.0", - "baseaddr": 28672 + "bram_0_axi_bram_ctrl_0": { + "vlnv": "xilinx.com:ip:axi_bram_ctrl:4.0", + "s_axi_baseaddr": 0, + "s_axi_highaddr": 8191, + "size": 8192 }, - "timer_0": { - "vlnv": "xilinx.com:ip:axi_timer:2.0", - "baseaddr": 16384, - "irqs": [ "axi_pcie_intc_0:0" ] - }, - "dma_0": { + "hier_0_axi_dma_axi_dma_0": { "vlnv": "xilinx.com:ip:axi_dma:7.1", - "baseaddr": 12288, - "irqs": [ "axi_pcie_intc_0:3" ] - }, - "axi_pcie_intc_0": { - "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0", - "baseaddr": 45056 - }, - "dma_1": { - "vlnv": "xilinx.com:ip:axi_dma:7.1", - "baseaddr": 8192, - "ports": { - "master": [ { "num": 0, "to": "switch_0:6" } ], - "slave": [ { "num": 0, "to": "switch_0:6" } ] + "memory-view": { + "SG": { + "bram_0_axi_bram_ctrl_0": { + "baseaddr": 0, + "highaddr": 8191 + }, + "hier_0_axi_dma_axi_dma_1": { + "baseaddr": 8192, + "highaddr": 12287 + }, + "hier_0_axi_dma_axi_dma_0": { + "baseaddr": 12288, + "highaddr": 16383 + }, + "timer_0_axi_timer_0": { + "baseaddr": 16384, + "highaddr": 20479 + }, + "hier_0_axis_interconnect_0_axis_interconnect_0_xbar": { + "baseaddr": 20480, + "highaddr": 24575 + }, + "hier_0_axi_fifo_mm_s_0": { + "baseaddr": 49152, + "highaddr": 57343 + }, + "pcie_0_axi_reset_0": { + "baseaddr": 28672, + "highaddr": 32767 + }, + "hier_0_rtds_axis_0": { + "baseaddr": 32768, + "highaddr": 36863 + }, + "hier_0_hls_dft_0": { + "baseaddr": 36864, + "highaddr": 40959 + }, + "pcie_0_axi_pcie_intc_0": { + "baseaddr": 45056, + "highaddr": 49151 + }, + "pcie_0_axi_pcie_0": { + "baseaddr": 268435456, + "highaddr": 536870911 + } + }, + "MM2S": { + "pcie_0_axi_pcie_0": { + "baseaddr": 2147483648, + "highaddr": 4294967295 + } + }, + "S2MM": { + "pcie_0_axi_pcie_0": { + "baseaddr": 2147483648, + "highaddr": 4294967295 + } + } }, - "irqs": [ "axi_pcie_intc_0:3" ] + "baseaddr": 12288, + "highaddr": 16383, + "ports": [ + { + "role": "initiator", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:1", + "name": "MM2S" + }, + { + "role": "target", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:1", + "name": "S2MM" + } + ] }, - "fifo_mm_s_0": { + "hier_0_axi_dma_axi_dma_1": { + "vlnv": "xilinx.com:ip:axi_dma:7.1", + "memory-view": { + "MM2S": { + "pcie_0_axi_pcie_0": { + "baseaddr": 2147483648, + "highaddr": 4294967295 + } + }, + "S2MM": { + "pcie_0_axi_pcie_0": { + "baseaddr": 2147483648, + "highaddr": 4294967295 + } + } + }, + "baseaddr": 8192, + "highaddr": 12287, + "ports": [ + { + "role": "initiator", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:6", + "name": "MM2S" + }, + { + "role": "target", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:6", + "name": "S2MM" + } + ] + }, + "hier_0_axi_fifo_mm_s_0": { "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", "baseaddr": 24576, - "baseaddr_axi4": 49152, - "ports": { - "master": [ { "num": 0, "to": "switch_0:2" } ], - "slave": [ { "num": 0, "to": "switch_0:2" } ] - }, - "irqs": [ "axi_pcie_intc_0:2" ] + "highaddr": 28671, + "axi4_baseaddr": 49152, + "axi4_highaddr": 57343, + "ports": [ + { + "role": "master", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:2", + "name": "STR_TXD" + }, + { + "role": "slave", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:2", + "name": "STR_RXD" + } + ], + "irqs": { + "interrupt": "pcie_0_axi_pcie_intc_0:2" + } }, - "rtds_axis_0": { + "hier_0_axis_interconnect_0_axis_interconnect_0_xbar": { + "vlnv": "xilinx.com:ip:axis_switch:1.1", + "baseaddr": 20480, + "highaddr": 24575, + "ports": [ + { + "role": "initiator", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:3", + "name": "M03_AXIS" + }, + { + "role": "target", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:3", + "name": "S03_AXIS" + }, + { + "role": "initiator", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:4", + "name": "M04_AXIS" + }, + { + "role": "target", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:4", + "name": "S04_AXIS" + } + ], + "num_ports": 14 + }, + "hier_0_hls_dft_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", + "s_axi_ctrl_baseaddr": 36864, + "s_axi_ctrl_highaddr": 40959, + "ports": [ + { + "role": "master", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:5", + "name": "output_r" + }, + { + "role": "slave", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:5", + "name": "input_r" + } + ] + }, + "hier_0_rtds_axis_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", "baseaddr": 32768, - "ports": { - "master": [ { "num": 0, "to": "switch_0:0" } ], - "slave": [ { "num": 0, "to": "switch_0:0" } ] - }, - "irqs": [ "axi_pcie_intc_0:5", "axi_pcie_intc_0:6" ] - }, - "hls_dft_0": { - "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", - "baseaddr": 36864, - "ports": { - "master": [ { "num": 0, "to": "switch_0:5" } ], - "slave": [ { "num": 0, "to": "switch_0:5" } ] - }, - "irqs": [ "axi_pcie_intc_0:1" ], - "period": 400, - "harmonics": [ - 0, - 1, - 3, - 5, - 7 + "highaddr": 36863, + "ports": [ + { + "role": "master", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:0", + "name": "m_axis" + }, + { + "role": "slave", + "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:0", + "name": "s_axis" + } ], - "decimation": 0 - }, - "axis_data_fifo_0": { - "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", - "ports": { - "master": [ { "num": 0, "to": "switch_0:3" } ], - "slave": [ { "num": 0, "to": "switch_0:3" } ] + "irqs": { + "irq_ts": "pcie_0_axi_pcie_intc_0:5", + "irq_overflow": "pcie_0_axi_pcie_intc_0:6", + "irq_case": "pcie_0_axi_pcie_intc_0:7" } }, - "switch_0": { - "vlnv": "xilinx.com:ip:axis_interconnect:2.1", - "baseaddr": 20480, - "ports": { - "master": [ - { "num": 0 }, - { "num": 1 }, - { "num": 2 }, - { "num": 3 }, - { "num": 4 }, - { "num": 5 }, - { "num": 6 }, - { "num": 7 }, - { "num": 8 }, - { "num": 9 } - ], - "slave": [ - { "num": 0 }, - { "num": 1 }, - { "num": 2 }, - { "num": 3 }, - { "num": 4 }, - { "num": 5 }, - { "num": 6 }, - { "num": 7 }, - { "num": 8 }, - { "num": 9 } - ] - } + "pcie_0_axi_pcie_intc_0": { + "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0", + "baseaddr": 45056, + "highaddr": 49151 }, - "axis_data_fifo_1": { - "vlnv": "xilinx.com:ip:axis_data_fifo:1.1", - "ports": { - "master": [ { "num": 0, "to": "switch_0:6" } ], - "slave": [ { "num": 0, "to": "switch_0:6" } ] + "pcie_0_axi_reset_0": { + "vlnv": "xilinx.com:ip:axi_gpio:2.0", + "baseaddr": 28672, + "highaddr": 32767 + }, + "timer_0_axi_timer_0": { + "vlnv": "xilinx.com:ip:axi_timer:2.0", + "baseaddr": 16384, + "highaddr": 20479, + "irqs": { + "generateout0": "pcie_0_axi_pcie_intc_0:0" } } } From f14df8aa326c0d77efb95281eaeee0f7e295a537 Mon Sep 17 00:00:00 2001 From: daniel-k Date: Tue, 23 Jan 2018 14:47:44 +0100 Subject: [PATCH 089/560] lib/ip: adapt to fit new config layout provided by hwdef-parse --- fpga/include/villas/fpga/ip.hpp | 2 +- fpga/include/villas/fpga/ip_node.hpp | 10 +-- fpga/include/villas/fpga/ips/switch.hpp | 5 +- fpga/lib/ip.cpp | 72 +++++++++-------- fpga/lib/ip_node.cpp | 101 ++++++++++-------------- fpga/lib/ips/fifo.cpp | 8 +- fpga/lib/ips/intc.cpp | 3 +- fpga/lib/ips/switch.cpp | 24 +++++- fpga/lib/ips/timer.cpp | 20 ++++- 9 files changed, 136 insertions(+), 109 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 258bfaade..53ac244ad 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -134,7 +134,7 @@ protected: PCIeCard* card; ///< FPGA card this IP is instantiated on IpIdentifier id; ///< VLNV and name defined in JSON config uintptr_t baseaddr; ///< The baseadress of this IP component - std::map irqs; ///< Interrupts of this IP component + std::map irqs; ///< Interrupts of this IP component std::map dependencies; ///< dependencies on other IPs }; diff --git a/fpga/include/villas/fpga/ip_node.hpp b/fpga/include/villas/fpga/ip_node.hpp index 6f7f92486..6dfe12011 100644 --- a/fpga/include/villas/fpga/ip_node.hpp +++ b/fpga/include/villas/fpga/ip_node.hpp @@ -54,18 +54,18 @@ public: std::string nodeName; }; - bool connect(int port, const StreamPort& to); - bool disconnect(int port); + bool connect(std::string portName, const StreamPort& to); + bool disconnect(std::string portName); bool loopbackPossible() const; bool connectLoopback(); private: - std::pair getLoopbackPorts() const; + std::pair getLoopbackPorts() const; protected: - std::map portsMaster; - std::map portsSlave; + std::map portsMaster; + std::map portsSlave; }; class IpNodeFactory : public IpCoreFactory { diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index 64767346b..b4418f5dd 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -61,6 +61,7 @@ private: IpCore* slaveIn; }; + int num_ports; XAxis_Switch xSwitch; std::map portMapping; }; @@ -71,6 +72,8 @@ public: AxiStreamSwitchFactory() : IpNodeFactory(getName()) {} + bool configureJson(IpCore& ip, json_t *json_ip); + IpCore* create() { return new AxiStreamSwitch; } @@ -81,7 +84,7 @@ public: { return "Xilinx's AXI4-Stream switch"; } Vlnv getCompatibleVlnv() const - { return Vlnv("xilinx.com:ip:axis_interconnect:"); } + { return Vlnv("xilinx.com:ip:axis_switch:"); } }; } // namespace ip diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index bd3dcda95..4daac1309 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -42,7 +42,7 @@ using DependencyGraph = villas::utils::DependencyGraph; static std::list -dependencyTokens = {"irq", "port", "memory"}; +dependencyTokens = {"irqs"}; static bool @@ -73,41 +73,45 @@ buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::st continue; } - const char* value = json_string_value(json_dependency); - if(value == nullptr) { - logger->warn("Property {} of {} is invalid", - dependencyToken, TXT_BOLD(name)); - continue; - } + const char* irq_name; + json_t* json_irq; + json_object_foreach(json_dependency, irq_name, json_irq) { + const char* value = json_string_value(json_irq); + if(value == nullptr) { + logger->warn("Property {} of {} is invalid", + dependencyToken, TXT_BOLD(name)); + continue; + } - auto mapping = villas::utils::tokenize(value, ":"); + auto mapping = villas::utils::tokenize(value, ":"); - if(mapping.size() != 2) { - logger->error("Invalid {} mapping of {}", - dependencyToken, TXT_BOLD(name)); + if(mapping.size() != 2) { + logger->error("Invalid {} mapping of {}", + dependencyToken, TXT_BOLD(name)); - dependencyGraph.removeNode(name); - return false; - } + dependencyGraph.removeNode(name); + return false; + } - if(name == mapping[0]) { - logger->error("IP {} cannot depend on itself", TXT_BOLD(name)); + if(name == mapping[0]) { + logger->error("IP {} cannot depend on itself", TXT_BOLD(name)); - dependencyGraph.removeNode(name); - return false; - } + dependencyGraph.removeNode(name); + return false; + } - // already add dependency, if adding it fails, removing the dependency - // will also remove the current one - dependencyGraph.addDependency(name, mapping[0]); + // already add dependency, if adding it fails, removing the dependency + // will also remove the current one + dependencyGraph.addDependency(name, mapping[0]); - if(not buildDependencyGraph(dependencyGraph, json_ips, mapping[0])) { - logger->error("Dependency {} of {} not satisfied", - mapping[0], TXT_BOLD(name)); + if(not buildDependencyGraph(dependencyGraph, json_ips, mapping[0])) { + logger->error("Dependency {} of {} not satisfied", + mapping[0], TXT_BOLD(name)); - dependencyGraph.removeNode(mapping[0]); - return false; + dependencyGraph.removeNode(mapping[0]); + return false; + } } } @@ -228,11 +232,13 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) } json_t* json_irqs = json_object_get(json_ip, "irqs"); - if(json_is_array(json_irqs)) { - size_t index; + if(json_is_object(json_irqs)) { + const char* irq_name; json_t* json_irq; - json_array_foreach(json_irqs, index, json_irq) { + json_object_foreach(json_irqs, irq_name, json_irq) { const char* irq = json_string_value(json_irq); + + auto tokens = utils::tokenize(irq, ":"); if(tokens.size() != 2) { logger->warn("Cannot parse IRQ '{}' of {}", @@ -247,9 +253,11 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) logger->warn("IRQ number is not an integer: '{}'", irq); continue; } - - ip->irqs[index] = {num, tokens[0], ""}; + logger->debug("IRQ: {} -> {}:{}", irq_name, tokens[0], num); + ip->irqs[irq_name] = {num, tokens[0], ""}; } + } else { + logger->debug("IP has no interrupts"); } diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index 6c2b5df04..6f25ac7ca 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -19,101 +19,79 @@ IpNodeFactory::configureJson(IpCore& ip, json_t* json_ip) auto logger = getLogger(); json_t* json_ports = json_object_get(json_ip, "ports"); - if(json_ports == nullptr) { - logger->error("IpNode {} has no ports property", ip); - return false; + if(not json_is_array(json_ports)) { + logger->debug("IP has no ports"); + return true; } - json_t* json_master = json_object_get(json_ports, "master"); - json_t* json_slave = json_object_get(json_ports, "slave"); - - const bool hasMasterPorts = json_is_array(json_master); - const bool hasSlavePorts = json_is_array(json_slave); - - if( (not hasMasterPorts) and (not hasSlavePorts)) { - logger->error("IpNode {} has no ports", ip); - return false; - } - - // intentionally use short-circuit evaluation to only call populatePorts - // if the property exists - bool masterPortsSuccess = - (hasMasterPorts and populatePorts(ipNode.portsMaster, json_master)) - or (not hasMasterPorts); - - bool slavePortsSuccess = - (hasSlavePorts and populatePorts(ipNode.portsSlave, json_slave)) - or (not hasSlavePorts); - - return (masterPortsSuccess and slavePortsSuccess); -} - -bool -IpNodeFactory::populatePorts(std::map& portMap, json_t* json) -{ - auto logger = getLogger(); - size_t index; json_t* json_port; - json_array_foreach(json, index, json_port) { - int myPortNum; - const char* to = nullptr; + json_array_foreach(json_ports, index, json_port) { + if(not json_is_object(json_port)) { + logger->error("Port {} is not an object", index); + return false; + } - int ret = json_unpack(json_port, "{ s : i, s? : s}", - "num", &myPortNum, - "to", &to); + const char *role_raw, *target_raw, *name_raw; + int ret = json_unpack(json_port, "{ s: s, s: s, s: s }", + "role", &role_raw, + "target", &target_raw, + "name", &name_raw); if(ret != 0) { - logger->error("Port definition required field 'num'"); + logger->error("Cannot parse port {}", index); return false; } - if(to == nullptr) { - logger->debug("Nothing connected to port {}", myPortNum); - portMap[myPortNum] = {}; - continue; - } - - const auto tokens = utils::tokenize(to, ":"); + const auto tokens = utils::tokenize(target_raw, ":"); if(tokens.size() != 2) { - logger->error("Too many tokens in property 'other'"); + logger->error("Cannot parse 'target' of port {}", index); return false; } - int otherPortNum; + int port_num; try { - otherPortNum = std::stoi(tokens[1]); + port_num = std::stoi(tokens[1]); } catch(const std::invalid_argument&) { - logger->error("Other port number is not an integral number"); + logger->error("Target port number is not an integer: '{}'", target_raw); return false; } - logger->debug("Adding port mapping: {}:{}", myPortNum, to); - portMap[myPortNum] = { otherPortNum, tokens[0] }; + IpNode::StreamPort port; + port.nodeName = tokens[0]; + port.portNumber = port_num; + + const std::string role(role_raw); + if(role == "master" or role == "initiator") { + ipNode.portsMaster[name_raw] = port; + } else /* slave */ { + ipNode.portsSlave[name_raw] = port; + } + } return true; } -std::pair +std::pair IpNode::getLoopbackPorts() const { - for(auto& [masterNum, masterTo] : portsMaster) { - for(auto& [slaveNum, slaveTo] : portsSlave) { + for(auto& [masterName, masterTo] : portsMaster) { + for(auto& [slaveName, slaveTo] : portsSlave) { // TODO: should we also check which IP both ports are connected to? if(masterTo.nodeName == slaveTo.nodeName) { - return { masterNum, slaveNum }; + return { masterName, slaveName }; } } } - return { -1, -1 }; + return { "", "" }; } bool IpNode::loopbackPossible() const { auto ports = getLoopbackPorts(); - return (ports.first != -1) and (ports.second != -1); + return (not ports.first.empty()) and (not ports.second.empty()); } bool @@ -125,12 +103,17 @@ IpNode::connectLoopback() const auto& portMaster = portsMaster[ports.first]; const auto& portSlave = portsSlave[ports.second]; + logger->debug("master port: {}", ports.first); + logger->debug("slave port: {}", ports.second); + + logger->debug("switch at: {}", portMaster.nodeName); + // TODO: verify this is really a switch! auto axiStreamSwitch = reinterpret_cast( card->lookupIp(portMaster.nodeName)); if(axiStreamSwitch == nullptr) { - logger->error("Cannot find IP {}", *axiStreamSwitch); + logger->error("Cannot find switch"); return false; } diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index eb7dc4669..087d3ad8f 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -51,8 +51,8 @@ FifoFactory::configureJson(IpCore &ip, json_t *json_ip) } auto& fifo = reinterpret_cast(ip); - if(json_unpack(json_ip, "{ s: i }", "baseaddr_axi4", &fifo.baseaddr_axi4) != 0) { - logger->warn("Cannot parse property 'baseaddr_axi4'"); + if(json_unpack(json_ip, "{ s: i }", "axi4_baseaddr", &fifo.baseaddr_axi4) != 0) { + logger->warn("Cannot parse property 'axi4_baseaddr'"); return false; } @@ -76,7 +76,7 @@ bool Fifo::init() XLlFifo_IntEnable(&xFifo, XLLF_INT_RC_MASK); auto intc = reinterpret_cast(dependencies["intc"]); - intc->enableInterrupt(irqs[0], false); + intc->enableInterrupt(irqs["interrupt"], false); return true; } @@ -113,7 +113,7 @@ size_t Fifo::read(void *buf, size_t len) auto intc = reinterpret_cast(dependencies["intc"]); while (!XLlFifo_IsRxDone(&xFifo)) - intc->waitForInterrupt(irqs[0].num); + intc->waitForInterrupt(irqs["interrupt"].num); XLlFifo_IntClear(&xFifo, XLLF_INT_RC_MASK); diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index e7716c1ff..90cbd7144 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -76,7 +76,8 @@ InterruptController::init() XIntc_Out32(base + XIN_IER_OFFSET, 0x00000000); /* Disable all IRQs by default */ XIntc_Out32(base + XIN_MER_OFFSET, XIN_INT_HARDWARE_ENABLE_MASK | XIN_INT_MASTER_ENABLE_MASK); - logger->debug("enabled interrupts");; + logger->debug("enabled interrupts"); + return true; } diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index 81110a7f7..9f5a73470 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -23,6 +23,7 @@ * along with this program. If not, see . *********************************************************************************/ +#include #include #include "log.hpp" @@ -39,8 +40,8 @@ AxiStreamSwitch::init() { /* Setup AXI-stream switch */ XAxis_Switch_Config sw_cfg; - sw_cfg.MaxNumMI = portsMaster.size(); - sw_cfg.MaxNumSI = portsSlave.size(); + sw_cfg.MaxNumMI = num_ports; + sw_cfg.MaxNumSI = num_ports; if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseaddr()) != XST_SUCCESS) { return false; @@ -122,6 +123,25 @@ AxiStreamSwitch::disconnectSlave(int port) return true; } +bool +AxiStreamSwitchFactory::configureJson(IpCore& ip, json_t* json_ip) +{ + if(not IpNodeFactory::configureJson(ip, json_ip)) + return false; + + auto logger = getLogger(); + + auto& axiSwitch = reinterpret_cast(ip); + + if(json_unpack(json_ip, "{ s: i }", "num_ports", &axiSwitch.num_ports) != 0) { + logger->error("Cannot parse 'num_ports'"); + return false; + } + + return true; +} + + } // namespace ip } // namespace fpga } // namespace villas diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index 5e0c56e4c..a6e19e257 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -39,21 +39,33 @@ static TimerFactory factory; bool Timer::init() { + auto logger = getLogger(); + XTmrCtr_Config xtmr_cfg; xtmr_cfg.SysClockFreqHz = getFrequency(); XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseaddr()); XTmrCtr_InitHw(&xTmr); + if(dependencies.find("intc") == dependencies.end()) { + logger->error("No intc"); + return false; + } + + if(irqs.find("generateout0") == irqs.end()) { + logger->error("no irq"); + return false; + } + intc = reinterpret_cast(dependencies["intc"]); - intc->disableInterrupt(irqs[0]); + intc->disableInterrupt(irqs["generateout0"]); return true; } bool Timer::start(uint32_t ticks) { - intc->enableInterrupt(irqs[0], false); + intc->enableInterrupt(irqs["generateout0"], false); XTmrCtr_SetOptions(&xTmr, 0, XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION); XTmrCtr_SetResetValue(&xTmr, 0, ticks); @@ -64,8 +76,8 @@ bool Timer::start(uint32_t ticks) bool Timer::wait() { - int count = intc->waitForInterrupt(irqs[0]); - intc->disableInterrupt(irqs[0]); + int count = intc->waitForInterrupt(irqs["generateout0"]); + intc->disableInterrupt(irqs["generateout0"]); return (count == 1); } From aa33a8e028df56a55de2ed553148081fe985726f Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 10:26:35 +0100 Subject: [PATCH 090/560] libxil: update upstream path --- fpga/.gitmodules | 2 +- fpga/thirdparty/libxil | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/.gitmodules b/fpga/.gitmodules index f6d6f0188..e67658aa1 100644 --- a/fpga/.gitmodules +++ b/fpga/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/Snaipe/Criterion.git [submodule "thirdparty/libxil"] path = thirdparty/libxil - url = https://git.rwth-aachen.de/VILLASframework/libxil.git + url = https://git.rwth-aachen.de/acs/public/villas/libxil.git diff --git a/fpga/thirdparty/libxil b/fpga/thirdparty/libxil index 89eb3ead0..f08dc8e87 160000 --- a/fpga/thirdparty/libxil +++ b/fpga/thirdparty/libxil @@ -1 +1 @@ -Subproject commit 89eb3ead0c210318144238f2b5b6a96ce3feec73 +Subproject commit f08dc8e878719ab8937c47fedd20ebf3aa68e123 From f6c02b84299c42f377c3911d09ed3daea63bb6f6 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 15:08:28 +0100 Subject: [PATCH 091/560] lib: add directed graph implementation incl. unittest --- fpga/include/villas/directed_graph.hpp | 201 +++++++++++++++++++++++++ fpga/tests/CMakeLists.txt | 1 + fpga/tests/graph.cpp | 32 ++++ fpga/tests/main.cpp | 11 ++ 4 files changed, 245 insertions(+) create mode 100644 fpga/include/villas/directed_graph.hpp create mode 100644 fpga/tests/graph.cpp diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp new file mode 100644 index 000000000..71716d49f --- /dev/null +++ b/fpga/include/villas/directed_graph.hpp @@ -0,0 +1,201 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "log.hpp" + + +namespace villas { +namespace graph { + +// use vector indices as identifiers +using VertexIdentifier = std::size_t; +using EdgeIdentifier = std::size_t; + +// forward declarations +class Edge; +class Vertex; + + +class Vertex { + template + friend class DirectedGraph; + +public: + bool + operator==(const Vertex& other) + { return this->id == other.id; } + +private: + VertexIdentifier id; + std::list edges; +}; + + +class Edge { + template + friend class DirectedGraph; + +public: + bool + operator==(const Edge& other) + { return this->id == other.id; } + +private: + EdgeIdentifier id; + VertexIdentifier from; + VertexIdentifier to; +}; + + +template +class DirectedGraph { +public: + + DirectedGraph(const std::string& name = "DirectedGraph") : + lastVertexId(0), lastEdgeId(0) + { + logger = loggerGetOrCreate(name); + } + + std::shared_ptr getVertex(VertexIdentifier vertexId) const + { + if(vertexId < 0 or vertexId >= lastVertexId) + throw std::invalid_argument("vertex doesn't exist"); + + // cannot use [] operator, because creates non-existing elements + // at() will throw std::out_of_range if element does not exist + return vertices.at(vertexId); + } + + std::shared_ptr getEdge(EdgeIdentifier edgeId) const + { + if(edgeId < 0 or edgeId >= lastEdgeId) + throw std::invalid_argument("edge doesn't exist"); + + // cannot use [] operator, because creates non-existing elements + // at() will throw std::out_of_range if element does not exist + return edges.at(edgeId); + } + + std::size_t getEdgeCount() const + { return edges.size(); } + + std::size_t getVertexCount() const + { return vertices.size(); } + + VertexIdentifier addVertex(std::shared_ptr vertex) + { + vertex->id = lastVertexId++; + + logger->debug("New vertex: {}", vertex->id); + vertices[vertex->id] = vertex; + + return vertex->id; + } + + EdgeIdentifier addEdge(VertexIdentifier fromVertexId, + VertexIdentifier toVertexId) + { + std::shared_ptr edge(new EdgeType); + edge->id = lastEdgeId++; + + logger->debug("New edge {}: {} -> {}", edge->id, fromVertexId, toVertexId); + + // connect it + edge->from = fromVertexId; + edge->to = toVertexId; + + // this is a directed graph, so only push edge to starting vertex + getVertex(fromVertexId)->edges.push_back(edge->id); + + // add new edge to graph + edges[edge->id] = edge; + + return edge->id; + } + + void removeEdge(EdgeIdentifier edgeId) + { + auto edge = getEdge(edgeId); + auto startVertex = getVertex(edge->from); + + // remove edge only from starting vertex (this is a directed graph) + logger->debug("Remove edge {} from vertex {}", edgeId, edge->from); + startVertex->edges.remove(edgeId); + + logger->debug("Remove edge {}", edgeId); + edges.erase(edgeId); + } + + void removeVertex(VertexIdentifier vertexId) + { + // delete every edge that start or ends at this vertex + auto it = edges.begin(); + while(it != edges.end()) { + auto& [edgeId, edge] = *it; + bool removeEdge = false; + + if(edge->to == vertexId) { + logger->debug("Remove edge {} from vertex {}'s edge list", + edgeId, edge->from); + + removeEdge = true; + + auto startVertex = getVertex(edge->from); + startVertex->edges.remove(edge->id); + } + + if((edge->from == vertexId) or removeEdge) { + logger->debug("Remove edge {}", edgeId); + // remove edge from global edge list + it = edges.erase(it); + } else { + ++it; + } + } + + logger->debug("Remove vertex {}", vertexId); + vertices.erase(vertexId); + } + + const std::list& + vertexGetEdges(VertexIdentifier vertexId) const + { return getVertex(vertexId)->edges; } + + void dump() + { + logger->info("Vertices:"); + for(auto& [vertexId, vertex] : vertices) { + // format connected vertices into a list + std::stringstream ssEdges; + for(auto& edge : vertex->edges) { + ssEdges << getEdge(edge)->to << " "; + } + + logger->info(" {} connected to: {}", vertexId, ssEdges.str()); + } + + logger->info("Edges:"); + for(auto& [edgeId, edge] : edges) { + logger->info(" {}: {} -> {}", edgeId, edge->from, edge->to); + } + } + +private: + VertexIdentifier lastVertexId; + EdgeIdentifier lastEdgeId; + + std::map> vertices; + std::map> edges; + + SpdLogger logger; +}; + +} // namespacae graph +} // namespace villas diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index bb9988ba2..edd6fdd3e 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES # rtds_rtt.c timer.cpp # xsg.c + graph.cpp ) add_executable(unit-tests ${SOURCES}) diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp new file mode 100644 index 000000000..92f81cc74 --- /dev/null +++ b/fpga/tests/graph.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include + +Test(graph, directed, .description = "DirectedGraph") +{ + villas::graph::DirectedGraph<> g; + + std::shared_ptr v1(new villas::graph::Vertex); + std::shared_ptr v2(new villas::graph::Vertex); + std::shared_ptr v3(new villas::graph::Vertex); + + auto v1id = g.addVertex(v1); + auto v2id = g.addVertex(v2); + auto v3id = g.addVertex(v3); + cr_assert(g.getVertexCount() == 3); + + g.addEdge(v1id, v2id); + g.addEdge(v3id, v2id); + g.addEdge(v1id, v3id); + g.addEdge(v2id, v1id); + cr_assert(g.getEdgeCount() == 4); + cr_assert(g.vertexGetEdges(v1id).size() == 2); + cr_assert(g.vertexGetEdges(v2id).size() == 1); + cr_assert(g.vertexGetEdges(v3id).size() == 1); + + g.removeVertex(v1id); + g.dump(); + cr_assert(g.getVertexCount() == 2); + cr_assert(g.vertexGetEdges(v2id).size() == 0); +} diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 680af8ab6..5f83151e3 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -111,3 +111,14 @@ TestSuite(fpga, .fini = fini, .description = "VILLASfpga" ); + +static void init_graph() +{ + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); +} + +TestSuite(graph, + .init = init_graph, + .description = "Graph library" +); From 7582966e16ce620c8f5227297588ab55c2fc90b9 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 15:11:29 +0100 Subject: [PATCH 092/560] lib: first draft of memory manager --- fpga/include/villas/memory_manager.hpp | 47 ++++++++++++++++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/memory_manager.cpp | 19 +++++++++++ 3 files changed, 67 insertions(+) create mode 100644 fpga/include/villas/memory_manager.hpp create mode 100644 fpga/lib/memory_manager.cpp diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp new file mode 100644 index 000000000..a992f84a0 --- /dev/null +++ b/fpga/include/villas/memory_manager.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "log.hpp" +#include "directed_graph.hpp" + +namespace villas { + + +class Mapping : public graph::Edge { +public: + // create mapping here (if needed) + Mapping() {} + + // destroy mapping here (if needed) + ~Mapping() {} + +private: + uintptr_t src; + uintptr_t dest; + size_t size; +}; + +class AddressSpace : public graph::Vertex { +private: + // do we need any metadata? maybe a name? + int id; +}; + + +// is or has a graph +class MemoryManager { + +// This is a singleton +private: + MemoryManager() = default; + static MemoryManager* instance; +public: + static MemoryManager& get(); + + +private: +// std::list<> +}; + +} // namespace villas diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 62cff3113..2302a32eb 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -25,6 +25,7 @@ set(SOURCES kernel/pci.c kernel/vfio.c + memory_manager.cpp plugin.c plugin.cpp utils.c diff --git a/fpga/lib/memory_manager.cpp b/fpga/lib/memory_manager.cpp new file mode 100644 index 000000000..d00460219 --- /dev/null +++ b/fpga/lib/memory_manager.cpp @@ -0,0 +1,19 @@ +#include "memory_manager.hpp" + +namespace villas { + +MemoryManager* +MemoryManager::instance = nullptr; + +MemoryManager& +MemoryManager::get() +{ + if(instance == nullptr) { + instance = new MemoryManager; + } + + return *instance; +} + + +} // namespace villas From ec8e9a1cd1af32570336e22c5867ee0d24f34632 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 15:11:47 +0100 Subject: [PATCH 093/560] lib/ip-node: remove dangling function prototype --- fpga/include/villas/fpga/ip_node.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/fpga/include/villas/fpga/ip_node.hpp b/fpga/include/villas/fpga/ip_node.hpp index 6dfe12011..37371b7ce 100644 --- a/fpga/include/villas/fpga/ip_node.hpp +++ b/fpga/include/villas/fpga/ip_node.hpp @@ -73,9 +73,6 @@ public: IpNodeFactory(std::string name) : IpCoreFactory("Ip Node - " + name) {} virtual bool configureJson(IpCore& ip, json_t *json_ip); - -private: - bool populatePorts(std::map& portMap, json_t* json); }; /** @} */ From 27c67f206e059dbb564a36d5c7bf84639725d68c Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 17:28:13 +0100 Subject: [PATCH 094/560] lib/graph: add path-finding with loop detection and corresponding unittest --- fpga/include/villas/directed_graph.hpp | 45 ++++++++++++++++ fpga/tests/graph.cpp | 71 +++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 71716d49f..d7752a9f0 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "log.hpp" @@ -168,6 +169,50 @@ public: vertexGetEdges(VertexIdentifier vertexId) const { return getVertex(vertexId)->edges; } + bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId, + std::list& path) + { + if(fromVertexId == toVertexId) { + // arrived at the destination + return true; + } else { + auto fromVertex = getVertex(fromVertexId); + + for(auto& edgeId : fromVertex->edges) { + auto edge = getEdge(edgeId); + + // loop detection + bool loop = false; + for(auto& edgeIdInPath : path) { + auto edgeInPath = getEdge(edgeIdInPath); + if(edgeInPath->from == edgeId) { + loop = true; + break; + } + } + + if(loop) { + logger->debug("Loop detected via edge {}", edgeId); + continue; + } + + // remember the path we're investigating to detect loops + path.push_back(edgeId); + + // recursive, depth-first search + if(getPath(edge->to, toVertexId, path)) { + // path found, we're done + return true; + } else { + // tear down path that didn't lead to the destination + path.pop_back(); + } + } + } + + return false; + } + void dump() { logger->info("Vertices:"); diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 92f81cc74..4eb25ac75 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -2,10 +2,14 @@ #include #include +#include -Test(graph, directed, .description = "DirectedGraph") +Test(graph, basic, .description = "DirectedGraph") { - villas::graph::DirectedGraph<> g; + auto logger = loggerGetOrCreate("unittest:basic"); + logger->info("Testing basic graph construction and modification"); + + villas::graph::DirectedGraph<> g("unittest:basic"); std::shared_ptr v1(new villas::graph::Vertex); std::shared_ptr v2(new villas::graph::Vertex); @@ -30,3 +34,66 @@ Test(graph, directed, .description = "DirectedGraph") cr_assert(g.getVertexCount() == 2); cr_assert(g.vertexGetEdges(v2id).size() == 0); } + +Test(graph, path, .description = "Find path") +{ + auto logger = loggerGetOrCreate("unittest:path"); + logger->info("Testing path finding algorithm"); + + villas::graph::DirectedGraph<> g("unittest:path"); + + std::shared_ptr v1(new villas::graph::Vertex); + std::shared_ptr v2(new villas::graph::Vertex); + std::shared_ptr v3(new villas::graph::Vertex); + std::shared_ptr v4(new villas::graph::Vertex); + std::shared_ptr v5(new villas::graph::Vertex); + std::shared_ptr v6(new villas::graph::Vertex); + + auto v1id = g.addVertex(v1); + auto v2id = g.addVertex(v2); + auto v3id = g.addVertex(v3); + + auto v4id = g.addVertex(v4); + auto v5id = g.addVertex(v5); + auto v6id = g.addVertex(v6); + + g.addEdge(v1id, v2id); + g.addEdge(v2id, v3id); + + // create circular subgraph + g.addEdge(v4id, v5id); + g.addEdge(v5id, v4id); + g.addEdge(v5id, v6id); + + g.dump(); + + logger->info("Find simple path via two edges"); + std::list path1; + cr_assert(g.getPath(v1id, v3id, path1)); + + logger->info(" Path from {} to {} via:", v1id, v3id); + for(auto& edge : path1) { + logger->info(" -> edge {}", edge); + } + + logger->info("Find path between two unconnected sub-graphs"); + std::list path2; + cr_assert(not g.getPath(v1id, v4id, path2)); + logger->info(" no path found -> ok"); + + + logger->info("Find non-existing path in circular sub-graph"); + std::list path3; + cr_assert(not g.getPath(v4id, v2id, path3)); + logger->info(" no path found -> ok"); + + + logger->info("Find path in circular graph"); + std::list path4; + cr_assert(g.getPath(v4id, v6id, path4)); + + logger->info(" Path from {} to {} via:", v4id, v6id); + for(auto& edge : path4) { + logger->info(" -> edge {}", edge); + } +} From ef9a31ea528bc520b46bb2752cd213dd04ce351e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 17:35:46 +0100 Subject: [PATCH 095/560] fix path of submodule --- fpga/.gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/.gitmodules b/fpga/.gitmodules index f6d6f0188..e67658aa1 100644 --- a/fpga/.gitmodules +++ b/fpga/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/Snaipe/Criterion.git [submodule "thirdparty/libxil"] path = thirdparty/libxil - url = https://git.rwth-aachen.de/VILLASframework/libxil.git + url = https://git.rwth-aachen.de/acs/public/villas/libxil.git From b202fa9e7d6bbf5639e587b96849f6a171edbe0a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 17:36:49 +0100 Subject: [PATCH 096/560] docker: fix invalid tag name --- fpga/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index f726ace65..fcde7bf97 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -2,7 +2,7 @@ variables: GIT_STRATEGY: fetch GIT_SUBMODULE_STRATEGY: recursive PREFIX: /usr/ - DOCKER_TAG_DEV: ${CI_COMMIT_REF_NAME} + DOCKER_TAG_DEV: ${CI_BUILD_REF_SLUG} DOCKER_IMAGE_DEV: villas/fpga-dev # For some reason, GitLab CI prunes the contents of the submodules so we need to restore them. From 1bbe0c28552bd1a16bd5122ae9d9ad310bc2cfcc Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 17:53:56 +0100 Subject: [PATCH 097/560] enable unit tests on CI --- fpga/.gitlab-ci.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index fcde7bf97..9c10fb7b8 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -12,7 +12,7 @@ before_script: stages: - prepare - build -# - test + - test # - deploy # Stage: prepare @@ -59,16 +59,16 @@ build:source: # Stage: test ############################################################################## -#test:unit: -# stage: test -# dependencies: -# - build:source -# script: -# - make test -# image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} -# tags: -# - docker -# - fpga +test:unit: + stage: test + dependencies: + - build:source + script: + - tests/unit-tests --filter 'graph/*' + image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} + tags: + - docker + - fpga # Stage: deploy ############################################################################## From c3129b35eb1206b2ccd0dfe58727fcb58cdf266c Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 17:55:34 +0100 Subject: [PATCH 098/560] use official Fedora image as base --- fpga/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/Dockerfile b/fpga/Dockerfile index 5e2a8fb69..7bb905cc2 100644 --- a/fpga/Dockerfile +++ b/fpga/Dockerfile @@ -28,7 +28,7 @@ # along with this program. If not, see . ################################################################################### -FROM registry.fedoraproject.org/fedora:27 +FROM fedora:27 LABEL \ org.label-schema.schema-version="1.0" \ From bd4f547e9787e6816bb149af819a25b3b2b34ff5 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 18:07:07 +0100 Subject: [PATCH 099/560] fix wrong tag in gitlab-ci.yml --- fpga/.gitlab-ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 9c10fb7b8..6bacbe506 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -59,16 +59,15 @@ build:source: # Stage: test ############################################################################## -test:unit: +test:unit-software: stage: test dependencies: - build:source script: - - tests/unit-tests --filter 'graph/*' + - build/tests/unit-tests --filter 'graph/*' image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} tags: - docker - - fpga # Stage: deploy ############################################################################## From abeaa0b0779991caa23a09438e62c9af0c24e4f3 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 18:27:13 +0100 Subject: [PATCH 100/560] execute FPGA unit on acs-villas --- fpga/.gitlab-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 6bacbe506..5575b86f6 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -69,6 +69,20 @@ test:unit-software: tags: - docker +test:unit-hardware: + stage: test + dependencies: + - build:source + tags: + - villas-fpga + script: | + if [ "$(who | wc -l)" -eq "0" ]; then + build/tests/unit-tests --filter 'fpga/*' + else + echo "System is currently used by: $(who)" + echo "We are skipping the test. Please restart manually." + fi + # Stage: deploy ############################################################################## From 269550c5dcf681fbc139acaa6d56d6b00b323149 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 18:34:27 +0100 Subject: [PATCH 101/560] install libraries to fix loading of libvillas-fpga.so --- fpga/.gitlab-ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 5575b86f6..2136dea25 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -71,13 +71,15 @@ test:unit-software: test:unit-hardware: stage: test - dependencies: - - build:source +# dependencies: +# - build:source tags: - villas-fpga script: | + mkdir build && cd build && cmake .. && make unit-tests + if [ "$(who | wc -l)" -eq "0" ]; then - build/tests/unit-tests --filter 'fpga/*' + tests/unit-tests --filter 'fpga/*' else echo "System is currently used by: $(who)" echo "We are skipping the test. Please restart manually." From 293f496db088f4a674f7246f64ca2968cfeab435 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:09:19 +0100 Subject: [PATCH 102/560] pci: add function to get currently loaded kernel driver --- fpga/include/villas/kernel/pci.h | 3 +++ fpga/lib/kernel/pci.c | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h index d784cedf8..db6beb0e6 100644 --- a/fpga/include/villas/kernel/pci.h +++ b/fpga/include/villas/kernel/pci.h @@ -57,6 +57,9 @@ int pci_device_compare(const struct pci_device *d, const struct pci_device *f); struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *filter); +/** Get currently loaded driver for device */ +int pci_get_driver(struct pci_device *d, char *buf, size_t buflen); + /** Bind a new LKM to the PCI device */ int pci_attach_driver(struct pci_device *d, const char *driver); diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index 56a8c17bd..e4d6793ba 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -254,10 +254,29 @@ struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *f) return list_search(&p->devices, (cmp_cb_t) pci_device_compare, (void *) f); } +int pci_get_driver(struct pci_device *d, char *buf, size_t buflen) +{ + int ret; + char sysfs[1024], syml[1024]; + + snprintf(sysfs, sizeof(sysfs), "%s/bus/pci/devices/%04x:%02x:%02x.%x/driver", SYSFS_PATH, + d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); + + ret = readlink(sysfs, syml, sizeof(syml)); + if (ret < 0) + return ret; + + char *driver = basename(syml); + + strncpy(buf, driver, buflen); + + return 0; +} + int pci_attach_driver(struct pci_device *d, const char *driver) { FILE *f; - char fn[256]; + char fn[1024]; /* Add new ID to driver */ snprintf(fn, sizeof(fn), "%s/bus/pci/drivers/%s/new_id", SYSFS_PATH, driver); From 3047f5bb7a2b34646c6b587c83da13b84401803b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:09:56 +0100 Subject: [PATCH 103/560] vfio: only rebind pci device to VFIO driver if not already bound --- fpga/lib/kernel/vfio.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fpga/lib/kernel/vfio.c b/fpga/lib/kernel/vfio.c index 018f42dcf..0c0de58d4 100644 --- a/fpga/lib/kernel/vfio.c +++ b/fpga/lib/kernel/vfio.c @@ -229,10 +229,13 @@ int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_ if (kernel_module_load("vfio_pci")) error("Failed to load kernel driver: %s", "vfio_pci"); - /* Bind PCI card to vfio-pci driver*/ - ret = pci_attach_driver(pdev, "vfio-pci"); - if (ret) - error("Failed to attach device to driver"); + /* Bind PCI card to vfio-pci driver if not already bound */ + ret = pci_get_driver(pdev, name, sizeof(name)); + if (ret || strcmp(name, "vfio-pci")) { + ret = pci_attach_driver(pdev, "vfio-pci"); + if (ret) + error("Failed to attach device to driver"); + } /* Get IOMMU group of device */ int index = pci_get_iommu_group(pdev); From 4f86b98fdd7c6dda813b9f9380780a0b010c55df Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:15:45 +0100 Subject: [PATCH 104/560] add script to configure system for non-root access to FPGA --- fpga/scripts/non_root.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 fpga/scripts/non_root.sh diff --git a/fpga/scripts/non_root.sh b/fpga/scripts/non_root.sh new file mode 100644 index 000000000..22e4f941d --- /dev/null +++ b/fpga/scripts/non_root.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +IOMMU_GROUP=24 +PCI_BDF="0000:03:00.0" + +modprobe vfio +modprobe vfio_pci + +echo "10ee 7022" > /sys/bus/pci/drivers/vfio-pci/new_id +echo ${PCI_BDF} > /sys/bus/pci/drivers/vfio-pci/bind + +groupadd -f fpga +usermod -G fpga -a svg + +chgrp fpga /dev/vfio/${IOMMU_GROUP} +chmod g+rw /dev/vfio/${IOMMU_GROUP} + + From 201bbde4b6af55105b7596cf1bd7baf1c06edfb3 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 19:08:55 +0100 Subject: [PATCH 105/560] lib/graph: move identifiers into classes --- fpga/include/villas/directed_graph.hpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index d7752a9f0..ca0f763a9 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -15,8 +15,6 @@ namespace villas { namespace graph { // use vector indices as identifiers -using VertexIdentifier = std::size_t; -using EdgeIdentifier = std::size_t; // forward declarations class Edge; @@ -28,13 +26,16 @@ class Vertex { friend class DirectedGraph; public: + using Identifier = std::size_t; + bool operator==(const Vertex& other) { return this->id == other.id; } private: - VertexIdentifier id; - std::list edges; + Identifier id; + // HACK: how to resolve this circular type dependency? + std::list edges; }; @@ -43,14 +44,16 @@ class Edge { friend class DirectedGraph; public: + using Identifier = std::size_t; + bool operator==(const Edge& other) { return this->id == other.id; } private: - EdgeIdentifier id; - VertexIdentifier from; - VertexIdentifier to; + Identifier id; + Vertex::Identifier from; + Vertex::Identifier to; }; @@ -58,6 +61,9 @@ template class DirectedGraph { public: + using VertexIdentifier = Vertex::Identifier; + using EdgeIdentifier = Edge::Identifier; + DirectedGraph(const std::string& name = "DirectedGraph") : lastVertexId(0), lastEdgeId(0) { From 22ce8f2b3fc6b3fdd1615ba764ac84aa9927c289 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 19:10:10 +0100 Subject: [PATCH 106/560] lib/graph: slightly change interface to allow for custom edges --- fpga/include/villas/directed_graph.hpp | 21 ++++++++++++++----- fpga/tests/graph.cpp | 29 +++++++++++++------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index ca0f763a9..192673f76 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -106,20 +106,21 @@ public: return vertex->id; } - EdgeIdentifier addEdge(VertexIdentifier fromVertexId, + EdgeIdentifier addEdge(std::shared_ptr edge, + VertexIdentifier fromVertexId, VertexIdentifier toVertexId) { - std::shared_ptr edge(new EdgeType); + // allocate edge id edge->id = lastEdgeId++; - logger->debug("New edge {}: {} -> {}", edge->id, fromVertexId, toVertexId); - // connect it edge->from = fromVertexId; edge->to = toVertexId; + logger->debug("New edge {}: {} -> {}", edge->id, edge->from, edge->to); + // this is a directed graph, so only push edge to starting vertex - getVertex(fromVertexId)->edges.push_back(edge->id); + getVertex(edge->from)->edges.push_back(edge->id); // add new edge to graph edges[edge->id] = edge; @@ -127,6 +128,16 @@ public: return edge->id; } + + EdgeIdentifier addDefaultEdge(VertexIdentifier fromVertexId, + VertexIdentifier toVertexId) + { + // create a new edge + std::shared_ptr edge(new EdgeType); + + return addEdge(edge, fromVertexId, toVertexId); + } + void removeEdge(EdgeIdentifier edgeId) { auto edge = getEdge(edgeId); diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 4eb25ac75..1de58e9b8 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -20,10 +20,10 @@ Test(graph, basic, .description = "DirectedGraph") auto v3id = g.addVertex(v3); cr_assert(g.getVertexCount() == 3); - g.addEdge(v1id, v2id); - g.addEdge(v3id, v2id); - g.addEdge(v1id, v3id); - g.addEdge(v2id, v1id); + g.addDefaultEdge(v1id, v2id); + g.addDefaultEdge(v3id, v2id); + g.addDefaultEdge(v1id, v3id); + g.addDefaultEdge(v2id, v1id); cr_assert(g.getEdgeCount() == 4); cr_assert(g.vertexGetEdges(v1id).size() == 2); cr_assert(g.vertexGetEdges(v2id).size() == 1); @@ -40,7 +40,8 @@ Test(graph, path, .description = "Find path") auto logger = loggerGetOrCreate("unittest:path"); logger->info("Testing path finding algorithm"); - villas::graph::DirectedGraph<> g("unittest:path"); + using Graph = villas::graph::DirectedGraph<>; + Graph g("unittest:path"); std::shared_ptr v1(new villas::graph::Vertex); std::shared_ptr v2(new villas::graph::Vertex); @@ -57,18 +58,18 @@ Test(graph, path, .description = "Find path") auto v5id = g.addVertex(v5); auto v6id = g.addVertex(v6); - g.addEdge(v1id, v2id); - g.addEdge(v2id, v3id); + g.addDefaultEdge(v1id, v2id); + g.addDefaultEdge(v2id, v3id); // create circular subgraph - g.addEdge(v4id, v5id); - g.addEdge(v5id, v4id); - g.addEdge(v5id, v6id); + g.addDefaultEdge(v4id, v5id); + g.addDefaultEdge(v5id, v4id); + g.addDefaultEdge(v5id, v6id); g.dump(); logger->info("Find simple path via two edges"); - std::list path1; + std::list path1; cr_assert(g.getPath(v1id, v3id, path1)); logger->info(" Path from {} to {} via:", v1id, v3id); @@ -77,19 +78,19 @@ Test(graph, path, .description = "Find path") } logger->info("Find path between two unconnected sub-graphs"); - std::list path2; + std::list path2; cr_assert(not g.getPath(v1id, v4id, path2)); logger->info(" no path found -> ok"); logger->info("Find non-existing path in circular sub-graph"); - std::list path3; + std::list path3; cr_assert(not g.getPath(v4id, v2id, path3)); logger->info(" no path found -> ok"); logger->info("Find path in circular graph"); - std::list path4; + std::list path4; cr_assert(g.getPath(v4id, v6id, path4)); logger->info(" Path from {} to {} via:", v4id, v6id); From ba7531ac46ac9cc6a1200bd5c41eb93825b80929 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 19:13:10 +0100 Subject: [PATCH 107/560] lib/graph: allow stringifying of vertex and edge derived types This yields nices debug messages and a much nice dump(). --- fpga/include/villas/directed_graph.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 192673f76..8d964dae1 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -28,6 +28,10 @@ class Vertex { public: using Identifier = std::size_t; + friend std::ostream& + operator<< (std::ostream& stream, const Vertex& vertex) + { return stream << vertex.id; } + bool operator==(const Vertex& other) { return this->id == other.id; } @@ -46,6 +50,10 @@ class Edge { public: using Identifier = std::size_t; + friend std::ostream& + operator<< (std::ostream& stream, const Edge& edge) + { return stream << edge.id; } + bool operator==(const Edge& other) { return this->id == other.id; } @@ -100,7 +108,7 @@ public: { vertex->id = lastVertexId++; - logger->debug("New vertex: {}", vertex->id); + logger->debug("New vertex: {}", *vertex); vertices[vertex->id] = vertex; return vertex->id; @@ -117,7 +125,7 @@ public: edge->from = fromVertexId; edge->to = toVertexId; - logger->debug("New edge {}: {} -> {}", edge->id, edge->from, edge->to); + logger->debug("New edge {}: {} -> {}", *edge, edge->from, edge->to); // this is a directed graph, so only push edge to starting vertex getVertex(edge->from)->edges.push_back(edge->id); @@ -240,12 +248,12 @@ public: ssEdges << getEdge(edge)->to << " "; } - logger->info(" {} connected to: {}", vertexId, ssEdges.str()); + logger->info(" {} connected to: {}", *vertex, ssEdges.str()); } logger->info("Edges:"); for(auto& [edgeId, edge] : edges) { - logger->info(" {}: {} -> {}", edgeId, edge->from, edge->to); + logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); } } From 5a7989d5524db593026e733b406c3bde1c6b2ef9 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 19:15:44 +0100 Subject: [PATCH 108/560] lib/memory-manager: start implementation using DirectedGraph --- fpga/include/villas/memory_manager.hpp | 77 +++++++++++++++++++++----- fpga/lib/memory_manager.cpp | 38 +++++++++++++ 2 files changed, 102 insertions(+), 13 deletions(-) diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index a992f84a0..935f262b3 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "log.hpp" #include "directed_graph.hpp" @@ -9,12 +10,25 @@ namespace villas { class Mapping : public graph::Edge { -public: - // create mapping here (if needed) - Mapping() {} + friend class MemoryManager; - // destroy mapping here (if needed) - ~Mapping() {} +public: + // create mapping here (if needed) + Mapping() {} + + // destroy mapping here (if needed) + virtual ~Mapping(); + + friend std::ostream& + operator<< (std::ostream& stream, const Mapping& mapping) + { + return stream << static_cast(mapping) << " = " + << std::hex + << "(src=0x" << mapping.src + << ", dest=0x" << mapping.dest + << ", size=0x" << mapping.size + << ")"; + } private: uintptr_t src; @@ -23,25 +37,62 @@ private: }; class AddressSpace : public graph::Vertex { + friend class MemoryManager; + +public: + friend std::ostream& + operator<< (std::ostream& stream, const AddressSpace& addrSpace) + { + return stream << static_cast(addrSpace) << " = " + << addrSpace.name; + } + private: - // do we need any metadata? maybe a name? - int id; + std::string name; }; // is or has a graph class MemoryManager { - -// This is a singleton private: - MemoryManager() = default; - static MemoryManager* instance; + // This is a singleton, so private constructor + MemoryManager() : + memoryGraph("MemoryGraph") {} + + // no copying or assigning + MemoryManager(const MemoryManager&) = delete; + MemoryManager& operator=(const MemoryManager&) = delete ; + + using MemoryGraph = graph::DirectedGraph; + public: - static MemoryManager& get(); + using AddressSpaceId = MemoryGraph::VertexIdentifier; + using MappingId = MemoryGraph::EdgeIdentifier; + static MemoryManager& get(); + + + AddressSpaceId createAddressSpace(std::string name); + + /// Create a default mapping + MappingId createMapping(uintptr_t src, uintptr_t dest, size_t size, + AddressSpaceId fromAddrSpace, + AddressSpaceId toAddrSpace); + + /// Add a mapping + /// + /// Can be used to derive from Mapping in order to implement custom + /// constructor/destructor. + MappingId addMapping(std::shared_ptr mapping, + AddressSpaceId fromAddrSpace, + AddressSpaceId toAddrSpace); + + void dump() + { memoryGraph.dump(); } private: -// std::list<> + MemoryGraph memoryGraph; + static MemoryManager* instance; }; } // namespace villas diff --git a/fpga/lib/memory_manager.cpp b/fpga/lib/memory_manager.cpp index d00460219..59269fe04 100644 --- a/fpga/lib/memory_manager.cpp +++ b/fpga/lib/memory_manager.cpp @@ -1,3 +1,5 @@ +#include + #include "memory_manager.hpp" namespace villas { @@ -15,5 +17,41 @@ MemoryManager::get() return *instance; } +MemoryManager::AddressSpaceId +MemoryManager::createAddressSpace(std::string name) +{ + std::shared_ptr addrSpace(new AddressSpace); + addrSpace->name = name; + + return memoryGraph.addVertex(addrSpace); +} + +MemoryManager::MappingId +MemoryManager::createMapping(uintptr_t src, uintptr_t dest, size_t size, + MemoryManager::AddressSpaceId fromAddrSpace, + MemoryManager::AddressSpaceId toAddrSpace) +{ + std::shared_ptr mapping(new Mapping); + mapping->src = src; + mapping->dest = dest; + mapping->size = size; + + return addMapping(mapping, fromAddrSpace, toAddrSpace); +} + +MemoryManager::MappingId +MemoryManager::addMapping(std::shared_ptr mapping, + MemoryManager::AddressSpaceId fromAddrSpace, + MemoryManager::AddressSpaceId toAddrSpace) +{ + return memoryGraph.addEdge(mapping, fromAddrSpace, toAddrSpace); +} + + +Mapping::~Mapping() +{ + +} + } // namespace villas From a45de4ec1a496fe2c9b5276e5dbde77c7875c807 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:27:22 +0100 Subject: [PATCH 109/560] FPGA tests fail if we attempt to run them in parallel --- fpga/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 2136dea25..236d26978 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -79,7 +79,7 @@ test:unit-hardware: mkdir build && cd build && cmake .. && make unit-tests if [ "$(who | wc -l)" -eq "0" ]; then - tests/unit-tests --filter 'fpga/*' + tests/unit-tests --jobs 1 --filter 'fpga/*' else echo "System is currently used by: $(who)" echo "We are skipping the test. Please restart manually." From 4a4a5fe17643dd2b61879e90204340cc6c2d5a1f Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:32:22 +0100 Subject: [PATCH 110/560] tiny change --- fpga/.gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index 236d26978..f623edf97 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -77,7 +77,6 @@ test:unit-hardware: - villas-fpga script: | mkdir build && cd build && cmake .. && make unit-tests - if [ "$(who | wc -l)" -eq "0" ]; then tests/unit-tests --jobs 1 --filter 'fpga/*' else From e25a3e24bc52d4706547460a874e4fd92e1669f8 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:35:03 +0100 Subject: [PATCH 111/560] cleanup build dir before building --- fpga/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index f623edf97..b45b20877 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -76,7 +76,7 @@ test:unit-hardware: tags: - villas-fpga script: | - mkdir build && cd build && cmake .. && make unit-tests + rm -r build && mkdir build && cd build && cmake .. && make unit-tests if [ "$(who | wc -l)" -eq "0" ]; then tests/unit-tests --jobs 1 --filter 'fpga/*' else From 3c7f6e968b51f01eb64144f8d16095090e695236 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 30 Jan 2018 19:36:06 +0100 Subject: [PATCH 112/560] do parallel build --- fpga/.gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml index b45b20877..ab5b0d02e 100644 --- a/fpga/.gitlab-ci.yml +++ b/fpga/.gitlab-ci.yml @@ -33,7 +33,7 @@ docker-dev: build:source: stage: build script: - - mkdir build && cd build && cmake .. && make + - mkdir build && cd build && cmake .. && make -j8 artifacts: expire_in: 1 week name: ${CI_PROJECT_NAME}-${CI_BUILD_REF} @@ -76,7 +76,7 @@ test:unit-hardware: tags: - villas-fpga script: | - rm -r build && mkdir build && cd build && cmake .. && make unit-tests + rm -r build && mkdir build && cd build && cmake .. && make unit-tests -j8 if [ "$(who | wc -l)" -eq "0" ]; then tests/unit-tests --jobs 1 --filter 'fpga/*' else From 3de2170ad617ad726bbf4c535b731c587e811f4a Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 31 Jan 2018 11:16:02 +0100 Subject: [PATCH 113/560] tests: move variables to global state and set criterion jobs to 1 --- fpga/tests/fifo.cpp | 4 ++-- fpga/tests/global.hpp | 21 +++++++++++++++++++++ fpga/tests/main.cpp | 24 +++++++++--------------- fpga/tests/timer.cpp | 5 ++--- 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 fpga/tests/global.hpp diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index 14c2e918a..6b01c6758 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -27,8 +27,8 @@ #include #include +#include "global.hpp" -extern villas::fpga::PCIeCard* fpga; Test(fpga, fifo, .description = "FIFO") { @@ -37,7 +37,7 @@ Test(fpga, fifo, .description = "FIFO") auto logger = loggerGetOrCreate("unittest:fifo"); - for(auto& ip : fpga->ips) { + for(auto& ip : state.cards.front()->ips) { // skip non-fifo IPs if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_fifo_mm_s:")) continue; diff --git a/fpga/tests/global.hpp b/fpga/tests/global.hpp new file mode 100644 index 000000000..6ae8a83ea --- /dev/null +++ b/fpga/tests/global.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +class FpgaState { +public: + FpgaState() { + // force criterion to only run one job at a time + setenv("CRITERION_JOBS", "1", 0); + } + + // list of all available FPGA cards, only first will be tested at the moment + villas::fpga::CardList cards; +}; + +// global state to be shared by unittests +extern FpgaState state; + + diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 5f83151e3..b5fa06f8b 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -35,6 +35,8 @@ #include +#include "global.hpp" + #define FPGA_CARD "vc707" #define TEST_CONFIG "../etc/fpga.json" #define TEST_LEN 0x1000 @@ -42,13 +44,10 @@ #define CPU_HZ 3392389000 #define FPGA_AXI_HZ 125000000 -struct pci pci; -struct vfio_container vc; -villas::fpga::CardList fpgaCards; -villas::fpga::PCIeCard* fpga; +FpgaState state; -// keep to make it compile with old C tests -struct fpga_card* card; +static struct pci pci; +static struct vfio_container vc; static void init() { @@ -88,22 +87,17 @@ static void init() villas::fpga::PCIeCardFactory* fpgaCardPlugin = dynamic_cast(plugin); // create all FPGA card instances using the corresponding plugin - fpgaCards = fpgaCardPlugin->make(fpgas, &pci, &vc); + state.cards = fpgaCardPlugin->make(fpgas, &pci, &vc); - if(fpgaCards.size() == 0) { - logger->error("No FPGA cards found!"); - } else { - fpga = fpgaCards.front().get(); - } - - cr_assert_not_null(fpga, "No FPGA card available"); + cr_assert(state.cards.size() != 0, "No FPGA cards found!"); json_decref(json); } static void fini() { - fpgaCards.clear(); + // release all cards + state.cards.clear(); } TestSuite(fpga, diff --git a/fpga/tests/timer.cpp b/fpga/tests/timer.cpp index 1f7013cf9..278f8f418 100644 --- a/fpga/tests/timer.cpp +++ b/fpga/tests/timer.cpp @@ -28,14 +28,13 @@ #include #include "config.h" - -extern villas::fpga::PCIeCard* fpga; +#include "global.hpp" Test(fpga, timer, .description = "Timer Counter") { auto logger = loggerGetOrCreate("unittest:timer"); - for(auto& ip : fpga->ips) { + for(auto& ip : state.cards.front()->ips) { // skip non-timer IPs if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_timer:")) { continue; From 32be16ef980c03fd896d62a677968af57df832c6 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 31 Jan 2018 11:21:02 +0100 Subject: [PATCH 114/560] tests/graph: move criterion setup to from main.cpp to graph.cpp --- fpga/tests/graph.cpp | 12 ++++++++++++ fpga/tests/main.cpp | 11 ----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 1de58e9b8..9b9366f27 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -4,6 +4,18 @@ #include #include +static void init_graph() +{ + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); +} + +TestSuite(graph, + .init = init_graph, + .description = "Graph library" +); + + Test(graph, basic, .description = "DirectedGraph") { auto logger = loggerGetOrCreate("unittest:basic"); diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index b5fa06f8b..9a3244380 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -105,14 +105,3 @@ TestSuite(fpga, .fini = fini, .description = "VILLASfpga" ); - -static void init_graph() -{ - spdlog::set_pattern("[%T] [%l] [%n] %v"); - spdlog::set_level(spdlog::level::debug); -} - -TestSuite(graph, - .init = init_graph, - .description = "Graph library" -); From 0aed1a1b12ee9a87dfb9c44eef54c48c695e8c49 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 31 Jan 2018 15:11:13 +0100 Subject: [PATCH 115/560] tests: moved initialization of FPGA stuff to fpga.cpp --- fpga/tests/CMakeLists.txt | 1 + fpga/tests/fpga.cpp | 98 +++++++++++++++++++++++++++++++++++++++ fpga/tests/main.cpp | 89 +---------------------------------- 3 files changed, 101 insertions(+), 87 deletions(-) create mode 100644 fpga/tests/fpga.cpp diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index edd6fdd3e..152413de1 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES main.cpp + fpga.cpp # dma.c fifo.cpp # hls.c diff --git a/fpga/tests/fpga.cpp b/fpga/tests/fpga.cpp new file mode 100644 index 000000000..b5147f3b6 --- /dev/null +++ b/fpga/tests/fpga.cpp @@ -0,0 +1,98 @@ +/** FPGA related code for bootstrapping the unit-tests + * + * @file + * @author Steffen Vogel + * @copyright 2018, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include +#include +#include + +#include "global.hpp" + +#include + +#define FPGA_CARD "vc707" +#define TEST_CONFIG "../etc/fpga.json" +#define TEST_LEN 0x1000 + +#define CPU_HZ 3392389000 +#define FPGA_AXI_HZ 125000000 + +static struct pci pci; +static struct vfio_container vc; + +FpgaState state; + +static void init() +{ + int ret; + + FILE *f; + json_error_t err; + + villas::Plugin::dumpList(); + + ret = pci_init(&pci); + cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); + + ret = vfio_init(&vc); + cr_assert_eq(ret, 0, "Failed to initiliaze VFIO sub-system"); + + /* Parse FPGA configuration */ + f = fopen(TEST_CONFIG, "r"); + cr_assert_not_null(f, "Cannot open config file"); + + json_t *json = json_loadf(f, 0, &err); + cr_assert_not_null(json, "Cannot load JSON config"); + + fclose(f); + + json_t *fpgas = json_object_get(json, "fpgas"); + cr_assert_not_null(fpgas, "No section 'fpgas' found in config"); + cr_assert(json_object_size(json) > 0, "No FPGAs defined in config"); + + // get the FPGA card plugin + villas::Plugin* plugin = villas::Plugin::lookup(villas::Plugin::Type::FpgaCard, ""); + cr_assert_not_null(plugin, "No plugin for FPGA card found"); + villas::fpga::PCIeCardFactory* fpgaCardPlugin = dynamic_cast(plugin); + + // create all FPGA card instances using the corresponding plugin + state.cards = fpgaCardPlugin->make(fpgas, &pci, &vc); + + cr_assert(state.cards.size() != 0, "No FPGA cards found!"); + + json_decref(json); +} + +static void fini() +{ + // release all cards + state.cards.clear(); +} + +TestSuite(fpga, + .init = init, + .fini = fini, + .description = "VILLASfpga" +); diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index b5fa06f8b..321e44a64 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -23,96 +23,11 @@ #include #include - -#include -#include -#include -#include +#include +#include #include -#include -#include #include -#include "global.hpp" -#define FPGA_CARD "vc707" -#define TEST_CONFIG "../etc/fpga.json" -#define TEST_LEN 0x1000 - -#define CPU_HZ 3392389000 -#define FPGA_AXI_HZ 125000000 - -FpgaState state; - -static struct pci pci; -static struct vfio_container vc; - -static void init() -{ - int ret; - - FILE *f; - json_error_t err; - - villas::Plugin::dumpList(); - - auto logger = loggerGetOrCreate("unittest"); - spdlog::set_pattern("[%T] [%l] [%n] %v"); - spdlog::set_level(spdlog::level::debug); - - ret = pci_init(&pci); - cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); - - ret = vfio_init(&vc); - cr_assert_eq(ret, 0, "Failed to initiliaze VFIO sub-system"); - - /* Parse FPGA configuration */ - f = fopen(TEST_CONFIG, "r"); - cr_assert_not_null(f, "Cannot open config file"); - - json_t *json = json_loadf(f, 0, &err); - cr_assert_not_null(json, "Cannot load JSON config"); - - fclose(f); - - json_t *fpgas = json_object_get(json, "fpgas"); - cr_assert_not_null(fpgas, "No section 'fpgas' found in config"); - cr_assert(json_object_size(json) > 0, "No FPGAs defined in config"); - - // get the FPGA card plugin - villas::Plugin* plugin = villas::Plugin::lookup(villas::Plugin::Type::FpgaCard, ""); - cr_assert_not_null(plugin, "No plugin for FPGA card found"); - villas::fpga::PCIeCardFactory* fpgaCardPlugin = dynamic_cast(plugin); - - // create all FPGA card instances using the corresponding plugin - state.cards = fpgaCardPlugin->make(fpgas, &pci, &vc); - - cr_assert(state.cards.size() != 0, "No FPGA cards found!"); - - json_decref(json); -} - -static void fini() -{ - // release all cards - state.cards.clear(); -} - -TestSuite(fpga, - .init = init, - .fini = fini, - .description = "VILLASfpga" -); - -static void init_graph() -{ - spdlog::set_pattern("[%T] [%l] [%n] %v"); - spdlog::set_level(spdlog::level::debug); -} - -TestSuite(graph, - .init = init_graph, - .description = "Graph library" -); From b0f4577dd3afa03f2838cbea74ad317f86470f24 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 31 Jan 2018 15:12:19 +0100 Subject: [PATCH 116/560] tests: automatically detect whether or not we can run tests in parallel --- fpga/tests/global.hpp | 7 ------ fpga/tests/main.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/fpga/tests/global.hpp b/fpga/tests/global.hpp index 6ae8a83ea..01519df9d 100644 --- a/fpga/tests/global.hpp +++ b/fpga/tests/global.hpp @@ -6,16 +6,9 @@ class FpgaState { public: - FpgaState() { - // force criterion to only run one job at a time - setenv("CRITERION_JOBS", "1", 0); - } - // list of all available FPGA cards, only first will be tested at the moment villas::fpga::CardList cards; }; // global state to be shared by unittests extern FpgaState state; - - diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 321e44a64..60ef1637f 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -30,4 +30,54 @@ #include +/** Returns true if there is at least one enabled test in this suite */ +static bool suite_enabled(struct criterion_test_set *tests, const char *name) +{ + FOREACH_SET(void *suite_ptr, tests->suites) { + struct criterion_suite_set *suite = (struct criterion_suite_set *) suite_ptr; + if (!strcmp(suite->suite.name, name)) { + FOREACH_SET(void *test_ptr, suite->tests) { + struct criterion_test *test = (struct criterion_test *) test_ptr; + + if (!test->data->disabled) + return true; + } + } + } + + return false; +} + +/* Limit number of parallel jobs to 1 in case we use the FPGA */ +ReportHook(PRE_ALL)(struct criterion_test_set *tests) { + if (suite_enabled(tests, "fpga")) { + auto logger = loggerGetOrCreate("unittest"); + + logger->info("FPGA tests enabled. Only 1 job is executed in parallel!.\n"); + criterion_options.jobs = 1; + } +} + +int main(int argc, char *argv[]) +{ + int ret; + + struct criterion_test_set *tests; + + auto logger = loggerGetOrCreate("unittest"); + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); + + /* Run criterion tests */ + tests = criterion_initialize(); + + ret = criterion_handle_args(argc, argv, true); + + if (ret) + ret = !criterion_run_all_tests(tests); + + criterion_finalize(tests); + + return ret; +} From 2a03d19d5345c16b0d1a4da934de15fe74602502 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 31 Jan 2018 15:12:36 +0100 Subject: [PATCH 117/560] tests: readd missing graph test suite --- fpga/tests/graph.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 1de58e9b8..b49659e5c 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -4,6 +4,17 @@ #include #include +static void init_graph() +{ + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); +} + +TestSuite(graph, + .init = init_graph, + .description = "Graph library" +); + Test(graph, basic, .description = "DirectedGraph") { auto logger = loggerGetOrCreate("unittest:basic"); From 51a3d0b8e9fb2fa2897b80d619d0175171b8ab8a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 31 Jan 2018 20:22:15 +0100 Subject: [PATCH 118/560] tests: some cleanups --- fpga/tests/main.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 60ef1637f..844e3b64b 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -50,11 +50,12 @@ static bool suite_enabled(struct criterion_test_set *tests, const char *name) } /* Limit number of parallel jobs to 1 in case we use the FPGA */ -ReportHook(PRE_ALL)(struct criterion_test_set *tests) { +ReportHook(PRE_ALL)(struct criterion_test_set *tests) +{ if (suite_enabled(tests, "fpga")) { auto logger = loggerGetOrCreate("unittest"); - logger->info("FPGA tests enabled. Only 1 job is executed in parallel!.\n"); + logger->info("FPGA tests enabled. Only 1 job is executed in parallel!."); criterion_options.jobs = 1; } } @@ -62,18 +63,15 @@ ReportHook(PRE_ALL)(struct criterion_test_set *tests) { int main(int argc, char *argv[]) { int ret; - - struct criterion_test_set *tests; auto logger = loggerGetOrCreate("unittest"); spdlog::set_pattern("[%T] [%l] [%n] %v"); spdlog::set_level(spdlog::level::debug); /* Run criterion tests */ - tests = criterion_initialize(); + auto tests = criterion_initialize(); ret = criterion_handle_args(argc, argv, true); - if (ret) ret = !criterion_run_all_tests(tests); From 2336acaf9826b123035ac483ec2b8ef7399a774e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 31 Jan 2018 20:23:48 +0100 Subject: [PATCH 119/560] tests: override some criteriod_log() functions in order to use spdlog style log output --- fpga/tests/CMakeLists.txt | 1 + fpga/tests/logging.cpp | 101 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 fpga/tests/logging.cpp diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index 152413de1..843c1b6a6 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES main.cpp fpga.cpp + logging.cpp # dma.c fifo.cpp # hls.c diff --git a/fpga/tests/logging.cpp b/fpga/tests/logging.cpp new file mode 100644 index 000000000..64be9274f --- /dev/null +++ b/fpga/tests/logging.cpp @@ -0,0 +1,101 @@ +#include +#include + +#include +#include + +#include +#include + +extern "C" { + /* We override criterions function here */ + void criterion_log_noformat(enum criterion_severity severity, const char *msg); + void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...); + void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args); +} + +struct criterion_prefix_data { + const char *prefix; + const char *color; +}; + +static void format_msg(char *buf, size_t buflen, const char *msg, va_list args) +{ + int len = vsnprintf(buf, buflen, msg, args); + + /* Strip new line */ + char *nl = strchr(buf, '\n'); + if (nl) + *nl = 0; +} + +void criterion_log_noformat(enum criterion_severity severity, const char *msg) +{ + auto logger = loggerGetOrCreate("criterion"); + + switch (severity) { + case CR_LOG_INFO: + logger->info(msg); + break; + + case CR_LOG_WARNING: + logger->warn(msg); + break; + + case CR_LOG_ERROR: + logger->error(msg); + break; + } +} + +void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args) +{ + char formatted_msg[1024]; + + if (level < criterion_options.logging_threshold) + return; + + format_msg(formatted_msg, sizeof(formatted_msg), msg, args); + + auto logger = loggerGetOrCreate("criterion"); + logger->info(formatted_msg); +} + +void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...) +{ + char formatted_msg[1024]; + + va_list args; + + if (level < criterion_options.logging_threshold) + return; + + va_start(args, msg); + format_msg(formatted_msg, sizeof(formatted_msg), msg, args); + va_end(args); + + auto logger = loggerGetOrCreate("criterion"); + + if (strstr(formatted_msg, "Warning")) + logger->warn(formatted_msg); + else if (strstr(formatted_msg, "Failed")) + logger->error(formatted_msg); + else if(!strcmp(prefix->prefix, "----") && !strcmp(prefix->color, "\33[0;34m")) + logger->info(formatted_msg); + else if (!strcmp(prefix->prefix, "----") && !strcmp(prefix->color, "\33[1;30m")) + logger->debug(formatted_msg); + else if (!strcmp(prefix->prefix, "====")) + logger->info(formatted_msg); + else if (!strcmp(prefix->prefix, "RUN ")) + logger->info("Run: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "SKIP")) + logger->info("Skip: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "PASS")) + logger->info("Pass: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "FAIL")) + logger->error("Fail: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "WARN")) + logger->warn(formatted_msg); + else if (!strcmp(prefix->prefix, "ERR ")) + logger->error(formatted_msg); +} From 8206f867a56a23fe3ed214d833826326175a29ec Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 31 Jan 2018 20:24:11 +0100 Subject: [PATCH 120/560] logging: use similar log style in all modules --- fpga/include/villas/plugin.hpp | 2 +- fpga/tests/graph.cpp | 15 ++++----------- fpga/tests/main.cpp | 2 -- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index c07501e83..35a4912ba 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -81,7 +81,7 @@ public: protected: static SpdLogger getStaticLogger() - { return loggerGetOrCreate("Plugin"); } + { return loggerGetOrCreate("plugin"); } private: /* Just using a standard std::list<> to hold plugins is problematic, because diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index b49659e5c..2dd8e6c9b 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -4,23 +4,16 @@ #include #include -static void init_graph() -{ - spdlog::set_pattern("[%T] [%l] [%n] %v"); - spdlog::set_level(spdlog::level::debug); -} - TestSuite(graph, - .init = init_graph, .description = "Graph library" ); Test(graph, basic, .description = "DirectedGraph") { - auto logger = loggerGetOrCreate("unittest:basic"); + auto logger = loggerGetOrCreate("test:graph:basic"); logger->info("Testing basic graph construction and modification"); - villas::graph::DirectedGraph<> g("unittest:basic"); + villas::graph::DirectedGraph<> g("test:graph:basic"); std::shared_ptr v1(new villas::graph::Vertex); std::shared_ptr v2(new villas::graph::Vertex); @@ -48,11 +41,11 @@ Test(graph, basic, .description = "DirectedGraph") Test(graph, path, .description = "Find path") { - auto logger = loggerGetOrCreate("unittest:path"); + auto logger = loggerGetOrCreate("test:graph:path"); logger->info("Testing path finding algorithm"); using Graph = villas::graph::DirectedGraph<>; - Graph g("unittest:path"); + Graph g("test:graph:path"); std::shared_ptr v1(new villas::graph::Vertex); std::shared_ptr v2(new villas::graph::Vertex); diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 844e3b64b..beb56f6b9 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -64,8 +64,6 @@ int main(int argc, char *argv[]) { int ret; - auto logger = loggerGetOrCreate("unittest"); - spdlog::set_pattern("[%T] [%l] [%n] %v"); spdlog::set_level(spdlog::level::debug); /* Run criterion tests */ From 6dab50824b82afff03de53adb2e867361cdd0479 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 7 Feb 2018 10:22:35 +0100 Subject: [PATCH 121/560] directed-graph: fix bug in loop detection --- fpga/include/villas/directed_graph.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 8d964dae1..fbac5a556 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -204,13 +204,13 @@ public: auto fromVertex = getVertex(fromVertexId); for(auto& edgeId : fromVertex->edges) { - auto edge = getEdge(edgeId); + auto edgeOfFromVertex = getEdge(edgeId); // loop detection bool loop = false; for(auto& edgeIdInPath : path) { auto edgeInPath = getEdge(edgeIdInPath); - if(edgeInPath->from == edgeId) { + if(edgeInPath->from == edgeOfFromVertex->to) { loop = true; break; } @@ -225,7 +225,7 @@ public: path.push_back(edgeId); // recursive, depth-first search - if(getPath(edge->to, toVertexId, path)) { + if(getPath(edgeOfFromVertex->to, toVertexId, path)) { // path found, we're done return true; } else { From 409340433dd464e69675d10829e0bd022a4b9989 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 16:03:38 +0100 Subject: [PATCH 122/560] enable -Wall, -Wextra and -Werror and fix new errors (fixes #20) --- fpga/CMakeLists.txt | 1 + fpga/include/villas/dependency_graph_impl.hpp | 1 + fpga/include/villas/directed_graph.hpp | 4 ++++ fpga/include/villas/fpga/card.hpp | 6 ++++-- fpga/include/villas/fpga/ip.hpp | 2 +- fpga/lib/CMakeLists.txt | 2 ++ fpga/lib/ips/fifo.cpp | 2 +- fpga/lib/ips/switch.cpp | 2 +- fpga/tests/logging.cpp | 6 ++++-- 9 files changed, 19 insertions(+), 7 deletions(-) diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index 540ccee44..4025ae5b9 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -5,6 +5,7 @@ project(VILLASfpga C CXX) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) set (CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") include_directories(thirdparty/spdlog/include) diff --git a/fpga/include/villas/dependency_graph_impl.hpp b/fpga/include/villas/dependency_graph_impl.hpp index 0b4be0869..b8090c2d0 100644 --- a/fpga/include/villas/dependency_graph_impl.hpp +++ b/fpga/include/villas/dependency_graph_impl.hpp @@ -97,6 +97,7 @@ DependencyGraph::getEvaluationOrder() const if(added == 0 and graph.size() > 0) { logger->error("Circular dependency detected! IPs not available:"); for(auto& [key, value] : graph) { + (void) value; logger->error(" {}", key); } break; diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 8d964dae1..d6d2ee56b 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -242,6 +242,8 @@ public: { logger->info("Vertices:"); for(auto& [vertexId, vertex] : vertices) { + (void) vertexId; + // format connected vertices into a list std::stringstream ssEdges; for(auto& edge : vertex->edges) { @@ -253,6 +255,8 @@ public: logger->info("Edges:"); for(auto& [edgeId, edge] : edges) { + (void) edgeId; + logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); } } diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 729745ab8..20c89876d 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -48,8 +48,10 @@ #define PCI_FILTER_DEFAULT_FPGA { \ .id = { \ .vendor = FPGA_PCI_VID_XILINX, \ - .device = FPGA_PCI_PID_VFPGA \ - } \ + .device = FPGA_PCI_PID_VFPGA, \ + .class_code = 0 \ + }, \ + .slot = { } \ } namespace villas { diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 53ac244ad..b71f5d700 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -161,7 +161,7 @@ private: virtual IpCore* create() = 0; /// Configure IP instance from JSON config - virtual bool configureJson(IpCore& ip, json_t *json) + virtual bool configureJson(IpCore& /* ip */, json_t* /* json */) { return true; } diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 2302a32eb..ae55a332f 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -45,6 +45,8 @@ find_package(Threads) add_library(villas-fpga SHARED ${SOURCES}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + target_compile_definitions(villas-fpga PRIVATE BUILDID=\"abc\" _GNU_SOURCE diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 087d3ad8f..9c465302e 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -67,7 +67,7 @@ bool Fifo::init() fifo_cfg.Axi4BaseAddress = getAddrMapped(this->baseaddr_axi4); // use AXI4 for Data, AXI4-Lite for control - fifo_cfg.Datainterface = (this->baseaddr_axi4 != -1) ? 1 : 0; + fifo_cfg.Datainterface = (this->baseaddr_axi4 != static_cast(-1)) ? 1 : 0; if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseaddr()) != XST_SUCCESS) return false; diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index 9f5a73470..98c6534e2 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -53,7 +53,7 @@ AxiStreamSwitch::init() XAxisScr_RegUpdateEnable(&xSwitch); // initialize internal mapping - for(int portMaster = 0; portMaster < portsMaster.size(); portMaster++) { + for(size_t portMaster = 0; portMaster < portsMaster.size(); portMaster++) { portMapping[portMaster] = PORT_DISABLED; } diff --git a/fpga/tests/logging.cpp b/fpga/tests/logging.cpp index 64be9274f..cbb3f80b7 100644 --- a/fpga/tests/logging.cpp +++ b/fpga/tests/logging.cpp @@ -19,14 +19,16 @@ struct criterion_prefix_data { const char *color; }; -static void format_msg(char *buf, size_t buflen, const char *msg, va_list args) +static int format_msg(char *buf, size_t buflen, const char *msg, va_list args) { int len = vsnprintf(buf, buflen, msg, args); - + /* Strip new line */ char *nl = strchr(buf, '\n'); if (nl) *nl = 0; + + return len; } void criterion_log_noformat(enum criterion_severity severity, const char *msg) From 10745f00b570ee6bfff1f6a442de5c6e95fa2e4d Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 16:14:46 +0100 Subject: [PATCH 123/560] libxil: update submodule Only add temporary files to gitignore --- fpga/thirdparty/libxil | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/thirdparty/libxil b/fpga/thirdparty/libxil index f08dc8e87..577d0cb72 160000 --- a/fpga/thirdparty/libxil +++ b/fpga/thirdparty/libxil @@ -1 +1 @@ -Subproject commit f08dc8e878719ab8937c47fedd20ebf3aa68e123 +Subproject commit 577d0cb724ad7bfd710e95716cae88478befd03b From 7d927155db10c3372243576944494417e85e6758 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 31 Jan 2018 12:21:36 +0100 Subject: [PATCH 124/560] tests: minimal test of memory manager --- fpga/tests/graph.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 2dd8e6c9b..409d26537 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -3,6 +3,13 @@ #include #include #include +#include + +static void init_graph() +{ + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); +} TestSuite(graph, .description = "Graph library" @@ -102,3 +109,15 @@ Test(graph, path, .description = "Find path") logger->info(" -> edge {}", edge); } } + +Test(graph, memory_manager, .description = "Global Memory Manager") +{ + auto& mm = villas::MemoryManager::get(); + + auto dmaRegs = mm.createAddressSpace("DMA Registers"); + auto pcieBridge = mm.createAddressSpace("PCIe Bridge"); + + mm.createMapping(0x1000, 0, 0x1000, dmaRegs, pcieBridge); + + mm.dump(); +} From 44ad8271212febe58a3af2cd2d9bda497e686a38 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 31 Jan 2018 12:22:04 +0100 Subject: [PATCH 125/560] hwdef-parse: treat PCIe bridge the same as all other IPs This is needed in order to construct a global memory graph. --- fpga/scripts/hwdef-parse.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 123ca3f9c..353e91043 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -34,7 +34,8 @@ whitelist = [ [ 'acs.eonerc.rwth-aachen.de', 'hls' ], [ 'acs.eonerc.rwth-aachen.de', 'sysgen' ], [ 'xilinx.com', 'ip', 'axi_gpio' ], - [ 'xilinx.com', 'ip', 'axi_bram_ctrl' ] + [ 'xilinx.com', 'ip', 'axi_bram_ctrl' ], + [ 'xilinx.com', 'ip', 'axi_pcie' ] ] # List of VLNI ids of AXI4-Stream infrastructure IP cores which do not alter data @@ -140,29 +141,19 @@ for module in modules: mem = ips[instance].setdefault('memory-view', {}) for mrange in mmap: - mem_interface = remove_prefix(mrange.get('MASTERBUSINTERFACE'), 'M_AXI_') + mem_interface = mrange.get('MASTERBUSINTERFACE') mem_instance = mrange.get('INSTANCE') + mem_block = mrange.get('ADDRESSBLOCK') - entry = mem.setdefault(mem_interface, {}).setdefault(mem_instance, {}) + _interface = mem.setdefault(mem_interface, {}) + _instance = _interface.setdefault(mem_instance, {}) + _block = _instance.setdefault(mem_block, {}) - entry['baseaddr'] = int(mrange.get('BASEVALUE'), 16); - entry['highaddr'] = int(mrange.get('HIGHVALUE'), 16); + _block['baseaddr'] = int(mrange.get('BASEVALUE'), 16) + _block['highaddr'] = int(mrange.get('HIGHVALUE'), 16) + _block['size'] = _block['highaddr'] - _block['baseaddr'] + 1 - -# find PCI-e module to extract memory map -pcie = root.find('.//MODULE[@MODTYPE="axi_pcie"]') -mmap = pcie.find('.//MEMORYMAP') -for mrange in mmap: - instance = mrange.get('INSTANCE') - - if instance in ips: - base_name = remove_prefix(mrange.get('BASENAME'), 'C_').lower() - high_name = remove_prefix(mrange.get('HIGHNAME'), 'C_').lower() - - ips[instance][base_name] = int(mrange.get('BASEVALUE'), 16); - ips[instance][high_name] = int(mrange.get('HIGHVALUE'), 16); - # find AXI-Stream switch port mapping switch = root.find('.//MODULE[@MODTYPE="axis_switch"]') busifs = switch.find('.//BUSINTERFACES') From be3538f6977f55331a9f600d184790a903b22258 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 31 Jan 2018 12:31:37 +0100 Subject: [PATCH 126/560] hwdef-parse: fix switch/num_port to be an integer --- fpga/scripts/hwdef-parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 353e91043..7f0c57863 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -188,7 +188,7 @@ for busif in busifs: ports[-1]['name'] = sanitize_name(busif_ep.get('NAME')) # set number of master/slave port pairs for switch -ips[switch.get('INSTANCE')]['num_ports'] = switch_ports / 2 +ips[switch.get('INSTANCE')]['num_ports'] = int(switch_ports / 2) # find Interrupt assignments From 912c3729d45fe5b5fc9aa5e82947692d74d5e131 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 6 Feb 2018 11:46:19 +0100 Subject: [PATCH 127/560] lib/ip: improve readability --- fpga/lib/ip.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 4daac1309..fd9bc3dcb 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -94,7 +94,9 @@ buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::st return false; } - if(name == mapping[0]) { + const std::string& dependencyName = mapping[0]; + + if(name == dependencyName) { logger->error("IP {} cannot depend on itself", TXT_BOLD(name)); dependencyGraph.removeNode(name); @@ -103,13 +105,13 @@ buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::st // already add dependency, if adding it fails, removing the dependency // will also remove the current one - dependencyGraph.addDependency(name, mapping[0]); + dependencyGraph.addDependency(name, dependencyName); - if(not buildDependencyGraph(dependencyGraph, json_ips, mapping[0])) { + if(not buildDependencyGraph(dependencyGraph, json_ips, dependencyName)) { logger->error("Dependency {} of {} not satisfied", - mapping[0], TXT_BOLD(name)); + dependencyName, TXT_BOLD(name)); - dependencyGraph.removeNode(mapping[0]); + dependencyGraph.removeNode(dependencyName); return false; } } From 95adaad32fff45779518626a06acf3a32dc910f2 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 10:03:16 +0100 Subject: [PATCH 128/560] etc/json: update config file with current output of hwdef-parse --- fpga/etc/fpga.json | 237 +++++++++++++++++++++++++++++++++------------ 1 file changed, 177 insertions(+), 60 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index 275cfbb73..2abcdc136 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -28,74 +28,114 @@ "ips": { "bram_0_axi_bram_ctrl_0": { "vlnv": "xilinx.com:ip:axi_bram_ctrl:4.0", - "s_axi_baseaddr": 0, - "s_axi_highaddr": 8191, "size": 8192 }, "hier_0_axi_dma_axi_dma_0": { "vlnv": "xilinx.com:ip:axi_dma:7.1", "memory-view": { - "SG": { + "M_AXI_SG": { "bram_0_axi_bram_ctrl_0": { - "baseaddr": 0, - "highaddr": 8191 + "Mem0": { + "baseaddr": 0, + "highaddr": 8191, + "size": 8192 + } }, "hier_0_axi_dma_axi_dma_1": { - "baseaddr": 8192, - "highaddr": 12287 + "Reg": { + "baseaddr": 8192, + "highaddr": 12287, + "size": 4096 + } }, "hier_0_axi_dma_axi_dma_0": { - "baseaddr": 12288, - "highaddr": 16383 + "Reg": { + "baseaddr": 12288, + "highaddr": 16383, + "size": 4096 + } }, "timer_0_axi_timer_0": { - "baseaddr": 16384, - "highaddr": 20479 + "Reg": { + "baseaddr": 16384, + "highaddr": 20479, + "size": 4096 + } }, "hier_0_axis_interconnect_0_axis_interconnect_0_xbar": { - "baseaddr": 20480, - "highaddr": 24575 + "Reg": { + "baseaddr": 20480, + "highaddr": 24575, + "size": 4096 + } }, "hier_0_axi_fifo_mm_s_0": { - "baseaddr": 49152, - "highaddr": 57343 + "Mem0": { + "baseaddr": 24576, + "highaddr": 28671, + "size": 4096 + }, + "Mem1": { + "baseaddr": 49152, + "highaddr": 57343, + "size": 8192 + } }, "pcie_0_axi_reset_0": { - "baseaddr": 28672, - "highaddr": 32767 + "Reg": { + "baseaddr": 28672, + "highaddr": 32767, + "size": 4096 + } }, "hier_0_rtds_axis_0": { - "baseaddr": 32768, - "highaddr": 36863 + "reg0": { + "baseaddr": 32768, + "highaddr": 36863, + "size": 4096 + } }, "hier_0_hls_dft_0": { - "baseaddr": 36864, - "highaddr": 40959 + "Reg": { + "baseaddr": 36864, + "highaddr": 40959, + "size": 4096 + } }, "pcie_0_axi_pcie_intc_0": { - "baseaddr": 45056, - "highaddr": 49151 + "Reg": { + "baseaddr": 45056, + "highaddr": 49151, + "size": 4096 + } }, "pcie_0_axi_pcie_0": { - "baseaddr": 268435456, - "highaddr": 536870911 + "CTL0": { + "baseaddr": 268435456, + "highaddr": 536870911, + "size": 268435456 + } } }, - "MM2S": { + "M_AXI_MM2S": { "pcie_0_axi_pcie_0": { - "baseaddr": 2147483648, - "highaddr": 4294967295 + "BAR0": { + "baseaddr": 2147483648, + "highaddr": 4294967295, + "size": 2147483648 + } } }, - "S2MM": { + "M_AXI_S2MM": { "pcie_0_axi_pcie_0": { - "baseaddr": 2147483648, - "highaddr": 4294967295 + "BAR0": { + "baseaddr": 2147483648, + "highaddr": 4294967295, + "size": 2147483648 + } } } }, - "baseaddr": 12288, - "highaddr": 16383, "ports": [ { "role": "initiator", @@ -112,21 +152,25 @@ "hier_0_axi_dma_axi_dma_1": { "vlnv": "xilinx.com:ip:axi_dma:7.1", "memory-view": { - "MM2S": { + "M_AXI_MM2S": { "pcie_0_axi_pcie_0": { - "baseaddr": 2147483648, - "highaddr": 4294967295 + "BAR0": { + "baseaddr": 2147483648, + "highaddr": 4294967295, + "size": 2147483648 + } } }, - "S2MM": { + "M_AXI_S2MM": { "pcie_0_axi_pcie_0": { - "baseaddr": 2147483648, - "highaddr": 4294967295 + "BAR0": { + "baseaddr": 2147483648, + "highaddr": 4294967295, + "size": 2147483648 + } } } }, - "baseaddr": 8192, - "highaddr": 12287, "ports": [ { "role": "initiator", @@ -142,10 +186,6 @@ }, "hier_0_axi_fifo_mm_s_0": { "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", - "baseaddr": 24576, - "highaddr": 28671, - "axi4_baseaddr": 49152, - "axi4_highaddr": 57343, "ports": [ { "role": "master", @@ -164,8 +204,6 @@ }, "hier_0_axis_interconnect_0_axis_interconnect_0_xbar": { "vlnv": "xilinx.com:ip:axis_switch:1.1", - "baseaddr": 20480, - "highaddr": 24575, "ports": [ { "role": "initiator", @@ -188,12 +226,10 @@ "name": "S04_AXIS" } ], - "num_ports": 14 + "num_ports": 7 }, "hier_0_hls_dft_0": { "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", - "s_axi_ctrl_baseaddr": 36864, - "s_axi_ctrl_highaddr": 40959, "ports": [ { "role": "master", @@ -209,8 +245,6 @@ }, "hier_0_rtds_axis_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", - "baseaddr": 32768, - "highaddr": 36863, "ports": [ { "role": "master", @@ -229,20 +263,103 @@ "irq_case": "pcie_0_axi_pcie_intc_0:7" } }, + "pcie_0_axi_pcie_0": { + "vlnv": "xilinx.com:ip:axi_pcie:2.8", + "memory-view": { + "M_AXI": { + "bram_0_axi_bram_ctrl_0": { + "Mem0": { + "baseaddr": 0, + "highaddr": 8191, + "size": 8192 + } + }, + "hier_0_axi_dma_axi_dma_1": { + "Reg": { + "baseaddr": 8192, + "highaddr": 12287, + "size": 4096 + } + }, + "hier_0_axi_dma_axi_dma_0": { + "Reg": { + "baseaddr": 12288, + "highaddr": 16383, + "size": 4096 + } + }, + "timer_0_axi_timer_0": { + "Reg": { + "baseaddr": 16384, + "highaddr": 20479, + "size": 4096 + } + }, + "hier_0_axis_interconnect_0_axis_interconnect_0_xbar": { + "Reg": { + "baseaddr": 20480, + "highaddr": 24575, + "size": 4096 + } + }, + "hier_0_axi_fifo_mm_s_0": { + "Mem0": { + "baseaddr": 24576, + "highaddr": 28671, + "size": 4096 + }, + "Mem1": { + "baseaddr": 49152, + "highaddr": 57343, + "size": 8192 + } + }, + "pcie_0_axi_reset_0": { + "Reg": { + "baseaddr": 28672, + "highaddr": 32767, + "size": 4096 + } + }, + "hier_0_rtds_axis_0": { + "reg0": { + "baseaddr": 32768, + "highaddr": 36863, + "size": 4096 + } + }, + "hier_0_hls_dft_0": { + "Reg": { + "baseaddr": 36864, + "highaddr": 40959, + "size": 4096 + } + }, + "pcie_0_axi_pcie_intc_0": { + "Reg": { + "baseaddr": 45056, + "highaddr": 49151, + "size": 4096 + } + }, + "pcie_0_axi_pcie_0": { + "CTL0": { + "baseaddr": 268435456, + "highaddr": 536870911, + "size": 268435456 + } + } + } + } + }, "pcie_0_axi_pcie_intc_0": { - "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0", - "baseaddr": 45056, - "highaddr": 49151 + "vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0" }, "pcie_0_axi_reset_0": { - "vlnv": "xilinx.com:ip:axi_gpio:2.0", - "baseaddr": 28672, - "highaddr": 32767 + "vlnv": "xilinx.com:ip:axi_gpio:2.0" }, "timer_0_axi_timer_0": { "vlnv": "xilinx.com:ip:axi_timer:2.0", - "baseaddr": 16384, - "highaddr": 20479, "irqs": { "generateout0": "pcie_0_axi_pcie_intc_0:0" } From 02e873e8ffdc02f1034133810ef11f8c3be659c8 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 10:16:02 +0100 Subject: [PATCH 129/560] lib/ip: remove dependency graph Replace by static list of VLNVs that will be initialized first. --- fpga/include/villas/dependency_graph.hpp | 55 ----- fpga/include/villas/dependency_graph_impl.hpp | 111 ---------- fpga/lib/ip.cpp | 200 ++++++++---------- 3 files changed, 88 insertions(+), 278 deletions(-) delete mode 100644 fpga/include/villas/dependency_graph.hpp delete mode 100644 fpga/include/villas/dependency_graph_impl.hpp diff --git a/fpga/include/villas/dependency_graph.hpp b/fpga/include/villas/dependency_graph.hpp deleted file mode 100644 index f488e8ad7..000000000 --- a/fpga/include/villas/dependency_graph.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef VILLAS_DEPENDENCY_GRAPH_HPP -#define VILLAS_DEPENDENCY_GRAPH_HPP - -#include -#include -#include - -#include "log.hpp" - -namespace villas { -namespace utils { - - -template -class DependencyGraph { -public: - using NodeList = std::list; - - /// Create a node without dependencies if it not yet exists, return if a new - /// node has been created. - bool addNode(const T& node); - - /// Remove a node and all other nodes that depend on it - void removeNode(const T& node); - - /// Add a dependency to a node. Will create the node if it not yet exists - void addDependency(const T& node, const T& dependency); - - void dump(); - - /// Return a sequential evaluation order list. If a circular dependency has been - /// detected, all nodes involved will not be part of that list. - NodeList getEvaluationOrder() const; - -private: - /// Return whether a node already exists or not - bool nodeExists(const T& node) - { return graph.find(node) != graph.end(); } - - static bool - nodeInList(const NodeList& list, const T& node) - { return list.end() != std::find(list.begin(), list.end(), node); } - -private: - using Graph = std::map; - - Graph graph; -}; - -} // namespace utils -} // namespace villas - -#include "dependency_graph_impl.hpp" - -#endif // VILLAS_DEPENDENCY_GRAPH_HPP diff --git a/fpga/include/villas/dependency_graph_impl.hpp b/fpga/include/villas/dependency_graph_impl.hpp deleted file mode 100644 index b8090c2d0..000000000 --- a/fpga/include/villas/dependency_graph_impl.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef VILLAS_DEPENDENCY_GRAPH_HPP -#error "Do not include this file directly, please include depedency_graph.hpp" -#endif - -#include -#include "dependency_graph.hpp" - -#include "log.hpp" - -static auto logger = loggerGetOrCreate("DependencyGraph"); - -namespace villas { -namespace utils { - -template -bool -DependencyGraph::addNode(const T &node) -{ - bool existedBefore = nodeExists(node); - - // accessing is enough to create if not exists - graph[node]; - - return existedBefore; -} - -template -void -DependencyGraph::removeNode(const T &node) -{ - graph.erase(node); - - // check if other nodes depend on this one - for(auto& [key, dependencies] : graph) { - if(nodeInList(dependencies, node)) { - // remove other node that depends on the one to delete - removeNode(key); - } - } - -} - -template -void -DependencyGraph::addDependency(const T &node, const T &dependency) -{ - NodeList& dependencies = graph[node]; - if(not nodeInList(dependencies, dependency)) - dependencies.push_back(dependency); -} - -template -void -DependencyGraph::dump() { - for(auto& node : graph) { - std::stringstream ss; - for(auto& dep : node.second) { - ss << dep << " "; - } - logger->info("{}: {}", node.first, ss.str()); - } -} - -template -typename DependencyGraph::NodeList -DependencyGraph::getEvaluationOrder() const -{ - // copy graph to preserve information (we have to delete entries later) - Graph graph = this->graph; - - // output list - NodeList out; - - while(graph.size() > 0) { - int added = 0; - - // look for nodes with no dependencies - for(auto& [key, dependencies] : graph) { - - for(auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) { - if(nodeInList(out, *dep)) { - // dependency has been pushed to list in last round - dep = dependencies.erase(dep); - } - } - - // nodes with no dependencies can be pushed to list - if(dependencies.empty()) { - out.push_back(key); - graph.erase(key); - added++; - } - } - - // if a round doesn't add any elements and is not the last, then - // there is a circular dependency - if(added == 0 and graph.size() > 0) { - logger->error("Circular dependency detected! IPs not available:"); - for(auto& [key, value] : graph) { - (void) value; - logger->error(" {}", key); - } - break; - } - } - - return out; -} - -} // namespace utils -} // namespace villas diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index fd9bc3dcb..a6d35bd7c 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -23,9 +23,9 @@ #include "log_config.h" #include "log.hpp" #include "plugin.h" -#include "dependency_graph.hpp" #include "utils.hpp" +#include "fpga/vlnv.hpp" #include "fpga/ip.hpp" #include "fpga/card.hpp" @@ -38,94 +38,28 @@ #include "log.hpp" -using DependencyGraph = villas::utils::DependencyGraph; +#include "fpga/ips/pcie.hpp" +#include "fpga/ips/intc.hpp" +#include "fpga/ips/switch.hpp" -static -std::list -dependencyTokens = {"irqs"}; - -static -bool -buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::string name) -{ - const bool nodeExists = dependencyGraph.addNode(name); - - // HACK: just get the right logger - auto logger = loggerGetOrCreate("IpCoreFactory"); - - // do not add IP multiple times - // this happens if more than 1 IP depends on a certain other IP - if(nodeExists) { - return true; - } - - json_t* json_ip = json_object_get(json_ips, name.c_str()); - if(json_ip == nullptr) { - logger->error("IP {} not found in config", name); - return false; - } - - for(auto& dependencyToken : dependencyTokens) { - json_t* json_dependency = json_object_get(json_ip, dependencyToken.c_str()); - if(json_dependency == nullptr) { - logger->debug("Property {} of {} is not present", - dependencyToken, TXT_BOLD(name)); - continue; - } - - const char* irq_name; - json_t* json_irq; - json_object_foreach(json_dependency, irq_name, json_irq) { - const char* value = json_string_value(json_irq); - if(value == nullptr) { - logger->warn("Property {} of {} is invalid", - dependencyToken, TXT_BOLD(name)); - continue; - } - - auto mapping = villas::utils::tokenize(value, ":"); - - - if(mapping.size() != 2) { - logger->error("Invalid {} mapping of {}", - dependencyToken, TXT_BOLD(name)); - - dependencyGraph.removeNode(name); - return false; - } - - const std::string& dependencyName = mapping[0]; - - if(name == dependencyName) { - logger->error("IP {} cannot depend on itself", TXT_BOLD(name)); - - dependencyGraph.removeNode(name); - return false; - } - - // already add dependency, if adding it fails, removing the dependency - // will also remove the current one - dependencyGraph.addDependency(name, dependencyName); - - if(not buildDependencyGraph(dependencyGraph, json_ips, dependencyName)) { - logger->error("Dependency {} of {} not satisfied", - dependencyName, TXT_BOLD(name)); - - dependencyGraph.removeNode(dependencyName); - return false; - } - } - } - - return true; -} namespace villas { namespace fpga { namespace ip { -void IpCore::dump() { +// Special IPs that have to be initialized first. Will be initialized in the +// same order as they appear in this list, i.e. first here will be initialized +// first. +static std::list +vlnvInitializationOrder = { + Vlnv(AxiPciExpressBridgeFactory::getCompatibleVlnvString()), + Vlnv(InterruptControllerFactory::getCompatibleVlnvString()), + Vlnv(AxiStreamSwitchFactory::getCompatibleVlnvString()), +}; + +void +IpCore::dump() { auto logger = getLogger(); logger->info("Base address = {:08x}", baseaddr); @@ -158,52 +92,68 @@ IpCore::getAddrMapped(uintptr_t address) const IpCoreList IpCoreFactory::make(PCIeCard* card, json_t *json_ips) { - DependencyGraph dependencyGraph; - IpCoreList initializedIps; + IpCoreList configuredIps; auto loggerStatic = getStaticLogger(); + std::list allIps; // all IPs available + std::list orderedIps; // IPs ordered in initialization order - loggerStatic->debug("Parsing IP dependency graph:"); - void* iter = json_object_iter(json_ips); - while(iter != nullptr) { - buildDependencyGraph(dependencyGraph, json_ips, json_object_iter_key(iter)); - iter = json_object_iter_next(json_ips, iter); - } - - - loggerStatic->debug("IP initialization order:"); - for(auto& ipName : dependencyGraph.getEvaluationOrder()) { - loggerStatic->debug(" {}", TXT_BOLD(ipName)); - } - - - for(auto& ipName : dependencyGraph.getEvaluationOrder()) { - loggerStatic->info("Initializing {}", TXT_BOLD(ipName)); - - json_t* json_ip = json_object_get(json_ips, ipName.c_str()); - - // extract VLNV from JSON + // parse all IP instance names and their VLNV into list `allIps` + const char* ipName; + json_t* json_ip; + json_object_foreach(json_ips, ipName, json_ip) { const char* vlnv; if(json_unpack(json_ip, "{ s: s }", "vlnv", &vlnv) != 0) { - loggerStatic->warn("IP {} has no entry 'vlnv'", ipName); + loggerStatic->warn("IP {} has no VLNV", ipName); continue; } - IpIdentifier id(Vlnv(vlnv), ipName); + allIps.push_back({vlnv, ipName}); + } + + + // Pick out IPs to be initialized first. + // + // Reverse order of the initialization order list, because we push to the + // front of the output list, so that the first element will also be the + // first to be initialized. + vlnvInitializationOrder.reverse(); + + for(auto& vlnvInitFirst : vlnvInitializationOrder) { + // iterate over IPs, if VLNV matches, push to front and remove from list + for(auto it = allIps.begin(); it != allIps.end(); ++it) { + if(vlnvInitFirst == it->getVlnv()) { + orderedIps.push_front(*it); + it = allIps.erase(it); + } + } + } + + // insert all other IPs at the end + orderedIps.splice(orderedIps.end(), allIps); + + + loggerStatic->debug("IP initialization order:"); + for(auto& id : orderedIps) { + loggerStatic->debug(" {}", TXT_BOLD(id.getName())); + } + + for(auto& id : orderedIps) { + loggerStatic->info("Initializing {}", id); // find the appropriate factory that can create the specified VLNV // Note: // This is the magic part! Factories automatically register as a // plugin as soon as they are instantiated. If there are multiple // candidates, the first suitable factory will be used. - IpCoreFactory* ipCoreFactory = lookup(id.vlnv); + IpCoreFactory* ipCoreFactory = lookup(id.getVlnv()); if(ipCoreFactory == nullptr) { - loggerStatic->warn("No plugin found to handle {}", vlnv); + loggerStatic->warn("No plugin found to handle {}", id.getVlnv()); continue; } else { loggerStatic->debug("Using {} for IP {}", - ipCoreFactory->getName(), vlnv); + ipCoreFactory->getName(), id.getVlnv()); } auto logger = ipCoreFactory->getLogger(); @@ -293,14 +243,40 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) continue; } - // TODO: currently fails, fix and remove comment + configuredIps.push_back(std::move(ip)); + } + + IpCoreList initializedIps; + for(auto& ip : configuredIps) { + + // Translate all memory blocks that the IP needs to be accessible from + // the process and cache in the instance, so this has not to be done at + // runtime. + for(auto& memoryBlock : ip->getMemoryBlocks()) { + // construct the global name of this address block + const auto addrSpaceName = + MemoryManager::getSlaveAddrSpaceName(ip->id.getName(), memoryBlock); + + // retrieve its address space identifier + const auto addrSpace = + MemoryManager::get().findAddressSpace(addrSpaceName); + + // get the translation to the address space + const auto& translation = + MemoryManager::get().getTranslationFromProcess(addrSpace); + + // cache it in the IP instance only with local name + ip->addressTranslations.emplace(memoryBlock, translation); + } + + if(not ip->init()) { - logger->error("Cannot start IP {}", ip->id.name); + loggerStatic->error("Cannot start IP {}", ip->id.getName()); continue; } if(not ip->check()) { - logger->error("Checking of IP {} failed", ip->id.name); + loggerStatic->error("Checking of IP {} failed", ip->id.getName()); continue; } From 33ba634d8779253f5cb330f17234172981b07835 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 10:17:33 +0100 Subject: [PATCH 130/560] lib/directed-graph: add findVertex() and minor refactoring --- fpga/include/villas/directed_graph.hpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index a90ef29bb..6335d8087 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -71,6 +71,7 @@ public: using VertexIdentifier = Vertex::Identifier; using EdgeIdentifier = Edge::Identifier; + using Path = std::list; DirectedGraph(const std::string& name = "DirectedGraph") : lastVertexId(0), lastEdgeId(0) @@ -88,6 +89,18 @@ public: return vertices.at(vertexId); } + template + VertexIdentifier findVertex(UnaryPredicate p) + { + for(auto& [vertexId, vertex] : vertices) { + if(p(vertex)) { + return vertexId; + } + } + + throw std::out_of_range("vertex not found"); + } + std::shared_ptr getEdge(EdgeIdentifier edgeId) const { if(edgeId < 0 or edgeId >= lastEdgeId) @@ -194,8 +207,9 @@ public: vertexGetEdges(VertexIdentifier vertexId) const { return getVertex(vertexId)->edges; } - bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId, - std::list& path) + bool getPath(VertexIdentifier fromVertexId, + VertexIdentifier toVertexId, + Path& path) { if(fromVertexId == toVertexId) { // arrived at the destination From 035c6f8b2a7798a0ddcf15f597028b876a38f036 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 10:29:16 +0100 Subject: [PATCH 131/560] lib/kernel/vfio: add function to get size of device memory region --- fpga/include/villas/kernel/vfio.h | 3 +++ fpga/lib/kernel/vfio.c | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/fpga/include/villas/kernel/vfio.h b/fpga/include/villas/kernel/vfio.h index 1b4a9c336..35a2465f9 100644 --- a/fpga/include/villas/kernel/vfio.h +++ b/fpga/include/villas/kernel/vfio.h @@ -104,6 +104,9 @@ void vfio_dump(struct vfio_container *c); /** Map a device memory region to the application address space (e.g. PCI BARs) */ void * vfio_map_region(struct vfio_device *d, int idx); +/** Get the size of a device memory region */ +size_t vfio_region_size(struct vfio_device *d, int idx); + /** Map VM to an IOVA, which is accessible by devices in the container */ int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); diff --git a/fpga/lib/kernel/vfio.c b/fpga/lib/kernel/vfio.c index 0c0de58d4..7103cc519 100644 --- a/fpga/lib/kernel/vfio.c +++ b/fpga/lib/kernel/vfio.c @@ -574,6 +574,14 @@ void * vfio_map_region(struct vfio_device *d, int idx) return d->mappings[idx]; } +size_t vfio_region_size(struct vfio_device *d, int idx) +{ + assert(d != NULL); + assert((size_t)idx < d->info.num_regions); + + return d->regions[idx].size; +} + int vfio_unmap_region(struct vfio_device *d, int idx) { int ret; From 95e29f27060276d06d37ec9fb96333bf37a3414d Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 12:22:50 +0100 Subject: [PATCH 132/560] memory-manager: allow for traversing address spaces Major rework of the memory manager. Adds a memory translation class to resolve addresses across address spaces and extents the memory manager in order to do so. --- fpga/include/villas/memory_manager.hpp | 199 +++++++++++++++++++------ fpga/lib/memory_manager.cpp | 126 +++++++++++++++- fpga/tests/graph.cpp | 15 +- 3 files changed, 286 insertions(+), 54 deletions(-) diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 935f262b3..941fd43c0 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -2,96 +2,207 @@ #include #include +#include #include "log.hpp" #include "directed_graph.hpp" namespace villas { - -class Mapping : public graph::Edge { - friend class MemoryManager; - +/** + * @brief Translation between a local (master) to a foreign (slave) address space + * + * Memory translations can be chained together using the `+=` operator which is + * used internally by the MemoryManager to compute a translation through + * multiple hops (memory mappings). + */ +class MemoryTranslation { public: - // create mapping here (if needed) - Mapping() {} - // destroy mapping here (if needed) - virtual ~Mapping(); + /** + * @brief MemoryTranslation + * @param src Base address of local address space + * @param dst Base address of foreign address space + * @param size Size of "memory window" + */ + MemoryTranslation(uintptr_t src, uintptr_t dst, size_t size) : + src(src), dst(dst), size(size) {} + + uintptr_t + getLocalAddr(uintptr_t addrInForeignAddrSpace) const; + + uintptr_t + getForeignAddr(uintptr_t addrInLocalAddrSpace) const; friend std::ostream& - operator<< (std::ostream& stream, const Mapping& mapping) + operator<< (std::ostream& stream, const MemoryTranslation& translation) { - return stream << static_cast(mapping) << " = " - << std::hex - << "(src=0x" << mapping.src - << ", dest=0x" << mapping.dest - << ", size=0x" << mapping.size + return stream << std::hex + << "(src=0x" << translation.src + << ", dst=0x" << translation.dst + << ", size=0x" << translation.size << ")"; } -private: - uintptr_t src; - uintptr_t dest; - size_t size; -}; - -class AddressSpace : public graph::Vertex { - friend class MemoryManager; - -public: - friend std::ostream& - operator<< (std::ostream& stream, const AddressSpace& addrSpace) - { - return stream << static_cast(addrSpace) << " = " - << addrSpace.name; - } + /// Merge two MemoryTranslations together + MemoryTranslation& operator+=(const MemoryTranslation& other); private: - std::string name; + uintptr_t src; ///< Base address of local address space + uintptr_t dst; ///< Base address of foreign address space + size_t size; ///< Size of "memory window" }; -// is or has a graph +/** + * @brief Global memory manager to resolve addresses across address spaces + * + * Every entity in the system has to register its (master) address space and + * create mappings to other (slave) address spaces that it can access. A + * directed graph is then constructed which allows to traverse addresses spaces + * through multiple mappings and resolve addresses through this "tunnel" of + * memory mappings. + */ class MemoryManager { private: - // This is a singleton, so private constructor + // This is a singleton, so private constructor ... MemoryManager() : - memoryGraph("MemoryGraph") {} + memoryGraph("MemoryGraph"), + logger(loggerGetOrCreate("MemoryManager")) {} - // no copying or assigning + // ... and no copying or assigning MemoryManager(const MemoryManager&) = delete; MemoryManager& operator=(const MemoryManager&) = delete ; + /** + * @brief Custom edge in memory graph representing a memory mapping + * + * A memory mapping maps from one address space into another and can only be + * traversed in the forward direction which reflects the nature of real + * memory mappings. + * + * Implementation Notes: + * The member #src is the address in the "from" address space, where the + * destination address space is mapped. The member #dest is the address in + * the destination address space, where the mapping points to. Often, #dest + * will be zero for mappings to hardware, but consider the example when + * mapping FPGA to application memory: + * The application allocates a block 1kB at address + * 0x843001000 in its address space. The mapping would then have a #dest + * address of 0x843001000 and a #size of 1024. + */ + class Mapping : public graph::Edge { + public: + std::string name; ///< Human-readable name + uintptr_t src; ///< Base address in "from" address space + uintptr_t dest; ///< Base address in "to" address space + size_t size; ///< Size of the mapping + + friend std::ostream& + operator<< (std::ostream& stream, const Mapping& mapping) + { + return stream << static_cast(mapping) << " = " + << mapping.name + << std::hex + << "(src=0x" << mapping.src + << ", dest=0x" << mapping.dest + << ", size=0x" << mapping.size + << ")"; + } + + }; + + + /** + * @brief Custom vertex in memory graph representing an address space + * + * Since most information in the memory graph is stored in the edges (memory + * mappings), this is just a small extension to the default vertex. It only + * associates an additional string #name for human-readability. + */ + class AddressSpace : public graph::Vertex { + public: + std::string name; ///< Human-readable name + + friend std::ostream& + operator<< (std::ostream& stream, const AddressSpace& addrSpace) + { + return stream << static_cast(addrSpace) << " = " + << addrSpace.name; + } + }; + + /// Memory graph with custom edges and vertices for address resolution using MemoryGraph = graph::DirectedGraph; public: using AddressSpaceId = MemoryGraph::VertexIdentifier; using MappingId = MemoryGraph::EdgeIdentifier; - static MemoryManager& get(); + /// Get singleton instance + static MemoryManager& + get(); + AddressSpaceId + getProcessAddressSpace() + { return getOrCreateAddressSpace("villas-fpga"); } - AddressSpaceId createAddressSpace(std::string name); + AddressSpaceId + getOrCreateAddressSpace(std::string name); /// Create a default mapping - MappingId createMapping(uintptr_t src, uintptr_t dest, size_t size, - AddressSpaceId fromAddrSpace, - AddressSpaceId toAddrSpace); + MappingId + createMapping(uintptr_t src, uintptr_t dest, size_t size, std::string name, + AddressSpaceId fromAddrSpace, + AddressSpaceId toAddrSpace); /// Add a mapping /// /// Can be used to derive from Mapping in order to implement custom /// constructor/destructor. - MappingId addMapping(std::shared_ptr mapping, - AddressSpaceId fromAddrSpace, - AddressSpaceId toAddrSpace); + MappingId + addMapping(std::shared_ptr mapping, + AddressSpaceId fromAddrSpace, + AddressSpaceId toAddrSpace); - void dump() + + AddressSpaceId + findAddressSpace(std::string name); + + MemoryTranslation + getTranslation(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId); + + MemoryTranslation + getTranslationFromProcess(AddressSpaceId foreignAddrSpaceId) + { return getTranslation(getProcessAddressSpace(), foreignAddrSpaceId); } + + static std::string + getSlaveAddrSpaceName(std::string ipInstance, std::string memoryBlock) + { return ipInstance + "/" + memoryBlock; } + + void + dump() { memoryGraph.dump(); } + private: + /// Convert a Mapping to MemoryTranslation for calculations + static MemoryTranslation + getTranslationFromMapping(const Mapping& mapping) + { return MemoryTranslation(mapping.src, mapping.dest, mapping.size); } + + +private: + /// Directed graph that stores address spaces and memory mappings MemoryGraph memoryGraph; + + /// Cache mapping of names to address space ids for fast lookup + std::map addrSpaceLookup; + + /// Logger for universal access in this class + SpdLogger logger; + + /// Static pointer to global instance, because this is a singleton static MemoryManager* instance; }; diff --git a/fpga/lib/memory_manager.cpp b/fpga/lib/memory_manager.cpp index 59269fe04..55f7858b1 100644 --- a/fpga/lib/memory_manager.cpp +++ b/fpga/lib/memory_manager.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "memory_manager.hpp" @@ -18,20 +20,32 @@ MemoryManager::get() } MemoryManager::AddressSpaceId -MemoryManager::createAddressSpace(std::string name) +MemoryManager::getOrCreateAddressSpace(std::string name) { - std::shared_ptr addrSpace(new AddressSpace); - addrSpace->name = name; + try { + // try fast lookup + return addrSpaceLookup.at(name); + } catch (const std::out_of_range&) { + // does not yet exist, create + std::shared_ptr addrSpace(new AddressSpace); + addrSpace->name = name; - return memoryGraph.addVertex(addrSpace); + // cache it for the next access + addrSpaceLookup[name] = memoryGraph.addVertex(addrSpace); + + return addrSpaceLookup[name]; + } } MemoryManager::MappingId MemoryManager::createMapping(uintptr_t src, uintptr_t dest, size_t size, + std::string name, MemoryManager::AddressSpaceId fromAddrSpace, MemoryManager::AddressSpaceId toAddrSpace) { std::shared_ptr mapping(new Mapping); + + mapping->name = name; mapping->src = src; mapping->dest = dest; mapping->size = size; @@ -47,11 +61,109 @@ MemoryManager::addMapping(std::shared_ptr mapping, return memoryGraph.addEdge(mapping, fromAddrSpace, toAddrSpace); } - -Mapping::~Mapping() +MemoryManager::AddressSpaceId +MemoryManager::findAddressSpace(std::string name) { - + return memoryGraph.findVertex( + [&](const std::shared_ptr& v) { + return v->name == name; + }); } +MemoryTranslation +MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId, + MemoryManager::AddressSpaceId toAddrSpaceId) +{ + // find a path through the memory graph + MemoryGraph::Path path; + if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, path)) { + auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId); + auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId); + + logger->error("No translation found from ({}) to ({})", + *fromAddrSpace, *toAddrSpace); + + throw std::out_of_range("no translation found"); + } + + // start with an identity mapping + MemoryTranslation translation(0, 0, SIZE_MAX); + + // iterate through path and merge all mappings into a single translation + for(auto& mappingId : path) { + auto mapping = memoryGraph.getEdge(mappingId); + translation += getTranslationFromMapping(*mapping); + } + + return translation; +} + +uintptr_t +MemoryTranslation::getLocalAddr(uintptr_t addrInForeignAddrSpace) const +{ + assert(addrInForeignAddrSpace >= dst); + assert(addrInForeignAddrSpace < (dst + size)); + return src + addrInForeignAddrSpace - dst; +} + +uintptr_t +MemoryTranslation::getForeignAddr(uintptr_t addrInLocalAddrSpace) const +{ + assert(addrInLocalAddrSpace >= src); + assert(addrInLocalAddrSpace < (src + size)); + return dst + addrInLocalAddrSpace - src; +} + +MemoryTranslation& +MemoryTranslation::operator+=(const MemoryTranslation& other) +{ + auto logger = loggerGetOrCreate("MemoryTranslation"); + // set level to debug to enable debug output + logger->set_level(spdlog::level::info); + + const uintptr_t this_dst_high = this->dst + this->size; + const uintptr_t other_src_high = other.src + other.size; + + // make sure there is a common memory area + assert(other.src < this_dst_high); + assert(this->dst < other_src_high); + + const uintptr_t hi = std::max(this_dst_high, other_src_high); + const uintptr_t lo = std::min(this->dst, other.src); + + const uintptr_t diff_hi = (this_dst_high > other_src_high) + ? (this_dst_high - other_src_high) + : (other_src_high - this_dst_high); + + const uintptr_t diff_lo = (this->dst > other.src) + ? (this->dst - other.src) + : (other.src - this->dst); + + const size_t size = (hi - lo) - diff_hi - diff_lo; + + logger->debug("this->src: 0x{:x}", this->src); + logger->debug("this->dst: 0x{:x}", this->dst); + logger->debug("this->size: 0x{:x}", this->size); + logger->debug("other.src: 0x{:x}", other.src); + logger->debug("other.dst: 0x{:x}", other.dst); + logger->debug("other.size: 0x{:x}", other.size); + logger->debug("this_dst_high: 0x{:x}", this_dst_high); + logger->debug("other_src_high: 0x{:x}", other_src_high); + logger->debug("hi: 0x{:x}", hi); + logger->debug("lo: 0x{:x}", lo); + logger->debug("diff_hi: 0x{:x}", diff_hi); + logger->debug("diff_hi: 0x{:x}", diff_lo); + logger->debug("size: 0x{:x}", size); + + this->src += other.src; + this->dst += other.dst; + this->size = size; + + logger->debug("result src: 0x{:x}", this->src); + logger->debug("result dst: 0x{:x}", this->dst); + logger->debug("result size: 0x{:x}", this->size); + + return *this; +} } // namespace villas diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 409d26537..4be542a5c 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -112,12 +112,21 @@ Test(graph, path, .description = "Find path") Test(graph, memory_manager, .description = "Global Memory Manager") { + auto logger = loggerGetOrCreate("unittest:mm"); auto& mm = villas::MemoryManager::get(); - auto dmaRegs = mm.createAddressSpace("DMA Registers"); - auto pcieBridge = mm.createAddressSpace("PCIe Bridge"); + logger->info("Create address spaces"); + auto dmaRegs = mm.getOrCreateAddressSpace("DMA Registers"); + auto pcieBridge = mm.getOrCreateAddressSpace("PCIe Bridge"); - mm.createMapping(0x1000, 0, 0x1000, dmaRegs, pcieBridge); + logger->info("Create a mapping"); + mm.createMapping(0x1000, 0, 0x1000, "Testmapping", dmaRegs, pcieBridge); + + logger->info("Find address space by name"); + auto vertex = mm.findAddressSpace("PCIe Bridge"); + logger->info(" found: {}", vertex); mm.dump(); + + logger->info(TXT_GREEN("Passed")); } From c3382b9e18467128264eccc440c482d57123c657 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 13:45:01 +0100 Subject: [PATCH 133/560] lib/card: pass string as const reference to lookupIp() --- fpga/include/villas/fpga/card.hpp | 2 +- fpga/lib/card.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 20c89876d..4d838559c 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -75,7 +75,7 @@ public: bool reset() { return true; } void dump() { } - ip::IpCore* lookupIp(std::string name) const; + ip::IpCore* lookupIp(const std::string& name) const; ip::IpCoreList ips; ///< IPs located on this FPGA card diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index da0e2d830..b15328566 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -132,7 +132,8 @@ fpga::PCIeCardFactory::create() ip::IpCore* -PCIeCard::lookupIp(std::string name) const { +PCIeCard::lookupIp(const std::string& name) const +{ for(auto& ip : ips) { if(*ip == name) { return ip.get(); From 5b0013b33522767e4767a9c2edb2bc8dfd471db8 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 13:45:38 +0100 Subject: [PATCH 134/560] lib/card: add IP lookup by VLNV --- fpga/include/villas/fpga/card.hpp | 1 + fpga/lib/card.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 4d838559c..4b64a33ec 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -76,6 +76,7 @@ public: void dump() { } ip::IpCore* lookupIp(const std::string& name) const; + ip::IpCore* lookupIp(const Vlnv& vlnv) const; ip::IpCoreList ips; ///< IPs located on this FPGA card diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index b15328566..5b38d210c 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -142,6 +142,17 @@ PCIeCard::lookupIp(const std::string& name) const return nullptr; } +ip::IpCore* +PCIeCard::lookupIp(const Vlnv& vlnv) const +{ + for(auto& ip : ips) { + if(*ip == vlnv) { + return ip.get(); + } + } + return nullptr; +} + bool fpga::PCIeCard::init() { From ef5f6fa3a8f05a3d081590f9e16f182f385dbc61 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 14:06:17 +0100 Subject: [PATCH 135/560] lib/card: use memory manager to store vfio mapping --- fpga/include/villas/fpga/card.hpp | 6 +++- fpga/lib/card.cpp | 50 ++++++++++++++++++------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 4b64a33ec..4676e0b58 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -45,6 +45,8 @@ #include "config.h" +#include "memory_manager.hpp" + #define PCI_FILTER_DEFAULT_FPGA { \ .id = { \ .vendor = FPGA_PCI_VID_XILINX, \ @@ -92,7 +94,9 @@ public: ::vfio_container *vfio_container; struct vfio_device vfio_device; /**< VFIO device handle. */ - char *map; /**< PCI BAR0 mapping for register access */ + /// Address space identifier of the master address space of this FPGA card. + /// This will be used for address resolution of all IPs on this card. + MemoryManager::AddressSpaceId addrSpaceId; size_t maplen; size_t dmalen; diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 5b38d210c..c4a184fc9 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -20,31 +20,18 @@ * along with this program. If not, see . *********************************************************************************/ -#include -#include +#include +#include +#include -#include "config.h" -#include "log.h" -#include "log_config.h" -#include "list.h" -#include "utils.h" +#include "log.hpp" #include "kernel/pci.h" #include "kernel/vfio.h" -#include -#include -#include -#include -#include -#include -#include - #include "fpga/ip.hpp" #include "fpga/card.hpp" -#include "log.hpp" - namespace villas { namespace fpga { @@ -101,7 +88,6 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) } - // TODO: currently fails, fix and remove comment if(not card->init()) { logger->warn("Cannot start FPGA card {}", card_name); continue; @@ -154,13 +140,16 @@ PCIeCard::lookupIp(const Vlnv& vlnv) const } -bool fpga::PCIeCard::init() +bool +fpga::PCIeCard::init() { int ret; struct pci_device *pdev; auto logger = getLogger(); + logger->info("Initializing FPGA card {}", name); + /* Search for FPGA card */ pdev = pci_lookup_device(pci, &filter); if (!pdev) { @@ -176,12 +165,31 @@ bool fpga::PCIeCard::init() } /* Map PCIe BAR */ - map = (char*) vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX); - if (map == MAP_FAILED) { + const void* bar0_mapped = vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX); + if (bar0_mapped == MAP_FAILED) { logger->error("Failed to mmap() BAR0"); return false; } + + /* Link mapped BAR0 to global memory graph */ + + // get the address space of the current application + auto villasAddrSpace = MemoryManager::get().getProcessAddressSpace(); + + // create a new address space for this FPGA card + this->addrSpaceId = MemoryManager::get().getOrCreateAddressSpace(name); + + // determine size of BAR0 region + const size_t bar0_size = vfio_region_size(&vfio_device, + VFIO_PCI_BAR0_REGION_INDEX); + + // create a mapping from our address space to the FPGA card via vfio + MemoryManager::get().createMapping(reinterpret_cast(bar0_mapped), + 0, bar0_size, "VFIO_map", + villasAddrSpace, this->addrSpaceId); + + /* Enable memory access and PCI bus mastering for DMA */ ret = vfio_pci_enable(&vfio_device); if (ret) { From acf273e4066b0751685553b6a08154b751b9839a Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 14:12:28 +0100 Subject: [PATCH 136/560] tests: let them fail if no Fifo or Timer is found --- fpga/tests/fifo.cpp | 11 ++++++++--- fpga/tests/graph.cpp | 4 ++++ fpga/tests/timer.cpp | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index 6b01c6758..e3f7a250b 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -34,6 +34,7 @@ Test(fpga, fifo, .description = "FIFO") { ssize_t len; char src[255], dst[255]; + size_t count = 0; auto logger = loggerGetOrCreate("unittest:fifo"); @@ -46,12 +47,14 @@ Test(fpga, fifo, .description = "FIFO") auto fifo = reinterpret_cast(*ip); - if(not fifo.loopbackPossible()) { - logger->info("Loopback test not possible for {}", *ip); + if(not fifo.connectLoopback()) { continue; } - if(not fifo.connectLoopback()) { + count++; + + if(not fifo.loopbackPossible()) { + logger->info("Loopback test not possible for {}", *ip); continue; } @@ -80,4 +83,6 @@ Test(fpga, fifo, .description = "FIFO") logger->info(TXT_GREEN("Passed")); } + + cr_assert(count > 0, "No fifo found"); } diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index 4be542a5c..ed14ee33e 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -44,6 +44,8 @@ Test(graph, basic, .description = "DirectedGraph") g.dump(); cr_assert(g.getVertexCount() == 2); cr_assert(g.vertexGetEdges(v2id).size() == 0); + + logger->info(TXT_GREEN("Passed")); } Test(graph, path, .description = "Find path") @@ -108,6 +110,8 @@ Test(graph, path, .description = "Find path") for(auto& edge : path4) { logger->info(" -> edge {}", edge); } + + logger->info(TXT_GREEN("Passed")); } Test(graph, memory_manager, .description = "Global Memory Manager") diff --git a/fpga/tests/timer.cpp b/fpga/tests/timer.cpp index 278f8f418..4317e51b6 100644 --- a/fpga/tests/timer.cpp +++ b/fpga/tests/timer.cpp @@ -34,6 +34,8 @@ Test(fpga, timer, .description = "Timer Counter") { auto logger = loggerGetOrCreate("unittest:timer"); + size_t count = 0; + for(auto& ip : state.cards.front()->ips) { // skip non-timer IPs if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_timer:")) { @@ -42,6 +44,8 @@ Test(fpga, timer, .description = "Timer Counter") logger->info("Testing {}", *ip); + count++; + auto timer = reinterpret_cast(*ip); logger->info("Test simple waiting"); @@ -68,5 +72,5 @@ Test(fpga, timer, .description = "Timer Counter") logger->info(TXT_GREEN("Passed")); } - return; + cr_assert(count > 0, "No timer found"); } From e93b31bbf117ae1b092d961182915acb8d002c04 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 14:13:14 +0100 Subject: [PATCH 137/560] lib/ips: make use of MemoryManager and new config layout --- fpga/include/villas/fpga/ip.hpp | 62 ++++-- fpga/include/villas/fpga/ips/fifo.hpp | 11 +- fpga/include/villas/fpga/ips/intc.hpp | 16 +- fpga/include/villas/fpga/ips/pcie.hpp | 80 ++++++++ fpga/include/villas/fpga/ips/switch.hpp | 11 +- fpga/include/villas/fpga/ips/timer.hpp | 13 +- fpga/lib/CMakeLists.txt | 24 ++- fpga/lib/ip.cpp | 250 +++++++++++++++--------- fpga/lib/ips/fifo.cpp | 28 ++- fpga/lib/ips/intc.cpp | 8 +- fpga/lib/ips/pcie.cpp | 53 +++++ fpga/lib/ips/switch.cpp | 8 +- fpga/lib/ips/timer.cpp | 21 +- 13 files changed, 419 insertions(+), 166 deletions(-) create mode 100644 fpga/include/villas/fpga/ips/pcie.hpp create mode 100644 fpga/lib/ips/pcie.cpp diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index b71f5d700..fc3a777e0 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -41,14 +41,23 @@ #include +#include "memory_manager.hpp" namespace villas { namespace fpga { +// forward declaration class PCIeCard; namespace ip { +// forward declarations +class IpCore; +class IpCoreFactory; +class InterruptController; + +using IpCoreList = std::list>; + class IpIdentifier { public: @@ -58,25 +67,30 @@ public: IpIdentifier(std::string vlnvString, std::string name = "") : vlnv(vlnvString), name(name) {} + const std::string& + getName() const + { return name; } + + const Vlnv& + getVlnv() const + { return vlnv; } + friend std::ostream& operator<< (std::ostream& stream, const IpIdentifier& id) { return stream << TXT_BOLD(id.name) << " vlnv=" << id.vlnv; } +private: Vlnv vlnv; std::string name; }; -using IpDependency = std::pair; - -// forward declarations -class IpCoreFactory; class IpCore { public: friend IpCoreFactory; - IpCore() : card(nullptr), baseaddr(0) {} + IpCore() : card(nullptr) {} virtual ~IpCore() {} // IPs can implement this interface @@ -88,10 +102,10 @@ public: bool operator== (const IpIdentifier& otherId) { - const bool vlnvMatch = id.vlnv == otherId.vlnv; - const bool nameWildcard = id.name.empty() or otherId.name.empty(); + const bool vlnvMatch = id.getVlnv() == otherId.getVlnv(); + const bool nameWildcard = id.getName().empty() or otherId.getName().empty(); - return vlnvMatch and (nameWildcard or id.name == otherId.name); + return vlnvMatch and (nameWildcard or id.getName() == otherId.getName()); } bool @@ -101,45 +115,55 @@ public: bool operator== (const Vlnv& otherVlnv) - { return id.vlnv == otherVlnv; } + { return id.getVlnv() == otherVlnv; } bool operator== (const std::string& otherName) - { return id.name == otherName; } + { return id.getName() == otherName; } friend std::ostream& operator<< (std::ostream& stream, const IpCore& ip) { return stream << ip.id; } + const std::string& + getInstanceName() + { return id.getName(); } + protected: - uintptr_t - getBaseaddr() const - { return getAddrMapped(this->baseaddr); } uintptr_t - getAddrMapped(uintptr_t address) const; + getBaseAddr(const std::string& block) const; + + uintptr_t + getLocalAddr(const std::string& block, uintptr_t address) const; SpdLogger - getLogger() { return loggerGetOrCreate(id.name); } + getLogger() { return loggerGetOrCreate(id.getName()); } struct IrqPort { int num; - std::string controllerName; + InterruptController* irqController; std::string description; }; + InterruptController* + getInterruptController(const std::string& interruptName); + protected: + virtual std::list getMemoryBlocks() const { return {}; } + // populated by FpgaIpFactory PCIeCard* card; ///< FPGA card this IP is instantiated on IpIdentifier id; ///< VLNV and name defined in JSON config - uintptr_t baseaddr; ///< The baseadress of this IP component std::map irqs; ///< Interrupts of this IP component std::map dependencies; ///< dependencies on other IPs + + /// Cached translations from the process address space to each memory block + std::map addressTranslations; }; -using IpCoreList = std::list>; class IpCoreFactory : public Plugin { @@ -164,11 +188,9 @@ private: virtual bool configureJson(IpCore& /* ip */, json_t* /* json */) { return true; } - virtual Vlnv getCompatibleVlnv() const = 0; virtual std::string getName() const = 0; virtual std::string getDescription() const = 0; - virtual std::list getDependencies() const { return {}; } protected: static SpdLogger diff --git a/fpga/include/villas/fpga/ips/fifo.hpp b/fpga/include/villas/fpga/ips/fifo.hpp index 5d9113448..001fbf19b 100644 --- a/fpga/include/villas/fpga/ips/fifo.hpp +++ b/fpga/include/villas/fpga/ips/fifo.hpp @@ -50,8 +50,14 @@ public: size_t read(void* buf, size_t len); private: + static constexpr char registerMemory[] = "Mem0"; + static constexpr char axi4Memory[] = "Mem1"; + static constexpr char irqName[] = "interrupt"; + + std::list getMemoryBlocks() const + { return { registerMemory, axi4Memory }; } + XLlFifo xFifo; - uintptr_t baseaddr_axi4; }; @@ -77,9 +83,6 @@ public: Vlnv getCompatibleVlnv() const { return {"xilinx.com:ip:axi_fifo_mm_s:"}; } - - std::list getDependencies() const - { return { {"intc", Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:") } }; } }; } // namespace ip diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 68819371d..7ccef8038 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -61,6 +61,13 @@ public: { return waitForInterrupt(irq.num); } private: + + static constexpr char registerMemory[] = "Reg"; + + std::list getMemoryBlocks() const + { return { registerMemory }; } + + struct Interrupt { int eventFd; /**< Event file descriptor */ int number; /**< Interrupt number from /proc/interrupts */ @@ -82,6 +89,10 @@ public: IpCoreFactory(getName()) {} + static constexpr const char* + getCompatibleVlnvString() + { return "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"; } + IpCore* create() { return new InterruptController; } @@ -94,7 +105,10 @@ public: { return "Xilinx's programmable interrupt controller"; } Vlnv getCompatibleVlnv() const - { return Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); } + { return Vlnv(getCompatibleVlnvString()); } + +// std::list getDependencies() const +// { return { {"pcie", Vlnv("xilinx.com:ip:axi_pcie:") } }; } }; } // namespace ip diff --git a/fpga/include/villas/fpga/ips/pcie.hpp b/fpga/include/villas/fpga/ips/pcie.hpp new file mode 100644 index 000000000..a63db2e20 --- /dev/null +++ b/fpga/include/villas/fpga/ips/pcie.hpp @@ -0,0 +1,80 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include +#include + +#include "fpga/ip_node.hpp" +#include "fpga/vlnv.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +class AxiPciExpressBridge : public IpCore { +public: + friend class AxiPciExpressBridgeFactory; + + bool init(); +}; + + +class AxiPciExpressBridgeFactory : public IpCoreFactory { +public: + AxiPciExpressBridgeFactory() : + IpCoreFactory(getName()) {} + + static constexpr const char* + getCompatibleVlnvString() + { return "xilinx.com:ip:axi_pcie:"; } + + IpCore* create() + { return new AxiPciExpressBridge; } + + std::string getName() const + { return "AxiPciExpressBridge"; } + + std::string getDescription() const + { return "Xilinx's AXI-PCIe Bridge"; } + + Vlnv getCompatibleVlnv() const + { return Vlnv(getCompatibleVlnvString()); } +}; + +} // namespace ip +} // namespace fpga +} // namespace villas + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index b4418f5dd..796cab711 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -56,6 +56,11 @@ public: private: static constexpr int PORT_DISABLED = -1; + static constexpr char registerMemory[] = "Reg"; + + std::list getMemoryBlocks() const + { return { registerMemory }; } + struct Path { IpCore* masterOut; IpCore* slaveIn; @@ -72,6 +77,10 @@ public: AxiStreamSwitchFactory() : IpNodeFactory(getName()) {} + static constexpr const char* + getCompatibleVlnvString() + { return "xilinx.com:ip:axis_switch:"; } + bool configureJson(IpCore& ip, json_t *json_ip); IpCore* create() @@ -84,7 +93,7 @@ public: { return "Xilinx's AXI4-Stream switch"; } Vlnv getCompatibleVlnv() const - { return Vlnv("xilinx.com:ip:axis_switch:"); } + { return Vlnv(getCompatibleVlnvString()); } }; } // namespace ip diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp index 771d2723b..56231069b 100644 --- a/fpga/include/villas/fpga/ips/timer.hpp +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -43,10 +43,10 @@ namespace ip { class Timer : public IpCore { + friend class TimerFactory; public: bool init(); - bool start(uint32_t ticks); bool wait(); uint32_t remaining(); @@ -62,8 +62,14 @@ public: { return FPGA_AXI_HZ; } private: + + std::list getMemoryBlocks() const + { return { registerMemory }; } + + static constexpr char irqName[] = "generateout0"; + static constexpr char registerMemory[] = "Reg"; + XTmrCtr xTmr; - InterruptController* intc; }; @@ -88,9 +94,6 @@ public: Vlnv getCompatibleVlnv() const { return {"xilinx.com:ip:axi_timer:"}; } - - std::list getDependencies() const - { return { {"intc", Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:") } }; } }; } // namespace ip diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index ae55a332f..cedc633f5 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,39 +1,43 @@ set(SOURCES - ip.cpp ip.c - ip_node.cpp - vlnv.cpp vlnv.c card.c + + vlnv.cpp card.cpp + ip.cpp + ip_node.cpp ips/timer.c - ips/timer.cpp ips/model.c ips/switch.c - ips/switch.cpp ips/dft.c ips/fifo.c - ips/fifo.cpp ips/dma.c - ips/intc.cpp ips/intc.c ips/gpio.c ips/rtds_axis.c + ips/timer.cpp + ips/switch.cpp + ips/fifo.cpp + ips/intc.cpp + ips/pcie.cpp + kernel/kernel.c kernel/pci.c kernel/vfio.c - memory_manager.cpp plugin.c - plugin.cpp utils.c - utils.cpp list.c log.c log_config.c log_helper.c + + plugin.cpp + utils.cpp + memory_manager.cpp ) include(FindPkgConfig) diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index a6d35bd7c..39e3c4805 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -20,30 +20,24 @@ * along with this program. If not, see . *********************************************************************************/ -#include "log_config.h" -#include "log.hpp" -#include "plugin.h" -#include "utils.hpp" - -#include "fpga/vlnv.hpp" -#include "fpga/ip.hpp" -#include "fpga/card.hpp" - -#include -#include -#include #include #include #include #include "log.hpp" +#include "utils.hpp" +#include "memory_manager.hpp" +#include "fpga/ip.hpp" +#include "fpga/vlnv.hpp" +#include "fpga/card.hpp" + +// needed to get VLNVs for initialization order list #include "fpga/ips/pcie.hpp" #include "fpga/ips/intc.hpp" #include "fpga/ips/switch.hpp" - namespace villas { namespace fpga { namespace ip { @@ -58,46 +52,20 @@ vlnvInitializationOrder = { Vlnv(AxiStreamSwitchFactory::getCompatibleVlnvString()), }; -void -IpCore::dump() { - auto logger = getLogger(); - - logger->info("Base address = {:08x}", baseaddr); - for(auto& [num, irq] : irqs) { - logger->info("IRQ {}: {}:{}", num, irq.controllerName, irq.num); - } -} - - -IpCoreFactory* IpCoreFactory::lookup(const Vlnv &vlnv) -{ - for(auto& ip : Plugin::lookup(Plugin::Type::FpgaIp)) { - IpCoreFactory* ipCoreFactory = dynamic_cast(ip); - - if(ipCoreFactory->getCompatibleVlnv() == vlnv) - return ipCoreFactory; - } - - return nullptr; -} - -uintptr_t -IpCore::getAddrMapped(uintptr_t address) const -{ - assert(card != nullptr); - return reinterpret_cast(card->map) + address; -} - IpCoreList IpCoreFactory::make(PCIeCard* card, json_t *json_ips) { - IpCoreList configuredIps; + // We only have this logger until we know the factory to build an IP with auto loggerStatic = getStaticLogger(); - std::list allIps; // all IPs available + std::list allIps; // all IPs available in config std::list orderedIps; // IPs ordered in initialization order + IpCoreList configuredIps; // Successfully configured IPs + IpCoreList initializedIps; // Initialized, i.e. ready-to-use IPs + + // parse all IP instance names and their VLNV into list `allIps` const char* ipName; json_t* json_ip; @@ -111,7 +79,6 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) allIps.push_back({vlnv, ipName}); } - // Pick out IPs to be initialized first. // // Reverse order of the initialization order list, because we push to the @@ -132,12 +99,12 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) // insert all other IPs at the end orderedIps.splice(orderedIps.end(), allIps); - loggerStatic->debug("IP initialization order:"); for(auto& id : orderedIps) { loggerStatic->debug(" {}", TXT_BOLD(id.getName())); } + // configure all IPs for(auto& id : orderedIps) { loggerStatic->info("Initializing {}", id); @@ -176,25 +143,38 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) ip->card = card; ip->id = id; - // extract base address if it has one - if(json_unpack(json_ip, "{ s?: i }", "baseaddr", &ip->baseaddr) != 0) { - logger->warn("Problem while parsing base address of IP {}", - TXT_BOLD(ipName)); - continue; - } + json_t* json_ip = json_object_get(json_ips, id.getName().c_str()); json_t* json_irqs = json_object_get(json_ip, "irqs"); if(json_is_object(json_irqs)) { - const char* irq_name; + logger->debug("Parse IRQs of {}", *ip); + + const char* irqName; json_t* json_irq; - json_object_foreach(json_irqs, irq_name, json_irq) { - const char* irq = json_string_value(json_irq); + json_object_foreach(json_irqs, irqName, json_irq) { + const char* irqEntry = json_string_value(json_irq); - auto tokens = utils::tokenize(irq, ":"); + auto tokens = utils::tokenize(irqEntry, ":"); if(tokens.size() != 2) { logger->warn("Cannot parse IRQ '{}' of {}", - irq, TXT_BOLD(ipName)); + irqEntry, TXT_BOLD(id.getName())); + continue; + } + + const std::string& irqControllerName = tokens[0]; + InterruptController* intc = nullptr; + + for(auto& configuredIp : configuredIps) { + if(*configuredIp == irqControllerName) { + intc = dynamic_cast(configuredIp.get()); + break; + } + } + + if(intc == nullptr) { + logger->error("Interrupt Controller {} for IRQ {} not found", + irqControllerName, irqName); continue; } @@ -202,39 +182,66 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) try { num = std::stoi(tokens[1]); } catch(const std::invalid_argument&) { - logger->warn("IRQ number is not an integer: '{}'", irq); + logger->warn("IRQ number is not an integer: '{}'", irqEntry); continue; } - logger->debug("IRQ: {} -> {}:{}", irq_name, tokens[0], num); - ip->irqs[irq_name] = {num, tokens[0], ""}; + logger->debug("IRQ: {} -> {}:{}", irqName, irqControllerName, num); + ip->irqs[irqName] = {num, intc, ""}; } - } else { - logger->debug("IP has no interrupts"); } - bool dependenciesOk = true; - for(auto& [depName, depVlnv] : ipCoreFactory->getDependencies()) { - // lookup dependency IP core in list of already initialized IPs - auto iter = std::find_if(initializedIps.begin(), - initializedIps.end(), - [&](const std::unique_ptr& ip) { - return *ip == depVlnv; - }); + json_t* json_memory_view = json_object_get(json_ip, "memory-view"); + if(json_is_object(json_memory_view)) { + logger->debug("Parse memory view of {}", *ip); - if(iter == initializedIps.end()) { - logger->error("Cannot find '{}' dependency {} of {}", - depName, depVlnv, TXT_BOLD(ipName)); - dependenciesOk = false; - break; + // create a master address space because this IP has a memory view + const MemoryManager::AddressSpaceId myAddrSpaceId = + MemoryManager::get().getOrCreateAddressSpace(id.getName()); + + // now find all slave address spaces this master can access + const char* bus_name; + json_t* json_bus; + json_object_foreach(json_memory_view, bus_name, json_bus) { + + const char* instance_name; + json_t* json_instance; + json_object_foreach(json_bus, instance_name, json_instance) { + + const char* block_name; + json_t* json_block; + json_object_foreach(json_instance, block_name, json_block) { + + int base, high, size; + int ret = json_unpack(json_block, "{ s: i, s: i, s: i }", + "baseaddr", &base, + "highaddr", &high, + "size", &size); + if(ret != 0) { + logger->error("Cannot parse address block {}/{}/{}/{}", + ip->getInstanceName(), + bus_name, instance_name, block_name); + continue; + + } + + // get or create the slave address space + const std::string slaveAddrSpace = + MemoryManager::getSlaveAddrSpaceName(instance_name, block_name); + + const MemoryManager::AddressSpaceId slaveAddrSpaceId = + MemoryManager::get().getOrCreateAddressSpace(slaveAddrSpace); + + // create a new mapping to the slave address space + MemoryManager::get().createMapping(static_cast(base), + 0, + static_cast(size), + bus_name, + myAddrSpaceId, + slaveAddrSpaceId); + } + } } - - logger->debug("Found dependency IP {}", (*iter)->id); - ip->dependencies[depName] = (*iter).get(); - } - - if(not dependenciesOk) { - continue; } // IP-specific setup via JSON config @@ -243,10 +250,11 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) continue; } + // IP has been configured now configuredIps.push_back(std::move(ip)); } - IpCoreList initializedIps; + // Start and check IPs now for(auto& ip : configuredIps) { // Translate all memory blocks that the IP needs to be accessible from @@ -255,38 +263,100 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) for(auto& memoryBlock : ip->getMemoryBlocks()) { // construct the global name of this address block const auto addrSpaceName = - MemoryManager::getSlaveAddrSpaceName(ip->id.getName(), memoryBlock); + MemoryManager::getSlaveAddrSpaceName(ip->getInstanceName(), + memoryBlock); // retrieve its address space identifier - const auto addrSpace = + const auto addrSpaceId = MemoryManager::get().findAddressSpace(addrSpaceName); // get the translation to the address space const auto& translation = - MemoryManager::get().getTranslationFromProcess(addrSpace); + MemoryManager::get().getTranslationFromProcess(addrSpaceId); // cache it in the IP instance only with local name ip->addressTranslations.emplace(memoryBlock, translation); } - if(not ip->init()) { - loggerStatic->error("Cannot start IP {}", ip->id.getName()); + loggerStatic->error("Cannot start IP {}", *ip); continue; } if(not ip->check()) { - loggerStatic->error("Checking of IP {} failed", ip->id.getName()); + loggerStatic->error("Checking failed for IP {}", *ip); continue; } + // will only be reached if the IP successfully was initialized initializedIps.push_back(std::move(ip)); } + loggerStatic->debug("Initialized IPs:"); + for(auto& ip : initializedIps) { + loggerStatic->debug(" {}", *ip); + } + return initializedIps; } + +void +IpCore::dump() { + auto logger = getLogger(); + + logger->info("IP: {}", *this); + for(auto& [num, irq] : irqs) { + logger->info("IRQ {}: {}:{}", + num, irq.irqController->getInstanceName(), irq.num); + } +} + + +IpCoreFactory* +IpCoreFactory::lookup(const Vlnv &vlnv) +{ + for(auto& ip : Plugin::lookup(Plugin::Type::FpgaIp)) { + IpCoreFactory* ipCoreFactory = dynamic_cast(ip); + + if(ipCoreFactory->getCompatibleVlnv() == vlnv) + return ipCoreFactory; + } + + return nullptr; +} + + +uintptr_t +IpCore::getBaseAddr(const std::string& block) const +{ + return getLocalAddr(block, 0); +} + + +uintptr_t +IpCore::getLocalAddr(const std::string& block, uintptr_t address) const +{ + // throws exception if block not present + auto& translation = addressTranslations.at(block); + + return translation.getLocalAddr(address); +} + + +InterruptController* +IpCore::getInterruptController(const std::string& interruptName) +{ + try { + const IrqPort irq = irqs.at(interruptName); + return irq.irqController; + } catch(const std::out_of_range&) { + return nullptr; + } +} + + } // namespace ip } // namespace fpga } // namespace villas diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 9c465302e..23766a7d0 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -50,33 +50,32 @@ FifoFactory::configureJson(IpCore &ip, json_t *json_ip) return false; } - auto& fifo = reinterpret_cast(ip); - if(json_unpack(json_ip, "{ s: i }", "axi4_baseaddr", &fifo.baseaddr_axi4) != 0) { - logger->warn("Cannot parse property 'axi4_baseaddr'"); - return false; - } - return true; } bool Fifo::init() { + auto logger = getLogger(); + XLlFifo_Config fifo_cfg; - fifo_cfg.Axi4BaseAddress = getAddrMapped(this->baseaddr_axi4); + fifo_cfg.Axi4BaseAddress = getBaseAddr(axi4Memory); // use AXI4 for Data, AXI4-Lite for control - fifo_cfg.Datainterface = (this->baseaddr_axi4 != static_cast(-1)) ? 1 : 0; + fifo_cfg.Datainterface = (fifo_cfg.Axi4BaseAddress != -1) ? 1 : 0; - if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseaddr()) != XST_SUCCESS) + if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseAddr(registerMemory)) != XST_SUCCESS) return false; + if(irqs.find(irqName) == irqs.end()) { + logger->error("IRQ '{}' not found but required", irqName); + return false; + } + // Receive complete IRQ XLlFifo_IntEnable(&xFifo, XLLF_INT_RC_MASK); - - auto intc = reinterpret_cast(dependencies["intc"]); - intc->enableInterrupt(irqs["interrupt"], false); + irqs[irqName].irqController->enableInterrupt(irqs[irqName], false); return true; } @@ -85,6 +84,7 @@ bool Fifo::stop() { // Receive complete IRQ XLlFifo_IntDisable(&xFifo, XLLF_INT_RC_MASK); + irqs[irqName].irqController->disableInterrupt(irqs[irqName]); return true; } @@ -110,10 +110,8 @@ size_t Fifo::read(void *buf, size_t len) size_t nextlen = 0; size_t rxlen; - auto intc = reinterpret_cast(dependencies["intc"]); - while (!XLlFifo_IsRxDone(&xFifo)) - intc->waitForInterrupt(irqs["interrupt"].num); + irqs[irqName].irqController->waitForInterrupt(irqs[irqName]); XLlFifo_IntClear(&xFifo, XLLF_INT_RC_MASK); diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 90cbd7144..6c841e4da 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -48,7 +48,7 @@ InterruptController::~InterruptController() bool InterruptController::init() { - const uintptr_t base = getBaseaddr(); + const uintptr_t base = getBaseAddr(registerMemory); auto logger = getLogger(); num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); @@ -86,7 +86,7 @@ bool InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool polling) { auto logger = getLogger(); - const uintptr_t base = getBaseaddr(); + const uintptr_t base = getBaseAddr(registerMemory); /* Current state of INTC */ const uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); @@ -120,7 +120,7 @@ InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool bool InterruptController::disableInterrupt(InterruptController::IrqMaskType mask) { - const uintptr_t base = getBaseaddr(); + const uintptr_t base = getBaseAddr(registerMemory); uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); @@ -133,7 +133,7 @@ InterruptController::waitForInterrupt(int irq) { assert(irq < maxIrqs); - const uintptr_t base = getBaseaddr(); + const uintptr_t base = getBaseAddr(registerMemory); if (this->polling[irq]) { uint32_t isr, mask = 1 << irq; diff --git a/fpga/lib/ips/pcie.cpp b/fpga/lib/ips/pcie.cpp new file mode 100644 index 000000000..df45d68cb --- /dev/null +++ b/fpga/lib/ips/pcie.cpp @@ -0,0 +1,53 @@ +/** AXI PCIe bridge + * + * @author Daniel Krebs + * @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS) + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "fpga/ips/pcie.hpp" +#include "fpga/card.hpp" + +#include "log.hpp" +#include "memory_manager.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +static AxiPciExpressBridgeFactory factory; + +bool +AxiPciExpressBridge::init() +{ + // Create an identity mapping from the FPGA card to this IP as an entry + // point to all other IPs in the FPGA, because Vivado will generate a + // memory view for this bridge that can see all others. + auto addrSpace = MemoryManager::get().findAddressSpace(getInstanceName()); + MemoryManager::get().createMapping(0x00, 0x00, SIZE_MAX, "PCIeBridge", + card->addrSpaceId, addrSpace); + + return true; +} + +} // namespace ip +} // namespace fpga +} // namespace villas diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index 98c6534e2..d9055c2f8 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -37,13 +37,16 @@ static AxiStreamSwitchFactory factory; bool AxiStreamSwitch::init() -{ +{ + auto logger = getLogger(); + /* Setup AXI-stream switch */ XAxis_Switch_Config sw_cfg; sw_cfg.MaxNumMI = num_ports; sw_cfg.MaxNumSI = num_ports; - if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseaddr()) != XST_SUCCESS) { + if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseAddr(registerMemory)) != XST_SUCCESS) { + logger->error("Cannot initialize switch"); return false; } @@ -57,7 +60,6 @@ AxiStreamSwitch::init() portMapping[portMaster] = PORT_DISABLED; } - return true; } diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index a6e19e257..1c7a023fe 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -44,28 +44,23 @@ bool Timer::init() XTmrCtr_Config xtmr_cfg; xtmr_cfg.SysClockFreqHz = getFrequency(); - XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseaddr()); + XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseAddr(registerMemory)); XTmrCtr_InitHw(&xTmr); - if(dependencies.find("intc") == dependencies.end()) { - logger->error("No intc"); + if(irqs.find(irqName) == irqs.end()) { + logger->error("IRQ '{}' not found but required", irqName); return false; } - if(irqs.find("generateout0") == irqs.end()) { - logger->error("no irq"); - return false; - } - - intc = reinterpret_cast(dependencies["intc"]); - intc->disableInterrupt(irqs["generateout0"]); + // disable so we don't receive any stray interrupts + irqs[irqName].irqController->disableInterrupt(irqs[irqName]); return true; } bool Timer::start(uint32_t ticks) { - intc->enableInterrupt(irqs["generateout0"], false); + irqs[irqName].irqController->enableInterrupt(irqs[irqName], false); XTmrCtr_SetOptions(&xTmr, 0, XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION); XTmrCtr_SetResetValue(&xTmr, 0, ticks); @@ -76,8 +71,8 @@ bool Timer::start(uint32_t ticks) bool Timer::wait() { - int count = intc->waitForInterrupt(irqs["generateout0"]); - intc->disableInterrupt(irqs["generateout0"]); + int count = irqs[irqName].irqController->waitForInterrupt(irqs[irqName]); + irqs[irqName].irqController->disableInterrupt(irqs[irqName]); return (count == 1); } From 21333379a9a672a61ba7b8dcac2ae3a28ab06770 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 14:58:58 +0100 Subject: [PATCH 138/560] lib/ips/fifo: fix decision if AXI4 data interface is present --- fpga/lib/ips/fifo.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 23766a7d0..d608b9958 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -60,10 +60,13 @@ bool Fifo::init() XLlFifo_Config fifo_cfg; - fifo_cfg.Axi4BaseAddress = getBaseAddr(axi4Memory); - - // use AXI4 for Data, AXI4-Lite for control - fifo_cfg.Datainterface = (fifo_cfg.Axi4BaseAddress != -1) ? 1 : 0; + try { + // if this throws an exception, then there's no AXI4 data interface + fifo_cfg.Axi4BaseAddress = getBaseAddr(axi4Memory); + fifo_cfg.Datainterface = 1; + } catch(const std::out_of_range&) { + fifo_cfg.Datainterface = 0; + } if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseAddr(registerMemory)) != XST_SUCCESS) return false; From e66350dbf68ac95821e9138e8ee4d7c97713bfc4 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 15:39:03 +0100 Subject: [PATCH 139/560] tests: minor fixes in logging --- fpga/tests/fpga.cpp | 3 +++ fpga/tests/graph.cpp | 9 +++++---- fpga/tests/main.cpp | 5 +++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/fpga/tests/fpga.cpp b/fpga/tests/fpga.cpp index b5147f3b6..1f718ff51 100644 --- a/fpga/tests/fpga.cpp +++ b/fpga/tests/fpga.cpp @@ -51,6 +51,9 @@ static void init() FILE *f; json_error_t err; + spdlog::set_level(spdlog::level::debug); + spdlog::set_pattern("[%T] [%l] [%n] %v"); + villas::Plugin::dumpList(); ret = pci_init(&pci); diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp index ed14ee33e..354b10d3e 100644 --- a/fpga/tests/graph.cpp +++ b/fpga/tests/graph.cpp @@ -12,16 +12,17 @@ static void init_graph() } TestSuite(graph, - .description = "Graph library" + .description = "Graph library", + .init = init_graph ); Test(graph, basic, .description = "DirectedGraph") { auto logger = loggerGetOrCreate("test:graph:basic"); - logger->info("Testing basic graph construction and modification"); - villas::graph::DirectedGraph<> g("test:graph:basic"); + logger->info("Testing basic graph construction and modification"); + std::shared_ptr v1(new villas::graph::Vertex); std::shared_ptr v2(new villas::graph::Vertex); std::shared_ptr v3(new villas::graph::Vertex); @@ -116,7 +117,7 @@ Test(graph, path, .description = "Find path") Test(graph, memory_manager, .description = "Global Memory Manager") { - auto logger = loggerGetOrCreate("unittest:mm"); + auto logger = loggerGetOrCreate("test:graph:mm"); auto& mm = villas::MemoryManager::get(); logger->info("Create address spaces"); diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index beb56f6b9..c57333cd3 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -63,9 +63,10 @@ ReportHook(PRE_ALL)(struct criterion_test_set *tests) int main(int argc, char *argv[]) { int ret; - + spdlog::set_level(spdlog::level::debug); - + spdlog::set_pattern("[%T] [%l] [%n] %v"); + /* Run criterion tests */ auto tests = criterion_initialize(); From 503d6b7f075f1e9685de84749c737c1a3fe81a56 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 16:38:51 +0100 Subject: [PATCH 140/560] lib/ip: cleanup formatting and comments of IpCore's member variables --- fpga/include/villas/fpga/ip.hpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index fc3a777e0..aff0ed891 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -150,14 +150,20 @@ protected: InterruptController* getInterruptController(const std::string& interruptName); -protected: - virtual std::list getMemoryBlocks() const { return {}; } + /// Each IP can declare via this function which memory blocks it requires + virtual std::list + getMemoryBlocks() const + { return {}; } - // populated by FpgaIpFactory - PCIeCard* card; ///< FPGA card this IP is instantiated on - IpIdentifier id; ///< VLNV and name defined in JSON config - std::map irqs; ///< Interrupts of this IP component - std::map dependencies; ///< dependencies on other IPs +protected: + /// FPGA card this IP is instantiated on (populated by FpgaIpFactory) + PCIeCard* card; + + /// Identifier of this IP with its instance name and VLNV + IpIdentifier id; + + /// All interrupts of this IP with their associated interrupt controller + std::map irqs; /// Cached translations from the process address space to each memory block std::map addressTranslations; From 41e90bfda0ae0be40b9afcd9e66bcfb1fe567768 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 17:26:18 +0100 Subject: [PATCH 141/560] lib/ip: cleanup operators --- fpga/include/villas/fpga/ip.hpp | 63 ++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index aff0ed891..8c23b38e6 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -79,6 +79,21 @@ public: operator<< (std::ostream& stream, const IpIdentifier& id) { return stream << TXT_BOLD(id.name) << " vlnv=" << id.vlnv; } + bool + operator==(const IpIdentifier& otherId) const { + const bool vlnvWildcard = otherId.getVlnv() == Vlnv::getWildcard(); + const bool nameWildcard = this->getName().empty() or otherId.getName().empty(); + + const bool vlnvMatch = vlnvWildcard or this->getVlnv() == otherId.getVlnv(); + const bool nameMatch = nameWildcard or this->getName() == otherId.getName(); + + return vlnvMatch and nameMatch; + } + + bool + operator!=(const IpIdentifier& otherId) const + { return !(*this == otherId); } + private: Vlnv vlnv; std::string name; @@ -86,10 +101,9 @@ private: class IpCore { -public: - friend IpCoreFactory; +public: IpCore() : card(nullptr) {} virtual ~IpCore() {} @@ -101,37 +115,46 @@ public: virtual void dump(); bool - operator== (const IpIdentifier& otherId) { - const bool vlnvMatch = id.getVlnv() == otherId.getVlnv(); - const bool nameWildcard = id.getName().empty() or otherId.getName().empty(); - - return vlnvMatch and (nameWildcard or id.getName() == otherId.getName()); - } - - bool - operator!= (const IpIdentifier& otherId) { - return !(*this == otherId); - } - - bool - operator== (const Vlnv& otherVlnv) + operator==(const Vlnv& otherVlnv) const { return id.getVlnv() == otherVlnv; } bool - operator== (const std::string& otherName) - { return id.getName() == otherName; } + operator!=(const Vlnv& otherVlnv) const + { return id.getVlnv() != otherVlnv; } + bool + operator==(const IpIdentifier& otherId) const + { return this->id == otherId; } + + bool + operator!=(const IpIdentifier& otherId) const + { return this->id != otherId; } + + bool + operator==(const std::string& otherName) const + { return getInstanceName() == otherName; } + + bool + operator!=(const std::string& otherName) const + { return getInstanceName() != otherName; } + + bool + operator==(const IpCore& otherIp) const + { return this->id == otherIp.id; } + + bool + operator!=(const IpCore& otherIp) const + { return this->id != otherIp.id; } friend std::ostream& operator<< (std::ostream& stream, const IpCore& ip) { return stream << ip.id; } const std::string& - getInstanceName() + getInstanceName() const { return id.getName(); } protected: - uintptr_t getBaseAddr(const std::string& block) const; From 817d20624313a5bd81d50e58881b9f07b37db122 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 17:40:40 +0100 Subject: [PATCH 142/560] lib/ip: formatting cleanup and more comments --- fpga/include/villas/fpga/ip.hpp | 55 +++++++++++++++++++++------------ fpga/lib/ip.cpp | 2 +- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 8c23b38e6..b708ed1e4 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -105,15 +105,40 @@ class IpCore { public: IpCore() : card(nullptr) {} - virtual ~IpCore() {} + virtual ~IpCore() = default; - // IPs can implement this interface +public: + /* Generic management interface for IPs */ + + /// Runtime setup of IP, should access and initialize hardware + virtual bool init() + { return true; } + + /// Runtime check of IP, should verify basic functionality virtual bool check() { return true; } - virtual bool init() { return true; } + + /// Generic disabling of IP, meaning may depend on IP virtual bool stop() { return true; } + + /// Reset the IP, it should behave like freshly initialized afterwards virtual bool reset() { return true; } + + /// Print some debug information about the IP virtual void dump(); +protected: + /// Each IP can declare via this function which memory blocks it requires + virtual std::list + getMemoryBlocks() const + { return {}; } + +public: + const std::string& + getInstanceName() const + { return id.getName(); } + + /* Operators */ + bool operator==(const Vlnv& otherVlnv) const { return id.getVlnv() == otherVlnv; } @@ -150,10 +175,6 @@ public: operator<< (std::ostream& stream, const IpCore& ip) { return stream << ip.id; } - const std::string& - getInstanceName() const - { return id.getName(); } - protected: uintptr_t getBaseAddr(const std::string& block) const; @@ -162,23 +183,19 @@ protected: getLocalAddr(const std::string& block, uintptr_t address) const; SpdLogger - getLogger() { return loggerGetOrCreate(id.getName()); } + getLogger() const + { return loggerGetOrCreate(getInstanceName()); } + InterruptController* + getInterruptController(const std::string& interruptName) const; + +protected: struct IrqPort { int num; InterruptController* irqController; std::string description; }; - InterruptController* - getInterruptController(const std::string& interruptName); - - /// Each IP can declare via this function which memory blocks it requires - virtual std::list - getMemoryBlocks() const - { return {}; } - -protected: /// FPGA card this IP is instantiated on (populated by FpgaIpFactory) PCIeCard* card; @@ -194,7 +211,6 @@ protected: - class IpCoreFactory : public Plugin { public: IpCoreFactory(std::string concreteName) : @@ -207,7 +223,8 @@ public: protected: SpdLogger - getLogger() { return loggerGetOrCreate(getName()); } + getLogger() const + { return loggerGetOrCreate(getName()); } private: /// Create a concrete IP instance diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 39e3c4805..7b93320a8 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -346,7 +346,7 @@ IpCore::getLocalAddr(const std::string& block, uintptr_t address) const InterruptController* -IpCore::getInterruptController(const std::string& interruptName) +IpCore::getInterruptController(const std::string& interruptName) const { try { const IrqPort irq = irqs.at(interruptName); From 3a8e332b426d2c9bea091f1eb993e6c2cbb5dddd Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 17:42:01 +0100 Subject: [PATCH 143/560] lib/vlnv: add != operator and minor cleanup --- fpga/include/villas/fpga/vlnv.hpp | 17 +++++------------ fpga/lib/vlnv.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fpga/include/villas/fpga/vlnv.hpp b/fpga/include/villas/fpga/vlnv.hpp index 52066a8d7..cce08f654 100644 --- a/fpga/include/villas/fpga/vlnv.hpp +++ b/fpga/include/villas/fpga/vlnv.hpp @@ -34,10 +34,8 @@ namespace villas { namespace fpga { - class Vlnv { public: - static constexpr char delimiter = ':'; Vlnv() : @@ -52,20 +50,15 @@ public: { return Vlnv(); } std::string - toString() const - { - std::stringstream stream; - std::string string; - - stream << *this; - stream >> string; - - return string; - } + toString() const; bool operator==(const Vlnv& other) const; + bool + operator!=(const Vlnv& other) const + { return !(*this == other); } + friend std::ostream& operator<< (std::ostream& stream, const Vlnv& vlnv) { diff --git a/fpga/lib/vlnv.cpp b/fpga/lib/vlnv.cpp index f9eaa6bb1..5d65d1329 100644 --- a/fpga/lib/vlnv.cpp +++ b/fpga/lib/vlnv.cpp @@ -66,5 +66,17 @@ Vlnv::parseFromString(std::string vlnv) if(version == "*") version = ""; } +std::string +Vlnv::toString() const +{ + std::stringstream stream; + std::string string; + + stream << *this; + stream >> string; + + return string; +} + } // namespace fpga } // namespace villas From 5940dcc0e5d397d98b37339a399712c932511d16 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 17:55:25 +0100 Subject: [PATCH 144/560] lib/ips/fifo: remove useless code and old cruft --- fpga/include/villas/fpga/ips/fifo.hpp | 6 +-- fpga/lib/ips/fifo.cpp | 77 ++------------------------- 2 files changed, 5 insertions(+), 78 deletions(-) diff --git a/fpga/include/villas/fpga/ips/fifo.hpp b/fpga/include/villas/fpga/ips/fifo.hpp index 001fbf19b..9d8528237 100644 --- a/fpga/include/villas/fpga/ips/fifo.hpp +++ b/fpga/include/villas/fpga/ips/fifo.hpp @@ -64,11 +64,7 @@ private: class FifoFactory : public IpNodeFactory { public: - FifoFactory() : - IpNodeFactory(getName()) - {} - - bool configureJson(IpCore& ip, json_t *json_ip); + FifoFactory(); IpCore* create() { return new Fifo; } diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index d608b9958..79e770b9c 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -40,17 +40,11 @@ namespace ip { // instantiate factory to make available to plugin infrastructure static FifoFactory factory; -bool -FifoFactory::configureJson(IpCore &ip, json_t *json_ip) + +FifoFactory::FifoFactory() : + IpNodeFactory(getName()) { - auto logger = getLogger(); - - if(not IpNodeFactory::configureJson(ip, json_ip)) { - logger->error("Configuring IpNode failed"); - return false; - } - - return true; + // nothing to do } @@ -128,69 +122,6 @@ size_t Fifo::read(void *buf, size_t len) return nextlen; } -#if 0 - - -ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo *xllfifo = &fifo->inst; - -} - -ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo *xllfifo = &fifo->inst; - - size_t nextlen = 0; - uint32_t rxlen; - - while (!XLlFifo_IsRxDone(xllfifo)) - intc_wait(c->card->intc, c->irq); - XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK); - - /* Get length of next frame */ - rxlen = XLlFifo_RxGetLen(xllfifo); - nextlen = MIN(rxlen, len); - - /* Read from FIFO */ - XLlFifo_Read(xllfifo, buf, nextlen); - - return nextlen; -} - -int fifo_parse(struct fpga_ip *c, json_t *cfg) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - int baseaddr_axi4 = -1, ret; - - json_error_t err; - - fifo->baseaddr_axi4 = -1; - - ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4); - if (ret) - jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); - - fifo->baseaddr_axi4 = baseaddr_axi4; - - return 0; -} - -int fifo_reset(struct fpga_ip *c) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo_Reset(&fifo->inst); - - return 0; -} -#endif - } // namespace ip } // namespace fpga } // namespace villas From e2ce25028813b4b81255884f158376bbfb7d32ee Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 14 Feb 2018 10:06:55 +0100 Subject: [PATCH 145/560] lib: remove leftover (warning) compiler options These are now set in the root CMakeLists.txt --- fpga/lib/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index cedc633f5..50d16ecf5 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -49,8 +49,6 @@ find_package(Threads) add_library(villas-fpga SHARED ${SOURCES}) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") - target_compile_definitions(villas-fpga PRIVATE BUILDID=\"abc\" _GNU_SOURCE From 6cb3b77c7a990fcf215af6bd0f2fb954dbcfdbc5 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 14 Feb 2018 14:32:07 +0100 Subject: [PATCH 146/560] ips/intc: don't fail if setting IRQ affinity is not possible This is the case when the application is not executed as root which is now possible, with the drawback that we cannot set the IRQ affinity anymore. --- fpga/lib/ips/intc.cpp | 16 ++++++++++++++-- fpga/lib/kernel/kernel.c | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 6c841e4da..56d8f7877 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -21,6 +21,7 @@ *********************************************************************************/ #include +#include #include "config.h" #include "log.h" @@ -60,8 +61,19 @@ InterruptController::init() /* For each IRQ */ for (int i = 0; i < num_irqs; i++) { - /* Pin to core */ - if(kernel_irq_setaffinity(nos[i], card->affinity, nullptr) != 0) { + + /* Try pinning to core */ + int ret = kernel_irq_setaffinity(nos[i], card->affinity, nullptr); + + switch(ret) { + case 0: + // everything is fine + break; + case EACCES: + logger->warn("No permission to change affinity of VFIO-MSI interrupt. " + "This may degrade performance (increasing jitter)"); + break; + default: logger->error("Failed to change affinity of VFIO-MSI interrupt"); return false; } diff --git a/fpga/lib/kernel/kernel.c b/fpga/lib/kernel/kernel.c index dcac932db..a795c9f0f 100644 --- a/fpga/lib/kernel/kernel.c +++ b/fpga/lib/kernel/kernel.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -268,7 +269,7 @@ int kernel_irq_setaffinity(unsigned irq, uintmax_t affinity, uintmax_t *old) f = fopen(fn, "w+"); if (!f) - return -1; /* IRQ does not exist */ + return errno; if (old) ret = fscanf(f, "%jx", old); From 73c6ae1f71d11b56bd7c62c8702bce13637a5567 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 18:56:42 +0100 Subject: [PATCH 147/560] hwdef-parse: follow OR-gate merging DMA interrupts Also update JSON config with the new output. --- fpga/etc/fpga.json | 12 ++++++++++-- fpga/scripts/hwdef-parse.py | 33 +++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index 2abcdc136..fe29ef418 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -147,7 +147,11 @@ "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:1", "name": "S2MM" } - ] + ], + "irqs": { + "mm2s_introut": "pcie_0_axi_pcie_intc_0:3", + "s2mm_introut": "pcie_0_axi_pcie_intc_0:4" + } }, "hier_0_axi_dma_axi_dma_1": { "vlnv": "xilinx.com:ip:axi_dma:7.1", @@ -182,7 +186,11 @@ "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:6", "name": "S2MM" } - ] + ], + "irqs": { + "mm2s_introut": "pcie_0_axi_pcie_intc_0:3", + "s2mm_introut": "pcie_0_axi_pcie_intc_0:4" + } }, "hier_0_axi_fifo_mm_s_0": { "vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1", diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 7f0c57863..12d96e4f2 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -200,7 +200,7 @@ ports = concat.xpath('.//PORT[@DIR="I"]') for port in ports: name = port.get('NAME') signame = port.get('SIGNAME') - + # Skip unconnected IRQs if not signame: continue @@ -213,13 +213,34 @@ for port in ports: instance = ip.get('INSTANCE') vlnv = ip.get('VLNV') + modtype = ip.get('MODTYPE') - port = ip.xpath('.//PORT[@SIGNAME="{}" and @DIR="O"]'.format(signame))[0] - irqname = port.get('NAME') + originators = [] - if instance in ips: - irqs = ips[instance].setdefault('irqs', {}) - irqs[irqname] = '{}:{}'.format(intc.get('INSTANCE'), irq) + # follow one level of OR gates merging interrupts (may be generalized later) + if modtype == 'util_vector_logic': + logic_op = ip.xpath('.//PARAMETER[@NAME="C_OPERATION"]')[0] + if logic_op.get('VALUE') == 'or': + # hardware interrupts sharing the same IRQ at the controller + ports = ip.xpath('.//PORT[@DIR="I"]') + for port in ports: + signame = port.get('SIGNAME') + ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] + instance = ip.get('INSTANCE') + originators.append((instance, signame)) + else: + # consider this instance as originator + originators.append((instance, signame)) + + + for instance, signame in originators: + ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] + port = ip.xpath('.//PORT[@SIGNAME="{}" and @DIR="O"]'.format(signame))[0] + irqname = port.get('NAME') + + if instance in ips: + irqs = ips[instance].setdefault('irqs', {}) + irqs[irqname] = '{}:{}'.format(intc.get('INSTANCE'), irq) # Find BRAM storage depths (size) brams = root.xpath('.//MODULE[@MODTYPE="axi_bram_ctrl"]') From e8ef3e43808533322a7a23147461ce9702a953a8 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 19:54:18 +0100 Subject: [PATCH 148/560] lib/memory-manager: pass strings as const reference --- fpga/include/villas/memory_manager.hpp | 11 ++++++++--- fpga/lib/memory_manager.cpp | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 941fd43c0..04bf60bf6 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -152,7 +152,8 @@ public: /// Create a default mapping MappingId - createMapping(uintptr_t src, uintptr_t dest, size_t size, std::string name, + createMapping(uintptr_t src, uintptr_t dest, size_t size, + const std::string& name, AddressSpaceId fromAddrSpace, AddressSpaceId toAddrSpace); @@ -167,7 +168,7 @@ public: AddressSpaceId - findAddressSpace(std::string name); + findAddressSpace(const std::string& name); MemoryTranslation getTranslation(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId); @@ -177,9 +178,13 @@ public: { return getTranslation(getProcessAddressSpace(), foreignAddrSpaceId); } static std::string - getSlaveAddrSpaceName(std::string ipInstance, std::string memoryBlock) + getSlaveAddrSpaceName(const std::string& ipInstance, const std::string& memoryBlock) { return ipInstance + "/" + memoryBlock; } + static std::string + getMasterAddrSpaceName(const std::string& ipInstance, const std::string& busInterface) + { return ipInstance + ":" + busInterface; } + void dump() { memoryGraph.dump(); } diff --git a/fpga/lib/memory_manager.cpp b/fpga/lib/memory_manager.cpp index 55f7858b1..f8e01e9b0 100644 --- a/fpga/lib/memory_manager.cpp +++ b/fpga/lib/memory_manager.cpp @@ -39,7 +39,7 @@ MemoryManager::getOrCreateAddressSpace(std::string name) MemoryManager::MappingId MemoryManager::createMapping(uintptr_t src, uintptr_t dest, size_t size, - std::string name, + const std::string& name, MemoryManager::AddressSpaceId fromAddrSpace, MemoryManager::AddressSpaceId toAddrSpace) { @@ -62,7 +62,7 @@ MemoryManager::addMapping(std::shared_ptr mapping, } MemoryManager::AddressSpaceId -MemoryManager::findAddressSpace(std::string name) +MemoryManager::findAddressSpace(const std::string& name) { return memoryGraph.findVertex( [&](const std::shared_ptr& v) { From 5d99f11a3480573765ba7e3e76e03371dcef7dfc Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 19:56:29 +0100 Subject: [PATCH 149/560] lib/ip: move definition of getBaseAddr() back to header This is a one-liner, so IMO increases readability. --- fpga/include/villas/fpga/ip.hpp | 3 ++- fpga/lib/ip.cpp | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index b708ed1e4..e41cc1652 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -177,7 +177,8 @@ public: protected: uintptr_t - getBaseAddr(const std::string& block) const; + getBaseAddr(const std::string& block) const + { return getLocalAddr(block, 0); } uintptr_t getLocalAddr(const std::string& block, uintptr_t address) const; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 7b93320a8..375739a6e 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -328,13 +328,6 @@ IpCoreFactory::lookup(const Vlnv &vlnv) } -uintptr_t -IpCore::getBaseAddr(const std::string& block) const -{ - return getLocalAddr(block, 0); -} - - uintptr_t IpCore::getLocalAddr(const std::string& block, uintptr_t address) const { From 36259d00e0f70086c2db2667f1450834056d4fd9 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 19:57:47 +0100 Subject: [PATCH 150/560] lib/ip: save address space ids for each bus master interface --- fpga/include/villas/fpga/ip.hpp | 3 +++ fpga/lib/ip.cpp | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index e41cc1652..b35048e3f 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -208,6 +208,9 @@ protected: /// Cached translations from the process address space to each memory block std::map addressTranslations; + + /// AXI bus master interfaces to access memory somewhere + std::map busMasterInterfaces; }; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 375739a6e..b7950c7da 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -195,15 +195,25 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) if(json_is_object(json_memory_view)) { logger->debug("Parse memory view of {}", *ip); - // create a master address space because this IP has a memory view - const MemoryManager::AddressSpaceId myAddrSpaceId = - MemoryManager::get().getOrCreateAddressSpace(id.getName()); // now find all slave address spaces this master can access const char* bus_name; json_t* json_bus; json_object_foreach(json_memory_view, bus_name, json_bus) { + // this IP has a memory view => it is a bus master somewhere + + // assemble name for master address space + const std::string myAddrSpaceName = + MemoryManager::getMasterAddrSpaceName(ip->getInstanceName(), + bus_name); + // create a master address space + const MemoryManager::AddressSpaceId myAddrSpaceId = + MemoryManager::get().getOrCreateAddressSpace(myAddrSpaceName); + + ip->busMasterInterfaces[bus_name] = myAddrSpaceId; + + const char* instance_name; json_t* json_instance; json_object_foreach(json_bus, instance_name, json_instance) { From aa2b0b324f6cc1501a3f2bef8f8f4e70f7d7755c Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 13 Feb 2018 19:58:22 +0100 Subject: [PATCH 151/560] lib/ips/pcie: use cached address space id and supply interface to create mapping --- fpga/include/villas/fpga/ips/pcie.hpp | 3 +++ fpga/lib/ips/pcie.cpp | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/fpga/ips/pcie.hpp b/fpga/include/villas/fpga/ips/pcie.hpp index a63db2e20..290a8c577 100644 --- a/fpga/include/villas/fpga/ips/pcie.hpp +++ b/fpga/include/villas/fpga/ips/pcie.hpp @@ -48,6 +48,9 @@ public: friend class AxiPciExpressBridgeFactory; bool init(); + +private: + static constexpr char axiInterface[] = "M_AXI"; }; diff --git a/fpga/lib/ips/pcie.cpp b/fpga/lib/ips/pcie.cpp index df45d68cb..5b02f1261 100644 --- a/fpga/lib/ips/pcie.cpp +++ b/fpga/lib/ips/pcie.cpp @@ -38,12 +38,16 @@ static AxiPciExpressBridgeFactory factory; bool AxiPciExpressBridge::init() { + // Throw an exception if the is no bus master interface and thus no + // address space we can use for translation -> error + const MemoryManager::AddressSpaceId myAddrSpaceid = + busMasterInterfaces.at(axiInterface); + // Create an identity mapping from the FPGA card to this IP as an entry // point to all other IPs in the FPGA, because Vivado will generate a // memory view for this bridge that can see all others. - auto addrSpace = MemoryManager::get().findAddressSpace(getInstanceName()); MemoryManager::get().createMapping(0x00, 0x00, SIZE_MAX, "PCIeBridge", - card->addrSpaceId, addrSpace); + card->addrSpaceId, myAddrSpaceid); return true; } From 676fd9171ce893859e8b9bd396d6b8c9de762466 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 14 Feb 2018 16:03:06 +0100 Subject: [PATCH 152/560] lib/ip: make logger a class member of IpCore Logging is used everywhere and there's no justification of the clutter and runtime overhead of `aut logger = getLogger();` everywhere. --- fpga/include/villas/fpga/ip.hpp | 7 +++---- fpga/lib/ip.cpp | 6 +++--- fpga/lib/ip_node.cpp | 2 -- fpga/lib/ips/fifo.cpp | 2 -- fpga/lib/ips/intc.cpp | 2 -- fpga/lib/ips/switch.cpp | 8 -------- fpga/lib/ips/timer.cpp | 2 -- 7 files changed, 6 insertions(+), 23 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index b35048e3f..28f9e14f6 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -183,10 +183,6 @@ protected: uintptr_t getLocalAddr(const std::string& block, uintptr_t address) const; - SpdLogger - getLogger() const - { return loggerGetOrCreate(getInstanceName()); } - InterruptController* getInterruptController(const std::string& interruptName) const; @@ -197,6 +193,9 @@ protected: std::string description; }; + /// Specialized logger instance with the IPs name set as category + SpdLogger logger; + /// FPGA card this IP is instantiated on (populated by FpgaIpFactory) PCIeCard* card; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index b7950c7da..dfbc60001 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -142,6 +142,7 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) // setup generic IP type properties ip->card = card; ip->id = id; + ip->logger = loggerGetOrCreate(id.getName()); json_t* json_ip = json_object_get(json_ips, id.getName().c_str()); @@ -313,9 +314,8 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) void -IpCore::dump() { - auto logger = getLogger(); - +IpCore::dump() +{ logger->info("IP: {}", *this); for(auto& [num, irq] : irqs) { logger->info("IRQ {}: {}:{}", diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index 6f25ac7ca..2ecb3f168 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -97,8 +97,6 @@ IpNode::loopbackPossible() const bool IpNode::connectLoopback() { - auto logger = getLogger(); - auto ports = getLoopbackPorts(); const auto& portMaster = portsMaster[ports.first]; const auto& portSlave = portsSlave[ports.second]; diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 79e770b9c..2993d5fad 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -50,8 +50,6 @@ FifoFactory::FifoFactory() : bool Fifo::init() { - auto logger = getLogger(); - XLlFifo_Config fifo_cfg; try { diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 56d8f7877..fa8524ed5 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -50,7 +50,6 @@ bool InterruptController::init() { const uintptr_t base = getBaseAddr(registerMemory); - auto logger = getLogger(); num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); if (num_irqs < 0) @@ -97,7 +96,6 @@ InterruptController::init() bool InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool polling) { - auto logger = getLogger(); const uintptr_t base = getBaseAddr(registerMemory); /* Current state of INTC */ diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index d9055c2f8..3439f9fbe 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -38,8 +38,6 @@ static AxiStreamSwitchFactory factory; bool AxiStreamSwitch::init() { - auto logger = getLogger(); - /* Setup AXI-stream switch */ XAxis_Switch_Config sw_cfg; sw_cfg.MaxNumMI = num_ports; @@ -66,8 +64,6 @@ AxiStreamSwitch::init() bool AxiStreamSwitch::connect(int portSlave, int portMaster) { - auto logger = getLogger(); - if(portMapping[portMaster] == portSlave) { logger->debug("Ports already connected"); return true; @@ -98,8 +94,6 @@ AxiStreamSwitch::connect(int portSlave, int portMaster) bool AxiStreamSwitch::disconnectMaster(int port) { - auto logger = getLogger(); - logger->debug("Disconnect slave {} from master {}", portMapping[port], port); @@ -111,8 +105,6 @@ AxiStreamSwitch::disconnectMaster(int port) bool AxiStreamSwitch::disconnectSlave(int port) { - auto logger = getLogger(); - for(auto [master, slave] : portMapping) { if(slave == port) { logger->debug("Disconnect slave {} from master {}", slave, master); diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index 1c7a023fe..91e36ade3 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -39,8 +39,6 @@ static TimerFactory factory; bool Timer::init() { - auto logger = getLogger(); - XTmrCtr_Config xtmr_cfg; xtmr_cfg.SysClockFreqHz = getFrequency(); From 64a89c2981161e553431915b468579751ff8fed5 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 14 Feb 2018 16:45:41 +0100 Subject: [PATCH 153/560] lib/ip: add debug output for 2-stage initialization --- fpga/lib/ip.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index dfbc60001..41ca7c52e 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -106,7 +106,7 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) // configure all IPs for(auto& id : orderedIps) { - loggerStatic->info("Initializing {}", id); + loggerStatic->info("Configuring {}", id); // find the appropriate factory that can create the specified VLNV // Note: @@ -289,6 +289,8 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) ip->addressTranslations.emplace(memoryBlock, translation); } + loggerStatic->info("Initializing {}", *ip); + if(not ip->init()) { loggerStatic->error("Cannot start IP {}", *ip); continue; From b21d6ddb9d1671153d7548d3417e3b1e0db4f7be Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 11:10:56 +0200 Subject: [PATCH 154/560] src: do not compile C benchmarks anymore --- fpga/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index 4025ae5b9..ed8552fd2 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -11,4 +11,3 @@ include_directories(thirdparty/spdlog/include) add_subdirectory(lib) add_subdirectory(tests) -add_subdirectory(src) From ac483b21106a0d3a7537a1329d3f15d1564af0ab Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 14:31:51 +0200 Subject: [PATCH 155/560] scripts: fix non-root script * load IOMMU type 1 kernel module * determine IOMMU group dynamically * add user dkr to fpga group --- fpga/scripts/non_root.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) mode change 100644 => 100755 fpga/scripts/non_root.sh diff --git a/fpga/scripts/non_root.sh b/fpga/scripts/non_root.sh old mode 100644 new mode 100755 index 22e4f941d..a380ccf3a --- a/fpga/scripts/non_root.sh +++ b/fpga/scripts/non_root.sh @@ -1,16 +1,22 @@ #!/bin/bash -IOMMU_GROUP=24 +# PCI-e parameters of FPGA card PCI_BDF="0000:03:00.0" +PCI_VID="10ee" +PCI_PID="7022" modprobe vfio modprobe vfio_pci +modprobe vfio_iommu_type1 -echo "10ee 7022" > /sys/bus/pci/drivers/vfio-pci/new_id -echo ${PCI_BDF} > /sys/bus/pci/drivers/vfio-pci/bind +IOMMU_GROUP=`basename $(readlink /sys/bus/pci/devices/${PCI_BDF}/iommu_group)` + +# bind to vfio driver +echo "${PCI_VID} ${PCI_PID}" > /sys/bus/pci/drivers/vfio-pci/new_id groupadd -f fpga usermod -G fpga -a svg +usermod -G fpga -a dkr chgrp fpga /dev/vfio/${IOMMU_GROUP} chmod g+rw /dev/vfio/${IOMMU_GROUP} From b01a50184c3677a522e9b46505f1d04bf3ecf1bf Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 15:39:26 +0200 Subject: [PATCH 156/560] kernel/vfio: port to C++ This commit is 1/2 of a series of patches and not working on its own. --- fpga/include/villas/fpga/card.hpp | 11 +- fpga/include/villas/kernel/vfio.h | 123 ----- fpga/include/villas/kernel/vfio.hpp | 153 ++++++ fpga/lib/CMakeLists.txt | 17 +- fpga/lib/card.cpp | 34 +- fpga/lib/ips/intc.cpp | 13 +- fpga/lib/kernel/vfio.c | 652 ----------------------- fpga/lib/kernel/vfio.cpp | 766 ++++++++++++++++++++++++++++ fpga/tests/fpga.cpp | 12 +- 9 files changed, 954 insertions(+), 827 deletions(-) delete mode 100644 fpga/include/villas/kernel/vfio.h create mode 100644 fpga/include/villas/kernel/vfio.hpp delete mode 100644 fpga/lib/kernel/vfio.c create mode 100644 fpga/lib/kernel/vfio.cpp diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index 4676e0b58..f8f9a0128 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -35,7 +35,7 @@ #include "common.h" #include "kernel/pci.h" -#include "kernel/vfio.h" +#include "kernel/vfio.hpp" #include #include @@ -91,8 +91,11 @@ public: struct pci *pci; struct pci_device filter; /**< Filter for PCI device. */ - ::vfio_container *vfio_container; - struct vfio_device vfio_device; /**< VFIO device handle. */ + /// The VFIO container that this card is part of + std::shared_ptr vfioContainer; + + /// The VFIO device that represents this card + VfioDevice* vfioDevice; /// Address space identifier of the master address space of this FPGA card. /// This will be used for address resolution of all IPs on this card. @@ -116,7 +119,7 @@ public: Plugin(Plugin::Type::FpgaCard, "FPGA Card plugin") {} static CardList - make(json_t *json, struct pci* pci, ::vfio_container* vc); + make(json_t *json, struct pci* pci, std::shared_ptr vc); static PCIeCard* create(); diff --git a/fpga/include/villas/kernel/vfio.h b/fpga/include/villas/kernel/vfio.h deleted file mode 100644 index 35a2465f9..000000000 --- a/fpga/include/villas/kernel/vfio.h +++ /dev/null @@ -1,123 +0,0 @@ -/** Virtual Function IO wrapper around kernel API - * - * @file - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - *********************************************************************************/ - -/** @addtogroup fpga Kernel @{ */ - -#pragma once - -#include -#include -#include - -#include -#include - -#include "list.h" - -#define VFIO_DEV(x) "/dev/vfio/" x - -#ifdef __cplusplus -extern "C" { -#endif - -/* Forward declarations */ -struct pci_device; - -struct vfio_group { - int fd; /**< VFIO group file descriptor */ - int index; /**< Index of the IOMMU group as listed under /sys/kernel/iommu_groups/ */ - - struct vfio_group_status status; /**< Status of group */ - - struct list devices; - - struct vfio_container *container; /**< The VFIO container to which this group is belonging */ -}; - -struct vfio_device { - char *name; /**< Name of the device as listed under /sys/kernel/iommu_groups/[vfio_group::index]/devices/ */ - int fd; /**< VFIO device file descriptor */ - - struct vfio_device_info info; - struct vfio_irq_info *irqs; - struct vfio_region_info *regions; - - void **mappings; - - struct pci_device *pci_device; /**< libpci handle of the device */ - struct vfio_group *group; /**< The VFIO group this device belongs to */ -}; - -struct vfio_container { - int fd; - int version; - int extensions; - - uint64_t iova_next; /**< Next free IOVA address */ - - struct list groups; -}; - -/** Initialize a new VFIO container. */ -int vfio_init(struct vfio_container *c); - -/** Initialize a VFIO group and attach it to an existing VFIO container. */ -int vfio_group_attach(struct vfio_group *g, struct vfio_container *c, int index); - -/** Initialize a VFIO device, lookup the VFIO group it belongs to, create the group if not already existing. */ -int vfio_device_attach(struct vfio_device *d, struct vfio_container *c, const char *name, int index); - -/** Initialie a VFIO-PCI device (uses vfio_device_attach() internally) */ -int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_device *pdev); - -/** Hot resets a VFIO-PCI device */ -int vfio_pci_reset(struct vfio_device *d); - -int vfio_pci_msi_init(struct vfio_device *d, int efds[32]); - -int vfio_pci_msi_deinit(struct vfio_device *d, int efds[32]); - -int vfio_pci_msi_find(struct vfio_device *d, int nos[32]); - -/** Enable memory accesses and bus mastering for PCI device */ -int vfio_pci_enable(struct vfio_device *d); - -/** Reset a VFIO device */ -int vfio_device_reset(struct vfio_device *d); - -/** Release memory and close container */ -int vfio_destroy(struct vfio_container *c); - -/** Release memory of group */ -int vfio_group_destroy(struct vfio_group *g); - -/** Release memory of device */ -int vfio_device_destroy(struct vfio_device *g); - -/** Print a dump of all attached groups and their devices including regions and IRQs */ -void vfio_dump(struct vfio_container *c); - -/** Map a device memory region to the application address space (e.g. PCI BARs) */ -void * vfio_map_region(struct vfio_device *d, int idx); - -/** Get the size of a device memory region */ -size_t vfio_region_size(struct vfio_device *d, int idx); - -/** Map VM to an IOVA, which is accessible by devices in the container */ -int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); - -/** Unmap DMA memory */ -int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); - -/** munmap() a region which has been mapped by vfio_map_region() */ -int vfio_unmap_region(struct vfio_device *d, int idx); - -#ifdef __cplusplus -} -#endif - -/** @} */ diff --git a/fpga/include/villas/kernel/vfio.hpp b/fpga/include/villas/kernel/vfio.hpp new file mode 100644 index 000000000..9f794baf5 --- /dev/null +++ b/fpga/include/villas/kernel/vfio.hpp @@ -0,0 +1,153 @@ +/** Virtual Function IO wrapper around kernel API + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @copyright 2018, Daniel Krebs + *********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include +#include +#include + +#include +#include + +#define VFIO_DEV(x) "/dev/vfio/" x + +/* Forward declarations */ +struct pci_device; + +namespace villas { + +class VfioContainer; +class VfioGroup; + + +class VfioDevice { + friend class VfioContainer; +public: + VfioDevice(const std::string& name, VfioGroup& group) : + name(name), group(group) {} + + ~VfioDevice(); + + bool reset(); + + /** Map a device memory region to the application address space (e.g. PCI BARs) */ + void* regionMap(size_t index); + + /** munmap() a region which has been mapped by vfio_map_region() */ + bool regionUnmap(size_t index); + + /** Get the size of a device memory region */ + size_t regionGetSize(size_t index); + + + /** Enable memory accesses and bus mastering for PCI device */ + bool pciEnable(); + + bool pciHotReset(); + int pciMsiInit(int efds[32]); + int pciMsiDeinit(int efds[32]); + bool pciMsiFind(int nos[32]); + + bool isVfioPciDevice() const; + +private: + /// Name of the device as listed under + /// /sys/kernel/iommu_groups/[vfio_group::index]/devices/ + std::string name; + + /// VFIO device file descriptor + int fd; + + struct vfio_device_info info; + + std::vector irqs; + std::vector regions; + std::vector mappings; + + /**< libpci handle of the device */ + const struct pci_device *pci_device; + + VfioGroup& group; /**< The VFIO group this device belongs to */ +}; + + + +class VfioGroup { + friend class VfioContainer; + friend VfioDevice; +private: + VfioGroup(int index) : fd(-1), index(index) {} +public: + ~VfioGroup(); + + static std::unique_ptr + attach(int containerFd, int groupIndex); + +private: + /// VFIO group file descriptor + int fd; + + /// Index of the IOMMU group as listed under /sys/kernel/iommu_groups/ + int index; + + /// Status of group + struct vfio_group_status status; + + /// All devices owned by this group + std::list> devices; + + VfioContainer* container; /**< The VFIO container to which this group is belonging */ +}; + + +class VfioContainer { +private: + VfioContainer(); +public: + ~VfioContainer(); + + static std::shared_ptr + create(); + + void dump(); + + VfioDevice& attachDevice(const char *name, int groupIndex); + VfioDevice& attachDevice(const struct pci_device *pdev); + + /** + * @brief Map VM to an IOVA, which is accessible by devices in the container + * @param virt virtual address of memory + * @param phys IOVA where to map @p virt, -1 to use VFIO internal allocator + * @param length size of memory region in bytes + * @return IOVA address, UINTPTR_MAX on failure + */ + uintptr_t memoryMap(uintptr_t virt, uintptr_t phys, size_t length); + + /** munmap() a region which has been mapped by vfio_map_region() */ + bool memoryUnmap(uintptr_t phys, size_t length); + +private: + VfioGroup& getOrAttachGroup(int index); + +private: + int fd; + int version; + int extensions; + uint64_t iova_next; /**< Next free IOVA address */ + + /// All groups bound to this container + std::list> groups; +}; + +/** @} */ + +} // namespace villas diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 50d16ecf5..7150b61eb 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,23 +1,9 @@ set(SOURCES - ip.c - vlnv.c - card.c - vlnv.cpp card.cpp ip.cpp ip_node.cpp - ips/timer.c - ips/model.c - ips/switch.c - ips/dft.c - ips/fifo.c - ips/dma.c - ips/intc.c - ips/gpio.c - ips/rtds_axis.c - ips/timer.cpp ips/switch.cpp ips/fifo.cpp @@ -26,9 +12,8 @@ set(SOURCES kernel/kernel.c kernel/pci.c - kernel/vfio.c + kernel/vfio.cpp - plugin.c utils.c list.c log.c diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index c4a184fc9..e0e2ba456 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -27,7 +27,7 @@ #include "log.hpp" #include "kernel/pci.h" -#include "kernel/vfio.h" +#include "kernel/vfio.hpp" #include "fpga/ip.hpp" #include "fpga/card.hpp" @@ -38,9 +38,8 @@ namespace fpga { // instantiate factory to register static PCIeCardFactory PCIeCardFactory; - CardList -fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) +PCIeCardFactory::make(json_t *json, struct pci* pci, std::shared_ptr vc) { CardList cards; auto logger = getStaticLogger(); @@ -73,7 +72,7 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc) // populate generic properties card->name = std::string(card_name); card->pci = pci; - card->vfio_container = vc; + card->vfioContainer = std::move(vc); card->affinity = affinity; card->do_reset = do_reset != 0; @@ -158,19 +157,26 @@ fpga::PCIeCard::init() } /* Attach PCIe card to VFIO container */ - ret = ::vfio_pci_attach(&vfio_device, vfio_container, pdev); - if (ret) { - logger->error("Failed to attach VFIO device"); + VfioDevice& device = vfioContainer->attachDevice(pdev); + this->vfioDevice = &device; + + + /* Enable memory access and PCI bus mastering for DMA */ + if (not device.pciEnable()) { + logger->error("Failed to enable PCI device"); return false; } /* Map PCIe BAR */ - const void* bar0_mapped = vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX); + const void* bar0_mapped = vfioDevice->regionMap(VFIO_PCI_BAR0_REGION_INDEX); if (bar0_mapped == MAP_FAILED) { logger->error("Failed to mmap() BAR0"); return false; } + // determine size of BAR0 region + const size_t bar0_size = vfioDevice->regionGetSize(VFIO_PCI_BAR0_REGION_INDEX); + /* Link mapped BAR0 to global memory graph */ @@ -180,9 +186,6 @@ fpga::PCIeCard::init() // create a new address space for this FPGA card this->addrSpaceId = MemoryManager::get().getOrCreateAddressSpace(name); - // determine size of BAR0 region - const size_t bar0_size = vfio_region_size(&vfio_device, - VFIO_PCI_BAR0_REGION_INDEX); // create a mapping from our address space to the FPGA card via vfio MemoryManager::get().createMapping(reinterpret_cast(bar0_mapped), @@ -190,18 +193,11 @@ fpga::PCIeCard::init() villasAddrSpace, this->addrSpaceId); - /* Enable memory access and PCI bus mastering for DMA */ - ret = vfio_pci_enable(&vfio_device); - if (ret) { - logger->error("Failed to enable PCI device"); - return false; - } /* Reset system? */ if (do_reset) { /* Reset / detect PCI device */ - ret = vfio_pci_reset(&vfio_device); - if (ret) { + if(not vfioDevice->pciHotReset()) { logger->error("Failed to reset PCI device"); return false; } diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index fa8524ed5..3fbad436d 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -27,7 +27,7 @@ #include "log.h" #include "plugin.hpp" -#include "kernel/vfio.h" +#include "kernel/vfio.hpp" #include "kernel/kernel.h" #include "fpga/card.hpp" @@ -43,7 +43,7 @@ static InterruptControllerFactory factory; InterruptController::~InterruptController() { - vfio_pci_msi_deinit(&card->vfio_device , this->efds); + card->vfioDevice->pciMsiDeinit(this->efds); } bool @@ -51,12 +51,13 @@ InterruptController::init() { const uintptr_t base = getBaseAddr(registerMemory); - num_irqs = vfio_pci_msi_init(&card->vfio_device, efds); + num_irqs = card->vfioDevice->pciMsiInit(efds); if (num_irqs < 0) return false; - if(vfio_pci_msi_find(&card->vfio_device, nos) != 0) + if(not card->vfioDevice->pciMsiFind(nos)) { return false; + } /* For each IRQ */ for (int i = 0; i < num_irqs; i++) { @@ -69,8 +70,8 @@ InterruptController::init() // everything is fine break; case EACCES: - logger->warn("No permission to change affinity of VFIO-MSI interrupt. " - "This may degrade performance (increasing jitter)"); + logger->warn("No permission to change affinity of VFIO-MSI interrupt, " + "performance may be degraded!"); break; default: logger->error("Failed to change affinity of VFIO-MSI interrupt"); diff --git a/fpga/lib/kernel/vfio.c b/fpga/lib/kernel/vfio.c deleted file mode 100644 index 7103cc519..000000000 --- a/fpga/lib/kernel/vfio.c +++ /dev/null @@ -1,652 +0,0 @@ -/** Virtual Function IO wrapper around kernel API - * - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -#define _DEFAULT_SOURCE - -#include -#include -#include -#include -#include - -#include -#include - -#include "utils.h" -#include "log.h" - -#include "config.h" - -#include "kernel/kernel.h" -#include "kernel/vfio.h" -#include "kernel/pci.h" - -static const char *vfio_pci_region_names[] = { - "PCI_BAR0", // VFIO_PCI_BAR0_REGION_INDEX, - "PCI_BAR1", // VFIO_PCI_BAR1_REGION_INDEX, - "PCI_BAR2", // VFIO_PCI_BAR2_REGION_INDEX, - "PCI_BAR3", // VFIO_PCI_BAR3_REGION_INDEX, - "PCI_BAR4", // VFIO_PCI_BAR4_REGION_INDEX, - "PCI_BAR5", // VFIO_PCI_BAR5_REGION_INDEX, - "PCI_ROM", // VFIO_PCI_ROM_REGION_INDEX, - "PCI_CONFIG", // VFIO_PCI_CONFIG_REGION_INDEX, - "PCI_VGA" // VFIO_PCI_INTX_IRQ_INDEX, -}; - -static const char *vfio_pci_irq_names[] = { - "PCI_INTX", // VFIO_PCI_INTX_IRQ_INDEX, - "PCI_MSI", // VFIO_PCI_MSI_IRQ_INDEX, - "PCI_MSIX", // VFIO_PCI_MSIX_IRQ_INDEX, - "PCI_ERR", // VFIO_PCI_ERR_IRQ_INDEX, - "PCI_REQ" // VFIO_PCI_REQ_IRQ_INDEX, -}; - -/* Helpers */ -int vfio_get_iommu_name(int index, char *buf, size_t len) -{ - FILE *f; - char fn[256]; - - snprintf(fn, sizeof(fn), "/sys/kernel/iommu_groups/%d/name", index); - - f = fopen(fn, "r"); - if (!f) - return -1; - - int ret = fgets(buf, len, f) == buf ? 0 : -1; - - /* Remove trailing newline */ - char *c = strrchr(buf, '\n'); - if (c) - *c = 0; - - fclose(f); - - return ret; -} - -/* Destructors */ -int vfio_destroy(struct vfio_container *v) -{ - int ret; - - /* Release memory and close fds */ - list_destroy(&v->groups, (dtor_cb_t) vfio_group_destroy, true); - - /* Close container */ - ret = close(v->fd); - if (ret < 0) - return -1; - - debug(5, "VFIO: closed container: fd=%d", v->fd); - - return 0; -} - -int vfio_group_destroy(struct vfio_group *g) -{ - int ret; - - list_destroy(&g->devices, (dtor_cb_t) vfio_device_destroy, false); - - ret = ioctl(g->fd, VFIO_GROUP_UNSET_CONTAINER); - if (ret) - return ret; - - debug(5, "VFIO: released group from container: group=%u", g->index); - - ret = close(g->fd); - if (ret) - return ret; - - debug(5, "VFIO: closed group: group=%u, fd=%d", g->index, g->fd); - - return 0; -} - -int vfio_device_destroy(struct vfio_device *d) -{ - int ret; - - for (int i = 0; i < d->info.num_regions; i++) - vfio_unmap_region(d, i); - - ret = close(d->fd); - if (ret) - return ret; - - debug(5, "VFIO: closed device: name=%s, fd=%d", d->name, d->fd); - - free(d->mappings); - free(d->name); - - return 0; -} - -/* Constructors */ -int vfio_init(struct vfio_container *v) -{ - int ret; - - /* Initialize datastructures */ - memset(v, 0, sizeof(*v)); - - list_init(&v->groups); - - /* Load VFIO kernel module */ - if (kernel_module_load("vfio")) - error("Failed to load kernel module: %s", "vfio"); - - /* Open VFIO API */ - v->fd = open(VFIO_DEV("vfio"), O_RDWR); - if (v->fd < 0) - error("Failed to open VFIO container"); - - /* Check VFIO API version */ - v->version = ioctl(v->fd, VFIO_GET_API_VERSION); - if (v->version < 0 || v->version != VFIO_API_VERSION) - error("Failed to get VFIO version"); - - /* Check available VFIO extensions (IOMMU types) */ - v->extensions = 0; - for (int i = 1; i < VFIO_DMA_CC_IOMMU; i++) { - ret = ioctl(v->fd, VFIO_CHECK_EXTENSION, i); - if (ret < 0) - error("Failed to get VFIO extensions"); - else if (ret > 0) - v->extensions |= (1 << i); - } - - return 0; -} - -int vfio_group_attach(struct vfio_group *g, struct vfio_container *c, int index) -{ - int ret; - char buf[128]; - - g->index = index; - g->container = c; - - list_init(&g->devices); - - /* Open group fd */ - snprintf(buf, sizeof(buf), VFIO_DEV("%u"), g->index); - g->fd = open(buf, O_RDWR); - if (g->fd < 0) - serror("Failed to open VFIO group: %u", g->index); - - /* Claim group ownership */ - ret = ioctl(g->fd, VFIO_GROUP_SET_CONTAINER, &c->fd); - if (ret < 0) - serror("Failed to attach VFIO group to container"); - - /* Set IOMMU type */ - ret = ioctl(c->fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); - if (ret < 0) - serror("Failed to set IOMMU type of container"); - - /* Check group viability and features */ - g->status.argsz = sizeof(g->status); - - ret = ioctl(g->fd, VFIO_GROUP_GET_STATUS, &g->status); - if (ret < 0) - serror("Failed to get VFIO group status"); - - if (!(g->status.flags & VFIO_GROUP_FLAGS_VIABLE)) - error("VFIO group is not available: bind all devices to the VFIO driver!"); - - list_push(&c->groups, g); - - return 0; -} - -int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_device *pdev) -{ - char name[32]; - int ret; - - /* Load PCI bus driver for VFIO */ - if (kernel_module_load("vfio_pci")) - error("Failed to load kernel driver: %s", "vfio_pci"); - - /* Bind PCI card to vfio-pci driver if not already bound */ - ret = pci_get_driver(pdev, name, sizeof(name)); - if (ret || strcmp(name, "vfio-pci")) { - ret = pci_attach_driver(pdev, "vfio-pci"); - if (ret) - error("Failed to attach device to driver"); - } - - /* Get IOMMU group of device */ - int index = pci_get_iommu_group(pdev); - if (index < 0) - error("Failed to get IOMMU group of device"); - - /* VFIO device name consists of PCI BDF */ - snprintf(name, sizeof(name), "%04x:%02x:%02x.%x", pdev->slot.domain, pdev->slot.bus, pdev->slot.device, pdev->slot.function); - - ret = vfio_device_attach(d, c, name, index); - if (ret < 0) - return ret; - - /* Check if this is really a vfio-pci device */ - if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) { - vfio_device_destroy(d); - return -1; - } - - d->pci_device = pdev; - - return 0; -} - -int vfio_device_attach(struct vfio_device *d, struct vfio_container *c, const char *name, int index) -{ - int ret; - struct vfio_group *g = NULL; - - /* Check if group already exists */ - for (size_t i = 0; i < list_length(&c->groups); i++) { - struct vfio_group *h = (struct vfio_group *) list_at(&c->groups, i); - - if (h->index == index) - g = h; - } - - if (!g) { - g = alloc(sizeof(struct vfio_group)); - - /* Aquire group ownership */ - ret = vfio_group_attach(g, c, index); - if (ret) - error("Failed to attach to IOMMU group: %u", index); - - info("Attached new group %u to VFIO container", g->index); - } - - d->group = g; - d->name = strdup(name); - - /* Open device fd */ - d->fd = ioctl(g->fd, VFIO_GROUP_GET_DEVICE_FD, d->name); - if (d->fd < 0) - serror("Failed to open VFIO device: %s", d->name); - - /* Get device info */ - d->info.argsz = sizeof(d->info); - - ret = ioctl(d->fd, VFIO_DEVICE_GET_INFO, &d->info); - if (ret < 0) - serror("Failed to get VFIO device info for: %s", d->name); - - d->irqs = alloc(d->info.num_irqs * sizeof(struct vfio_irq_info)); - d->regions = alloc(d->info.num_regions * sizeof(struct vfio_region_info)); - d->mappings = alloc(d->info.num_regions * sizeof(void *)); - - /* Get device regions */ - for (int i = 0; i < d->info.num_regions && i < 8; i++) { - struct vfio_region_info *region = &d->regions[i]; - - region->argsz = sizeof(*region); - region->index = i; - - ret = ioctl(d->fd, VFIO_DEVICE_GET_REGION_INFO, region); - if (ret < 0) - serror("Failed to get regions of VFIO device: %s", d->name); - } - - /* Get device irqs */ - for (int i = 0; i < d->info.num_irqs; i++) { - struct vfio_irq_info *irq = &d->irqs[i]; - - irq->argsz = sizeof(*irq); - irq->index = i; - - ret = ioctl(d->fd, VFIO_DEVICE_GET_IRQ_INFO, irq); - if (ret < 0) - serror("Failed to get IRQs of VFIO device: %s", d->name); - } - - list_push(&d->group->devices, d); - - return 0; -} - -int vfio_pci_reset(struct vfio_device *d) -{ - int ret; - - /* Check if this is really a vfio-pci device */ - if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) - return -1; - - size_t reset_infolen = sizeof(struct vfio_pci_hot_reset_info) + sizeof(struct vfio_pci_dependent_device) * 64; - size_t resetlen = sizeof(struct vfio_pci_hot_reset) + sizeof(int32_t) * 1; - - struct vfio_pci_hot_reset_info *reset_info = (struct vfio_pci_hot_reset_info *) alloc(reset_infolen); - struct vfio_pci_hot_reset *reset = (struct vfio_pci_hot_reset *) alloc(resetlen); - - reset_info->argsz = reset_infolen; - reset->argsz = resetlen; - - ret = ioctl(d->fd, VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, reset_info); - if (ret) - return ret; - - debug(5, "VFIO: dependent devices for hot-reset:"); - for (int i = 0; i < reset_info->count; i++) { INDENT - struct vfio_pci_dependent_device *dd = &reset_info->devices[i]; - debug(5, "%04x:%02x:%02x.%01x: iommu_group=%u", dd->segment, dd->bus, PCI_SLOT(dd->devfn), PCI_FUNC(dd->devfn), dd->group_id); - - if (dd->group_id != d->group->index) - return -3; - } - - reset->count = 1; - reset->group_fds[0] = d->group->fd; - - ret = ioctl(d->fd, VFIO_DEVICE_PCI_HOT_RESET, reset); - - free(reset_info); - - return ret; -} - -int vfio_pci_msi_find(struct vfio_device *d, int nos[32]) -{ - int ret, idx, irq; - char *end, *col, *last, line[1024], name[13]; - FILE *f; - - f = fopen("/proc/interrupts", "r"); - if (!f) - return -1; - - for (int i = 0; i < 32; i++) - nos[i] = -1; - - /* For each line in /proc/interruipts */ - while (fgets(line, sizeof(line), f)) { - col = strtok(line, " "); - - /* IRQ number is in first column */ - irq = strtol(col, &end, 10); - if (col == end) - continue; - - /* Find last column of line */ - do { - last = col; - } while ((col = strtok(NULL, " "))); - - - ret = sscanf(last, "vfio-msi[%u](%12[0-9:])", &idx, name); - if (ret == 2) { - if (strstr(d->name, name) == d->name) - nos[idx] = irq; - } - } - - fclose(f); - - return 0; -} - -int vfio_pci_msi_deinit(struct vfio_device *d, int efds[32]) -{ - int ret, irq_setlen, irq_count = d->irqs[VFIO_PCI_MSI_IRQ_INDEX].count; - struct vfio_irq_set *irq_set; - - /* Check if this is really a vfio-pci device */ - if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) - return -1; - - irq_setlen = sizeof(struct vfio_irq_set) + sizeof(int) * irq_count; - irq_set = alloc(irq_setlen); - - irq_set->argsz = irq_setlen; - irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; - irq_set->index = VFIO_PCI_MSI_IRQ_INDEX; - irq_set->count = irq_count; - irq_set->start = 0; - - for (int i = 0; i < irq_count; i++) { - close(efds[i]); - efds[i] = -1; - } - - memcpy(irq_set->data, efds, sizeof(int) * irq_count); - - ret = ioctl(d->fd, VFIO_DEVICE_SET_IRQS, irq_set); - if (ret) - return -4; - - free(irq_set); - - return irq_count; -} - -int vfio_pci_msi_init(struct vfio_device *d, int efds[32]) -{ - int ret, irq_setlen, irq_count = d->irqs[VFIO_PCI_MSI_IRQ_INDEX].count; - struct vfio_irq_set *irq_set; - - /* Check if this is really a vfio-pci device */ - if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) - return -1; - - irq_setlen = sizeof(struct vfio_irq_set) + sizeof(int) * irq_count; - irq_set = alloc(irq_setlen); - - irq_set->argsz = irq_setlen; - irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; - irq_set->index = VFIO_PCI_MSI_IRQ_INDEX; - irq_set->start = 0; - irq_set->count = irq_count; - - /* Now set the new eventfds */ - for (int i = 0; i < irq_count; i++) { - efds[i] = eventfd(0, 0); - if (efds[i] < 0) - return -3; - } - memcpy(irq_set->data, efds, sizeof(int) * irq_count); - - ret = ioctl(d->fd, VFIO_DEVICE_SET_IRQS, irq_set); - if (ret) - return -4; - - free(irq_set); - - return irq_count; -} - -int vfio_pci_enable(struct vfio_device *d) -{ - int ret; - uint32_t reg; - off_t offset = ((off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40) + PCI_COMMAND; - - /* Check if this is really a vfio-pci device */ - if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) - return -1; - - ret = pread(d->fd, ®, sizeof(reg), offset); - if (ret != sizeof(reg)) - return -1; - - /* Enable memory access and PCI bus mastering which is required for DMA */ - reg |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; - - ret = pwrite(d->fd, ®, sizeof(reg), offset); - if (ret != sizeof(reg)) - return -1; - - return 0; -} - -int vfio_device_reset(struct vfio_device *d) -{ - if (d->info.flags & VFIO_DEVICE_FLAGS_RESET) - return ioctl(d->fd, VFIO_DEVICE_RESET); - else - return -1; /* not supported by this device */ -} - -void vfio_dump(struct vfio_container *v) -{ - info("VFIO Version: %u", v->version); - info("VFIO Extensions: %#x", v->extensions); - - for (size_t i = 0; i < list_length(&v->groups); i++) { - struct vfio_group *g = (struct vfio_group *) list_at(&v->groups, i); - - info("VFIO Group %u, viable=%u, container=%d", g->index, - (g->status.flags & VFIO_GROUP_FLAGS_VIABLE) > 0, - (g->status.flags & VFIO_GROUP_FLAGS_CONTAINER_SET) > 0 - ); - - - for (size_t i = 0; i < list_length(&g->devices); i++) { INDENT - struct vfio_device *d = (struct vfio_device *) list_at(&g->devices, i); - - info("Device %s: regions=%u, irqs=%u, flags=%#x", d->name, - d->info.num_regions, - d->info.num_irqs, - d->info.flags - ); - - for (int i = 0; i < d->info.num_regions && i < 8; i++) { INDENT - struct vfio_region_info *region = &d->regions[i]; - - if (region->size > 0) - info("Region %u %s: size=%#llx, offset=%#llx, flags=%u", - region->index, (d->info.flags & VFIO_DEVICE_FLAGS_PCI) ? vfio_pci_region_names[i] : "", - region->size, - region->offset, - region->flags - ); - } - - for (int i = 0; i < d->info.num_irqs; i++) { INDENT - struct vfio_irq_info *irq = &d->irqs[i]; - - if (irq->count > 0) - info("IRQ %u %s: count=%u, flags=%u", - irq->index, (d->info.flags & VFIO_DEVICE_FLAGS_PCI ) ? vfio_pci_irq_names[i] : "", - irq->count, - irq->flags - ); - } - } - } -} - -void * vfio_map_region(struct vfio_device *d, int idx) -{ - struct vfio_region_info *r = &d->regions[idx]; - - if (!(r->flags & VFIO_REGION_INFO_FLAG_MMAP)) - return MAP_FAILED; - - d->mappings[idx] = mmap(NULL, r->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_32BIT, d->fd, r->offset); - - return d->mappings[idx]; -} - -size_t vfio_region_size(struct vfio_device *d, int idx) -{ - assert(d != NULL); - assert((size_t)idx < d->info.num_regions); - - return d->regions[idx].size; -} - -int vfio_unmap_region(struct vfio_device *d, int idx) -{ - int ret; - struct vfio_region_info *r = &d->regions[idx]; - - if (!d->mappings[idx]) - return -1; /* was not mapped */ - - debug(3, "VFIO: unmap region %u from device", idx); - - ret = munmap(d->mappings[idx], r->size); - if (ret) - return -2; - - d->mappings[idx] = NULL; - - return 0; -} - -int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len) -{ - int ret; - - if (len & 0xFFF) { - len += 0x1000; - len &= ~0xFFF; - } - - /* Super stupid allocator */ - if (phys == -1) { - phys = c->iova_next; - c->iova_next += len; - } - - struct vfio_iommu_type1_dma_map dma_map = { - .argsz = sizeof(struct vfio_iommu_type1_dma_map), - .vaddr = virt, - .iova = phys, - .size = len, - .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE - }; - - ret = ioctl(c->fd, VFIO_IOMMU_MAP_DMA, &dma_map); - if (ret) - serror("Failed to create DMA mapping"); - - info("DMA map size=%#llx, iova=%#llx, vaddr=%#llx", dma_map.size, dma_map.iova, dma_map.vaddr); - - return 0; -} - -int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len) -{ - int ret; - - struct vfio_iommu_type1_dma_unmap dma_unmap = { - .argsz = sizeof(struct vfio_iommu_type1_dma_unmap), - .flags = 0, - .iova = phys, - .size = len, - }; - - ret = ioctl(c->fd, VFIO_IOMMU_UNMAP_DMA, &dma_unmap); - if (ret) - serror("Failed to unmap DMA mapping"); - - return 0; -} diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp new file mode 100644 index 000000000..09464b1f8 --- /dev/null +++ b/fpga/lib/kernel/vfio.cpp @@ -0,0 +1,766 @@ +/** Virtual Function IO wrapper around kernel API + * + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017, Steffen Vogel + * @copyright 2018, Daniel Krebs + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +#define _DEFAULT_SOURCE + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "kernel/pci.h" +#include "kernel/kernel.h" + +#include "kernel/vfio.hpp" +#include "log.hpp" + +static auto logger = loggerGetOrCreate("Vfio"); + +static const char *vfio_pci_region_names[] = { + "PCI_BAR0", // VFIO_PCI_BAR0_REGION_INDEX, + "PCI_BAR1", // VFIO_PCI_BAR1_REGION_INDEX, + "PCI_BAR2", // VFIO_PCI_BAR2_REGION_INDEX, + "PCI_BAR3", // VFIO_PCI_BAR3_REGION_INDEX, + "PCI_BAR4", // VFIO_PCI_BAR4_REGION_INDEX, + "PCI_BAR5", // VFIO_PCI_BAR5_REGION_INDEX, + "PCI_ROM", // VFIO_PCI_ROM_REGION_INDEX, + "PCI_CONFIG", // VFIO_PCI_CONFIG_REGION_INDEX, + "PCI_VGA" // VFIO_PCI_INTX_IRQ_INDEX, +}; + +static const char *vfio_pci_irq_names[] = { + "PCI_INTX", // VFIO_PCI_INTX_IRQ_INDEX, + "PCI_MSI", // VFIO_PCI_MSI_IRQ_INDEX, + "PCI_MSIX", // VFIO_PCI_MSIX_IRQ_INDEX, + "PCI_ERR", // VFIO_PCI_ERR_IRQ_INDEX, + "PCI_REQ" // VFIO_PCI_REQ_IRQ_INDEX, +}; + +namespace villas { + + +VfioContainer::VfioContainer() + : iova_next(0) +{ + /* Load VFIO kernel module */ + if (kernel_module_load("vfio") != 0) { + logger->error("Failed to load kernel module: vfio"); + throw std::exception(); + } + + /* Open VFIO API */ + fd = open(VFIO_DEV("vfio"), O_RDWR); + if (fd < 0) { + logger->error("Failed to open VFIO container"); + throw std::exception(); + } + + /* Check VFIO API version */ + version = ioctl(fd, VFIO_GET_API_VERSION); + if (version < 0 || version != VFIO_API_VERSION) { + logger->error("Failed to get VFIO version"); + throw std::exception(); + } + + /* Check available VFIO extensions (IOMMU types) */ + extensions = 0; + for (int i = 1; i < VFIO_DMA_CC_IOMMU; i++) { + int ret = ioctl(fd, VFIO_CHECK_EXTENSION, i); + if (ret < 0) { + logger->error("Failed to get VFIO extensions"); + throw std::exception(); + } + else if (ret > 0) + extensions |= (1 << i); + } + + logger->debug("Version: {:#x}", version); + logger->debug("Extensions: {:#x}", extensions); +} + + +VfioContainer::~VfioContainer() +{ + logger->debug("Clean up container with fd {}", this->fd); + + /* Release memory and close fds */ + groups.clear(); + + /* Close container */ + int ret = close(fd); + if (ret < 0) { + logger->error("Cannot close vfio container"); + } + + logger->debug("VFIO: closed container: fd={}", fd); +} + + +std::shared_ptr +VfioContainer::create() +{ + std::shared_ptr container { new VfioContainer }; + return container; +} + + +void +VfioContainer::dump() +{ + logger->info("File descriptor: {}", fd); + logger->info("Version: {}", version); + logger->info("Extensions: 0x{:x}", extensions); + + for(auto& group : groups) { + logger->info("VFIO Group {}, viable={}, container={}", + group->index, + (group->status.flags & VFIO_GROUP_FLAGS_VIABLE) > 0, + (group->status.flags & VFIO_GROUP_FLAGS_CONTAINER_SET) > 0 + ); + + for(auto& device : group->devices) { + logger->info("Device {}: regions={}, irqs={}, flags={}", + device->name, + device->info.num_regions, + device->info.num_irqs, + device->info.flags + ); + + for (size_t i = 0; i < device->info.num_regions && i < 8; i++) { + struct vfio_region_info *region = &device->regions[i]; + + if (region->size > 0) + logger->info("Region {} {}: size={}, offset={}, flags={}", + region->index, + (device->info.flags & VFIO_DEVICE_FLAGS_PCI) ? + vfio_pci_region_names[i] : "", + region->size, + region->offset, + region->flags + ); + } + + for (size_t i = 0; i < device->info.num_irqs; i++) { + struct vfio_irq_info *irq = &device->irqs[i]; + + if (irq->count > 0) + logger->info("IRQ {} {}: count={}, flags={}", + irq->index, + (device->info.flags & VFIO_DEVICE_FLAGS_PCI ) ? + vfio_pci_irq_names[i] : "", + irq->count, + irq->flags + ); + } + } + } +} + + +VfioDevice& +VfioContainer::attachDevice(const char* name, int index) +{ + VfioGroup& group = getOrAttachGroup(index); + auto device = std::make_unique(name, group); + + /* Open device fd */ + device->fd = ioctl(group.fd, VFIO_GROUP_GET_DEVICE_FD, name); + if (device->fd < 0) { + logger->error("Failed to open VFIO device: {}", device->name); + throw std::exception(); + } + + /* Get device info */ + device->info.argsz = sizeof(device->info); + + int ret = ioctl(device->fd, VFIO_DEVICE_GET_INFO, &device->info); + if (ret < 0) { + logger->error("Failed to get VFIO device info for: {}", device->name); + throw std::exception(); + } + + logger->debug("Device has {} regions", device->info.num_regions); + logger->debug("Device has {} IRQs", device->info.num_irqs); + + // reserve slots already so that we can use the []-operator for access + device->irqs.resize(device->info.num_irqs); + device->regions.resize(device->info.num_regions); + device->mappings.resize(device->info.num_regions); + + /* Get device regions */ + for (size_t i = 0; i < device->info.num_regions && i < 8; i++) { + struct vfio_region_info region; + memset(®ion, 0, sizeof (region)); + + region.argsz = sizeof(region); + region.index = i; + + ret = ioctl(device->fd, VFIO_DEVICE_GET_REGION_INFO, ®ion); + if (ret < 0) { + logger->error("Failed to get region of VFIO device: {}", device->name); + throw std::exception(); + } + + device->regions[i] = region; + } + + + /* Get device irqs */ + for (size_t i = 0; i < device->info.num_irqs; i++) { + struct vfio_irq_info irq; + memset(&irq, 0, sizeof (irq)); + + irq.argsz = sizeof(irq); + irq.index = i; + + ret = ioctl(device->fd, VFIO_DEVICE_GET_IRQ_INFO, &irq); + if (ret < 0) { + logger->error("Failed to get IRQs of VFIO device: {}", device->name); + throw std::exception(); + } + + device->irqs[i] = irq; + } + + group.devices.push_back(std::move(device)); + + return *group.devices.back().get(); +} + + +VfioDevice& +VfioContainer::attachDevice(const pci_device* pdev) +{ + int ret; + char name[32]; + static constexpr char kernelDriver[] = "vfio-pci"; + + /* Load PCI bus driver for VFIO */ + if (kernel_module_load("vfio_pci")) { + logger->error("Failed to load kernel driver: vfio_pci"); + throw std::exception(); + } + + /* Bind PCI card to vfio-pci driver if not already bound */ + ret = pci_get_driver(pdev, name, sizeof(name)); + if (ret || strcmp(name, kernelDriver)) { + logger->debug("Bind PCI card to kernel driver '{}'", kernelDriver); + ret = pci_attach_driver(pdev, kernelDriver); + if (ret) { + logger->error("Failed to attach device to driver"); + throw std::exception(); + } + } + + /* Get IOMMU group of device */ + int index = pci_get_iommu_group(pdev); + if (index < 0) { + logger->error("Failed to get IOMMU group of device"); + throw std::exception(); + } + + /* VFIO device name consists of PCI BDF */ + snprintf(name, sizeof(name), "%04x:%02x:%02x.%x", pdev->slot.domain, + pdev->slot.bus, pdev->slot.device, pdev->slot.function); + + logger->info("Attach to device {} with index {}", std::string(name), index); + auto& device = attachDevice(name, index); + + device.pci_device = pdev; + + /* Check if this is really a vfio-pci device */ + if(not device.isVfioPciDevice()) { + logger->error("Device is not a vfio-pci device"); + throw std::exception(); + } + + return device; +} + + +uintptr_t +VfioContainer::memoryMap(uintptr_t virt, uintptr_t phys, size_t length) +{ + int ret; + + if (length & 0xFFF) { + length += 0x1000; + length &= ~0xFFF; + } + + /* Super stupid allocator */ + size_t iovaIncrement = 0; + if (phys == UINTPTR_MAX) { + phys = this->iova_next; + iovaIncrement = length; + } + + struct vfio_iommu_type1_dma_map dmaMap; + memset(&dmaMap, 0, sizeof(dmaMap)); + + dmaMap.argsz = sizeof(dmaMap); + dmaMap.vaddr = virt; + dmaMap.iova = phys; + dmaMap.size = length; + dmaMap.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE; + + ret = ioctl(this->fd, VFIO_IOMMU_MAP_DMA, &dmaMap); + if (ret) { + logger->error("Failed to create DMA mapping: {}", ret); + return UINTPTR_MAX; + } + + logger->info("DMA map size={:#x}, iova={:#x}, vaddr={:#x}", dmaMap.size, dmaMap.iova, dmaMap.vaddr); + + // mapping successful, advance IOVA allocator + this->iova_next += iovaIncrement; + + // we intentionally don't return the actual mapped length, the users are only + // guaranteed to have their demanded memory mapped correctly + return dmaMap.iova; +} + + +bool +VfioContainer::memoryUnmap(uintptr_t phys, size_t length) +{ + int ret; + + struct vfio_iommu_type1_dma_unmap dmaUnmap; + dmaUnmap.argsz = sizeof(struct vfio_iommu_type1_dma_unmap); + dmaUnmap.flags = 0; + dmaUnmap.iova = phys; + dmaUnmap.size = length; + + ret = ioctl(this->fd, VFIO_IOMMU_UNMAP_DMA, &dmaUnmap); + if (ret) { + logger->error("Failed to unmap DMA mapping"); + return false; + } + + return true; +} + + +VfioGroup& +VfioContainer::getOrAttachGroup(int index) +{ + // search if group with index already exists + for(auto& group : groups) { + if(group->index == index) { + return *group; + } + } + + // group not yet part of this container, so acquire ownership + auto group = VfioGroup::attach(fd, index); + if(not group) { + logger->error("Failed to attach to IOMMU group: {}", index); + throw std::exception(); + } else { + logger->info("Attached new group {} to VFIO container", index); + } + + // push to our list + groups.push_back(std::move(group)); + + return *groups.back(); +} + + +VfioDevice::~VfioDevice() +{ + logger->debug("clean up device {} with fd {}", this->name, this->fd); + + for(auto& region : regions) { + regionUnmap(region.index); + } + + int ret = close(fd); + if (ret != 0) { + logger->error("Closing device fd {} failed", fd); + } + + logger->debug("VFIO: closed device: name={}, fd={}", name, fd); +} + + +bool +VfioDevice::reset() +{ + if (this->info.flags & VFIO_DEVICE_FLAGS_RESET) + return ioctl(this->fd, VFIO_DEVICE_RESET) == 0; + else + return false; /* not supported by this device */ +} + + +void* +VfioDevice::regionMap(size_t index) +{ + struct vfio_region_info *r = ®ions[index]; + + if (!(r->flags & VFIO_REGION_INFO_FLAG_MMAP)) + return MAP_FAILED; + + mappings[index] = mmap(nullptr, r->size, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_32BIT, + fd, r->offset); + + return mappings[index]; +} + + +bool +VfioDevice::regionUnmap(size_t index) +{ + int ret; + struct vfio_region_info *r = ®ions[index]; + + if (!mappings[index]) + return false; /* was not mapped */ + + logger->debug("VFIO: unmap region {} from device", index); + + ret = munmap(mappings[index], r->size); + if (ret) + return false; + + mappings[index] = nullptr; + + return true; +} + + +size_t +VfioDevice::regionGetSize(size_t index) +{ + if(index >= regions.size()) { + logger->error("Index out of range: {} >= {}", index, regions.size()); + throw std::out_of_range("Index out of range"); + } + + return regions[index].size; +} + + +bool +VfioDevice::pciEnable() +{ + int ret; + uint32_t reg; + off_t offset = ((off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40) + PCI_COMMAND; + + /* Check if this is really a vfio-pci device */ + if (!(this->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return false; + + ret = pread(this->fd, ®, sizeof(reg), offset); + if (ret != sizeof(reg)) + return false; + + /* Enable memory access and PCI bus mastering which is required for DMA */ + reg |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + + ret = pwrite(this->fd, ®, sizeof(reg), offset); + if (ret != sizeof(reg)) + return false; + + return true; +} + + +bool +VfioDevice::pciHotReset() +{ + /* Check if this is really a vfio-pci device */ + if (not isVfioPciDevice()) + return false; + + const size_t reset_infolen = sizeof(struct vfio_pci_hot_reset_info) + + sizeof(struct vfio_pci_dependent_device) * 64; + auto reset_info = reinterpret_cast + (calloc(1, reset_infolen)); + + reset_info->argsz = reset_infolen; + + + if (ioctl(this->fd, VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, reset_info) != 0) { + free(reset_info); + return false; + } + + logger->debug("VFIO: dependent devices for hot-reset:"); + for (size_t i = 0; i < reset_info->count; i++) { + struct vfio_pci_dependent_device *dd = &reset_info->devices[i]; + logger->debug("{:04x}:{:02x}:{:02x}.{:01x}: iommu_group={}", + dd->segment, dd->bus, + PCI_SLOT(dd->devfn), PCI_FUNC(dd->devfn), dd->group_id); + + if (static_cast(dd->group_id) != this->group.index) { + free(reset_info); + return false; + } + } + + const size_t resetlen = sizeof(struct vfio_pci_hot_reset) + + sizeof(int32_t) * 1; + auto reset = reinterpret_cast + (calloc(1, resetlen)); + + reset->argsz = resetlen; + reset->count = 1; + reset->group_fds[0] = this->group.fd; + + const bool success = ioctl(this->fd, VFIO_DEVICE_PCI_HOT_RESET, reset) == 0; + + free(reset); + free(reset_info); + + return success; +} + + +int +VfioDevice::pciMsiInit(int efds[]) +{ + /* Check if this is really a vfio-pci device */ + if(not isVfioPciDevice()) + return -1; + + const size_t irqCount = irqs[VFIO_PCI_MSI_IRQ_INDEX].count; + const size_t irqSetSize = sizeof(struct vfio_irq_set) + + sizeof(int) * irqCount; + + auto irqSet = reinterpret_cast(calloc(1, irqSetSize)); + if(irqSet == nullptr) + return -1; + + irqSet->argsz = irqSetSize; + irqSet->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irqSet->index = VFIO_PCI_MSI_IRQ_INDEX; + irqSet->start = 0; + irqSet->count = irqCount; + + /* Now set the new eventfds */ + for (size_t i = 0; i < irqCount; i++) { + efds[i] = eventfd(0, 0); + if (efds[i] < 0) { + free(irqSet); + return -1; + } + } + + memcpy(irqSet->data, efds, sizeof(int) * irqCount); + + if(ioctl(fd, VFIO_DEVICE_SET_IRQS, irqSet) != 0) { + free(irqSet); + return -1; + } + + free(irqSet); + + return irqCount; +} + + +int +VfioDevice::pciMsiDeinit(int efds[]) +{ + /* Check if this is really a vfio-pci device */ + if(not isVfioPciDevice()) + return -1; + + const size_t irqCount = irqs[VFIO_PCI_MSI_IRQ_INDEX].count; + const size_t irqSetSize = sizeof(struct vfio_irq_set) + + sizeof(int) * irqCount; + + auto irqSet = reinterpret_cast(calloc(1, irqSetSize)); + if(irqSet == nullptr) + return -1; + + irqSet->argsz = irqSetSize; + irqSet->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irqSet->index = VFIO_PCI_MSI_IRQ_INDEX; + irqSet->count = irqCount; + irqSet->start = 0; + + for (size_t i = 0; i < irqCount; i++) { + close(efds[i]); + efds[i] = -1; + } + + memcpy(irqSet->data, efds, sizeof(int) * irqCount); + + if (ioctl(fd, VFIO_DEVICE_SET_IRQS, irqSet) != 0) { + free(irqSet); + return -1; + } + + free(irqSet); + + return irqCount; +} + + +bool +VfioDevice::pciMsiFind(int nos[]) +{ + int ret, idx, irq; + char *end, *col, *last, line[1024], name[13]; + FILE *f; + + f = fopen("/proc/interrupts", "r"); + if (!f) + return false; + + for (int i = 0; i < 32; i++) + nos[i] = -1; + + /* For each line in /proc/interrupts */ + while (fgets(line, sizeof(line), f)) { + col = strtok(line, " "); + + /* IRQ number is in first column */ + irq = strtol(col, &end, 10); + if (col == end) + continue; + + /* Find last column of line */ + do { + last = col; + } while ((col = strtok(nullptr, " "))); + + + ret = sscanf(last, "vfio-msi[%u](%12[0-9:])", &idx, name); + if (ret == 2) { + if (strstr(this->name.c_str(), name) == this->name.c_str()) + nos[idx] = irq; + } + } + + fclose(f); + + return true; +} + + +bool +VfioDevice::isVfioPciDevice() const +{ + return info.flags & VFIO_DEVICE_FLAGS_PCI; +} + + +VfioGroup::~VfioGroup() +{ + logger->debug("clean up group {} with fd {}", this->index, this->fd); + int ret; + + /* Release memory and close fds */ + devices.clear(); + + if(fd < 0) { + logger->debug("Destructing group that has not been attached"); + } else { + ret = ioctl(fd, VFIO_GROUP_UNSET_CONTAINER); + if (ret != 0) { + logger->error("Cannot unset container for group fd {}", fd); + } + + logger->debug("Released group from container: group={}", index); + + ret = close(fd); + if (ret != 0) { + logger->error("Cannot close group fd {}", fd); + } + + logger->debug("Closed group: group={}, fd={}", index, fd); + } +} + + +std::unique_ptr +VfioGroup::attach(int containerFd, int groupIndex) +{ + std::unique_ptr group { new VfioGroup(groupIndex) }; + + /* Open group fd */ + std::stringstream groupPath; + groupPath << VFIO_DEV("") << groupIndex; + group->fd = open(groupPath.str().c_str(), O_RDWR); + if (group->fd < 0) { + logger->error("Failed to open VFIO group: {}", group->index); + return nullptr; + } + + logger->debug("VFIO group {} (fd {}) has path {}", + groupIndex, group->fd, groupPath.str()); + + int ret; + + /* Claim group ownership */ + ret = ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &containerFd); + if (ret < 0) { + logger->error("Failed to attach VFIO group {} to container fd {} (error {})", + group->index, containerFd, ret); + return nullptr; + } + + /* Set IOMMU type */ + ret = ioctl(containerFd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); + if (ret < 0) { + logger->error("Failed to set IOMMU type of container: {}", ret); + return nullptr; + } + + /* Check group viability and features */ + group->status.argsz = sizeof(group->status); + + ret = ioctl(group->fd, VFIO_GROUP_GET_STATUS, &group->status); + if (ret < 0) { + logger->error("Failed to get VFIO group status"); + return nullptr; + } + + if (!(group->status.flags & VFIO_GROUP_FLAGS_VIABLE)) { + logger->error("VFIO group is not available: bind all devices to the VFIO driver!"); + return nullptr; + } + + return group; +} + +} // namespace villas + diff --git a/fpga/tests/fpga.cpp b/fpga/tests/fpga.cpp index 1f718ff51..fe9e79847 100644 --- a/fpga/tests/fpga.cpp +++ b/fpga/tests/fpga.cpp @@ -24,9 +24,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "global.hpp" @@ -40,7 +40,6 @@ #define FPGA_AXI_HZ 125000000 static struct pci pci; -static struct vfio_container vc; FpgaState state; @@ -59,8 +58,7 @@ static void init() ret = pci_init(&pci); cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); - ret = vfio_init(&vc); - cr_assert_eq(ret, 0, "Failed to initiliaze VFIO sub-system"); + auto vfioContainer = villas::VfioContainer::create(); /* Parse FPGA configuration */ f = fopen(TEST_CONFIG, "r"); @@ -81,7 +79,7 @@ static void init() villas::fpga::PCIeCardFactory* fpgaCardPlugin = dynamic_cast(plugin); // create all FPGA card instances using the corresponding plugin - state.cards = fpgaCardPlugin->make(fpgas, &pci, &vc); + state.cards = fpgaCardPlugin->make(fpgas, &pci, vfioContainer); cr_assert(state.cards.size() != 0, "No FPGA cards found!"); From 60882f1086190c3ed54f097b4bbbce2a48252b79 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 15:48:57 +0200 Subject: [PATCH 157/560] lib/memory: implement memory handling with allocators and blocks This commit is 2/2 of a series of patches and not working on its own. --- fpga/include/villas/fpga/card.hpp | 22 ++++- fpga/include/villas/fpga/ips/pcie.hpp | 1 + fpga/include/villas/memory.hpp | 122 +++++++++++++++++++++++++ fpga/include/villas/memory_manager.hpp | 16 +++- fpga/lib/CMakeLists.txt | 1 + fpga/lib/card.cpp | 84 +++++++++++++++-- fpga/lib/ips/pcie.cpp | 16 +++- fpga/lib/memory.cpp | 24 +++++ 8 files changed, 267 insertions(+), 19 deletions(-) create mode 100644 fpga/include/villas/memory.hpp create mode 100644 fpga/lib/memory.cpp diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index f8f9a0128..f9d78151a 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -38,6 +38,7 @@ #include "kernel/vfio.hpp" #include +#include #include #include "plugin.hpp" @@ -46,6 +47,7 @@ #include "config.h" #include "memory_manager.hpp" +#include "memory.hpp" #define PCI_FILTER_DEFAULT_FPGA { \ .id = { \ @@ -70,6 +72,7 @@ public: friend PCIeCardFactory; PCIeCard() : filter(PCI_FILTER_DEFAULT_FPGA) {} + ~PCIeCard(); bool init(); bool stop() { return true; } @@ -80,12 +83,19 @@ public: ip::IpCore* lookupIp(const std::string& name) const; ip::IpCore* lookupIp(const Vlnv& vlnv) const; + bool + mapMemoryBlock(const MemoryBlock& block); + +private: + /// Cache a set of already mapped memory blocks + std::set memoryBlocksMapped; + +public: // TODO: make this private ip::IpCoreList ips; ///< IPs located on this FPGA card bool do_reset; /**< Reset VILLASfpga during startup? */ int affinity; /**< Affinity for MSI interrupts */ - std::string name; /**< The name of the FPGA card */ struct pci *pci; @@ -97,17 +107,19 @@ public: /// The VFIO device that represents this card VfioDevice* vfioDevice; + /// Slave address space ID to access the PCIe address space from the FPGA + MemoryManager::AddressSpaceId addrSpaceIdDeviceToHost; + /// Address space identifier of the master address space of this FPGA card. /// This will be used for address resolution of all IPs on this card. - MemoryManager::AddressSpaceId addrSpaceId; - - size_t maplen; - size_t dmalen; + MemoryManager::AddressSpaceId addrSpaceIdHostToDevice; protected: SpdLogger getLogger() const { return loggerGetOrCreate(name); } + + SpdLogger logger; }; using CardList = std::list>; diff --git a/fpga/include/villas/fpga/ips/pcie.hpp b/fpga/include/villas/fpga/ips/pcie.hpp index 290a8c577..28b01c5aa 100644 --- a/fpga/include/villas/fpga/ips/pcie.hpp +++ b/fpga/include/villas/fpga/ips/pcie.hpp @@ -51,6 +51,7 @@ public: private: static constexpr char axiInterface[] = "M_AXI"; + static constexpr char pcieMemory[] = "BAR0"; }; diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp new file mode 100644 index 000000000..85dae02ea --- /dev/null +++ b/fpga/include/villas/memory.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include + +#include "log.hpp" +#include "memory_manager.hpp" + +namespace villas { + +class MemoryBlock { +protected: + MemoryBlock(MemoryManager::AddressSpaceId addrSpaceId, size_t size) : + addrSpaceId(addrSpaceId), size(size) {} + +public: + MemoryManager::AddressSpaceId getAddrSpaceId() const + { return addrSpaceId; } + + size_t getSize() const + { return size; } + +private: + MemoryManager::AddressSpaceId addrSpaceId; + size_t size; +}; + + +class MemoryAllocator { +}; + + +class HostRam : public MemoryAllocator { +public: + + template + class MemoryBlockHostRam : public MemoryBlock { + friend class HostRam; + private: + MemoryBlockHostRam(void* addr, size_t size, MemoryManager::AddressSpaceId foreignAddrSpaceId) : + MemoryBlock(foreignAddrSpaceId, size), + addr(addr), + translation(MemoryManager::get().getTranslationFromProcess(foreignAddrSpaceId)) + {} + public: + using Type = T; + + MemoryBlockHostRam() = delete; + + T& operator*() { + return *reinterpret_cast(translation.getLocalAddr(0)); + } + + T& operator [](int idx) { + const size_t offset = sizeof(T) * idx; + return *reinterpret_cast(translation.getLocalAddr(offset)); + } + + T* operator &() const { + return reinterpret_cast(translation.getLocalAddr(0)); + } + + private: + // addr needed for freeing later + void* addr; + + // cached memory translation for fast access + MemoryTranslation translation; + }; + + template + static MemoryBlockHostRam + allocate(size_t num) + { + /* Align to next bigger page size chunk */ + size_t length = num * sizeof(T); + if (length & size_t(0xFFF)) { + length += size_t(0x1000); + length &= size_t(~0xFFF); + } + + void* const addr = HostRam::allocate(length); + if(addr == nullptr) { + throw std::bad_alloc(); + } + + auto& mm = MemoryManager::get(); + + // assemble name for this block + std::stringstream name; + name << std::showbase << std::hex << reinterpret_cast(addr); + + auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str()); + + // create mapping from VA space of process to this new block + mm.createMapping(reinterpret_cast(addr), 0, length, + "VA", + mm.getProcessAddressSpace(), + blockAddrSpaceId); + + // create object and corresponding address space in memory manager + return MemoryBlockHostRam(addr, length, blockAddrSpaceId); + } + + template + static inline bool + free(const MemoryBlockHostRam& block) + { + // TODO: remove address space from memory manager + // TODO: how to prevent use after free? + return HostRam::free(block.addr, block.size); + } + +private: + static void* + allocate(size_t length, int flags = 0); + + static bool + free(void*, size_t length); +}; + +} // namespace villas diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 04bf60bf6..fb3db33c2 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "log.hpp" #include "directed_graph.hpp" @@ -34,6 +35,10 @@ public: uintptr_t getForeignAddr(uintptr_t addrInLocalAddrSpace) const; + size_t + getSize() const + { return size; } + friend std::ostream& operator<< (std::ostream& stream, const MemoryTranslation& translation) { @@ -87,9 +92,9 @@ private: * the destination address space, where the mapping points to. Often, #dest * will be zero for mappings to hardware, but consider the example when * mapping FPGA to application memory: - * The application allocates a block 1kB at address - * 0x843001000 in its address space. The mapping would then have a #dest - * address of 0x843001000 and a #size of 1024. + * The application allocates a block 1kB at address 0x843001000 in its + * address space. The mapping would then have a #dest address of 0x843001000 + * and a #size of 1024. */ class Mapping : public graph::Edge { public: @@ -147,6 +152,11 @@ public: getProcessAddressSpace() { return getOrCreateAddressSpace("villas-fpga"); } + AddressSpaceId + getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock) + { return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); } + + AddressSpaceId getOrCreateAddressSpace(std::string name); diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 7150b61eb..34901a74e 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -23,6 +23,7 @@ set(SOURCES plugin.cpp utils.cpp memory_manager.cpp + memory.cpp ) include(FindPkgConfig) diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index e0e2ba456..fb7369823 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -109,13 +109,33 @@ PCIeCardFactory::make(json_t *json, struct pci* pci, std::shared_ptrdebug("Unmap block {} at IOVA {:#x} of size {:#x}", + mappedMemoryBlock, iova, size); + vfioContainer->memoryUnmap(iova, size); + } +} + + ip::IpCore* PCIeCard::lookupIp(const std::string& name) const { @@ -124,9 +144,11 @@ PCIeCard::lookupIp(const std::string& name) const return ip.get(); } } + return nullptr; } + ip::IpCore* PCIeCard::lookupIp(const Vlnv& vlnv) const { @@ -135,17 +157,58 @@ PCIeCard::lookupIp(const Vlnv& vlnv) const return ip.get(); } } + return nullptr; } +bool +PCIeCard::mapMemoryBlock(const MemoryBlock& block) +{ + auto& mm = MemoryManager::get(); + const auto& addrSpaceId = block.getAddrSpaceId(); + + if(memoryBlocksMapped.find(addrSpaceId) != memoryBlocksMapped.end()) { + // block already mapped + return true; + } else { + logger->debug("Create VFIO mapping for {}", addrSpaceId); + } + + + auto translationFromProcess = mm.getTranslationFromProcess(addrSpaceId); + uintptr_t processBaseAddr = translationFromProcess.getLocalAddr(0); + uintptr_t iovaAddr = vfioContainer->memoryMap(processBaseAddr, + UINTPTR_MAX, + block.getSize()); + + if(iovaAddr == UINTPTR_MAX) { + logger->error("Cannot map memory at {:#x} of size {:#x}", + processBaseAddr, block.getSize()); + return false; + } + + + + mm.createMapping(iovaAddr, 0, block.getSize(), + "vfio", + this->addrSpaceIdDeviceToHost, + addrSpaceId); + + // remember that this block has already been mapped for later + memoryBlocksMapped.insert(addrSpaceId); + + return true; +} + + bool fpga::PCIeCard::init() { - int ret; struct pci_device *pdev; - auto logger = getLogger(); + auto& mm = MemoryManager::get(); + logger = getLogger(); logger->info("Initializing FPGA card {}", name); @@ -181,17 +244,18 @@ fpga::PCIeCard::init() /* Link mapped BAR0 to global memory graph */ // get the address space of the current application - auto villasAddrSpace = MemoryManager::get().getProcessAddressSpace(); + const auto villasAddrSpace = mm.getProcessAddressSpace(); + + // get the address space for the PCIe proxy we use with VFIO + const auto cardPCIeAddrSpaceName = mm.getMasterAddrSpaceName(name, "PCIe"); // create a new address space for this FPGA card - this->addrSpaceId = MemoryManager::get().getOrCreateAddressSpace(name); - + addrSpaceIdHostToDevice = mm.getOrCreateAddressSpace(cardPCIeAddrSpaceName); // create a mapping from our address space to the FPGA card via vfio - MemoryManager::get().createMapping(reinterpret_cast(bar0_mapped), + mm.createMapping(reinterpret_cast(bar0_mapped), 0, bar0_size, "VFIO_map", - villasAddrSpace, this->addrSpaceId); - + villasAddrSpace, addrSpaceIdHostToDevice); /* Reset system? */ diff --git a/fpga/lib/ips/pcie.cpp b/fpga/lib/ips/pcie.cpp index 5b02f1261..59174318b 100644 --- a/fpga/lib/ips/pcie.cpp +++ b/fpga/lib/ips/pcie.cpp @@ -38,6 +38,8 @@ static AxiPciExpressBridgeFactory factory; bool AxiPciExpressBridge::init() { + auto& mm = MemoryManager::get(); + // Throw an exception if the is no bus master interface and thus no // address space we can use for translation -> error const MemoryManager::AddressSpaceId myAddrSpaceid = @@ -47,7 +49,19 @@ AxiPciExpressBridge::init() // point to all other IPs in the FPGA, because Vivado will generate a // memory view for this bridge that can see all others. MemoryManager::get().createMapping(0x00, 0x00, SIZE_MAX, "PCIeBridge", - card->addrSpaceId, myAddrSpaceid); + card->addrSpaceIdHostToDevice, myAddrSpaceid); + + + /* Make PCIe (IOVA) address space available to FPGA via BAR0 */ + + // IPs that can access this address space will know it via their memory view + const auto addrSpaceNameDeviceToHost = + mm.getSlaveAddrSpaceName(getInstanceName(), pcieMemory); + + // save ID in card so we can create mappings later when needed (e.g. when + // allocating DMA memory in host RAM) + card->addrSpaceIdDeviceToHost = + mm.getOrCreateAddressSpace(addrSpaceNameDeviceToHost); return true; } diff --git a/fpga/lib/memory.cpp b/fpga/lib/memory.cpp new file mode 100644 index 000000000..f3d2802b4 --- /dev/null +++ b/fpga/lib/memory.cpp @@ -0,0 +1,24 @@ +#include +#include + +#include "memory.hpp" + +namespace villas { + +bool +HostRam::free(void* addr, size_t length) +{ + return munmap(addr, length) == 0; +} + + +void* +HostRam::allocate(size_t length, int flags) +{ + const int mmap_flags = flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT; + const int mmap_protection = PROT_READ | PROT_WRITE; + + return mmap(nullptr, length, mmap_protection, mmap_flags, 0, 0); +} + +} // namespace villas From 272462278777431cefe7aa70931c5f274599e02f Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 15:40:58 +0200 Subject: [PATCH 158/560] kernel/pci: make some arguments const and fix debug output --- fpga/include/villas/kernel/pci.h | 6 +++--- fpga/lib/kernel/pci.c | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h index db6beb0e6..10fd86ef6 100644 --- a/fpga/include/villas/kernel/pci.h +++ b/fpga/include/villas/kernel/pci.h @@ -58,13 +58,13 @@ int pci_device_compare(const struct pci_device *d, const struct pci_device *f); struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *filter); /** Get currently loaded driver for device */ -int pci_get_driver(struct pci_device *d, char *buf, size_t buflen); +int pci_get_driver(const struct pci_device *d, char *buf, size_t buflen); /** Bind a new LKM to the PCI device */ -int pci_attach_driver(struct pci_device *d, const char *driver); +int pci_attach_driver(const struct pci_device *d, const char *driver); /** Return the IOMMU group of this PCI device or -1 if the device is not in a group. */ -int pci_get_iommu_group(struct pci_device *d); +int pci_get_iommu_group(const struct pci_device *d); #ifdef __cplusplus } diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index e4d6793ba..1f7336742 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -254,7 +254,7 @@ struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *f) return list_search(&p->devices, (cmp_cb_t) pci_device_compare, (void *) f); } -int pci_get_driver(struct pci_device *d, char *buf, size_t buflen) +int pci_get_driver(const struct pci_device *d, char *buf, size_t buflen) { int ret; char sysfs[1024], syml[1024]; @@ -273,7 +273,7 @@ int pci_get_driver(struct pci_device *d, char *buf, size_t buflen) return 0; } -int pci_attach_driver(struct pci_device *d, const char *driver) +int pci_attach_driver(const struct pci_device *d, const char *driver) { FILE *f; char fn[1024]; @@ -284,7 +284,7 @@ int pci_attach_driver(struct pci_device *d, const char *driver) if (!f) serror("Failed to add PCI id to %s driver (%s)", driver, fn); - debug(5, "Adding ID to %s module: %04x %04x", driver, d->id.vendor, d->id.device); + info("Adding ID to %s module: %04x %04x", driver, d->id.vendor, d->id.device); fprintf(f, "%04x %04x", d->id.vendor, d->id.device); fclose(f); @@ -294,14 +294,14 @@ int pci_attach_driver(struct pci_device *d, const char *driver) if (!f) serror("Failed to bind PCI device to %s driver (%s)", driver, fn); - debug(5, "Bind device to %s driver", driver); + info("Bind device to %s driver", driver); fprintf(f, "%04x:%02x:%02x.%x\n", d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); fclose(f); return 0; } -int pci_get_iommu_group(struct pci_device *d) +int pci_get_iommu_group(const struct pci_device *d) { int ret; char *group, link[1024], sysfs[1024]; From aa1592ae2be143e1d14682e189637541eac7d9b1 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 15:41:40 +0200 Subject: [PATCH 159/560] utils: read_random() now returns the number of bytes written --- fpga/include/villas/utils.h | 2 +- fpga/lib/utils.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fpga/include/villas/utils.h b/fpga/include/villas/utils.h index 272afd6eb..828796fb6 100644 --- a/fpga/include/villas/utils.h +++ b/fpga/include/villas/utils.h @@ -240,7 +240,7 @@ int version_parse(const char *s, struct version *v); #endif /** Fill buffer with random data */ -ssize_t read_random(char *buf, size_t len); +size_t read_random(char *buf, size_t len); /** Get CPU timestep counter */ __attribute__((always_inline)) static inline uint64_t rdtsc() diff --git a/fpga/lib/utils.c b/fpga/lib/utils.c index 2e295191d..10fc8348f 100644 --- a/fpga/lib/utils.c +++ b/fpga/lib/utils.c @@ -260,14 +260,14 @@ void * memdup(const void *src, size_t bytes) return dst; } -ssize_t read_random(char *buf, size_t len) +size_t read_random(char *buf, size_t len) { int fd; ssize_t bytes, total; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) - return -1; + return 0; bytes = 0; total = 0; From f025f5dcc7d5faeb7f6a1c72c50f9329e32f3c25 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 15:51:41 +0200 Subject: [PATCH 160/560] lib/ip: read base addresses from JSON into unsigned integer This caused sign extension for addresses with the MSB set which is obviously wrong since this is an address and not a number. With Jansson, there seems to be now other way since it only supports reading (signed) integers. --- fpga/lib/ip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 41ca7c52e..d1d2d33a6 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -223,7 +223,7 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) json_t* json_block; json_object_foreach(json_instance, block_name, json_block) { - int base, high, size; + unsigned int base, high, size; int ret = json_unpack(json_block, "{ s: i, s: i, s: i }", "baseaddr", &base, "highaddr", &high, From 4f6694420faef4ab212fec800df46bf7f2bd4e79 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 15:56:00 +0200 Subject: [PATCH 161/560] lib: remove old and unused C files --- fpga/include/villas/fpga/card.h | 103 ---- fpga/include/villas/fpga/ip.h | 126 ----- fpga/include/villas/fpga/ips/dma.h | 88 ---- fpga/include/villas/fpga/ips/fifo.h | 52 -- fpga/include/villas/fpga/ips/intc.h | 56 --- fpga/include/villas/fpga/ips/switch.h | 67 --- fpga/include/villas/fpga/ips/timer.h | 43 -- fpga/include/villas/fpga/vlnv.h | 61 --- fpga/include/villas/plugin.h | 82 ---- fpga/lib/card.c | 315 ------------ fpga/lib/ip.c | 167 ------- fpga/lib/ips/dma.c | 657 -------------------------- fpga/lib/ips/fifo.c | 153 ------ fpga/lib/ips/intc.c | 180 ------- fpga/lib/ips/switch.c | 221 --------- fpga/lib/ips/timer.c | 61 --- fpga/lib/plugin.c | 111 ----- fpga/lib/vlnv.c | 64 --- 18 files changed, 2607 deletions(-) delete mode 100644 fpga/include/villas/fpga/card.h delete mode 100644 fpga/include/villas/fpga/ip.h delete mode 100644 fpga/include/villas/fpga/ips/dma.h delete mode 100644 fpga/include/villas/fpga/ips/fifo.h delete mode 100644 fpga/include/villas/fpga/ips/intc.h delete mode 100644 fpga/include/villas/fpga/ips/switch.h delete mode 100644 fpga/include/villas/fpga/ips/timer.h delete mode 100644 fpga/include/villas/fpga/vlnv.h delete mode 100644 fpga/include/villas/plugin.h delete mode 100644 fpga/lib/card.c delete mode 100644 fpga/lib/ip.c delete mode 100644 fpga/lib/ips/dma.c delete mode 100644 fpga/lib/ips/fifo.c delete mode 100644 fpga/lib/ips/intc.c delete mode 100644 fpga/lib/ips/switch.c delete mode 100644 fpga/lib/ips/timer.c delete mode 100644 fpga/lib/plugin.c delete mode 100644 fpga/lib/vlnv.c diff --git a/fpga/include/villas/fpga/card.h b/fpga/include/villas/fpga/card.h deleted file mode 100644 index f352fb081..000000000 --- a/fpga/include/villas/fpga/card.h +++ /dev/null @@ -1,103 +0,0 @@ -/** FPGA card - * - * This class represents a FPGA device. - * - * @file - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include - -#include "common.h" -#include "kernel/pci.h" -#include "kernel/vfio.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* Forward declarations */ -struct fpga_ip; -struct vfio_container; - -struct fpga_card { - char *name; /**< The name of the FPGA card */ - - enum state state; /**< The state of this FPGA card. */ - - struct pci *pci; - struct pci_device filter; /**< Filter for PCI device. */ - - struct vfio_container *vfio_container; - struct vfio_device vfio_device; /**< VFIO device handle. */ - - int do_reset; /**< Reset VILLASfpga during startup? */ - int affinity; /**< Affinity for MSI interrupts */ - - struct list ips; /**< List of IP components on FPGA. */ - - char *map; /**< PCI BAR0 mapping for register access */ - - size_t maplen; - size_t dmalen; - - /* Some IP cores are special and referenced here */ - struct fpga_ip *intc; - struct fpga_ip *reset; - struct fpga_ip *sw; -}; - -/** Initialize FPGA card and its IP components. */ -int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc); - -/** Parse configuration of FPGA card including IP cores from config. */ -int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name); - -int fpga_card_parse_list(struct list *l, json_t *cfg); - -/** Check if the FPGA card configuration is plausible. */ -int fpga_card_check(struct fpga_card *c); - -/** Start FPGA card. */ -int fpga_card_start(struct fpga_card *c); - -/** Stop FPGA card. */ -int fpga_card_stop(struct fpga_card *c); - -/** Destroy FPGA card. */ -int fpga_card_destroy(struct fpga_card *c); - -/** Dump details of FPGA card to stdout. */ -void fpga_card_dump(struct fpga_card *c); - -/** Reset the FPGA to a known state */ -int fpga_card_reset(struct fpga_card *c); - -#ifdef __cplusplus -} -#endif - -/** @} */ diff --git a/fpga/include/villas/fpga/ip.h b/fpga/include/villas/fpga/ip.h deleted file mode 100644 index b6723043d..000000000 --- a/fpga/include/villas/fpga/ip.h +++ /dev/null @@ -1,126 +0,0 @@ -/** Interlectual Property component. - * - * This class represents a module within the FPGA. - * - * @file - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include - -#include "common.h" - -#include "fpga/vlnv.h" - -#include "fpga/ips/dma.h" -#include "fpga/ips/switch.h" -#include "fpga/ips/fifo.h" -#include "fpga/ips/rtds_axis.h" -#include "fpga/ips/timer.h" -#include "fpga/ips/model.h" -#include "fpga/ips/dft.h" -#include "fpga/ips/intc.h" - -#ifdef __cplusplus -extern "C" { -#endif - -enum fpga_ip_types { - FPGA_IP_TYPE_DM_DMA, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ - FPGA_IP_TYPE_DM_FIFO, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ - FPGA_IP_TYPE_MODEL, /**< A model IP simulates a system on the FPGA. */ - FPGA_IP_TYPE_MATH, /**< A math IP performs some kind of mathematical operation on the streaming data */ - FPGA_IP_TYPE_MISC, /**< Other IP components like timer, counters, interrupt conctrollers or routing. */ - FPGA_IP_TYPE_INTERFACE /**< A interface IP connects the FPGA to another system or controller. */ -}; - -struct fpga_ip_type { - struct fpga_vlnv vlnv; - - enum fpga_ip_types type; - - int (*init)(struct fpga_ip *c); - int (*parse)(struct fpga_ip *c, json_t *cfg); - int (*check)(struct fpga_ip *c); - int (*start)(struct fpga_ip *c); - int (*stop)(struct fpga_ip *c); - int (*destroy)(struct fpga_ip *c); - int (*reset)(struct fpga_ip *c); - void (*dump)(struct fpga_ip *c); - - size_t size; /**< Amount of memory which should be reserved for struct fpga_ip::_vd */ -}; - -struct fpga_ip { - char *name; /**< Name of the FPGA IP component. */ - struct fpga_vlnv vlnv; /**< The Vendor, Library, Name, Version tag of the FPGA IP component. */ - - enum state state; /**< The current state of the FPGA IP component. */ - - struct fpga_ip_type *_vt; /**< Vtable containing FPGA IP type function pointers. */ - void *_vd; /**< Virtual data (used by struct fpga_ip::_vt functions) */ - - uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ - uintptr_t baseaddr_axi4; /**< Used by AXI4 FIFO DM */ - - int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ - int irq; /**< The interrupt number of the FPGA IP component. */ - - struct fpga_card *card; /**< The FPGA to which this IP instance belongs to. */ -}; - -/** Initialize IP core. */ -int fpga_ip_init(struct fpga_ip *c, struct fpga_ip_type *vt); - -/** Parse IP core configuration from configuration file */ -int fpga_ip_parse(struct fpga_ip *c, json_t *cfg, const char *name); - -/** Check configuration of IP core. */ -int fpga_ip_check(struct fpga_ip *c); - -/** Start IP core. */ -int fpga_ip_start(struct fpga_ip *c); - -/** Stop IP core. */ -int fpga_ip_stop(struct fpga_ip *c); - -/** Release dynamic memory allocated by this IP core. */ -int fpga_ip_destroy(struct fpga_ip *c); - -/** Dump details about this IP core to stdout. */ -void fpga_ip_dump(struct fpga_ip *c); - -/** Reset IP component to its initial state. */ -int fpga_ip_reset(struct fpga_ip *c); - -/** Find a registered FPGA IP core type with the given VLNV identifier. */ -struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr); - -#ifdef __cplusplus -} -#endif - -/** @} */ diff --git a/fpga/include/villas/fpga/ips/dma.h b/fpga/include/villas/fpga/ips/dma.h deleted file mode 100644 index 7a13fb903..000000000 --- a/fpga/include/villas/fpga/ips/dma.h +++ /dev/null @@ -1,88 +0,0 @@ -/** DMA related helper functions. - * - * These functions present a simpler interface to Xilinx' DMA driver (XAxiDma_*). - * - * @file - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include -#include -#include - -#include - -/* Forward declarations */ -struct fpga_ip; - -#define FPGA_DMA_BASEADDR 0x00000000 -#define FPGA_DMA_BOUNDARY 0x1000 -#define FPGA_DMA_BD_OFFSET 0xC0000000 -#define FPGA_DMA_BD_SIZE (32 << 20) // 32 MB - -#define XAXIDMA_SR_SGINCL_MASK 0x00000008 - -struct dma_mem { - char *base_virt; - char *base_phys; - size_t len; -}; - -struct dma { - XAxiDma inst; - - struct dma_mem bd; -}; - -struct ip; - -int dma_mem_split(struct dma_mem *o, struct dma_mem *a, struct dma_mem *b); - -int dma_alloc(struct fpga_ip *c, struct dma_mem *mem, size_t len, int flags); -int dma_free(struct fpga_ip *c, struct dma_mem *mem); - -int dma_write(struct fpga_ip *c, char *buf, size_t len); -int dma_read(struct fpga_ip *c, char *buf, size_t len); -int dma_read_complete(struct fpga_ip *c, char **buf, size_t *len); -int dma_write_complete(struct fpga_ip *c, char **buf, size_t *len); - -int dma_sg_write(struct fpga_ip *c, char *buf, size_t len); -int dma_sg_read(struct fpga_ip *c, char *buf, size_t len); - -int dma_sg_write_complete(struct fpga_ip *c, char **buf, size_t *len); -int dma_sg_read_complete(struct fpga_ip *c, char **buf, size_t *len); - -int dma_simple_read(struct fpga_ip *c, char *buf, size_t len); -int dma_simple_write(struct fpga_ip *c, char *buf, size_t len); - -int dma_simple_read_complete(struct fpga_ip *c, char **buf, size_t *len); -int dma_simple_write_complete(struct fpga_ip *c, char **buf, size_t *len); - -int dma_ping_pong(struct fpga_ip *c, char *src, char *dst, size_t len); - -int dma_start(struct fpga_ip *c); - -/** @} */ diff --git a/fpga/include/villas/fpga/ips/fifo.h b/fpga/include/villas/fpga/ips/fifo.h deleted file mode 100644 index 4724c1ac5..000000000 --- a/fpga/include/villas/fpga/ips/fifo.h +++ /dev/null @@ -1,52 +0,0 @@ -/** FIFO related helper functions - * - * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) - * - * @file - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include - -#include -#include - -struct fifo { - XLlFifo inst; - - uint32_t baseaddr_axi4; -}; - -/* Forward declarations */ -struct ip; - -int fifo_start(struct fpga_ip *c); - -ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len); - -ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len); - -/** @} */ diff --git a/fpga/include/villas/fpga/ips/intc.h b/fpga/include/villas/fpga/ips/intc.h deleted file mode 100644 index bb7d44739..000000000 --- a/fpga/include/villas/fpga/ips/intc.h +++ /dev/null @@ -1,56 +0,0 @@ -/** AXI-PCIe Interrupt controller - * - * @file - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include - -enum intc_flags { - INTC_ENABLED = (1 << 0), - INTC_POLLING = (1 << 1) -}; - -struct intc { - int num_irqs; /**< Number of available MSI vectors */ - - int efds[32]; /**< Event FDs */ - int nos[32]; /**< Interrupt numbers from /proc/interrupts */ - - int flags[32]; /**< Mask of intc_flags */ -}; - -int intc_init(struct fpga_ip *c); - -int intc_destroy(struct fpga_ip *c); - -int intc_enable(struct fpga_ip *c, uint32_t mask, int poll); - -int intc_disable(struct fpga_ip *c, uint32_t mask); - -uint64_t intc_wait(struct fpga_ip *c, int irq); - -/** @} */ diff --git a/fpga/include/villas/fpga/ips/switch.h b/fpga/include/villas/fpga/ips/switch.h deleted file mode 100644 index 7fcf08fa6..000000000 --- a/fpga/include/villas/fpga/ips/switch.h +++ /dev/null @@ -1,67 +0,0 @@ -/** AXI Stream interconnect related helper functions - * - * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) - * - * @file - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include -#include - -#include "list.h" - -/* Forward declarations */ -struct ip; - -struct sw_path { - const char *in; - const char *out; -}; - -struct sw { - XAxis_Switch inst; - - int num_ports; - struct list paths; -}; - -struct ip; - -int switch_start(struct fpga_ip *c); - -/** Initialize paths which have been parsed by switch_parse() */ -int switch_init_paths(struct fpga_ip *c); - -int switch_destroy(struct fpga_ip *c); - -int switch_parse(struct fpga_ip *c, json_t *cfg); - -int switch_connect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si); - -int switch_disconnect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si); - -/** @} */ diff --git a/fpga/include/villas/fpga/ips/timer.h b/fpga/include/villas/fpga/ips/timer.h deleted file mode 100644 index 31fac741e..000000000 --- a/fpga/include/villas/fpga/ips/timer.h +++ /dev/null @@ -1,43 +0,0 @@ -/** Timer related helper functions - * - * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) - * - * @file - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#pragma once - -#include - -/* Forward declarations */ -struct fpga_ip; - -struct timer { - XTmrCtr inst; -}; - -int timer_start(struct fpga_ip *c); - -/** @} */ diff --git a/fpga/include/villas/fpga/vlnv.h b/fpga/include/villas/fpga/vlnv.h deleted file mode 100644 index 2de8bbbcb..000000000 --- a/fpga/include/villas/fpga/vlnv.h +++ /dev/null @@ -1,61 +0,0 @@ -/** Vendor, Library, Name, Version (VLNV) tag. - * - * @file - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -/** @addtogroup fpga VILLASfpga - * @{ - */ - -#ifndef _FPGA_VLNV_H_ -#define _FPGA_VLNV_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* Forward declarations */ -struct list; - -struct fpga_vlnv { - char *vendor; - char *library; - char *name; - char *version; -}; - -/** Return the first IP block in list \p l which matches the VLNV */ -struct fpga_ip * fpga_vlnv_lookup(struct list *l, struct fpga_vlnv *v); - -/** Check if IP block \p c matched VLNV. */ -int fpga_vlnv_cmp(struct fpga_vlnv *a, struct fpga_vlnv *b); - -/** Tokenizes VLNV \p vlnv and stores it into \p c */ -int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv); - -/** Release memory allocated by fpga_vlnv_parse(). */ -int fpga_vlnv_destroy(struct fpga_vlnv *v); - -#ifdef __cplusplus -} -#endif - -#endif /** _FPGA_VLNV_H_ @} */ diff --git a/fpga/include/villas/plugin.h b/fpga/include/villas/plugin.h deleted file mode 100644 index 8c915b573..000000000 --- a/fpga/include/villas/plugin.h +++ /dev/null @@ -1,82 +0,0 @@ -/** Loadable / plugin support. - * - * @file - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that 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 . - *********************************************************************************/ - -#pragma once - -#include "utils.h" -#include "fpga/ip.h" - -/** @todo This is ugly as hell and broken on OS X / Clang anyway. */ -#define REGISTER_PLUGIN(p) \ -__attribute__((constructor(110))) static void UNIQUE(__ctor)() {\ - if (plugins.state == STATE_DESTROYED) \ - list_init(&plugins); \ - list_push(&plugins, p); \ -} \ -__attribute__((destructor(110))) static void UNIQUE(__dtor)() { \ - if (plugins.state != STATE_DESTROYED) \ - list_remove(&plugins, p); \ -} - -extern struct list plugins; - -enum plugin_type { - PLUGIN_TYPE_FPGA_IP, -}; - -struct plugin { - const char *name; - const char *description; - void *handle; - char *path; - - enum plugin_type type; - - enum state state; - - int (*load)(struct plugin *p); - int (*unload)(struct plugin *p); - - struct fpga_ip_type ip; -}; - -/** Return a pointer to the plugin structure */ -#define plugin(vt) ((struct plugin *) ((char *) (vt) - offsetof(struct plugin, api))) - -#define plugin_name(vt) plugin(vt)->name -#define plugin_description(vt) plugin(vt)->description - -int plugin_init(struct plugin *p); - -int plugin_destroy(struct plugin *p); - -int plugin_parse(struct plugin *p, json_t *cfg); - -int plugin_load(struct plugin *p); - -int plugin_unload(struct plugin *p); - -void plugin_dump(enum plugin_type type); - -/** Find registered and loaded plugin with given name and type. */ -struct plugin * plugin_lookup(enum plugin_type type, const char *name); diff --git a/fpga/lib/card.c b/fpga/lib/card.c deleted file mode 100644 index 651ab76ac..000000000 --- a/fpga/lib/card.c +++ /dev/null @@ -1,315 +0,0 @@ -/** FPGA card. - * - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include - -#include "config.h" -#include "log.h" -#include "log_config.h" -#include "list.h" -#include "utils.h" - -#include "kernel/pci.h" -#include "kernel/vfio.h" - -#include "fpga/ip.h" -#include "fpga/card.h" - -int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) -{ - assert(c->state == STATE_DESTROYED); - - c->vfio_container = vc; - c->pci = pci; - - list_init(&c->ips); - - /* Default values */ - c->filter.id.vendor = FPGA_PCI_VID_XILINX; - c->filter.id.device = FPGA_PCI_PID_VFPGA; - - c->affinity = 0; - c->do_reset = 0; - - c->state = STATE_INITIALIZED; - - return 0; -} - -int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name) -{ - int ret; - - json_t *json_ips; - json_t *json_slot = NULL; - json_t *json_id = NULL; - json_error_t err; - - c->name = strdup(name); - - ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: b, s?: o, s?: o, s: o }", - "affinity", &c->affinity, - "do_reset", &c->do_reset, - "slot", &json_slot, - "id", &json_id, - "ips", &json_ips - ); - if (ret) - jerror(&err, "Failed to parse FPGA vard configuration"); - - if (json_slot) { - const char *err, *slot; - - slot = json_string_value(json_slot); - if (slot) { - ret = pci_device_parse_slot(&c->filter, slot, &err); - if (ret) - error("Failed to parse PCI slot: %s", err); - } - else - error("PCI slot must be a string"); - } - - if (json_id) { - const char *err, *id; - - id = json_string_value(json_id); - if (id) { - ret = pci_device_parse_id(&c->filter, (char*) id, &err); - if (ret) - error("Failed to parse PCI id: %s", err); - } - else - error("PCI ID must be a string"); - } - - if (!json_is_object(json_ips)) - error("FPGA card IPs section must be an object"); - - const char *name_ip; - json_t *json_ip; - json_object_foreach(json_ips, name_ip, json_ip) { - const char *vlnv; - - struct fpga_ip_type *vt; - struct fpga_ip *ip = (struct fpga_ip *) alloc(sizeof(struct fpga_ip)); - - ip->card = c; - - ret = json_unpack_ex(json_ip, &err, 0, "{ s: s }", "vlnv", &vlnv); - if (ret) - error("Failed to parse FPGA IP '%s' of card '%s'", name_ip, name); - - vt = fpga_ip_type_lookup(vlnv); - if (!vt) - error("FPGA IP core VLNV identifier '%s' is invalid", vlnv); - - ret = fpga_ip_init(ip, vt); - if (ret) - error("Failed to initalize FPGA IP core"); - - ret = fpga_ip_parse(ip, json_ip, name_ip); - if (ret) - error("Failed to parse FPGA IP core"); - - list_push(&c->ips, ip); - } - - c->state = STATE_PARSED; - - return 0; -} - -int fpga_card_parse_list(struct list *cards, json_t *cfg) -{ - int ret; - - if (!json_is_object(cfg)) - error("FPGA card configuration section must be a JSON object"); - - const char *name; - json_t *json_fpga; - json_object_foreach(cfg, name, json_fpga) { - struct fpga_card *c = (struct fpga_card *) alloc(sizeof(struct fpga_card)); - - ret = fpga_card_parse(c, json_fpga, name); - if (ret) - error("Failed to parse FPGA card configuration"); - - list_push(cards, c); - } - - return 0; -} - -int fpga_card_start(struct fpga_card *c) -{ - int ret; - - struct pci_device *pdev; - - assert(c->state == STATE_INITIALIZED); - - /* Search for FPGA card */ - pdev = pci_lookup_device(c->pci, &c->filter); - if (!pdev) - error("Failed to find PCI device"); - - /* Attach PCIe card to VFIO container */ - ret = vfio_pci_attach(&c->vfio_device, c->vfio_container, pdev); - if (ret) - error("Failed to attach VFIO device"); - - /* Map PCIe BAR */ - c->map = vfio_map_region(&c->vfio_device, VFIO_PCI_BAR0_REGION_INDEX); - if (c->map == MAP_FAILED) - serror("Failed to mmap() BAR0"); - - /* Enable memory access and PCI bus mastering for DMA */ - ret = vfio_pci_enable(&c->vfio_device); - if (ret) - serror("Failed to enable PCI device"); - - /* Reset system? */ - if (c->do_reset) { - /* Reset / detect PCI device */ - ret = vfio_pci_reset(&c->vfio_device); - if (ret) - serror("Failed to reset PCI device"); - - ret = fpga_card_reset(c); - if (ret) - error("Failed to reset FGPA card"); - } - - /* Initialize IP cores */ - for (size_t j = 0; j < list_length(&c->ips); j++) { - struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); - - ret = fpga_ip_start(i); - if (ret) - error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); - } - - c->state = STATE_STARTED; - - return 0; -} - -int fpga_card_stop(struct fpga_card *c) -{ - int ret; - - assert(c->state == STATE_STOPPED); - - for (size_t j = 0; j < list_length(&c->ips); j++) { - struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); - - ret = fpga_ip_stop(i); - if (ret) - error("Failed to stop FPGA IP core: %s (%u)", i->name, ret); - } - - c->state = STATE_STOPPED; - - return 0; -} - -void fpga_card_dump(struct fpga_card *c) -{ - info("VILLASfpga card:"); - { INDENT - info("Slot: %04x:%02x:%02x.%d", c->vfio_device.pci_device->slot.domain, c->vfio_device.pci_device->slot.bus, c->vfio_device.pci_device->slot.device, c->vfio_device.pci_device->slot.function); - info("Vendor ID: %04x", c->vfio_device.pci_device->id.vendor); - info("Device ID: %04x", c->vfio_device.pci_device->id.device); - info("Class ID: %04x", c->vfio_device.pci_device->id.class_code); - - info("BAR0 mapped at %p", c->map); - - info("IP blocks:"); - for (size_t j = 0; j < list_length(&c->ips); j++) { INDENT - struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); - - fpga_ip_dump(i); - } - } - - vfio_dump(c->vfio_device.group->container); -} - -int fpga_card_check(struct fpga_card *c) -{ - assert(c->state == STATE_PARSED); - - /* Check FPGA configuration */ - c->reset = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_gpio", NULL }); - if (!c->reset) - error("FPGA is missing a reset controller"); - - c->intc = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }); - if (!c->intc) - error("FPGA is missing a interrupt controller"); - - c->sw = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axis_interconnect", NULL }); - if (!c->sw) - warn("FPGA is missing an AXI4-Stream switch"); - - return 0; -} - -int fpga_card_destroy(struct fpga_card *c) -{ - list_destroy(&c->ips, (dtor_cb_t) fpga_ip_destroy, true); - - return 0; -} - -int fpga_card_reset(struct fpga_card *c) -{ - int ret; - char state[4096]; - - /* Save current state of PCI configuration space */ - ret = pread(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); - if (ret != sizeof(state)) - return -1; - - uint32_t *rst_reg = (uint32_t *) (c->map + c->reset->baseaddr); - - debug(3, "FPGA: reset"); - rst_reg[0] = 1; - - usleep(100000); - - /* Restore previous state of PCI configuration space */ - ret = pwrite(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); - if (ret != sizeof(state)) - return -1; - - /* After reset the value should be zero again */ - if (rst_reg[0]) - return -2; - - c->state = STATE_INITIALIZED; - - return 0; -} diff --git a/fpga/lib/ip.c b/fpga/lib/ip.c deleted file mode 100644 index 816bf2e7f..000000000 --- a/fpga/lib/ip.c +++ /dev/null @@ -1,167 +0,0 @@ -/** FPGA IP component. - * - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include "log_config.h" -#include "log.h" -#include "plugin.h" - -int fpga_ip_init(struct fpga_ip *c, struct fpga_ip_type *vt) -{ - int ret; - - assert(c->state == STATE_DESTROYED); - - c->_vt = vt; - c->_vd = alloc(vt->size); - - ret = c->_vt->init ? c->_vt->init(c) : 0; - if (ret) - return ret; - - c->state = STATE_INITIALIZED; - - debug(8, "IP Core %s initalized (%u)", c->name, ret); - - return ret; -} - -int fpga_ip_parse(struct fpga_ip *c, json_t *cfg, const char *name) -{ - int ret, baseaddr = -1; - - assert(c->state != STATE_STARTED && c->state != STATE_DESTROYED); - - c->name = strdup(name); - c->baseaddr = -1; - c->irq = -1; - c->port = -1; - - json_error_t err; - - ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: i, s?: i }", - "baseaddr", &baseaddr, - "irq", &c->irq, - "port", &c->port - ); - if (ret) - jerror(&err, "Failed to parse configuration for FPGA IP '%s'", name); - - c->baseaddr = baseaddr; - - /* Type sepecific settings */ - ret = c->_vt && c->_vt->parse ? c->_vt->parse(c, cfg) : 0; - if (ret) - error("Failed to parse settings for IP core '%s'", name); - - c->state = STATE_PARSED; - - return 0; -} - -int fpga_ip_start(struct fpga_ip *c) -{ - int ret; - - assert(c->state == STATE_CHECKED); - - ret = c->_vt->start ? c->_vt->start(c) : 0; - if (ret) - return ret; - - c->state = STATE_STARTED; - - return 0; -} - -int fpga_ip_stop(struct fpga_ip *c) -{ - int ret; - - assert(c->state == STATE_STARTED); - - ret = c->_vt->stop ? c->_vt->stop(c) : 0; - if (ret) - return ret; - - c->state = STATE_STOPPED; - - return 0; -} - -int fpga_ip_destroy(struct fpga_ip *c) -{ - int ret; - - assert(c->state != STATE_DESTROYED); - - fpga_vlnv_destroy(&c->vlnv); - - ret = c->_vt->destroy ? c->_vt->destroy(c) : 0; - if (ret) - return ret; - - c->state = STATE_DESTROYED; - - free(c->_vd); - - return 0; -} - -int fpga_ip_reset(struct fpga_ip *c) -{ - debug(3, "Reset IP core: %s", c->name); - - return c->_vt->reset ? c->_vt->reset(c) : 0; -} - -void fpga_ip_dump(struct fpga_ip *c) -{ - assert(c->state != STATE_DESTROYED); - - info("IP %s: vlnv=%s:%s:%s:%s baseaddr=%#jx, irq=%d, port=%d", - c->name, c->vlnv.vendor, c->vlnv.library, c->vlnv.name, c->vlnv.version, - c->baseaddr, c->irq, c->port); - - if (c->_vt->dump) - c->_vt->dump(c); -} - -struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr) -{ - int ret; - - struct fpga_vlnv vlnv; - - ret = fpga_vlnv_parse(&vlnv, vstr); - if (ret) - return NULL; - - /* Try to find matching IP type */ - for (size_t i = 0; i < list_length(&plugins); i++) { - struct plugin *p = (struct plugin *) list_at(&plugins, i); - - if (p->type == PLUGIN_TYPE_FPGA_IP && !fpga_vlnv_cmp(&vlnv, &p->ip.vlnv)) - return &p->ip; - } - - return NULL; -} diff --git a/fpga/lib/ips/dma.c b/fpga/lib/ips/dma.c deleted file mode 100644 index c02175709..000000000 --- a/fpga/lib/ips/dma.c +++ /dev/null @@ -1,657 +0,0 @@ -/** DMA related helper functions - * - * These functions present a simpler interface to Xilinx' DMA driver (XAxiDma_*) - * - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include -#include -#include -#include - -#include "log.h" -#include "plugin.h" -#include "utils.h" - -#include "fpga/ip.h" -#include "fpga/card.h" -#include "fpga/ips/dma.h" - -int dma_mem_split(struct dma_mem *o, struct dma_mem *a, struct dma_mem *b) -{ - int split = o->len / 2; - - a->base_virt = o->base_virt; - a->base_phys = o->base_phys; - - b->base_virt = a->base_virt + split; - b->base_phys = a->base_phys + split; - - a->len = split; - b->len = o->len - split; - - return 0; -} - -int dma_alloc(struct fpga_ip *c, struct dma_mem *mem, size_t len, int flags) -{ - int ret; - - struct fpga_card *f = c->card; - - /* Align to next bigger page size chunk */ - if (len & 0xFFF) { - len += 0x1000; - len &= ~0xFFF; - } - - mem->len = len; - mem->base_phys = (void *) -1; /* find free */ - mem->base_virt = mmap(0, mem->len, PROT_READ | PROT_WRITE, flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, 0, 0); - if (mem->base_virt == MAP_FAILED) - return -1; - - ret = vfio_map_dma(f->vfio_device.group->container, (uint64_t) mem->base_virt, (uint64_t) mem->base_phys, mem->len); - if (ret) - return -2; - - return 0; -} - -int dma_free(struct fpga_ip *c, struct dma_mem *mem) -{ - int ret; - - ret = vfio_unmap_dma(c->card->vfio_device.group->container, (uint64_t) mem->base_virt, (uint64_t) mem->base_phys, mem->len); - if (ret) - return ret; - - ret = munmap(mem->base_virt, mem->len); - if (ret) - return ret; - - return 0; -} - -int dma_ping_pong(struct fpga_ip *c, char *src, char *dst, size_t len) -{ - int ret; - - ret = dma_read(c, dst, len); - if (ret) - return ret; - - ret = dma_write(c, src, len); - if (ret) - return ret; - - ret = dma_write_complete(c, NULL, NULL); - if (ret) - return ret; - - ret = dma_read_complete(c, NULL, NULL); - if (ret) - return ret; - - return 0; -} - -int dma_write(struct fpga_ip *c, char *buf, size_t len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - - debug(25, "DMA write: dmac=%s buf=%p len=%#zx", c->name, buf, len); - - return xdma->HasSg - ? dma_sg_write(c, buf, len) - : dma_simple_write(c, buf, len); -} - -int dma_read(struct fpga_ip *c, char *buf, size_t len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - - debug(25, "DMA read: dmac=%s buf=%p len=%#zx", c->name, buf, len); - - return xdma->HasSg - ? dma_sg_read(c, buf, len) - : dma_simple_read(c, buf, len); -} - -int dma_read_complete(struct fpga_ip *c, char **buf, size_t *len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - - debug(25, "DMA read complete: dmac=%s", c->name); - - return xdma->HasSg - ? dma_sg_read_complete(c, buf, len) - : dma_simple_read_complete(c, buf, len); -} - -int dma_write_complete(struct fpga_ip *c, char **buf, size_t *len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - - debug(25, "DMA write complete: dmac=%s", c->name); - - return xdma->HasSg - ? dma_sg_write_complete(c, buf, len) - : dma_simple_write_complete(c, buf, len); -} - -int dma_sg_write(struct fpga_ip *c, char *buf, size_t len) -{ - int ret, bdcnt; - - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); - XAxiDma_Bd *bds, *bd; - - uint32_t remaining, bdlen, bdbuf, cr; - - /* Checks */ - if (!xdma->HasSg) - return -1; - - if (len < 1) - return -2; - - if (!xdma->HasMm2S) - return -3; - - if (!ring->HasDRE) { - uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; - if ((uintptr_t) buf & mask) - return -4; - } - - bdcnt = CEIL(len, FPGA_DMA_BOUNDARY); - ret = XAxiDma_BdRingAlloc(ring, bdcnt, &bds); - if (ret != XST_SUCCESS) - return -5; - - remaining = len; - bdbuf = (uintptr_t) buf; - bd = bds; - for (int i = 0; i < bdcnt; i++) { - bdlen = MIN(remaining, FPGA_DMA_BOUNDARY); - - ret = XAxiDma_BdSetBufAddr(bd, bdbuf); - if (ret != XST_SUCCESS) - goto out; - - ret = XAxiDma_BdSetLength(bd, bdlen, ring->MaxTransferLen); - if (ret != XST_SUCCESS) - goto out; - - /* Set SOF / EOF / ID */ - cr = 0; - if (i == 0) - cr |= XAXIDMA_BD_CTRL_TXSOF_MASK; - if (i == bdcnt - 1) - cr |= XAXIDMA_BD_CTRL_TXEOF_MASK; - - XAxiDma_BdSetCtrl(bd, cr); - XAxiDma_BdSetId(bd, (uintptr_t) buf); - - remaining -= bdlen; - bdbuf += bdlen; - bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); - } - - /* Give the BD to DMA to kick off the transmission. */ - ret = XAxiDma_BdRingToHw(ring, bdcnt, bds); - if (ret != XST_SUCCESS) - return -8; - - return 0; - -out: - ret = XAxiDma_BdRingUnAlloc(ring, bdcnt, bds); - if (ret != XST_SUCCESS) - return -6; - - return -5; -} - -int dma_sg_read(struct fpga_ip *c, char *buf, size_t len) -{ - int ret, bdcnt; - - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); - XAxiDma_Bd *bds, *bd; - - uint32_t remaining, bdlen, bdbuf; - - /* Checks */ - if (!xdma->HasSg) - return -1; - - if (len < 1) - return -2; - - if (!xdma->HasS2Mm) - return -3; - - if (!ring->HasDRE) { - uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; - if ((uintptr_t) buf & mask) - return -4; - } - - bdcnt = CEIL(len, FPGA_DMA_BOUNDARY); - ret = XAxiDma_BdRingAlloc(ring, bdcnt, &bds); - if (ret != XST_SUCCESS) - return -5; - - bdbuf = (uintptr_t) buf; - remaining = len; - bd = bds; - for (int i = 0; i < bdcnt; i++) { - bdlen = MIN(remaining, FPGA_DMA_BOUNDARY); - ret = XAxiDma_BdSetLength(bd, bdlen, ring->MaxTransferLen); - if (ret != XST_SUCCESS) - goto out; - - ret = XAxiDma_BdSetBufAddr(bd, bdbuf); - if (ret != XST_SUCCESS) - goto out; - - /* Receive BDs do not need to set anything for the control - * The hardware will set the SOF/EOF bits per stream ret */ - XAxiDma_BdSetCtrl(bd, 0); - XAxiDma_BdSetId(bd, (uintptr_t) buf); - - remaining -= bdlen; - bdbuf += bdlen; - bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); - } - - ret = XAxiDma_BdRingToHw(ring, bdcnt, bds); - if (ret != XST_SUCCESS) - return -8; - - return 0; - -out: - ret = XAxiDma_BdRingUnAlloc(ring, bdcnt, bds); - if (ret != XST_SUCCESS) - return -6; - - return -5; -} - -int dma_sg_write_complete(struct fpga_ip *c, char **buf, size_t *len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); - XAxiDma_Bd *bds; - - int processed, ret; - - /* Wait until the one BD TX transaction is done */ - while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) - intc_wait(c->card->intc, c->irq); - XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); - - processed = XAxiDma_BdRingFromHw(ring, XAXIDMA_ALL_BDS, &bds); - - if (len != NULL) - *len = XAxiDma_BdGetActualLength(bds, XAXIDMA_MAX_TRANSFER_LEN); - - if (buf != NULL) - *buf = (char *) (uintptr_t) XAxiDma_BdGetId(bds); - - /* Free all processed TX BDs for future transmission */ - ret = XAxiDma_BdRingFree(ring, processed, bds); - if (ret != XST_SUCCESS) - return -1; - - return 0; -} - -int dma_sg_read_complete(struct fpga_ip *c, char **buf, size_t *len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); - XAxiDma_Bd *bds, *bd; - - int ret, bdcnt; - uint32_t recvlen, sr; - uintptr_t recvbuf = 0; - - if (!xdma->HasSg) - return -1; - - while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) - intc_wait(c->card->intc, c->irq + 1); - XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); - - bdcnt = XAxiDma_BdRingFromHw(ring, XAXIDMA_ALL_BDS, &bds); - - recvlen = 0; - - bd = bds; - for (int i = 0; i < bdcnt; i++) { - recvlen += XAxiDma_BdGetActualLength(bd, ring->MaxTransferLen); - - sr = XAxiDma_BdGetSts(bd); - if (sr & XAXIDMA_BD_STS_RXSOF_MASK) - if (i != 0) - warn("sof not first"); - - if (sr & XAXIDMA_BD_STS_RXEOF_MASK) - if (i != bdcnt - 1) - warn("eof not last"); - - recvbuf = XAxiDma_BdGetId(bd); - - bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); - } - - if (len != NULL) - *len = recvlen; - if (buf != NULL) - *buf = (char *) recvbuf; - - /* Free all processed RX BDs for future transmission */ - ret = XAxiDma_BdRingFree(ring, bdcnt, bds); - if (ret != XST_SUCCESS) - return -3; - - return 0; -} - -int dma_simple_read(struct fpga_ip *c, char *buf, size_t len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); - - /* Checks */ - if (xdma->HasSg) - return -1; - - if ((len < 1) || (len > FPGA_DMA_BOUNDARY)) - return -2; - - if (!xdma->HasS2Mm) - return -3; - - if (!ring->HasDRE) { - uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; - if ((uintptr_t) buf & mask) - return -4; - } - - if(!(XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) { - if (XAxiDma_Busy(xdma, XAXIDMA_DEVICE_TO_DMA)) - return -5; - } - - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET, LOWER_32_BITS((uintptr_t) buf)); - if (xdma->AddrWidth > 32) - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET, UPPER_32_BITS((uintptr_t) buf)); - - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET) | XAXIDMA_CR_RUNSTOP_MASK); - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); - - return XST_SUCCESS; -} - -int dma_simple_write(struct fpga_ip *c, char *buf, size_t len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); - - /* Checks */ - if (xdma->HasSg) - return -1; - - if ((len < 1) || (len > FPGA_DMA_BOUNDARY)) - return -2; - - if (!xdma->HasMm2S) - return -3; - - if (!ring->HasDRE) { - uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; - if ((uintptr_t) buf & mask) - return -4; - } - - /* If the engine is doing transfer, cannot submit */ - if(!(XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) { - if (XAxiDma_Busy(xdma, XAXIDMA_DMA_TO_DEVICE)) - return -5; - } - - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET, LOWER_32_BITS((uintptr_t) buf)); - if (xdma->AddrWidth > 32) - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET, UPPER_32_BITS((uintptr_t) buf)); - - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET) | XAXIDMA_CR_RUNSTOP_MASK); - XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); - - return XST_SUCCESS; -} - -int dma_simple_read_complete(struct fpga_ip *c, char **buf, size_t *len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); - - while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) - intc_wait(c->card->intc, c->irq + 1); - XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); - - if (len) - *len = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET); - - if (buf) { - *buf = (char *) (uintptr_t) XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET); - if (xdma->AddrWidth > 32) - *buf += XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET); - } - - return 0; -} - -int dma_simple_write_complete(struct fpga_ip *c, char **buf, size_t *len) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); - - while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) - intc_wait(c->card->intc, c->irq); - XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); - - if (len) - *len = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET); - - if (buf) { - *buf = (char *) (uintptr_t) XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET); - if (xdma->AddrWidth > 32) - *buf += XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET); - } - - return 0; -} - -static int dma_setup_ring(XAxiDma_BdRing *ring, struct dma_mem *bdbuf) -{ - int delay = 0; - int coalesce = 1; - int ret, cnt; - - XAxiDma_Bd clearbd; - - /* Disable all RX interrupts before RxBD space setup */ - XAxiDma_BdRingIntDisable(ring, XAXIDMA_IRQ_ALL_MASK); - - /* Set delay and coalescing */ - XAxiDma_BdRingSetCoalesce(ring, coalesce, delay); - - /* Setup Rx BD space */ - cnt = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, bdbuf->len); - - ret = XAxiDma_BdRingCreate(ring, (uintptr_t) bdbuf->base_phys, (uintptr_t) bdbuf->base_virt, XAXIDMA_BD_MINIMUM_ALIGNMENT, cnt); - if (ret != XST_SUCCESS) - return -1; - - XAxiDma_BdClear(&clearbd); - ret = XAxiDma_BdRingClone(ring, &clearbd); - if (ret != XST_SUCCESS) - return -2; - - /* Start the channel */ - ret = XAxiDma_BdRingStart(ring); - if (ret != XST_SUCCESS) - return -3; - - return XST_SUCCESS; -} - -static int dma_init_rings(XAxiDma *xdma, struct dma_mem *bd) -{ - int ret; - - struct dma_mem bd_rx, bd_tx; - - ret = dma_mem_split(bd, &bd_rx, &bd_tx); - if (ret) - return -1; - - ret = dma_setup_ring(XAxiDma_GetRxRing(xdma), &bd_rx); - if (ret != XST_SUCCESS) - return -2; - - ret = dma_setup_ring(XAxiDma_GetTxRing(xdma), &bd_tx); - if (ret != XST_SUCCESS) - return -3; - - return 0; -} - -int dma_start(struct fpga_ip *c) -{ - int ret, sg; - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma *xdma = &dma->inst; - - /* Guess DMA type */ - sg = (XAxiDma_In32((uintptr_t) c->card->map + c->baseaddr + XAXIDMA_TX_OFFSET+ XAXIDMA_SR_OFFSET) & - XAxiDma_In32((uintptr_t) c->card->map + c->baseaddr + XAXIDMA_RX_OFFSET+ XAXIDMA_SR_OFFSET) & XAXIDMA_SR_SGINCL_MASK) ? 1 : 0; - - XAxiDma_Config xdma_cfg = { - .BaseAddr = (uintptr_t) c->card->map + c->baseaddr, - .HasStsCntrlStrm = 0, - .HasMm2S = 1, - .HasMm2SDRE = 1, - .Mm2SDataWidth = 128, - .HasS2Mm = 1, - .HasS2MmDRE = 1, /* Data Realignment Engine */ - .HasSg = sg, - .S2MmDataWidth = 128, - .Mm2sNumChannels = 1, - .S2MmNumChannels = 1, - .Mm2SBurstSize = 64, - .S2MmBurstSize = 64, - .MicroDmaMode = 0, - .AddrWidth = 32 - }; - - ret = XAxiDma_CfgInitialize(xdma, &xdma_cfg); - if (ret != XST_SUCCESS) - return -1; - - /* Perform selftest */ - ret = XAxiDma_Selftest(xdma); - if (ret != XST_SUCCESS) - return -2; - - /* Map buffer descriptors */ - if (xdma->HasSg) { - ret = dma_alloc(c, &dma->bd, FPGA_DMA_BD_SIZE, 0); - if (ret) - return -3; - - ret = dma_init_rings(xdma, &dma->bd); - if (ret) - return -4; - } - - /* Enable completion interrupts for both channels */ - XAxiDma_IntrEnable(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); - XAxiDma_IntrEnable(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); - - return 0; -} - -int dma_reset(struct fpga_ip *c) -{ - struct dma *dma = (struct dma *) c->_vd; - - XAxiDma_Reset(&dma->inst); - - return 0; -} - -static struct plugin p = { - .name = "Xilinx's AXI4 Direct Memory Access Controller", - .description = "Transfer data streams between VILLASnode and VILLASfpga", - .type = PLUGIN_TYPE_FPGA_IP, - .ip = { - .vlnv = { "xilinx.com", "ip", "axi_dma", NULL }, - .type = FPGA_IP_TYPE_DM_DMA, - .init = dma_start, - .reset = dma_reset, - .size = sizeof(struct dma) - } -}; - -REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/fifo.c b/fpga/lib/ips/fifo.c deleted file mode 100644 index 1f4c058f3..000000000 --- a/fpga/lib/ips/fifo.c +++ /dev/null @@ -1,153 +0,0 @@ -/** FIFO related helper functions - * - * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) - * - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include - -#include "utils.h" -#include "plugin.h" - -#include "fpga/ip.h" -#include "fpga/card.h" -#include "fpga/ips/fifo.h" -#include "fpga/ips/intc.h" - -int fifo_start(struct fpga_ip *c) -{ - int ret; - - struct fpga_card *f = c->card; - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo *xfifo = &fifo->inst; - XLlFifo_Config fifo_cfg = { - .BaseAddress = (uintptr_t) f->map + c->baseaddr, - .Axi4BaseAddress = (uintptr_t) c->card->map + fifo->baseaddr_axi4, - .Datainterface = (fifo->baseaddr_axi4 != -1) ? 1 : 0 /* use AXI4 for Data, AXI4-Lite for control */ - }; - - ret = XLlFifo_CfgInitialize(xfifo, &fifo_cfg, (uintptr_t) c->card->map + c->baseaddr); - if (ret != XST_SUCCESS) - return -1; - - XLlFifo_IntEnable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */ - - return 0; -} - -int fifo_stop(struct fpga_ip *c) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo *xfifo = &fifo->inst; - - XLlFifo_IntDisable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */ - - return 0; -} - -ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo *xllfifo = &fifo->inst; - - uint32_t tdfv; - - tdfv = XLlFifo_TxVacancy(xllfifo); - if (tdfv < len) - return -1; - - XLlFifo_Write(xllfifo, buf, len); - XLlFifo_TxSetLen(xllfifo, len); - - return len; -} - -ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo *xllfifo = &fifo->inst; - - size_t nextlen = 0; - uint32_t rxlen; - - while (!XLlFifo_IsRxDone(xllfifo)) - intc_wait(c->card->intc, c->irq); - XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK); - - /* Get length of next frame */ - rxlen = XLlFifo_RxGetLen(xllfifo); - nextlen = MIN(rxlen, len); - - /* Read from FIFO */ - XLlFifo_Read(xllfifo, buf, nextlen); - - return nextlen; -} - -int fifo_parse(struct fpga_ip *c, json_t *cfg) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - int baseaddr_axi4 = -1, ret; - - json_error_t err; - - fifo->baseaddr_axi4 = -1; - - ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4); - if (ret) - jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); - - fifo->baseaddr_axi4 = baseaddr_axi4; - - return 0; -} - -int fifo_reset(struct fpga_ip *c) -{ - struct fifo *fifo = (struct fifo *) c->_vd; - - XLlFifo_Reset(&fifo->inst); - - return 0; -} - -static struct plugin p = { - .name = "Xilinx's AXI4 FIFO data mover", - .description = "", - .type = PLUGIN_TYPE_FPGA_IP, - .ip = { - .vlnv = { "xilinx.com", "ip", "axi_fifo_mm_s", NULL }, - .type = FPGA_IP_TYPE_DM_FIFO, - .start = fifo_start, - .stop = fifo_stop, - .parse = fifo_parse, - .reset = fifo_reset, - .size = sizeof(struct fifo) - } -}; - -REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/intc.c b/fpga/lib/ips/intc.c deleted file mode 100644 index 7fcf109d6..000000000 --- a/fpga/lib/ips/intc.c +++ /dev/null @@ -1,180 +0,0 @@ -/** AXI-PCIe Interrupt controller - * - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include - -#include "config.h" -#include "log.h" -#include "plugin.h" - -#include "kernel/vfio.h" -#include "kernel/kernel.h" - -#include "fpga/ip.h" -#include "fpga/card.h" -#include "fpga/ips/intc.h" - -int intc_start(struct fpga_ip *c) -{ - int ret; - - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; - - uintptr_t base = (uintptr_t) f->map + c->baseaddr; - - if (c != f->intc) - error("There can be only one interrupt controller per FPGA"); - - intc->num_irqs = vfio_pci_msi_init(&f->vfio_device, intc->efds); - if (intc->num_irqs < 0) - return -1; - - ret = vfio_pci_msi_find(&f->vfio_device, intc->nos); - if (ret) - return -2; - - /* For each IRQ */ - for (int i = 0; i < intc->num_irqs; i++) { - /* Pin to core */ - ret = kernel_irq_setaffinity(intc->nos[i], f->affinity, NULL); - if (ret) - serror("Failed to change affinity of VFIO-MSI interrupt"); - - /* Setup vector */ - XIntc_Out32(base + XIN_IVAR_OFFSET + i * 4, i); - } - - XIntc_Out32(base + XIN_IMR_OFFSET, 0); /* Use manual acknowlegement for all IRQs */ - XIntc_Out32(base + XIN_IAR_OFFSET, 0xFFFFFFFF); /* Acknowlege all pending IRQs manually */ - XIntc_Out32(base + XIN_IMR_OFFSET, 0xFFFFFFFF); /* Use fast acknowlegement for all IRQs */ - XIntc_Out32(base + XIN_IER_OFFSET, 0x00000000); /* Disable all IRQs by default */ - XIntc_Out32(base + XIN_MER_OFFSET, XIN_INT_HARDWARE_ENABLE_MASK | XIN_INT_MASTER_ENABLE_MASK); - - debug(4, "FPGA: enabled interrupts"); - - return 0; -} - -int intc_destroy(struct fpga_ip *c) -{ - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; - - vfio_pci_msi_deinit(&f->vfio_device, intc->efds); - - return 0; -} - -int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) -{ - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; - - uint32_t ier, imr; - uintptr_t base = (uintptr_t) f->map + c->baseaddr; - - /* Current state of INTC */ - ier = XIntc_In32(base + XIN_IER_OFFSET); - imr = XIntc_In32(base + XIN_IMR_OFFSET); - - /* Clear pending IRQs */ - XIntc_Out32(base + XIN_IAR_OFFSET, mask); - - for (int i = 0; i < intc->num_irqs; i++) { - if (mask & (1 << i)) - intc->flags[i] = flags; - } - - if (flags & INTC_POLLING) { - XIntc_Out32(base + XIN_IMR_OFFSET, imr & ~mask); - XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); - } - else { - XIntc_Out32(base + XIN_IER_OFFSET, ier | mask); - XIntc_Out32(base + XIN_IMR_OFFSET, imr | mask); - } - - debug(3, "New ier = %#x", XIntc_In32(base + XIN_IER_OFFSET)); - debug(3, "New imr = %#x", XIntc_In32(base + XIN_IMR_OFFSET)); - debug(3, "New isr = %#x", XIntc_In32(base + XIN_ISR_OFFSET)); - - debug(8, "FPGA: Interupt enabled: mask=%#x flags=%#x", mask, flags); - - return 0; -} - -int intc_disable(struct fpga_ip *c, uint32_t mask) -{ - struct fpga_card *f = c->card; - - uintptr_t base = (uintptr_t) f->map + c->baseaddr; - uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); - - XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); - - return 0; -} - -uint64_t intc_wait(struct fpga_ip *c, int irq) -{ - struct fpga_card *f = c->card; - struct intc *intc = (struct intc *) c->_vd; - - uintptr_t base = (uintptr_t) f->map + c->baseaddr; - - if (intc->flags[irq] & INTC_POLLING) { - uint32_t isr, mask = 1 << irq; - - do { - isr = XIntc_In32(base + XIN_ISR_OFFSET); - pthread_testcancel(); - } while ((isr & mask) != mask); - - XIntc_Out32(base + XIN_IAR_OFFSET, mask); - - return 1; - } - else { - uint64_t cnt; - ssize_t ret = read(intc->efds[irq], &cnt, sizeof(cnt)); - if (ret != sizeof(cnt)) - return 0; - - return cnt; - } -} - -static struct plugin p = { - .name = "Xilinx's programmable interrupt controller", - .description = "", - .type = PLUGIN_TYPE_FPGA_IP, - .ip = { - .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }, - .type = FPGA_IP_TYPE_MISC, - .start = intc_start, - .destroy = intc_destroy, - .size = sizeof(struct intc) - } -}; - -REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/switch.c b/fpga/lib/ips/switch.c deleted file mode 100644 index 73c647be1..000000000 --- a/fpga/lib/ips/switch.c +++ /dev/null @@ -1,221 +0,0 @@ -/** AXI Stream interconnect related helper functions - * - * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) - * - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include "list.h" -#include "log.h" -#include "log_config.h" -#include "plugin.h" - -#include "fpga/ip.h" -#include "fpga/card.h" -#include "fpga/ips/switch.h" - -int switch_start(struct fpga_ip *c) -{ - int ret; - - struct fpga_card *f = c->card; - struct sw *sw = (struct sw *) c->_vd; - - XAxis_Switch *xsw = &sw->inst; - - if (c != f->sw) - error("There can be only one AXI4-Stream interconnect per FPGA"); - - - /* Setup AXI-stream switch */ - XAxis_Switch_Config sw_cfg = { - .BaseAddress = (uintptr_t) f->map + c->baseaddr, - .MaxNumMI = sw->num_ports, - .MaxNumSI = sw->num_ports - }; - - ret = XAxisScr_CfgInitialize(xsw, &sw_cfg, (uintptr_t) c->card->map + c->baseaddr); - if (ret != XST_SUCCESS) - return -1; - - /* Disable all masters */ - XAxisScr_RegUpdateDisable(xsw); - XAxisScr_MiPortDisableAll(xsw); - XAxisScr_RegUpdateEnable(xsw); - - switch_init_paths(c); - - return 0; -} - -int switch_init_paths(struct fpga_ip *c) -{ - int ret; - struct sw *sw = (struct sw *) c->_vd; - - XAxis_Switch *xsw = &sw->inst; - - XAxisScr_RegUpdateDisable(xsw); - XAxisScr_MiPortDisableAll(xsw); - - for (size_t i = 0; i < list_length(&sw->paths); i++) { - struct sw_path *p = (struct sw_path *) list_at(&sw->paths, i); - struct fpga_ip *mi, *si; - - mi = list_lookup(&c->card->ips, p->out); - si = list_lookup(&c->card->ips, p->in); - - if (!mi || !si || mi->port == -1 || si->port == -1) - error("Invalid path configuration for FPGA"); - - ret = switch_connect(c, mi, si); - if (ret) - error("Failed to configure switch"); - } - - XAxisScr_RegUpdateEnable(xsw); - - return 0; -} - -int switch_destroy(struct fpga_ip *c) -{ - struct sw *sw = (struct sw *) c->_vd; - - list_destroy(&sw->paths, NULL, true); - - return 0; -} - -int switch_parse(struct fpga_ip *c, json_t *cfg) -{ - struct sw *sw = (struct sw *) c->_vd; - - int ret; - size_t index; - json_error_t err; - json_t *json_path, *json_paths = NULL; - - list_init(&sw->paths); - - ret = json_unpack_ex(cfg, &err, 0, "{ s: i, s?: o }", - "num_ports", &sw->num_ports, - "paths", &json_paths - ); - if (ret) - jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); - - if (!json_paths) - return 0; /* no switch config available */ - - if (!json_is_array(json_paths)) - error("Setting 'paths' of FPGA IP '%s' should be an array of JSON objects", c->name); - - json_array_foreach(json_paths, index, json_path) { - struct sw_path *p = (struct sw_path *) alloc(sizeof(struct sw_path)); - int reverse = 0; - - ret = json_unpack_ex(json_path, &err, 0, "{ s?: b, s: s, s: s }", - "reverse", &reverse, - "in", &p->in, - "out", &p->out - ); - if (ret) - jerror(&err, "Failed to parse path %zu of FPGA IP '%s'", index, c->name); - - list_push(&sw->paths, p); - - if (reverse) { - struct sw_path *r = memdup(p, sizeof(struct sw_path)); - - r->in = p->out; - r->out = p->in; - - list_push(&sw->paths, r); - } - } - - return 0; -} - -int switch_connect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si) -{ - struct sw *sw = (struct sw *) c->_vd; - XAxis_Switch *xsw = &sw->inst; - - uint32_t mux, port; - - /* Check if theres already something connected */ - for (int i = 0; i < sw->num_ports; i++) { - mux = XAxisScr_ReadReg(xsw->Config.BaseAddress, XAXIS_SCR_MI_MUX_START_OFFSET + i * 4); - if (!(mux & XAXIS_SCR_MI_X_DISABLE_MASK)) { - port = mux & ~XAXIS_SCR_MI_X_DISABLE_MASK; - - if (port == si->port) { - warn("Switch: Slave port %s (%u) has been connected already to port %u. Disconnecting...", si->name, si->port, i); - XAxisScr_RegUpdateDisable(xsw); - XAxisScr_MiPortDisable(xsw, i); - XAxisScr_RegUpdateEnable(xsw); - } - } - } - - /* Reconfigure switch */ - XAxisScr_RegUpdateDisable(xsw); - XAxisScr_MiPortEnable(xsw, mi->port, si->port); - XAxisScr_RegUpdateEnable(xsw); - - /* Reset IPs */ - /*ip_reset(mi); - ip_reset(si);*/ - - debug(8, "FPGA: Switch connected %s (%u) to %s (%u)", mi->name, mi->port, si->name, si->port); - - return 0; -} - -int switch_disconnect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si) -{ - struct sw *sw = (struct sw *) c->_vd; - XAxis_Switch *xsw = &sw->inst; - - if (!XAxisScr_IsMiPortEnabled(xsw, mi->port, si->port)) - return -1; - - XAxisScr_MiPortDisable(xsw, mi->port); - - return 0; -} - -static struct plugin p = { - .name = "Xilinx's AXI4-Stream switch", - .description = "", - .type = PLUGIN_TYPE_FPGA_IP, - .ip = { - .vlnv = { "xilinx.com", "ip", "axis_interconnect", NULL }, - .type = FPGA_IP_TYPE_MISC, - .start = switch_start, - .destroy = switch_destroy, - .parse = switch_parse, - .size = sizeof(struct sw) - } -}; - -REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/timer.c b/fpga/lib/ips/timer.c deleted file mode 100644 index 57eb1a67a..000000000 --- a/fpga/lib/ips/timer.c +++ /dev/null @@ -1,61 +0,0 @@ -/** Timer related helper functions - * - * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) - * - * @author Steffen Vogel - * @copyright 2017, Steffen Vogel - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include "config.h" -#include "plugin.h" - -#include "fpga/ip.h" -#include "fpga/card.h" -#include "fpga/ips/timer.h" - -int timer_start(struct fpga_ip *c) -{ - struct fpga_card *f = c->card; - struct timer *tmr = (struct timer *) c->_vd; - - XTmrCtr *xtmr = &tmr->inst; - XTmrCtr_Config xtmr_cfg = { - .BaseAddress = (uintptr_t) f->map + c->baseaddr, - .SysClockFreqHz = FPGA_AXI_HZ - }; - - XTmrCtr_CfgInitialize(xtmr, &xtmr_cfg, (uintptr_t) f->map + c->baseaddr); - XTmrCtr_InitHw(xtmr); - - return 0; -} - -static struct plugin p = { - .name = "Xilinx's programmable timer / counter", - .description = "", - .type = PLUGIN_TYPE_FPGA_IP, - .ip = { - .vlnv = { "xilinx.com", "ip", "axi_timer", NULL }, - .type = FPGA_IP_TYPE_MISC, - .start = timer_start, - .size = sizeof(struct timer) - } -}; - -REGISTER_PLUGIN(&p) diff --git a/fpga/lib/plugin.c b/fpga/lib/plugin.c deleted file mode 100644 index 908d8eea4..000000000 --- a/fpga/lib/plugin.c +++ /dev/null @@ -1,111 +0,0 @@ -/** Loadable / plugin support. - * - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include -#include - -#include "plugin.h" - -/** Global list of all known plugins */ -struct list plugins = { .state = STATE_DESTROYED }; - -LIST_INIT_STATIC(&plugins) - -int plugin_init(struct plugin *p) -{ - assert(p->state == STATE_DESTROYED); - - p->state = STATE_INITIALIZED; - - return 0; -} - -int plugin_parse(struct plugin *p, json_t *cfg) -{ - const char *path; - - path = json_string_value(cfg); - if (!path) - return -1; - - p->path = strdup(path); - - return 0; -} - -int plugin_load(struct plugin *p) -{ - p->handle = dlopen(p->path, RTLD_NOW); - if (!p->path) - return -1; - - p->state = STATE_LOADED; - - return 0; -} - -int plugin_unload(struct plugin *p) -{ - int ret; - - assert(p->state == STATE_LOADED); - - ret = dlclose(p->handle); - if (ret) - return -1; - - p->state = STATE_UNLOADED; - - return 0; -} - -int plugin_destroy(struct plugin *p) -{ - assert(p->state != STATE_DESTROYED && p->state != STATE_LOADED); - - if (p->path) - free(p->path); - - return 0; -} - -struct plugin * plugin_lookup(enum plugin_type type, const char *name) -{ - for (size_t i = 0; i < list_length(&plugins); i++) { - struct plugin *p = (struct plugin *) list_at(&plugins, i); - - if (p->type == type && strcmp(p->name, name) == 0) - return p; - } - - return NULL; -} - -void plugin_dump(enum plugin_type type) -{ - for (size_t i = 0; i < list_length(&plugins); i++) { - struct plugin *p = (struct plugin *) list_at(&plugins, i); - - if (p->type == type) - printf(" - %-13s: %s\n", p->name, p->description); - } -} diff --git a/fpga/lib/vlnv.c b/fpga/lib/vlnv.c deleted file mode 100644 index e6c693302..000000000 --- a/fpga/lib/vlnv.c +++ /dev/null @@ -1,64 +0,0 @@ -/** Vendor, Library, Name, Version (VLNV) tag - * - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASfpga - * - * 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 - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#include -#include - -#include "fpga/vlnv.h" -#include "fpga/ip.h" - -struct fpga_ip * fpga_vlnv_lookup(struct list *l, struct fpga_vlnv *v) -{ - return (struct fpga_ip *) list_search(l, (cmp_cb_t) fpga_vlnv_cmp, v); -} - -int fpga_vlnv_cmp(struct fpga_vlnv *a, struct fpga_vlnv *b) -{ - return ((!a->vendor || !b->vendor || !strcmp(a->vendor, b->vendor )) && - (!a->library || !b->library || !strcmp(a->library, b->library)) && - (!a->name || !b->name || !strcmp(a->name, b->name )) && - (!a->version || !b->version || !strcmp(a->version, b->version))) ? 0 : 1; -} - -int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv) -{ - char *tmp = strdup(vlnv); - - c->vendor = strdup(strtok(tmp, ":")); - c->library = strdup(strtok(NULL, ":")); - c->name = strdup(strtok(NULL, ":")); - c->version = strdup(strtok(NULL, ":")); - - free(tmp); - - return 0; -} - -int fpga_vlnv_destroy(struct fpga_vlnv *v) -{ - free(v->vendor); - free(v->library); - free(v->name); - free(v->version); - - return 0; -} From 507ea77ad6727d0a251e144c207d43739c919e68 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 16:14:37 +0200 Subject: [PATCH 162/560] ips/dma: add (simple) DMA driver --- fpga/include/villas/fpga/ips/dma.hpp | 119 +++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/dma.cpp | 355 +++++++++++++++++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 fpga/include/villas/fpga/ips/dma.hpp create mode 100644 fpga/lib/ips/dma.cpp diff --git a/fpga/include/villas/fpga/ips/dma.hpp b/fpga/include/villas/fpga/ips/dma.hpp new file mode 100644 index 000000000..b5cd34f51 --- /dev/null +++ b/fpga/include/villas/fpga/ips/dma.hpp @@ -0,0 +1,119 @@ +/** DMA driver + * + * @author Daniel Krebs + * @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS) + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + ******************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include + +#include "fpga/ip_node.hpp" +#include "memory.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +class Dma : public IpNode +{ +public: + friend class DmaFactory; + + bool init(); + bool reset(); + + size_t write(const MemoryBlock& mem, size_t len); + size_t read(const MemoryBlock& mem, size_t len); + + bool writeComplete() + { return hasScatterGather() ? writeCompleteSG() : writeCompleteSimple(); } + + bool readComplete() + { return hasScatterGather() ? readCompleteSG() : readCompleteSimple(); } + + bool pingPong(const MemoryBlock& src, const MemoryBlock& dst, size_t len); + + inline bool + hasScatterGather() const + { return hasSG; } + +private: + size_t writeSG(const void* buf, size_t len); + size_t readSG(void* buf, size_t len); + bool writeCompleteSG(); + bool readCompleteSG(); + + size_t writeSimple(const void* buf, size_t len); + size_t readSimple(void* buf, size_t len); + bool writeCompleteSimple(); + bool readCompleteSimple(); + +private: + static constexpr char registerMemory[] = "Reg"; + + static constexpr char mm2sInterrupt[] = "mm2s_introut"; + static constexpr char mm2sInterface[] = "M_AXI_MM2S"; + + static constexpr char s2mmInterrupt[] = "s2mm_introut"; + static constexpr char s2mmInterface[] = "M_AXI_S2MM"; + + // optional Scatter-Gather interface to access descriptors + static constexpr char sgInterface[] = "M_AXI_SG"; + + std::list getMemoryBlocks() const + { return { registerMemory }; } + + XAxiDma xDma; + bool hasSG; +}; + + + +class DmaFactory : public IpNodeFactory { +public: + DmaFactory(); + + IpCore* create() + { return new Dma; } + + std::string + getName() const + { return "Dma"; } + + std::string + getDescription() const + { return "Xilinx's AXI4 Direct Memory Access Controller"; } + + Vlnv getCompatibleVlnv() const + { return {"xilinx.com:ip:axi_dma:"}; } +}; + +} // namespace ip +} // namespace fpga +} // namespace villas + +/** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 34901a74e..f16350efe 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES ips/fifo.cpp ips/intc.cpp ips/pcie.cpp + ips/dma.cpp kernel/kernel.c kernel/pci.c diff --git a/fpga/lib/ips/dma.cpp b/fpga/lib/ips/dma.cpp new file mode 100644 index 000000000..3a5878441 --- /dev/null +++ b/fpga/lib/ips/dma.cpp @@ -0,0 +1,355 @@ +/** DMA driver + * + * @author Daniel Krebs + * @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS) + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ******************************************************************************/ + +#include +#include + +#include + +#include "fpga/card.hpp" +#include "fpga/ips/dma.hpp" +#include "fpga/ips/intc.hpp" + +#include "log.hpp" +#include "memory_manager.hpp" + +// max. size of a DMA transfer in simple mode +#define FPGA_DMA_BOUNDARY 0x1000 + + +namespace villas { +namespace fpga { +namespace ip { + +// instantiate factory to make available to plugin infrastructure +static DmaFactory factory; + +DmaFactory::DmaFactory() : + IpNodeFactory(getName()) +{ + // nothing to do +} + + +bool +Dma::init() +{ + // if there is a scatter-gather interface, then this instance has it + hasSG = busMasterInterfaces.count(sgInterface) == 1; + logger->info("Scatter-Gather support: {}", hasScatterGather()); + + XAxiDma_Config xdma_cfg; + + xdma_cfg.BaseAddr = getBaseAddr(registerMemory); + xdma_cfg.HasStsCntrlStrm = 0; + xdma_cfg.HasMm2S = 1; + xdma_cfg.HasMm2SDRE = 1; + xdma_cfg.Mm2SDataWidth = 128; + xdma_cfg.HasS2Mm = 1; + xdma_cfg.HasS2MmDRE = 1; /* Data Realignment Engine */ + xdma_cfg.HasSg = hasScatterGather(); + xdma_cfg.S2MmDataWidth = 128; + xdma_cfg.Mm2sNumChannels = 1; + xdma_cfg.S2MmNumChannels = 1; + xdma_cfg.Mm2SBurstSize = 64; + xdma_cfg.S2MmBurstSize = 64; + xdma_cfg.MicroDmaMode = 0; + xdma_cfg.AddrWidth = 32; + + if (XAxiDma_CfgInitialize(&xDma, &xdma_cfg) != XST_SUCCESS) { + logger->error("Cannot initialize Xilinx DMA driver"); + return false; + } + + if (XAxiDma_Selftest(&xDma) != XST_SUCCESS) { + logger->error("DMA selftest failed"); + return false; + } else { + logger->debug("DMA selftest passed"); + } + + /* Map buffer descriptors */ + if (hasScatterGather()) { + logger->warn("Scatter Gather not yet implemented"); + return false; + +// ret = dma_alloc(c, &dma->bd, FPGA_DMA_BD_SIZE, 0); +// if (ret) +// return -3; + +// ret = dma_init_rings(&xDma, &dma->bd); +// if (ret) +// return -4; + } + + /* Enable completion interrupts for both channels */ + XAxiDma_IntrEnable(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + XAxiDma_IntrEnable(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + irqs[mm2sInterrupt].irqController->enableInterrupt(irqs[mm2sInterrupt], false); + irqs[s2mmInterrupt].irqController->enableInterrupt(irqs[s2mmInterrupt], false); + + return true; +} + + +bool +Dma::reset() +{ + XAxiDma_Reset(&xDma); + + // value taken from libxil implementation + int timeout = 500; + + while(timeout > 0) { + if(XAxiDma_ResetIsDone(&xDma)) + return true; + + timeout--; + } + + return false; +} + + +bool +Dma::pingPong(const MemoryBlock& src, const MemoryBlock& dst, size_t len) +{ + if(this->read(dst, len) == 0) + return false; + + if(this->write(src, len) == 0) + return false; + + if(not this->writeComplete()) + return false; + + if(not this->readComplete()) + return false; + + return true; +} + + +size_t +Dma::write(const MemoryBlock& mem, size_t len) +{ + // make sure memory is reachable + if(not card->mapMemoryBlock(mem)) { + logger->error("Memory not accessible by DMA"); + return 0; + } + + auto& mm = MemoryManager::get(); + auto translation = mm.getTranslation(busMasterInterfaces[mm2sInterface], + mem.getAddrSpaceId()); + const void* buf = reinterpret_cast(translation.getLocalAddr(0)); + + return hasScatterGather() ? writeSG(buf, len) : writeSimple(buf, len); +} + + +size_t +Dma::read(const MemoryBlock& mem, size_t len) +{ + // make sure memory is reachable + if(not card->mapMemoryBlock(mem)) { + logger->error("Memory not accessible by DMA"); + return 0; + } + + auto& mm = MemoryManager::get(); + auto translation = mm.getTranslation(busMasterInterfaces[s2mmInterface], + mem.getAddrSpaceId()); + void* buf = reinterpret_cast(translation.getLocalAddr(0)); + + return hasScatterGather() ? readSG(buf, len) : readSimple(buf, len); +} + + +size_t +Dma::writeSG(const void* buf, size_t len) +{ + (void) buf; + (void) len; + logger->error("DMA Scatter Gather write not implemented"); + + return 0; +} + + +size_t +Dma::readSG(void* buf, size_t len) +{ + (void) buf; + (void) len; + logger->error("DMA Scatter Gather read not implemented"); + + return 0; +} + + +bool +Dma::writeCompleteSG() +{ + logger->error("DMA Scatter Gather write not implemented"); + + return false; +} + + +bool +Dma::readCompleteSG() +{ + logger->error("DMA Scatter Gather read not implemented"); + + return false; +} + + +size_t +Dma::writeSimple(const void *buf, size_t len) +{ + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(&xDma); + + if ((len == 0) || (len > FPGA_DMA_BOUNDARY)) + return 0; + + if (not ring->HasDRE) { + const uint32_t mask = xDma.MicroDmaMode + ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN + : ring->DataWidth - 1; + + if (reinterpret_cast(buf) & mask) { + return 0; + } + } + + const bool dmaChannelHalted = + XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK; + + const bool dmaToDeviceBusy = XAxiDma_Busy(&xDma, XAXIDMA_DMA_TO_DEVICE); + + /* If the engine is doing a transfer, cannot submit */ + if (not dmaChannelHalted and dmaToDeviceBusy) { + return 0; + } + + // set lower 32 bit of source address + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET, + LOWER_32_BITS(reinterpret_cast(buf))); + + // if neccessary, set upper 32 bit of source address + if (xDma.AddrWidth > 32) { + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET, + UPPER_32_BITS(reinterpret_cast(buf))); + } + + // start DMA channel + auto channelControl = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET); + channelControl |= XAXIDMA_CR_RUNSTOP_MASK; + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, channelControl); + + // set tail descriptor pointer + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); + + + return len; +} + + +size_t +Dma::readSimple(void *buf, size_t len) +{ + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(&xDma); + + if ((len == 0) || (len > FPGA_DMA_BOUNDARY)) + return 0; + + if (not ring->HasDRE) { + const uint32_t mask = xDma.MicroDmaMode + ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN + : ring->DataWidth - 1; + + if (reinterpret_cast(buf) & mask) { + return 0; + } + } + + const bool dmaChannelHalted = + XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK; + + const bool deviceToDmaBusy = XAxiDma_Busy(&xDma, XAXIDMA_DEVICE_TO_DMA); + + /* If the engine is doing a transfer, cannot submit */ + if (not dmaChannelHalted and deviceToDmaBusy) { + return 0; + } + + // set lower 32 bit of destination address + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET, + LOWER_32_BITS(reinterpret_cast(buf))); + + // if neccessary, set upper 32 bit of destination address + if (xDma.AddrWidth > 32) + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET, + UPPER_32_BITS(reinterpret_cast(buf))); + + // start DMA channel + auto channelControl = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET); + channelControl |= XAXIDMA_CR_RUNSTOP_MASK; + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, channelControl); + + // set tail descriptor pointer + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); + + return len; +} + + +bool +Dma::writeCompleteSimple() +{ + while (!(XAxiDma_IntrGetIrq(&xDma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) + irqs[mm2sInterrupt].irqController->waitForInterrupt(irqs[mm2sInterrupt]); + + XAxiDma_IntrAckIrq(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + + return true; +} + + +bool +Dma::readCompleteSimple() +{ + while (!(XAxiDma_IntrGetIrq(&xDma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) + irqs[s2mmInterrupt].irqController->waitForInterrupt(irqs[s2mmInterrupt]); + + XAxiDma_IntrAckIrq(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + return true; +} + + +} // namespace ip +} // namespace fpga +} // namespace villas From e28345b992e10a328d92f91cb16c378da585c78d Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 16:15:31 +0200 Subject: [PATCH 163/560] tests/dma: add test for DMA driver --- fpga/tests/CMakeLists.txt | 2 +- fpga/tests/dma.cpp | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 fpga/tests/dma.cpp diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index 843c1b6a6..2e4cfe041 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -2,7 +2,7 @@ set(SOURCES main.cpp fpga.cpp logging.cpp -# dma.c + dma.cpp fifo.cpp # hls.c # intc.c diff --git a/fpga/tests/dma.cpp b/fpga/tests/dma.cpp new file mode 100644 index 000000000..fd69cfc94 --- /dev/null +++ b/fpga/tests/dma.cpp @@ -0,0 +1,63 @@ +#include + +#include +#include +#include + +#include + +#include "global.hpp" + +#include + + +Test(fpga, dma, .description = "DMA") +{ + auto logger = loggerGetOrCreate("unittest:dma"); + + size_t count = 0; + for(auto& ip : state.cards.front()->ips) { + // skip non-dma IPs + if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_dma:")) + continue; + + logger->info("Testing {}", *ip); + + auto dma = reinterpret_cast(*ip); + + if(not dma.connectLoopback()) { + continue; + } + + count++; + + if(not dma.loopbackPossible()) { + logger->info("Loopback test not possible for {}", *ip); + continue; + } + + // Simple DMA can only transfer up to 4 kb due to PCIe page size burst + // limitation + size_t len = 4 * (1 << 10); + + /* Allocate memory to use with DMA */ + auto src = villas::HostRam::allocate(len); + auto dst = villas::HostRam::allocate(len); + + /* Get new random data */ + const size_t lenRandom = read_random(&src, len); + cr_assert(len == lenRandom, "Failed to get random data"); + + /* Start transfer */ + cr_assert(dma.pingPong(src, dst, len), "DMA ping pong failed"); + + /* Compare data */ + cr_assert(memcmp(&src, &dst, len) == 0, "Data not equal"); + + logger->info(TXT_GREEN("Passed")); + } + + villas::MemoryManager::get().dump(); + + cr_assert(count > 0, "No Dma found"); +} From ce7e6b36d5bbf9573a3d3ab0328b4be06b10afbd Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 14:59:05 +0200 Subject: [PATCH 164/560] kernel/vfio: check if all required kernel modules are loaded --- fpga/lib/kernel/vfio.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index 09464b1f8..9fae25ca8 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -72,10 +72,17 @@ namespace villas { VfioContainer::VfioContainer() : iova_next(0) { - /* Load VFIO kernel module */ - if (kernel_module_load("vfio") != 0) { - logger->error("Failed to load kernel module: vfio"); - throw std::exception(); + + static constexpr const char* requiredKernelModules[] = { + "vfio", "vfio_pci", "vfio_iommu_type1" + }; + + for(const char* module : requiredKernelModules) { + if(kernel_module_loaded(module) != 0) { + logger->error("Kernel module '{}' required but not loaded. " + "Please load manually!", module); + throw std::exception(); + } } /* Open VFIO API */ From 192aa106272dce34ad892a45a22b926e39be1b57 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 14:59:52 +0200 Subject: [PATCH 165/560] kernel/vfio: fix check for vfio extensions --- fpga/lib/kernel/vfio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index 9fae25ca8..b52d0e1a3 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -101,7 +101,7 @@ VfioContainer::VfioContainer() /* Check available VFIO extensions (IOMMU types) */ extensions = 0; - for (int i = 1; i < VFIO_DMA_CC_IOMMU; i++) { + for (unsigned int i = VFIO_TYPE1_IOMMU; i <= VFIO_NOIOMMU_IOMMU; i++) { int ret = ioctl(fd, VFIO_CHECK_EXTENSION, i); if (ret < 0) { logger->error("Failed to get VFIO extensions"); From 8954e65b4fd06e78946cd585705e8dea25bccd09 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 16:45:54 +0200 Subject: [PATCH 166/560] kernel/vfio: some more cleanup --- fpga/lib/kernel/vfio.cpp | 44 +++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index b52d0e1a3..dcc29c2e8 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -118,7 +119,7 @@ VfioContainer::VfioContainer() VfioContainer::~VfioContainer() { - logger->debug("Clean up container with fd {}", this->fd); + logger->debug("Clean up container with fd {}", fd); /* Release memory and close fds */ groups.clear(); @@ -126,10 +127,8 @@ VfioContainer::~VfioContainer() /* Close container */ int ret = close(fd); if (ret < 0) { - logger->error("Cannot close vfio container"); + logger->error("Cannot close vfio container fd {}", fd); } - - logger->debug("VFIO: closed container: fd={}", fd); } @@ -270,7 +269,7 @@ VfioContainer::attachDevice(const pci_device* pdev) { int ret; char name[32]; - static constexpr char kernelDriver[] = "vfio-pci"; + static constexpr const char* kernelDriver = "vfio-pci"; /* Load PCI bus driver for VFIO */ if (kernel_module_load("vfio_pci")) { @@ -347,13 +346,14 @@ VfioContainer::memoryMap(uintptr_t virt, uintptr_t phys, size_t length) return UINTPTR_MAX; } - logger->info("DMA map size={:#x}, iova={:#x}, vaddr={:#x}", dmaMap.size, dmaMap.iova, dmaMap.vaddr); + logger->debug("DMA map size={:#x}, iova={:#x}, vaddr={:#x}", + dmaMap.size, dmaMap.iova, dmaMap.vaddr); // mapping successful, advance IOVA allocator this->iova_next += iovaIncrement; - // we intentionally don't return the actual mapped length, the users are only - // guaranteed to have their demanded memory mapped correctly + // we intentionally don't return the actual mapped length, the users are + // only guaranteed to have their demanded memory mapped correctly return dmaMap.iova; } @@ -395,7 +395,7 @@ VfioContainer::getOrAttachGroup(int index) logger->error("Failed to attach to IOMMU group: {}", index); throw std::exception(); } else { - logger->info("Attached new group {} to VFIO container", index); + logger->debug("Attached new group {} to VFIO container", index); } // push to our list @@ -407,7 +407,7 @@ VfioContainer::getOrAttachGroup(int index) VfioDevice::~VfioDevice() { - logger->debug("clean up device {} with fd {}", this->name, this->fd); + logger->debug("Clean up device {} with fd {}", this->name, this->fd); for(auto& region : regions) { regionUnmap(region.index); @@ -417,8 +417,6 @@ VfioDevice::~VfioDevice() if (ret != 0) { logger->error("Closing device fd {} failed", fd); } - - logger->debug("VFIO: closed device: name={}, fd={}", name, fd); } @@ -458,7 +456,7 @@ VfioDevice::regionUnmap(size_t index) if (!mappings[index]) return false; /* was not mapped */ - logger->debug("VFIO: unmap region {} from device", index); + logger->debug("Unmap region {} from device {}", index, name); ret = munmap(mappings[index], r->size); if (ret) @@ -487,7 +485,8 @@ VfioDevice::pciEnable() { int ret; uint32_t reg; - off_t offset = ((off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40) + PCI_COMMAND; + const off_t offset = PCI_COMMAND + + (static_cast(VFIO_PCI_CONFIG_REGION_INDEX) << 40); /* Check if this is really a vfio-pci device */ if (!(this->info.flags & VFIO_DEVICE_FLAGS_PCI)) @@ -528,10 +527,10 @@ VfioDevice::pciHotReset() return false; } - logger->debug("VFIO: dependent devices for hot-reset:"); + logger->debug("Dependent devices for hot-reset:"); for (size_t i = 0; i < reset_info->count; i++) { struct vfio_pci_dependent_device *dd = &reset_info->devices[i]; - logger->debug("{:04x}:{:02x}:{:02x}.{:01x}: iommu_group={}", + logger->debug(" {:04x}:{:02x}:{:02x}.{:01x}: iommu_group={}", dd->segment, dd->bus, PCI_SLOT(dd->devfn), PCI_FUNC(dd->devfn), dd->group_id); @@ -568,7 +567,7 @@ VfioDevice::pciMsiInit(int efds[]) const size_t irqCount = irqs[VFIO_PCI_MSI_IRQ_INDEX].count; const size_t irqSetSize = sizeof(struct vfio_irq_set) + - sizeof(int) * irqCount; + sizeof(int) * irqCount; auto irqSet = reinterpret_cast(calloc(1, irqSetSize)); if(irqSet == nullptr) @@ -692,8 +691,7 @@ VfioDevice::isVfioPciDevice() const VfioGroup::~VfioGroup() { - logger->debug("clean up group {} with fd {}", this->index, this->fd); - int ret; + logger->debug("Clean up group {} with fd {}", this->index, this->fd); /* Release memory and close fds */ devices.clear(); @@ -701,19 +699,15 @@ VfioGroup::~VfioGroup() if(fd < 0) { logger->debug("Destructing group that has not been attached"); } else { - ret = ioctl(fd, VFIO_GROUP_UNSET_CONTAINER); + int ret = ioctl(fd, VFIO_GROUP_UNSET_CONTAINER); if (ret != 0) { logger->error("Cannot unset container for group fd {}", fd); } - logger->debug("Released group from container: group={}", index); - ret = close(fd); if (ret != 0) { logger->error("Cannot close group fd {}", fd); } - - logger->debug("Closed group: group={}, fd={}", index, fd); } } @@ -728,7 +722,7 @@ VfioGroup::attach(int containerFd, int groupIndex) groupPath << VFIO_DEV("") << groupIndex; group->fd = open(groupPath.str().c_str(), O_RDWR); if (group->fd < 0) { - logger->error("Failed to open VFIO group: {}", group->index); + logger->error("Failed to open VFIO group {}", group->index); return nullptr; } From 3f5bef34b35d42a2b16d1d93de7d1257f190cdf9 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 5 Apr 2018 11:29:16 +0200 Subject: [PATCH 167/560] added pkg-config file and CMake configuration for building RPM packages --- fpga/CMakeLists.txt | 44 +++++++++++++++++++++++++++++++++++++++ fpga/lib/CMakeLists.txt | 12 +++++++++++ fpga/libvillas-fpga.pc.in | 10 +++++++++ fpga/src/CMakeLists.txt | 8 +++++++ 4 files changed, 74 insertions(+) create mode 100644 fpga/libvillas-fpga.pc.in diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index 4025ae5b9..a6046eda0 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -12,3 +12,47 @@ include_directories(thirdparty/spdlog/include) add_subdirectory(lib) add_subdirectory(tests) add_subdirectory(src) + +# Project settings +set(PROJECT_NAME "VILLASfpga") +set(PROJECT_DESCRIPTION "Host library for configuring and communicating with VILLASfpga") +set(PROJECT_VENDOR "Institute for Automation of Complex Power Systems, RWTH Aachen University") +set(PROJECT_URL "https://www.fein-aachen.org/projects/villas-fpga/") +set(PROJECT_VERSION_MAJOR "0") +set(PROJECT_VERSION_MINOR "1") +set(PROJECT_VERSION_PATCH "0") +set(PROJECT_RELEASE "1") + +# pkg-config +configure_file("libvillas-fpga.pc.in" "libvillas-fpga.pc" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libvillas-fpga.pc" DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/pkgconfig") + +# CPack +SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) +SET(CPACK_PACKAGE_VENDOR ${PROJECT_VENDOR}) +SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") +SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING.md") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + +set(CPACK_PACKAGE_NAME "villas-fpga") +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + +set(CPACK_SOURCE_GENERATOR "TGZ") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") +set(CPACK_SOURCE_IGNORE_FILES "/build/;/.git/;~$;${CPACK_SOURCE_IGNORE_FILES}") + +set(CPACK_RPM_PACKAGE_RELEASE ${PROJECT_RELEASE}) +set(CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64") +set(CPACK_RPM_PACKAGE_LICENSE "GPLv3") +set(CPACK_RPM_PACKAGE_URL ${PROJECT_URL}) +set(CPACK_RPM_PACKAGE_REQUIRES "libxil") +set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries") + +# As close as possible to Fedoras naming +set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}.${CPACK_RPM_PACKAGE_ARCHITECTURE}") + +set(CPACK_GENERATOR "RPM") +include(CPack) diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 50d16ecf5..af2aede70 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -67,3 +67,15 @@ target_link_libraries(villas-fpga PUBLIC ${CMAKE_DL_LIBS} m ) + +include(GNUInstallDirs) + +install(TARGETS villas-fpga + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static +) + +install(DIRECTORY ../include/villas DESTINATION include) + + diff --git a/fpga/libvillas-fpga.pc.in b/fpga/libvillas-fpga.pc.in new file mode 100644 index 000000000..2bae976fa --- /dev/null +++ b/fpga/libvillas-fpga.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=${prefix}/include +libdir=${exec_prefix}/lib + +Name: @PROJECT_NAME@ +Description: @CPACK_PACKAGE_DESCRIPTION_SUMMARY@ +URL: @PROJECT_URL@ +Version: @PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@ +Cflags: -I${includedir} diff --git a/fpga/src/CMakeLists.txt b/fpga/src/CMakeLists.txt index f38667024..0cf2a5ae3 100644 --- a/fpga/src/CMakeLists.txt +++ b/fpga/src/CMakeLists.txt @@ -26,3 +26,11 @@ if(LAPACK_FOUND) target_include_directories(fpga PUBLIC ${LAPACK_INCLUDE_DIRS}) target_compile_definitions(fpga PUBLIC WITH_LAPACK) endif() + +include(GNUInstallDirs) + +install(TARGETS fpga + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static +) From 7e80f88d7081ee4590f83c873c7e52b147e10345 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 5 Apr 2018 11:39:53 +0200 Subject: [PATCH 168/560] cpack: fix name of source tarbar --- fpga/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index a6046eda0..53bbcc9e4 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -41,7 +41,7 @@ set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(CPACK_SOURCE_GENERATOR "TGZ") -set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") set(CPACK_SOURCE_IGNORE_FILES "/build/;/.git/;~$;${CPACK_SOURCE_IGNORE_FILES}") set(CPACK_RPM_PACKAGE_RELEASE ${PROJECT_RELEASE}) @@ -52,7 +52,7 @@ set(CPACK_RPM_PACKAGE_REQUIRES "libxil") set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries") # As close as possible to Fedoras naming -set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}.${CPACK_RPM_PACKAGE_ARCHITECTURE}") +set(CPACK_PACKAGE_FILE_NAME "${CPACK_SOURCE_PACKAGE_FILE_NAME}-${CPACK_RPM_PACKAGE_RELEASE}.${CPACK_RPM_PACKAGE_ARCHITECTURE}") set(CPACK_GENERATOR "RPM") include(CPack) From ae2bd2a41d5a447e0871f23dcea14d89f72e3ad0 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Fri, 13 Apr 2018 15:24:20 +0200 Subject: [PATCH 169/560] lib/ip: alias type for memory block name and cache addres space IDs in IP --- fpga/include/villas/fpga/ip.hpp | 18 ++++++++++++++---- fpga/include/villas/fpga/ips/dma.hpp | 2 +- fpga/include/villas/fpga/ips/fifo.hpp | 2 +- fpga/include/villas/fpga/ips/intc.hpp | 2 +- fpga/include/villas/fpga/ips/switch.hpp | 2 +- fpga/include/villas/fpga/ips/timer.hpp | 2 +- fpga/lib/ip.cpp | 5 ++++- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 28f9e14f6..f565aa4a2 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -127,8 +127,11 @@ public: virtual void dump(); protected: + /// Key-type for accessing maps addressTranslations and slaveAddressSpaces + using MemoryBlockName = std::string; + /// Each IP can declare via this function which memory blocks it requires - virtual std::list + virtual std::list getMemoryBlocks() const { return {}; } @@ -177,11 +180,15 @@ public: protected: uintptr_t - getBaseAddr(const std::string& block) const + getBaseAddr(const MemoryBlockName& block) const { return getLocalAddr(block, 0); } uintptr_t - getLocalAddr(const std::string& block, uintptr_t address) const; + getLocalAddr(const MemoryBlockName& block, uintptr_t address) const; + + MemoryManager::AddressSpaceId + getAddressSpaceId(const MemoryBlockName& block) const + { return slaveAddressSpaces.at(block); } InterruptController* getInterruptController(const std::string& interruptName) const; @@ -206,7 +213,10 @@ protected: std::map irqs; /// Cached translations from the process address space to each memory block - std::map addressTranslations; + std::map addressTranslations; + + /// Lookup for IP's slave address spaces (= memory blocks) + std::map slaveAddressSpaces; /// AXI bus master interfaces to access memory somewhere std::map busMasterInterfaces; diff --git a/fpga/include/villas/fpga/ips/dma.hpp b/fpga/include/villas/fpga/ips/dma.hpp index b5cd34f51..41a1fe115 100644 --- a/fpga/include/villas/fpga/ips/dma.hpp +++ b/fpga/include/villas/fpga/ips/dma.hpp @@ -84,7 +84,7 @@ private: // optional Scatter-Gather interface to access descriptors static constexpr char sgInterface[] = "M_AXI_SG"; - std::list getMemoryBlocks() const + std::list getMemoryBlocks() const { return { registerMemory }; } XAxiDma xDma; diff --git a/fpga/include/villas/fpga/ips/fifo.hpp b/fpga/include/villas/fpga/ips/fifo.hpp index 9d8528237..82fc3156a 100644 --- a/fpga/include/villas/fpga/ips/fifo.hpp +++ b/fpga/include/villas/fpga/ips/fifo.hpp @@ -54,7 +54,7 @@ private: static constexpr char axi4Memory[] = "Mem1"; static constexpr char irqName[] = "interrupt"; - std::list getMemoryBlocks() const + std::list getMemoryBlocks() const { return { registerMemory, axi4Memory }; } XLlFifo xFifo; diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index 7ccef8038..d8d3e5f86 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -64,7 +64,7 @@ private: static constexpr char registerMemory[] = "Reg"; - std::list getMemoryBlocks() const + std::list getMemoryBlocks() const { return { registerMemory }; } diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index 796cab711..587ae4b70 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -58,7 +58,7 @@ private: static constexpr char registerMemory[] = "Reg"; - std::list getMemoryBlocks() const + std::list getMemoryBlocks() const { return { registerMemory }; } struct Path { diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp index 56231069b..fcf4d75ea 100644 --- a/fpga/include/villas/fpga/ips/timer.hpp +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -63,7 +63,7 @@ public: private: - std::list getMemoryBlocks() const + std::list getMemoryBlocks() const { return { registerMemory }; } static constexpr char irqName[] = "generateout0"; diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index d1d2d33a6..63cffdebc 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -281,6 +281,9 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) const auto addrSpaceId = MemoryManager::get().findAddressSpace(addrSpaceName); + // ... and save it in IP + ip->slaveAddressSpaces.emplace(memoryBlock, addrSpaceId); + // get the translation to the address space const auto& translation = MemoryManager::get().getTranslationFromProcess(addrSpaceId); @@ -341,7 +344,7 @@ IpCoreFactory::lookup(const Vlnv &vlnv) uintptr_t -IpCore::getLocalAddr(const std::string& block, uintptr_t address) const +IpCore::getLocalAddr(const MemoryBlockName& block, uintptr_t address) const { // throws exception if block not present auto& translation = addressTranslations.at(block); From 5242b87e4c28699dd025f54d581cff3c55cd5be7 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Fri, 13 Apr 2018 15:31:18 +0200 Subject: [PATCH 170/560] lib/memory: rework allocators to make them extensible and more abstract This is change renders memory allocators only dependend on an address space id that they are managing, allowing easy implementation of other algorithms and instantiation in memory IP blocks. --- fpga/include/villas/memory.hpp | 278 +++++++++++++++++-------- fpga/include/villas/memory_manager.hpp | 8 +- fpga/lib/memory.cpp | 131 +++++++++++- fpga/tests/dma.cpp | 7 +- 4 files changed, 325 insertions(+), 99 deletions(-) diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp index 85dae02ea..8a1895b23 100644 --- a/fpga/include/villas/memory.hpp +++ b/fpga/include/villas/memory.hpp @@ -8,115 +8,221 @@ namespace villas { +/** + * @brief Basic memory block backed by an address space in the memory graph + * + * This is a generic representation of a chunk of memory in the system. It can + * reside anywhere and represent different types of memory. + */ class MemoryBlock { -protected: - MemoryBlock(MemoryManager::AddressSpaceId addrSpaceId, size_t size) : - addrSpaceId(addrSpaceId), size(size) {} - public: + using deallocator_fn = std::function; + + MemoryBlock(size_t offset, size_t size, MemoryManager::AddressSpaceId addrSpaceId) : + offset(offset), size(size), addrSpaceId(addrSpaceId) {} + MemoryManager::AddressSpaceId getAddrSpaceId() const { return addrSpaceId; } size_t getSize() const { return size; } -private: - MemoryManager::AddressSpaceId addrSpaceId; - size_t size; + size_t getOffset() const + { return offset; } + +protected: + size_t offset; ///< Offset (or address) inside address space + size_t size; ///< Size in bytes of this block + MemoryManager::AddressSpaceId addrSpaceId; ///< Identifier in memory graph }; -class MemoryAllocator { -}; - - -class HostRam : public MemoryAllocator { +/** + * @brief Wrapper for a MemoryBlock to access the underlying memory directly + * + * The underlying memory block has to be accessible for the current process, + * that means it has to be mapped accordingly and registered to the global + * memory graph. + * Furthermore, this wrapper can be owning the memory block when initialized + * with a moved unique pointer. Otherwise, it just stores a reference to the + * memory block and it's the users responsibility to take care that the memory + * block is valid. + */ +template +class MemoryAccessor { public: + using Type = T; + + // take ownership of the MemoryBlock + MemoryAccessor(std::unique_ptr mem) : + translation(MemoryManager::get().getTranslationFromProcess(mem->getAddrSpaceId())), + memoryBlock(std::move(mem)) {} + + // just act as an accessor, do not take ownership of MemoryBlock + MemoryAccessor(const MemoryBlock& mem) : + translation(MemoryManager::get().getTranslationFromProcess(mem.getAddrSpaceId())) {} + + + T& operator*() const { + return *reinterpret_cast(translation.getLocalAddr(0)); + } + + T& operator[](size_t idx) const { + const size_t offset = sizeof(T) * idx; + return *reinterpret_cast(translation.getLocalAddr(offset)); + } + + T* operator&() const { + return reinterpret_cast(translation.getLocalAddr(0)); + } + + T* operator->() const { + return reinterpret_cast(translation.getLocalAddr(0)); + } + + const MemoryBlock& + getMemoryBlock() const + { if(not memoryBlock) throw std::bad_alloc(); else return *memoryBlock; } + +private: + /// cached memory translation for fast access + MemoryTranslation translation; + + /// take the unique pointer in case user wants this class to have ownership + std::unique_ptr memoryBlock; +}; + + +/** + * @brief Base memory allocator + * + * Note the usage of CRTP idiom here to access methods of derived allocators. + * The concept is explained here at [1]. + * + * [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern + */ +template +class BaseAllocator { +public: + /// memoryAddrSpaceId: memory that is managed by this allocator + BaseAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId) : + memoryAddrSpaceId(memoryAddrSpaceId) + { + // CRTP + derivedAlloc = static_cast(this); + logger = loggerGetOrCreate(derivedAlloc->getName()); + + // default deallocation callback + free = [&](MemoryBlock* mem) { + logger->warn("no free callback defined for addr space {}, not freeing", + mem->getAddrSpaceId()); + }; + } + + virtual std::unique_ptr + allocateBlock(size_t size) = 0; template - class MemoryBlockHostRam : public MemoryBlock { - friend class HostRam; - private: - MemoryBlockHostRam(void* addr, size_t size, MemoryManager::AddressSpaceId foreignAddrSpaceId) : - MemoryBlock(foreignAddrSpaceId, size), - addr(addr), - translation(MemoryManager::get().getTranslationFromProcess(foreignAddrSpaceId)) - {} - public: - using Type = T; - - MemoryBlockHostRam() = delete; - - T& operator*() { - return *reinterpret_cast(translation.getLocalAddr(0)); - } - - T& operator [](int idx) { - const size_t offset = sizeof(T) * idx; - return *reinterpret_cast(translation.getLocalAddr(offset)); - } - - T* operator &() const { - return reinterpret_cast(translation.getLocalAddr(0)); - } - - private: - // addr needed for freeing later - void* addr; - - // cached memory translation for fast access - MemoryTranslation translation; - }; - - template - static MemoryBlockHostRam + MemoryAccessor allocate(size_t num) { - /* Align to next bigger page size chunk */ - size_t length = num * sizeof(T); - if (length & size_t(0xFFF)) { - length += size_t(0x1000); - length &= size_t(~0xFFF); - } - - void* const addr = HostRam::allocate(length); - if(addr == nullptr) { - throw std::bad_alloc(); - } - - auto& mm = MemoryManager::get(); - - // assemble name for this block - std::stringstream name; - name << std::showbase << std::hex << reinterpret_cast(addr); - - auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str()); - - // create mapping from VA space of process to this new block - mm.createMapping(reinterpret_cast(addr), 0, length, - "VA", - mm.getProcessAddressSpace(), - blockAddrSpaceId); - - // create object and corresponding address space in memory manager - return MemoryBlockHostRam(addr, length, blockAddrSpaceId); + const size_t size = num * sizeof(T); + auto mem = allocateBlock(size); + return MemoryAccessor(std::move(mem)); } - template - static inline bool - free(const MemoryBlockHostRam& block) +protected: + void insertMemoryBlock(const MemoryBlock& mem) { - // TODO: remove address space from memory manager - // TODO: how to prevent use after free? - return HostRam::free(block.addr, block.size); + auto& mm = MemoryManager::get(); + mm.createMapping(mem.getOffset(), 0, mem.getSize(), + derivedAlloc->getName(), + memoryAddrSpaceId, + mem.getAddrSpaceId()); } + void removeMemoryBlock(const MemoryBlock& mem) + { + // this will also remove any mapping to and from the memory block + auto& mm = MemoryManager::get(); + mm.removeAddressSpace(mem.getAddrSpaceId()); + } + + MemoryManager::AddressSpaceId getAddrSpaceId() const + { return memoryAddrSpaceId; } + +protected: + MemoryBlock::deallocator_fn free; + SpdLogger logger; + private: - static void* - allocate(size_t length, int flags = 0); + MemoryManager::AddressSpaceId memoryAddrSpaceId; + DerivedAllocator* derivedAlloc; +}; - static bool - free(void*, size_t length); + +/** + * @brief Linear memory allocator + * + * This is the simplest kind of allocator. The idea is to keep a pointer at the + * first memory address of your memory chunk and move it every time an + * allocation is done. Due to its simplicity, this allocator doesn't allow + * specific positions of memory to be freed. Usually, all memory is freed + * together. + */ +class LinearAllocator : public BaseAllocator { +public: + LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId, + size_t memorySize, + size_t internalOffset = 0); + + size_t getAvailableMemory() const + { return memorySize - nextFreeAddress; } + + std::string getName() const; + + std::unique_ptr + allocateBlock(size_t size); + +private: + static constexpr size_t alignBytes = sizeof(uintptr_t); + static constexpr size_t alignMask = alignBytes - 1; + + size_t getAlignmentPadding(uintptr_t addr) const + { return (alignBytes - (addr & alignMask)) & alignMask; } + +private: + size_t nextFreeAddress; ///< next chunk will be allocated here + size_t memorySize; ///< total size of managed memory + size_t internalOffset; ///< offset in address space (usually 0) +}; + + +/** + * @brief Wrapper around mmap() to create villas memory blocks + * + * This class simply wraps around mmap() and munmap() to allocate memory in the + * host memory via the OS. + */ +class HostRam { +public: + class HostRamAllocator : public BaseAllocator { + public: + HostRamAllocator(); + + std::string getName() const + { return "HostRamAlloc"; } + + std::unique_ptr + allocateBlock(size_t size); + }; + + static HostRamAllocator& + getAllocator() + { return allocator; } + +private: + static HostRamAllocator allocator; }; } // namespace villas diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index fb3db33c2..96d1ccda2 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -77,7 +77,7 @@ private: // ... and no copying or assigning MemoryManager(const MemoryManager&) = delete; - MemoryManager& operator=(const MemoryManager&) = delete ; + MemoryManager& operator=(const MemoryManager&) = delete; /** * @brief Custom edge in memory graph representing a memory mapping @@ -153,13 +153,17 @@ public: { return getOrCreateAddressSpace("villas-fpga"); } AddressSpaceId - getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock) + getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock) { return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); } AddressSpaceId getOrCreateAddressSpace(std::string name); + void + removeAddressSpace(AddressSpaceId addrSpaceId) + { memoryGraph.removeVertex(addrSpaceId); } + /// Create a default mapping MappingId createMapping(uintptr_t src, uintptr_t dest, size_t size, diff --git a/fpga/lib/memory.cpp b/fpga/lib/memory.cpp index f3d2802b4..f2def1432 100644 --- a/fpga/lib/memory.cpp +++ b/fpga/lib/memory.cpp @@ -5,20 +5,135 @@ namespace villas { -bool -HostRam::free(void* addr, size_t length) +std::unique_ptr +HostRam::HostRamAllocator::allocateBlock(size_t size) { - return munmap(addr, length) == 0; + /* Align to next bigger page size chunk */ + if (size & size_t(0xFFF)) { + size += size_t(0x1000); + size &= size_t(~0xFFF); + } + + const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT; + const int mmap_protection = PROT_READ | PROT_WRITE; + + const void* addr = mmap(nullptr, size, mmap_protection, mmap_flags, 0, 0); + if(addr == nullptr) { + throw std::bad_alloc(); + } + + auto& mm = MemoryManager::get(); + + // assemble name for this block + std::stringstream name; + name << std::showbase << std::hex << reinterpret_cast(addr); + + auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str()); + + const auto localAddr = reinterpret_cast(addr); + std::unique_ptr + mem(new MemoryBlock(localAddr, size, blockAddrSpaceId), this->free); + + insertMemoryBlock(*mem); + + return mem; } -void* -HostRam::allocate(size_t length, int flags) +LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId, + size_t memorySize, + size_t internalOffset) : + BaseAllocator(memoryAddrSpaceId), + nextFreeAddress(0), + memorySize(memorySize), + internalOffset(internalOffset) { - const int mmap_flags = flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT; - const int mmap_protection = PROT_READ | PROT_WRITE; + // make sure to start at aligned offset, reduce size in case we need padding + if(const size_t paddingBytes = getAlignmentPadding(internalOffset)) { + assert(paddingBytes < memorySize); - return mmap(nullptr, length, mmap_protection, mmap_flags, 0, 0); + internalOffset += paddingBytes; + memorySize -= paddingBytes; + } + + // deallocation callback + free = [&](MemoryBlock* mem) { + logger->debug("freeing {:#x} bytes at local addr {:#x} (addr space {})", + mem->getSize(), mem->getOffset(), mem->getAddrSpaceId()); + logger->warn("free() not implemented"); + logger->debug("available memory: {:#x} bytes", getAvailableMemory()); + }; +} + + +std::string +LinearAllocator::getName() const +{ + std::stringstream name; + name << "LinearAlloc" << getAddrSpaceId() + << "@0x" << std::hex << internalOffset; + return name.str(); +} + + +std::unique_ptr +LinearAllocator::allocateBlock(size_t size) +{ + if(size > getAvailableMemory()) { + throw std::bad_alloc(); + } + + // assign address + const uintptr_t localAddr = nextFreeAddress + internalOffset; + + // reserve memory + nextFreeAddress += size; + + // make sure it is aligned + if(const size_t paddingBytes = getAlignmentPadding(nextFreeAddress)) { + nextFreeAddress += paddingBytes; + + // if next free address is outside this block due to padding, cap it + nextFreeAddress = std::min(nextFreeAddress, memorySize); + } + + + auto& mm = MemoryManager::get(); + + // assemble name for this block + std::stringstream blockName; + blockName << std::showbase << std::hex << localAddr; + + // create address space + auto addrSpaceName = mm.getSlaveAddrSpaceName(getName(), blockName.str()); + auto addrSpaceId = mm.getOrCreateAddressSpace(addrSpaceName); + + logger->debug("Allocated {:#x} bytes for {}, {:#x} bytes remaining", + size, addrSpaceId, getAvailableMemory()); + + std::unique_ptr + mem(new MemoryBlock(localAddr, size, addrSpaceId), this->free); + + // mount block into the memory graph + insertMemoryBlock(*mem); + + + return mem; +} + + +HostRam::HostRamAllocator +HostRam::allocator; + +HostRam::HostRamAllocator::HostRamAllocator() : + BaseAllocator(MemoryManager::get().getProcessAddressSpace()) +{ + free = [&](MemoryBlock* mem) { + if(::munmap(reinterpret_cast(mem->getOffset()), mem->getSize()) != 0) { + logger->warn("munmap() failed for {:#x} of size {:#x}", + mem->getOffset(), mem->getSize()); + } + }; } } // namespace villas diff --git a/fpga/tests/dma.cpp b/fpga/tests/dma.cpp index fd69cfc94..e8bb4c08d 100644 --- a/fpga/tests/dma.cpp +++ b/fpga/tests/dma.cpp @@ -41,15 +41,16 @@ Test(fpga, dma, .description = "DMA") size_t len = 4 * (1 << 10); /* Allocate memory to use with DMA */ - auto src = villas::HostRam::allocate(len); - auto dst = villas::HostRam::allocate(len); + auto src = villas::HostRam::getAllocator().allocate(len); + auto dst = villas::HostRam::getAllocator().allocate(len); /* Get new random data */ const size_t lenRandom = read_random(&src, len); cr_assert(len == lenRandom, "Failed to get random data"); /* Start transfer */ - cr_assert(dma.pingPong(src, dst, len), "DMA ping pong failed"); + cr_assert(dma.pingPong(src.getMemoryBlock(), dst.getMemoryBlock(), len), + "DMA ping pong failed"); /* Compare data */ cr_assert(memcmp(&src, &dst, len) == 0, "Data not equal"); From 3e505c74bf5fe80fe0464c8a365ece2898160c97 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Fri, 13 Apr 2018 15:30:12 +0200 Subject: [PATCH 171/560] ips/bram: add block RAM IP and use it with DMA test --- fpga/include/villas/fpga/ips/bram.hpp | 87 +++++++++++++++++++++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/bram.cpp | 32 ++++++++++ fpga/tests/dma.cpp | 8 ++- 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 fpga/include/villas/fpga/ips/bram.hpp create mode 100644 fpga/lib/ips/bram.cpp diff --git a/fpga/include/villas/fpga/ips/bram.hpp b/fpga/include/villas/fpga/ips/bram.hpp new file mode 100644 index 000000000..01a46256f --- /dev/null +++ b/fpga/include/villas/fpga/ips/bram.hpp @@ -0,0 +1,87 @@ +/** Block-Raam related helper functions + * * + * @author Daniel Krebs + * @copyright 2018, Daniel Krebs + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * 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 + * any later version. + * + * This program is distributed in the hope that 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include "memory.hpp" +#include "fpga/ip.hpp" + +namespace villas { +namespace fpga { +namespace ip { + + +class Bram : public IpCore +{ + friend class BramFactory; +public: + + bool init(); + + LinearAllocator& + getAllocator() + { return *allocator; } + +private: + static constexpr const char* memoryBlock = "Mem0"; + std::list getMemoryBlocks() const + { return { memoryBlock }; } + + size_t size; + std::unique_ptr allocator; +}; + + + +class BramFactory : public IpCoreFactory { +public: + + BramFactory() : + IpCoreFactory(getName()) + {} + + bool configureJson(IpCore& ip, json_t *json_ip); + + IpCore* create() + { return new Bram; } + + std::string + getName() const + { return "Bram"; } + + std::string + getDescription() const + { return "Block RAM"; } + + Vlnv getCompatibleVlnv() const + { return {"xilinx.com:ip:axi_bram_ctrl:"}; } +}; + +} // namespace ip +} // namespace fpga +} // namespace villas + +/** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index d30414b8e..84733d3f8 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES ips/intc.cpp ips/pcie.cpp ips/dma.cpp + ips/bram.cpp kernel/kernel.c kernel/pci.c diff --git a/fpga/lib/ips/bram.cpp b/fpga/lib/ips/bram.cpp new file mode 100644 index 000000000..11dda7f67 --- /dev/null +++ b/fpga/lib/ips/bram.cpp @@ -0,0 +1,32 @@ +#include "fpga/ips/bram.hpp" + +namespace villas { +namespace fpga { +namespace ip { + +static BramFactory factory; + +bool +BramFactory::configureJson(IpCore& ip, json_t* json_ip) +{ + auto& bram = reinterpret_cast(ip); + + if(json_unpack(json_ip, "{ s: i }", "size", &bram.size) != 0) { + getLogger()->error("Cannot parse 'size'"); + return false; + } + + return true; +} + +bool Bram::init() +{ + allocator = std::make_unique + (getAddressSpaceId(memoryBlock), this->size, 0); + + return true; +} + +} // namespace ip +} // namespace fpga +} // namespace villas diff --git a/fpga/tests/dma.cpp b/fpga/tests/dma.cpp index e8bb4c08d..63c1c0470 100644 --- a/fpga/tests/dma.cpp +++ b/fpga/tests/dma.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -36,13 +37,18 @@ Test(fpga, dma, .description = "DMA") continue; } + // find a block RAM IP to write to + auto bramIp = state.cards.front()->lookupIp(villas::fpga::Vlnv("xilinx.com:ip:axi_bram_ctrl:")); + auto bram = reinterpret_cast(bramIp); + cr_assert_not_null(bram, "Couldn't find BRAM"); + // Simple DMA can only transfer up to 4 kb due to PCIe page size burst // limitation size_t len = 4 * (1 << 10); /* Allocate memory to use with DMA */ auto src = villas::HostRam::getAllocator().allocate(len); - auto dst = villas::HostRam::getAllocator().allocate(len); + auto dst = bram->getAllocator().allocate(len); /* Get new random data */ const size_t lenRandom = read_random(&src, len); From 9870749546e7344c99cbce940b2fb57d06a463ae Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Fri, 13 Apr 2018 16:09:02 +0200 Subject: [PATCH 172/560] lib/common: move plugin, utils and memory stuf into common library --- fpga/lib/CMakeLists.txt | 9 ++++----- fpga/lib/common/CMakeLists.txt | 13 +++++++++++++ fpga/lib/{ => common}/memory.cpp | 0 fpga/lib/{ => common}/memory_manager.cpp | 0 fpga/lib/{ => common}/plugin.cpp | 0 fpga/lib/{ => common}/utils.cpp | 0 6 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 fpga/lib/common/CMakeLists.txt rename fpga/lib/{ => common}/memory.cpp (100%) rename fpga/lib/{ => common}/memory_manager.cpp (100%) rename fpga/lib/{ => common}/plugin.cpp (100%) rename fpga/lib/{ => common}/utils.cpp (100%) diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 84733d3f8..834a7c856 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(common) + set(SOURCES vlnv.cpp card.cpp @@ -21,11 +23,6 @@ set(SOURCES log.c log_config.c log_helper.c - - plugin.cpp - utils.cpp - memory_manager.cpp - memory.cpp ) include(FindPkgConfig) @@ -37,6 +34,8 @@ find_package(Threads) add_library(villas-fpga SHARED ${SOURCES}) +target_link_libraries(villas-fpga PUBLIC villas-common) + target_compile_definitions(villas-fpga PRIVATE BUILDID=\"abc\" _GNU_SOURCE diff --git a/fpga/lib/common/CMakeLists.txt b/fpga/lib/common/CMakeLists.txt new file mode 100644 index 000000000..7b6ba9e27 --- /dev/null +++ b/fpga/lib/common/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.7) + +project(villas-common) + +add_library(villas-common SHARED + plugin.cpp + utils.cpp + memory.cpp + memory_manager.cpp) + +target_include_directories(villas-common + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../include/villas) diff --git a/fpga/lib/memory.cpp b/fpga/lib/common/memory.cpp similarity index 100% rename from fpga/lib/memory.cpp rename to fpga/lib/common/memory.cpp diff --git a/fpga/lib/memory_manager.cpp b/fpga/lib/common/memory_manager.cpp similarity index 100% rename from fpga/lib/memory_manager.cpp rename to fpga/lib/common/memory_manager.cpp diff --git a/fpga/lib/plugin.cpp b/fpga/lib/common/plugin.cpp similarity index 100% rename from fpga/lib/plugin.cpp rename to fpga/lib/common/plugin.cpp diff --git a/fpga/lib/utils.cpp b/fpga/lib/common/utils.cpp similarity index 100% rename from fpga/lib/utils.cpp rename to fpga/lib/common/utils.cpp From 1f42f5bb63a81ae6156e84420fd88e19f12aed08 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 11:50:48 +0200 Subject: [PATCH 173/560] common/cmake: add spdlog include and compatibility include (villas/...) --- fpga/lib/common/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fpga/lib/common/CMakeLists.txt b/fpga/lib/common/CMakeLists.txt index 7b6ba9e27..20bb3b6e2 100644 --- a/fpga/lib/common/CMakeLists.txt +++ b/fpga/lib/common/CMakeLists.txt @@ -10,4 +10,6 @@ add_library(villas-common SHARED target_include_directories(villas-common PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/../../include/villas) + ${CMAKE_CURRENT_LIST_DIR}/../../include/villas + ${CMAKE_CURRENT_LIST_DIR}/../../include + ${CMAKE_CURRENT_LIST_DIR}/../../thirdparty/spdlog/include) From 94ba899b21efd06ba15141881a5ea7cbc6229da3 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 11:22:29 +0200 Subject: [PATCH 174/560] vfio: track if IOMMU is enabled to prepare for non-IOMMU mode --- fpga/include/villas/kernel/vfio.hpp | 4 ++++ fpga/lib/kernel/vfio.cpp | 37 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/fpga/include/villas/kernel/vfio.hpp b/fpga/include/villas/kernel/vfio.hpp index 9f794baf5..0e3fc453f 100644 --- a/fpga/include/villas/kernel/vfio.hpp +++ b/fpga/include/villas/kernel/vfio.hpp @@ -135,6 +135,9 @@ public: /** munmap() a region which has been mapped by vfio_map_region() */ bool memoryUnmap(uintptr_t phys, size_t length); + bool isIommuEnabled() const + { return this->hasIommu; } + private: VfioGroup& getOrAttachGroup(int index); @@ -143,6 +146,7 @@ private: int version; int extensions; uint64_t iova_next; /**< Next free IOVA address */ + bool hasIommu; /// All groups bound to this container std::list> groups; diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index dcc29c2e8..7d29c9ee8 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -108,12 +108,25 @@ VfioContainer::VfioContainer() logger->error("Failed to get VFIO extensions"); throw std::exception(); } - else if (ret > 0) + else if (ret > 0) { extensions |= (1 << i); + } } - logger->debug("Version: {:#x}", version); + hasIommu = false; + + if(not (extensions & (1 << VFIO_NOIOMMU_IOMMU))) { + if(not (extensions & (1 << VFIO_TYPE1_IOMMU))) { + logger->error("No supported IOMMU extension found"); + throw std::exception(); + } else { + hasIommu = true; + } + } + + logger->debug("Version: {:#x}", version); logger->debug("Extensions: {:#x}", extensions); + logger->debug("IOMMU: {}", hasIommu ? "yes" : "no"); } @@ -319,6 +332,11 @@ VfioContainer::memoryMap(uintptr_t virt, uintptr_t phys, size_t length) { int ret; + if(not hasIommu) { + logger->error("DMA mapping not supported without IOMMU"); + return UINTPTR_MAX; + } + if (length & 0xFFF) { length += 0x1000; length &= ~0xFFF; @@ -363,6 +381,10 @@ VfioContainer::memoryUnmap(uintptr_t phys, size_t length) { int ret; + if(not hasIommu) { + return true; + } + struct vfio_iommu_type1_dma_unmap dmaUnmap; dmaUnmap.argsz = sizeof(struct vfio_iommu_type1_dma_unmap); dmaUnmap.flags = 0; @@ -554,6 +576,11 @@ VfioDevice::pciHotReset() free(reset); free(reset_info); + if(not success and not group.container->isIommuEnabled()) { + logger->info("PCI hot reset failed, but this is expected without IOMMU"); + return true; + } + return success; } @@ -740,7 +767,11 @@ VfioGroup::attach(int containerFd, int groupIndex) } /* Set IOMMU type */ - ret = ioctl(containerFd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); + int iommu_type = container.isIommuEnabled() ? + VFIO_TYPE1_IOMMU : + VFIO_NOIOMMU_IOMMU; + + ret = ioctl(containerFd, VFIO_SET_IOMMU, iommu_type); if (ret < 0) { logger->error("Failed to set IOMMU type of container: {}", ret); return nullptr; From b6ff452e53c639de9b6aea520c85074a829a89ab Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 11:23:48 +0200 Subject: [PATCH 175/560] vfio: minor refactoring --- fpga/include/villas/kernel/vfio.hpp | 8 ++++++-- fpga/lib/kernel/vfio.cpp | 26 +++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/fpga/include/villas/kernel/vfio.hpp b/fpga/include/villas/kernel/vfio.hpp index 0e3fc453f..1a09e2e16 100644 --- a/fpga/include/villas/kernel/vfio.hpp +++ b/fpga/include/villas/kernel/vfio.hpp @@ -18,7 +18,8 @@ #include #include -#define VFIO_DEV(x) "/dev/vfio/" x +#define VFIO_PATH "/dev/vfio/" +#define VFIO_DEV VFIO_PATH "vfio" /* Forward declarations */ struct pci_device; @@ -90,7 +91,7 @@ public: ~VfioGroup(); static std::unique_ptr - attach(int containerFd, int groupIndex); + attach(const VfioContainer& container, int groupIndex); private: /// VFIO group file descriptor @@ -138,6 +139,9 @@ public: bool isIommuEnabled() const { return this->hasIommu; } + const int& getFd() const + { return fd; } + private: VfioGroup& getOrAttachGroup(int index); diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index 7d29c9ee8..67cc6d19e 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -87,7 +87,7 @@ VfioContainer::VfioContainer() } /* Open VFIO API */ - fd = open(VFIO_DEV("vfio"), O_RDWR); + fd = open(VFIO_DEV, O_RDWR); if (fd < 0) { logger->error("Failed to open VFIO container"); throw std::exception(); @@ -412,7 +412,7 @@ VfioContainer::getOrAttachGroup(int index) } // group not yet part of this container, so acquire ownership - auto group = VfioGroup::attach(fd, index); + auto group = VfioGroup::attach(*this, index); if(not group) { logger->error("Failed to attach to IOMMU group: {}", index); throw std::exception(); @@ -538,6 +538,7 @@ VfioDevice::pciHotReset() const size_t reset_infolen = sizeof(struct vfio_pci_hot_reset_info) + sizeof(struct vfio_pci_dependent_device) * 64; + auto reset_info = reinterpret_cast (calloc(1, reset_infolen)); @@ -562,6 +563,8 @@ VfioDevice::pciHotReset() } } + free(reset_info); + const size_t resetlen = sizeof(struct vfio_pci_hot_reset) + sizeof(int32_t) * 1; auto reset = reinterpret_cast @@ -571,10 +574,10 @@ VfioDevice::pciHotReset() reset->count = 1; reset->group_fds[0] = this->group.fd; - const bool success = ioctl(this->fd, VFIO_DEVICE_PCI_HOT_RESET, reset) == 0; + int ret = ioctl(this->fd, VFIO_DEVICE_PCI_HOT_RESET, reset); + const bool success = (ret == 0); free(reset); - free(reset_info); if(not success and not group.container->isIommuEnabled()) { logger->info("PCI hot reset failed, but this is expected without IOMMU"); @@ -740,13 +743,16 @@ VfioGroup::~VfioGroup() std::unique_ptr -VfioGroup::attach(int containerFd, int groupIndex) +VfioGroup::attach(const VfioContainer& container, int groupIndex) { std::unique_ptr group { new VfioGroup(groupIndex) }; /* Open group fd */ std::stringstream groupPath; - groupPath << VFIO_DEV("") << groupIndex; + groupPath << VFIO_PATH + << (container.isIommuEnabled() ? "" : "noiommu-") + << groupIndex; + group->fd = open(groupPath.str().c_str(), O_RDWR); if (group->fd < 0) { logger->error("Failed to open VFIO group {}", group->index); @@ -756,13 +762,11 @@ VfioGroup::attach(int containerFd, int groupIndex) logger->debug("VFIO group {} (fd {}) has path {}", groupIndex, group->fd, groupPath.str()); - int ret; - /* Claim group ownership */ - ret = ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &containerFd); + int ret = ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &container.getFd()); if (ret < 0) { logger->error("Failed to attach VFIO group {} to container fd {} (error {})", - group->index, containerFd, ret); + group->index, container.getFd(), ret); return nullptr; } @@ -771,7 +775,7 @@ VfioGroup::attach(int containerFd, int groupIndex) VFIO_TYPE1_IOMMU : VFIO_NOIOMMU_IOMMU; - ret = ioctl(containerFd, VFIO_SET_IOMMU, iommu_type); + ret = ioctl(container.getFd(), VFIO_SET_IOMMU, iommu_type); if (ret < 0) { logger->error("Failed to set IOMMU type of container: {}", ret); return nullptr; From 80386d1085f3110cd6e3fbf80337887bf121c564 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 24 Apr 2018 13:11:25 +0200 Subject: [PATCH 176/560] vfio: correctly set container on group --- fpga/include/villas/kernel/vfio.hpp | 2 +- fpga/lib/kernel/vfio.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fpga/include/villas/kernel/vfio.hpp b/fpga/include/villas/kernel/vfio.hpp index 1a09e2e16..473873d04 100644 --- a/fpga/include/villas/kernel/vfio.hpp +++ b/fpga/include/villas/kernel/vfio.hpp @@ -91,7 +91,7 @@ public: ~VfioGroup(); static std::unique_ptr - attach(const VfioContainer& container, int groupIndex); + attach(VfioContainer& container, int groupIndex); private: /// VFIO group file descriptor diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index 67cc6d19e..2bf90b590 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -743,10 +743,12 @@ VfioGroup::~VfioGroup() std::unique_ptr -VfioGroup::attach(const VfioContainer& container, int groupIndex) +VfioGroup::attach(VfioContainer& container, int groupIndex) { std::unique_ptr group { new VfioGroup(groupIndex) }; + group->container = &container; + /* Open group fd */ std::stringstream groupPath; groupPath << VFIO_PATH From c3993a22c6acfa0d245259f02aaa4080a8db5903 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 24 Apr 2018 13:12:02 +0200 Subject: [PATCH 177/560] vfio: IOMMU group is always 0 if no IOMMU is present --- fpga/lib/kernel/vfio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index 2bf90b590..4451536ba 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -302,7 +302,7 @@ VfioContainer::attachDevice(const pci_device* pdev) } /* Get IOMMU group of device */ - int index = pci_get_iommu_group(pdev); + int index = isIommuEnabled() ? pci_get_iommu_group(pdev) : 0; if (index < 0) { logger->error("Failed to get IOMMU group of device"); throw std::exception(); From 5b8f573337f0a3174ec4f16f55b7824cd87005c8 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 24 Apr 2018 13:12:32 +0200 Subject: [PATCH 178/560] json: parse 64bit numbers, this is required for numbers > 2^31 Our current JSON library jansson only parses signed integers, so it cannot correctly parse numbers between 2^31 and 2^32 into a 32 bit type. --- fpga/lib/ip.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 63cffdebc..0a21d16e3 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -223,8 +223,8 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips) json_t* json_block; json_object_foreach(json_instance, block_name, json_block) { - unsigned int base, high, size; - int ret = json_unpack(json_block, "{ s: i, s: i, s: i }", + json_int_t base, high, size; + int ret = json_unpack(json_block, "{ s: I, s: I, s: I }", "baseaddr", &base, "highaddr", &high, "size", &size); From 01803abadec116cf8fcd13441f07b4bb3657a8f4 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 15:08:30 +0200 Subject: [PATCH 179/560] hwdef-parse: parse PCI and AXI BARs --- fpga/scripts/hwdef-parse.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index 12d96e4f2..cb617f703 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -255,4 +255,27 @@ for bram in brams: if instance in ips: ips[instance]['size'] = int(size) +pcies = root.xpath('.//MODULE[@MODTYPE="axi_pcie"]') +for pcie in pcies: + instance = pcie.get('INSTANCE') + axi_bars = ips[instance].setdefault('axi_bars', {}) + pcie_bars = ips[instance].setdefault('pcie_bars', {}) + + for from_bar, to_bar, from_bars in (('AXIBAR', 'PCIEBAR', axi_bars), ('PCIEBAR', 'AXIBAR', pcie_bars)): + from_bar_num = int(pcie.find('.//PARAMETER[@NAME="C_{}_NUM"]'.format(from_bar)).get('VALUE')) + + for i in range(0, from_bar_num): + from_bar_to_bar_offset = int(pcie.find('.//PARAMETER[@NAME="C_{}2{}_{}"]'.format(from_bar, to_bar, i)).get('VALUE'), 16) + from_bars['BAR{}'.format(i)] = { 'translation': from_bar_to_bar_offset } + + if from_bar == 'AXIBAR': + axi_bar_lo = int(pcie.find('.//PARAMETER[@NAME="C_{}_{}"]'.format(from_bar, i)).get('VALUE'), 16) + axi_bar_hi = int(pcie.find('.//PARAMETER[@NAME="C_{}_HIGHADDR_{}"]'.format(from_bar, i)).get('VALUE'), 16) + axi_bar_size = axi_bar_hi - axi_bar_lo + 1 + + axi_bar = from_bars['BAR{}'.format(i)] + axi_bar['baseaddr'] = axi_bar_lo + axi_bar['highaddr'] = axi_bar_hi + axi_bar['size'] = axi_bar_size + print(json.dumps(ips, indent=2)) From 68e5481d97d6dc24e41f974bd47390b31614a84f Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 24 Apr 2018 13:14:41 +0200 Subject: [PATCH 180/560] config: new config for changed bitstream AXI-BAR0 on PCIe bridge now allows access to whole PCI address space. --- fpga/etc/fpga.json | 52 ++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/fpga/etc/fpga.json b/fpga/etc/fpga.json index fe29ef418..5f982e2a8 100644 --- a/fpga/etc/fpga.json +++ b/fpga/etc/fpga.json @@ -120,30 +120,30 @@ "M_AXI_MM2S": { "pcie_0_axi_pcie_0": { "BAR0": { - "baseaddr": 2147483648, + "baseaddr": 0, "highaddr": 4294967295, - "size": 2147483648 + "size": 4294967296 } } }, "M_AXI_S2MM": { "pcie_0_axi_pcie_0": { "BAR0": { - "baseaddr": 2147483648, + "baseaddr": 0, "highaddr": 4294967295, - "size": 2147483648 + "size": 4294967296 } } } }, "ports": [ { - "role": "initiator", + "role": "master", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:1", "name": "MM2S" }, { - "role": "target", + "role": "slave", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:1", "name": "S2MM" } @@ -159,30 +159,30 @@ "M_AXI_MM2S": { "pcie_0_axi_pcie_0": { "BAR0": { - "baseaddr": 2147483648, + "baseaddr": 0, "highaddr": 4294967295, - "size": 2147483648 + "size": 4294967296 } } }, "M_AXI_S2MM": { "pcie_0_axi_pcie_0": { "BAR0": { - "baseaddr": 2147483648, + "baseaddr": 0, "highaddr": 4294967295, - "size": 2147483648 + "size": 4294967296 } } } }, "ports": [ { - "role": "initiator", + "role": "master", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:6", "name": "MM2S" }, { - "role": "target", + "role": "slave", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:6", "name": "S2MM" } @@ -214,22 +214,22 @@ "vlnv": "xilinx.com:ip:axis_switch:1.1", "ports": [ { - "role": "initiator", + "role": "master", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:3", "name": "M03_AXIS" }, { - "role": "target", + "role": "slave", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:3", "name": "S03_AXIS" }, { - "role": "initiator", + "role": "master", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:4", "name": "M04_AXIS" }, { - "role": "target", + "role": "slave", "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:4", "name": "S04_AXIS" } @@ -237,7 +237,7 @@ "num_ports": 7 }, "hier_0_hls_dft_0": { - "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0", + "vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.1", "ports": [ { "role": "master", @@ -249,7 +249,10 @@ "target": "hier_0_axis_interconnect_0_axis_interconnect_0_xbar:5", "name": "input_r" } - ] + ], + "irqs": { + "interrupt": "pcie_0_axi_pcie_intc_0:1" + } }, "hier_0_rtds_axis_0": { "vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0", @@ -358,6 +361,19 @@ } } } + }, + "axi_bars": { + "BAR0": { + "translation": 0, + "baseaddr": 0, + "highaddr": 4294967295, + "size": 4294967296 + } + }, + "pcie_bars": { + "BAR0": { + "translation": 0 + } } }, "pcie_0_axi_pcie_intc_0": { From 9490594167439937cc669c7215ee4f07d67cc7c7 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 12:00:31 +0200 Subject: [PATCH 181/560] allocator: properly remove memory block from memory graph --- fpga/include/villas/memory.hpp | 2 ++ fpga/lib/common/memory.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp index 8a1895b23..12779bf01 100644 --- a/fpga/include/villas/memory.hpp +++ b/fpga/include/villas/memory.hpp @@ -116,6 +116,8 @@ public: free = [&](MemoryBlock* mem) { logger->warn("no free callback defined for addr space {}, not freeing", mem->getAddrSpaceId()); + + removeMemoryBlock(*mem); }; } diff --git a/fpga/lib/common/memory.cpp b/fpga/lib/common/memory.cpp index f2def1432..c6d5b386e 100644 --- a/fpga/lib/common/memory.cpp +++ b/fpga/lib/common/memory.cpp @@ -62,6 +62,8 @@ LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId mem->getSize(), mem->getOffset(), mem->getAddrSpaceId()); logger->warn("free() not implemented"); logger->debug("available memory: {:#x} bytes", getAvailableMemory()); + + removeMemoryBlock(*mem); }; } @@ -133,6 +135,8 @@ HostRam::HostRamAllocator::HostRamAllocator() : logger->warn("munmap() failed for {:#x} of size {:#x}", mem->getOffset(), mem->getSize()); } + + removeMemoryBlock(*mem); }; } From d81fc6fe11a61858602687272faa7010cca70a4b Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Fri, 13 Apr 2018 15:59:34 +0200 Subject: [PATCH 182/560] gpu: add empty library for GPU-related stuff --- fpga/lib/CMakeLists.txt | 2 ++ fpga/lib/gpu/CMakeLists.txt | 14 ++++++++++++++ fpga/lib/gpu/gpu.cpp | 7 +++++++ fpga/lib/gpu/include/villas/gpu.hpp | 7 +++++++ 4 files changed, 30 insertions(+) create mode 100644 fpga/lib/gpu/CMakeLists.txt create mode 100644 fpga/lib/gpu/gpu.cpp create mode 100644 fpga/lib/gpu/include/villas/gpu.hpp diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 834a7c856..fff01b8e1 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(common) +add_subdirectory(gpu) set(SOURCES vlnv.cpp @@ -35,6 +36,7 @@ find_package(Threads) add_library(villas-fpga SHARED ${SOURCES}) target_link_libraries(villas-fpga PUBLIC villas-common) +target_link_libraries(villas-fpga PUBLIC villas-gpu) target_compile_definitions(villas-fpga PRIVATE BUILDID=\"abc\" diff --git a/fpga/lib/gpu/CMakeLists.txt b/fpga/lib/gpu/CMakeLists.txt new file mode 100644 index 000000000..68c3993ea --- /dev/null +++ b/fpga/lib/gpu/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.7) + +project(villas-gpu + VERSION 1.0 + DESCRIPTION "VILLASgpu" + LANGUAGES C CXX) + +# fail if CUDA not found +find_package(CUDA QUIET REQUIRED) + +cuda_add_library(villas-gpu SHARED gpu.cpp) + +target_include_directories(villas-gpu + PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) diff --git a/fpga/lib/gpu/gpu.cpp b/fpga/lib/gpu/gpu.cpp new file mode 100644 index 000000000..4314fcb65 --- /dev/null +++ b/fpga/lib/gpu/gpu.cpp @@ -0,0 +1,7 @@ +#include + +namespace villas { +namespace gpu { + +} // namespace villas +} // namespace gpu diff --git a/fpga/lib/gpu/include/villas/gpu.hpp b/fpga/lib/gpu/include/villas/gpu.hpp new file mode 100644 index 000000000..903dd08c2 --- /dev/null +++ b/fpga/lib/gpu/include/villas/gpu.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace villas { +namespace gpu { + +} // namespace villas +} // namespace gpu From 3f71793327446bf30f98af49a5b87adfb70f472c Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 18 Apr 2018 21:56:24 +0200 Subject: [PATCH 183/560] gpu: add gdrcopy submodule --- fpga/.gitmodules | 3 +++ fpga/lib/gpu/gdrcopy | 1 + 2 files changed, 4 insertions(+) create mode 160000 fpga/lib/gpu/gdrcopy diff --git a/fpga/.gitmodules b/fpga/.gitmodules index e67658aa1..8bbca3dab 100644 --- a/fpga/.gitmodules +++ b/fpga/.gitmodules @@ -4,3 +4,6 @@ [submodule "thirdparty/libxil"] path = thirdparty/libxil url = https://git.rwth-aachen.de/acs/public/villas/libxil.git +[submodule "lib/gpu/gdrcopy"] + path = lib/gpu/gdrcopy + url = https://github.com/daniel-k/gdrcopy.git diff --git a/fpga/lib/gpu/gdrcopy b/fpga/lib/gpu/gdrcopy new file mode 160000 index 000000000..2b933176d --- /dev/null +++ b/fpga/lib/gpu/gdrcopy @@ -0,0 +1 @@ +Subproject commit 2b933176d0fd20f10bddfdf574a1d3229ca1ecdf From 29709aed7abd566dbcfa61571f282a47c6c689fa Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 11:45:32 +0200 Subject: [PATCH 184/560] directed-graph: make compile with C++11 (no C++17 with CUDA) --- fpga/include/villas/directed_graph.hpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 6335d8087..b75912335 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -81,7 +81,7 @@ public: std::shared_ptr getVertex(VertexIdentifier vertexId) const { - if(vertexId < 0 or vertexId >= lastVertexId) + if(vertexId >= lastVertexId) throw std::invalid_argument("vertex doesn't exist"); // cannot use [] operator, because creates non-existing elements @@ -92,7 +92,10 @@ public: template VertexIdentifier findVertex(UnaryPredicate p) { - for(auto& [vertexId, vertex] : vertices) { + for(auto& v : vertices) { + auto& vertexId = v.first; + auto& vertex = v.second; + if(p(vertex)) { return vertexId; } @@ -103,7 +106,7 @@ public: std::shared_ptr getEdge(EdgeIdentifier edgeId) const { - if(edgeId < 0 or edgeId >= lastEdgeId) + if(edgeId >= lastEdgeId) throw std::invalid_argument("edge doesn't exist"); // cannot use [] operator, because creates non-existing elements @@ -177,7 +180,9 @@ public: // delete every edge that start or ends at this vertex auto it = edges.begin(); while(it != edges.end()) { - auto& [edgeId, edge] = *it; + auto& edgeId = it->first; + auto& edge = it->second; + bool removeEdge = false; if(edge->to == vertexId) { @@ -255,8 +260,8 @@ public: void dump() { logger->info("Vertices:"); - for(auto& [vertexId, vertex] : vertices) { - (void) vertexId; + for(auto& v : vertices) { + auto& vertex = v.second; // format connected vertices into a list std::stringstream ssEdges; @@ -268,8 +273,8 @@ public: } logger->info("Edges:"); - for(auto& [edgeId, edge] : edges) { - (void) edgeId; + for(auto& e : edges) { + auto& edge = e.second; logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); } From 105f47d2d088949a2eb867803bf69635228fc040 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 12:13:51 +0200 Subject: [PATCH 185/560] common/memory: add check-callback to getPath() to select desired path This is a workaround until we have a better heuristic (maybe shortest path?) to choose between multiple paths in the graph. Since the (abstract) graph has no idea about memory translations, getPath() may even yield paths that are no valid translation because a pair of inbound/outbound edges must not neccessarily share a common address window, but from the perspective of the abstract graph present a valid path. The callback function is used by the MemoryManager to verify if a path candidate represents a valid translation. --- fpga/include/villas/directed_graph.hpp | 13 +++++++++-- fpga/include/villas/memory_manager.hpp | 14 +++++++++++- fpga/include/villas/utils.hpp | 9 ++++++++ fpga/lib/common/memory_manager.cpp | 30 +++++++++++++++++++++++--- 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index b75912335..48899a606 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -212,9 +212,17 @@ public: vertexGetEdges(VertexIdentifier vertexId) const { return getVertex(vertexId)->edges; } + + using check_path_fn = std::function; + + static bool + checkPath(const Path&) + { return true; } + bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId, - Path& path) + Path& path, + check_path_fn pathCheckFunc = checkPath) { if(fromVertexId == toVertexId) { // arrived at the destination @@ -244,7 +252,8 @@ public: path.push_back(edgeId); // recursive, depth-first search - if(getPath(edgeOfFromVertex->to, toVertexId, path)) { + if(getPath(edgeOfFromVertex->to, toVertexId, path, pathCheckFunc) and + pathCheckFunc(path)) { // path found, we're done return true; } else { diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 96d1ccda2..012910509 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "log.hpp" @@ -73,7 +74,12 @@ private: // This is a singleton, so private constructor ... MemoryManager() : memoryGraph("MemoryGraph"), - logger(loggerGetOrCreate("MemoryManager")) {} + logger(loggerGetOrCreate("MemoryManager")) + { + pathCheckFunc = [&](const MemoryGraph::Path& path) { + return this->pathCheck(path); + }; + } // ... and no copying or assigning MemoryManager(const MemoryManager&) = delete; @@ -144,6 +150,8 @@ public: using AddressSpaceId = MemoryGraph::VertexIdentifier; using MappingId = MemoryGraph::EdgeIdentifier; + struct InvalidTranslation : public std::exception {}; + /// Get singleton instance static MemoryManager& get(); @@ -210,6 +218,8 @@ private: getTranslationFromMapping(const Mapping& mapping) { return MemoryTranslation(mapping.src, mapping.dest, mapping.size); } + bool + pathCheck(const MemoryGraph::Path& path); private: /// Directed graph that stores address spaces and memory mappings @@ -221,6 +231,8 @@ private: /// Logger for universal access in this class SpdLogger logger; + MemoryGraph::check_path_fn pathCheckFunc; + /// Static pointer to global instance, because this is a singleton static MemoryManager* instance; }; diff --git a/fpga/include/villas/utils.hpp b/fpga/include/villas/utils.hpp index b2b5b314c..d368de915 100644 --- a/fpga/include/villas/utils.hpp +++ b/fpga/include/villas/utils.hpp @@ -10,6 +10,15 @@ namespace utils { std::vector tokenize(std::string s, std::string delimiter); + +template +void +assertExcept(bool condition, const T& exception) +{ + if(not condition) + throw exception; +} + } // namespace utils } // namespace villas diff --git a/fpga/lib/common/memory_manager.cpp b/fpga/lib/common/memory_manager.cpp index f8e01e9b0..d832896c6 100644 --- a/fpga/lib/common/memory_manager.cpp +++ b/fpga/lib/common/memory_manager.cpp @@ -2,8 +2,11 @@ #include #include +#include #include "memory_manager.hpp" +using namespace villas::utils; + namespace villas { MemoryManager* @@ -76,7 +79,8 @@ MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId, { // find a path through the memory graph MemoryGraph::Path path; - if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, path)) { + if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, path, pathCheckFunc)) { + auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId); auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId); @@ -98,6 +102,26 @@ MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId, return translation; } +bool +MemoryManager::pathCheck(const MemoryGraph::Path& path) +{ + // start with an identity mapping + MemoryTranslation translation(0, 0, SIZE_MAX); + + // Try to add all mappings together to a common translation. If this fails + // there is a non-overlapping window + for(auto& mappingId : path) { + auto mapping = memoryGraph.getEdge(mappingId); + try { + translation += getTranslationFromMapping(*mapping); + } catch(const InvalidTranslation&) { + return false; + } + } + + return true; +} + uintptr_t MemoryTranslation::getLocalAddr(uintptr_t addrInForeignAddrSpace) const { @@ -125,8 +149,8 @@ MemoryTranslation::operator+=(const MemoryTranslation& other) const uintptr_t other_src_high = other.src + other.size; // make sure there is a common memory area - assert(other.src < this_dst_high); - assert(this->dst < other_src_high); + assertExcept(other.src < this_dst_high, MemoryManager::InvalidTranslation()); + assertExcept(this->dst < other_src_high, MemoryManager::InvalidTranslation()); const uintptr_t hi = std::max(this_dst_high, other_src_high); const uintptr_t lo = std::min(this->dst, other.src); From 218008955e6b99ba9c72278f186b2c15772a47cb Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 13:12:26 +0200 Subject: [PATCH 186/560] common/memory: fix memory translation merging --- fpga/lib/common/memory_manager.cpp | 60 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/fpga/lib/common/memory_manager.cpp b/fpga/lib/common/memory_manager.cpp index d832896c6..88da8536f 100644 --- a/fpga/lib/common/memory_manager.cpp +++ b/fpga/lib/common/memory_manager.cpp @@ -83,8 +83,7 @@ MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId, auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId); auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId); - - logger->error("No translation found from ({}) to ({})", + logger->debug("No translation found from ({}) to ({})", *fromAddrSpace, *toAddrSpace); throw std::out_of_range("no translation found"); @@ -148,6 +147,15 @@ MemoryTranslation::operator+=(const MemoryTranslation& other) const uintptr_t this_dst_high = this->dst + this->size; const uintptr_t other_src_high = other.src + other.size; + logger->debug("this->src: {:#x}", this->src); + logger->debug("this->dst: {:#x}", this->dst); + logger->debug("this->size: {:#x}", this->size); + logger->debug("other.src: {:#x}", other.src); + logger->debug("other.dst: {:#x}", other.dst); + logger->debug("other.size: {:#x}", other.size); + logger->debug("this_dst_high: {:#x}", this_dst_high); + logger->debug("other_src_high: {:#x}", other_src_high); + // make sure there is a common memory area assertExcept(other.src < this_dst_high, MemoryManager::InvalidTranslation()); assertExcept(this->dst < other_src_high, MemoryManager::InvalidTranslation()); @@ -159,33 +167,39 @@ MemoryTranslation::operator+=(const MemoryTranslation& other) ? (this_dst_high - other_src_high) : (other_src_high - this_dst_high); - const uintptr_t diff_lo = (this->dst > other.src) + const bool otherSrcIsSmaller = this->dst > other.src; + const uintptr_t diff_lo = (otherSrcIsSmaller) ? (this->dst - other.src) : (other.src - this->dst); - const size_t size = (hi - lo) - diff_hi - diff_lo; + logger->debug("hi: {:#x}", hi); + logger->debug("lo: {:#x}", lo); + logger->debug("diff_hi: {:#x}", diff_hi); + logger->debug("diff_lo: {:#x}", diff_lo); - logger->debug("this->src: 0x{:x}", this->src); - logger->debug("this->dst: 0x{:x}", this->dst); - logger->debug("this->size: 0x{:x}", this->size); - logger->debug("other.src: 0x{:x}", other.src); - logger->debug("other.dst: 0x{:x}", other.dst); - logger->debug("other.size: 0x{:x}", other.size); - logger->debug("this_dst_high: 0x{:x}", this_dst_high); - logger->debug("other_src_high: 0x{:x}", other_src_high); - logger->debug("hi: 0x{:x}", hi); - logger->debug("lo: 0x{:x}", lo); - logger->debug("diff_hi: 0x{:x}", diff_hi); - logger->debug("diff_hi: 0x{:x}", diff_lo); - logger->debug("size: 0x{:x}", size); + // new size of aperture, can only stay or shrink + this->size = (hi - lo) - diff_hi - diff_lo; - this->src += other.src; - this->dst += other.dst; - this->size = size; + // new translation will come out other's destination (by default) + this->dst = other.dst; - logger->debug("result src: 0x{:x}", this->src); - logger->debug("result dst: 0x{:x}", this->dst); - logger->debug("result size: 0x{:x}", this->size); + // the source stays the same and can only increase with merged translations + this->src = this->src; + + if(otherSrcIsSmaller) { + // other mapping starts at lower addresses, so we actually arrive at + // higher addresses + this->dst += diff_lo; + } else { + // other mapping starts at higher addresses than this, so we have to + // increase the start + // NOTE: for addresses equality, this just adds 0 + this->src += diff_lo; + } + + logger->debug("result src: {:#x}", this->src); + logger->debug("result dst: {:#x}", this->dst); + logger->debug("result size: {:#x}", this->size); return *this; } From cea353aa7f078f06480ca0a66a79f519792870b6 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 13:13:15 +0200 Subject: [PATCH 187/560] directed-graph: add getters for vertices of an edge --- fpga/include/villas/directed_graph.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 48899a606..0af3abf36 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -58,6 +58,12 @@ public: operator==(const Edge& other) { return this->id == other.id; } + Vertex::Identifier getVertexTo() const + { return to; } + + Vertex::Identifier getVertexFrom() const + { return from; } + private: Identifier id; Vertex::Identifier from; From 14704907473f0945ca81618b64db5bef0cceb364 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 13:15:29 +0200 Subject: [PATCH 188/560] common/memory: provide findPath() to get a path of address spaces --- fpga/include/villas/memory_manager.hpp | 3 +++ fpga/lib/common/memory_manager.cpp | 27 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 012910509..877456abe 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -192,6 +192,9 @@ public: AddressSpaceId findAddressSpace(const std::string& name); + std::list + findPath(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId); + MemoryTranslation getTranslation(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId); diff --git a/fpga/lib/common/memory_manager.cpp b/fpga/lib/common/memory_manager.cpp index 88da8536f..0754e0c13 100644 --- a/fpga/lib/common/memory_manager.cpp +++ b/fpga/lib/common/memory_manager.cpp @@ -73,6 +73,33 @@ MemoryManager::findAddressSpace(const std::string& name) }); } +std::list +MemoryManager::findPath(MemoryManager::AddressSpaceId fromAddrSpaceId, + MemoryManager::AddressSpaceId toAddrSpaceId) +{ + std::list path; + + auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId); + auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId); + + // find a path through the memory graph + MemoryGraph::Path pathGraph; + if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, pathGraph, pathCheckFunc)) { + + logger->debug("No translation found from ({}) to ({})", + *fromAddrSpace, *toAddrSpace); + + throw std::out_of_range("no translation found"); + } + + for(auto& mappingId : pathGraph) { + auto mapping = memoryGraph.getEdge(mappingId); + path.push_back(mapping->getVertexTo()); + } + + return path; +} + MemoryTranslation MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId, MemoryManager::AddressSpaceId toAddrSpaceId) From 2477ed4b4b5db3c8d64c817bcc9351d0cf788667 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 13:21:52 +0200 Subject: [PATCH 189/560] common/memory: provide getPciAddressSpace() for a common PCIe address space --- fpga/include/villas/memory_manager.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 877456abe..387f5c0a2 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -115,7 +115,7 @@ private: return stream << static_cast(mapping) << " = " << mapping.name << std::hex - << "(src=0x" << mapping.src + << " (src=0x" << mapping.src << ", dest=0x" << mapping.dest << ", size=0x" << mapping.size << ")"; @@ -160,6 +160,10 @@ public: getProcessAddressSpace() { return getOrCreateAddressSpace("villas-fpga"); } + AddressSpaceId + getPciAddressSpace() + { return getOrCreateAddressSpace("PCIe"); } + AddressSpaceId getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock) { return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); } From 6b7d6941035157d88d07c4ceba72c8fb443ea9f0 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 13:26:59 +0200 Subject: [PATCH 190/560] common/BaseAllocator: test allocated memory for accessibility Write to and read-verify allocated memory block when using allocate() wrapper. --- fpga/include/villas/memory.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp index 12779bf01..38b801c45 100644 --- a/fpga/include/villas/memory.hpp +++ b/fpga/include/villas/memory.hpp @@ -130,6 +130,21 @@ public: { const size_t size = num * sizeof(T); auto mem = allocateBlock(size); + + // Check if the allocated memory is really accessible by writing to the + // allocated memory and reading back. Exponentially increase offset to + // speed up testing. + MemoryAccessor byteAccessor(*mem); + size_t idx = 0; + for(int i = 0; idx < mem->getSize(); i++, idx = (1 << i)) { + auto val = static_cast(i); + byteAccessor[idx] = val; + if(byteAccessor[idx] != val) { + logger->error("Cannot access allocated memory"); + throw std::bad_alloc(); + } + } + return MemoryAccessor(std::move(mem)); } @@ -181,6 +196,9 @@ public: size_t getAvailableMemory() const { return memorySize - nextFreeAddress; } + size_t getSize() const + { return memorySize; } + std::string getName() const; std::unique_ptr From 1b2e7d312e2caff88f250dfe44e73a6db60a2f57 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 13:30:22 +0200 Subject: [PATCH 191/560] common/memory: add host DMA memory allocator using udmabuf --- fpga/.gitmodules | 3 + fpga/include/villas/memory.hpp | 37 ++++++++++ fpga/lib/common/memory.cpp | 121 +++++++++++++++++++++++++++++++++ fpga/thirdparty/udmabuf | 1 + 4 files changed, 162 insertions(+) create mode 160000 fpga/thirdparty/udmabuf diff --git a/fpga/.gitmodules b/fpga/.gitmodules index 8bbca3dab..c457c7c52 100644 --- a/fpga/.gitmodules +++ b/fpga/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/gpu/gdrcopy"] path = lib/gpu/gdrcopy url = https://github.com/daniel-k/gdrcopy.git +[submodule "thirdparty/udmabuf"] + path = thirdparty/udmabuf + url = https://github.com/ikwzm/udmabuf diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp index 38b801c45..b640743fb 100644 --- a/fpga/include/villas/memory.hpp +++ b/fpga/include/villas/memory.hpp @@ -245,4 +245,41 @@ private: static HostRamAllocator allocator; }; + +class HostDmaRam { +private: + + static std::string + getUdmaBufName(int num); + + static std::string + getUdmaBufBasePath(int num); + + static size_t + getUdmaBufBufSize(int num); + + static uintptr_t + getUdmaBufPhysAddr(int num); + +public: + class HostDmaRamAllocator : public LinearAllocator { + public: + HostDmaRamAllocator(int num); + + virtual ~HostDmaRamAllocator(); + + std::string getName() const + { return getUdmaBufName(num); } + + private: + int num; + }; + + static HostDmaRamAllocator& + getAllocator(int num = 0); + +private: + static std::map> allocators; +}; + } // namespace villas diff --git a/fpga/lib/common/memory.cpp b/fpga/lib/common/memory.cpp index c6d5b386e..3a44ab63e 100644 --- a/fpga/lib/common/memory.cpp +++ b/fpga/lib/common/memory.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include + #include "memory.hpp" namespace villas { @@ -140,4 +143,122 @@ HostRam::HostRamAllocator::HostRamAllocator() : }; } + +std::map> +HostDmaRam::allocators; + +HostDmaRam::HostDmaRamAllocator::HostDmaRamAllocator(int num) : + LinearAllocator(MemoryManager::get().getOrCreateAddressSpace(getUdmaBufName(num)), getUdmaBufBufSize(num)), + num(num) +{ + auto& mm = MemoryManager::get(); + logger = loggerGetOrCreate(getName()); + + if(getSize() == 0) { + logger->error("Zero-sized DMA buffer not supported, is the kernel module loaded?"); + throw std::bad_alloc(); + } + + const uintptr_t base = getUdmaBufPhysAddr(num); + + mm.createMapping(base, 0, getSize(), getName() + "-PCI", + mm.getPciAddressSpace(), getAddrSpaceId()); + + const auto bufPath = std::string("/dev/") + getUdmaBufName(num); + const int bufFd = open(bufPath.c_str(), O_RDWR | O_SYNC); + if(bufFd != -1) { + void* buf = mmap(nullptr, getSize(), PROT_READ|PROT_WRITE, MAP_SHARED, bufFd, 0); + close(bufFd); + + if(buf != MAP_FAILED) { + mm.createMapping(reinterpret_cast(buf), 0, getSize(), + getName() + "-VA", + mm.getProcessAddressSpace(), getAddrSpaceId()); + } else { + logger->warn("Cannot map {}", bufPath); + } + } else { + logger->warn("Cannot open {}", bufPath); + } + + logger->info("Mapped {} of size {} bytes", bufPath, getSize()); +} + +HostDmaRam::HostDmaRamAllocator::~HostDmaRamAllocator() +{ + auto& mm = MemoryManager::get(); + + void* baseVirt; + try { + auto translation = mm.getTranslationFromProcess(getAddrSpaceId()); + baseVirt = reinterpret_cast(translation.getLocalAddr(0)); + } catch(const std::out_of_range&) { + // not mapped, nothing to do + return; + } + + logger->debug("Unmapping {}", getName()); + + // try to unmap it + if(::munmap(baseVirt, getSize()) != 0) { + logger->warn("munmap() failed for {:p} of size {:#x}", + baseVirt, getSize()); + } +} + +std::string +HostDmaRam::getUdmaBufName(int num) +{ + std::stringstream name; + name << "udmabuf" << num; + + return name.str(); +} + +std::string +HostDmaRam::getUdmaBufBasePath(int num) +{ + std::stringstream path; + path << "/sys/class/udmabuf/udmabuf" << num << "/"; + return path.str(); +} + +size_t +HostDmaRam::getUdmaBufBufSize(int num) +{ + std::fstream s(getUdmaBufBasePath(num) + "size", s.in); + if(s.is_open()) { + std::string line; + if(std::getline(s, line)) { + return std::strtoul(line.c_str(), nullptr, 10); + } + } + + return 0; +} + +uintptr_t +HostDmaRam::getUdmaBufPhysAddr(int num) +{ + std::fstream s(getUdmaBufBasePath(num) + "phys_addr", s.in); + if(s.is_open()) { + std::string line; + if(std::getline(s, line)) { + return std::strtoul(line.c_str(), nullptr, 16); + } + } + + return UINTPTR_MAX; +} + +HostDmaRam::HostDmaRamAllocator&HostDmaRam::getAllocator(int num) +{ + auto& allocator = allocators[num]; + if(not allocator) { + allocator = std::make_unique(num); + } + + return *allocator; +} + } // namespace villas diff --git a/fpga/thirdparty/udmabuf b/fpga/thirdparty/udmabuf new file mode 160000 index 000000000..65762ca33 --- /dev/null +++ b/fpga/thirdparty/udmabuf @@ -0,0 +1 @@ +Subproject commit 65762ca3333cb230e3b80ecf4cb5e605390474f2 From e819829560c10ab442bbd6c6e388c449cfdf5bd2 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 14:48:49 +0200 Subject: [PATCH 192/560] directed-graph: add dumping to dot-file (graphviz) --- fpga/include/villas/directed_graph.hpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index 0af3abf36..c2b92674a 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -272,7 +273,7 @@ public: return false; } - void dump() + void dump(const std::string& fileName = "") { logger->info("Vertices:"); for(auto& v : vertices) { @@ -287,11 +288,29 @@ public: logger->info(" {} connected to: {}", *vertex, ssEdges.str()); } + std::fstream s(fileName, s.out | s.trunc); + if(s.is_open()) { + s << "digraph memgraph {" << std::endl; + } + logger->info("Edges:"); for(auto& e : edges) { auto& edge = e.second; logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); + if(s.is_open()) { + auto from = getVertex(edge->from); + auto to = getVertex(edge->to); + + s << std::dec; + s << " \"" << *from << "\" -> \"" << *to << "\"" + << " [label=\"" << *edge << "\"];" << std::endl; + } + } + + if(s.is_open()) { + s << "}" << std::endl; + s.close(); } } From 2bfb9e24500bcb8473f1133efa7dd913fadef137 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 15:12:17 +0200 Subject: [PATCH 193/560] common/memory: expose method to dump memory graph to file --- fpga/include/villas/memory_manager.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 387f5c0a2..555baa694 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -218,6 +218,9 @@ public: dump() { memoryGraph.dump(); } + void + dumpToFile(const std::string& fileName) + { memoryGraph.dump(fileName); } private: /// Convert a Mapping to MemoryTranslation for calculations From ad820a3618c819463b89f4fe40a710edc4081694 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 15:11:47 +0200 Subject: [PATCH 194/560] kernel/pci: parse BAR regions --- fpga/include/villas/kernel/pci.h | 10 +++++ fpga/lib/kernel/pci.c | 67 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h index 10fd86ef6..3cd9565ed 100644 --- a/fpga/include/villas/kernel/pci.h +++ b/fpga/include/villas/kernel/pci.h @@ -9,6 +9,7 @@ #pragma once +#include #include "list.h" #define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) @@ -33,6 +34,13 @@ struct pci_device { } slot; /**< Bus, Device, Function (BDF) */ }; +struct pci_region { + int num; + uintptr_t start; + uintptr_t end; + unsigned long long flags; +}; + struct pci { struct list devices; /**< List of available PCI devices in the system (struct pci_device) */ }; @@ -66,6 +74,8 @@ int pci_attach_driver(const struct pci_device *d, const char *driver); /** Return the IOMMU group of this PCI device or -1 if the device is not in a group. */ int pci_get_iommu_group(const struct pci_device *d); +size_t pci_get_regions(const struct pci_device *d, struct pci_region** regions); + #ifdef __cplusplus } #endif diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index 1f7336742..f920ea6d5 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -254,6 +254,73 @@ struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *f) return list_search(&p->devices, (cmp_cb_t) pci_device_compare, (void *) f); } +size_t pci_get_regions(const struct pci_device *d, struct pci_region** regions) +{ + FILE* f; + char sysfs[1024]; + + assert(regions != NULL); + + snprintf(sysfs, sizeof(sysfs), "%s/bus/pci/devices/%04x:%02x:%02x.%x/resource", + SYSFS_PATH, d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); + + f = fopen(sysfs, "r"); + if (!f) + serror("Failed to open resource mapping %s", sysfs); + + struct pci_region _regions[8]; + struct pci_region* cur_region = _regions; + size_t valid_regions = 0; + + ssize_t bytesRead; + char* line = NULL; + size_t len = 0; + + int region = 0; + // cap to 8 regions, just because we don't know how many may exist + while(region < 8 && (bytesRead = getline(&line, &len, f)) != -1) { + unsigned long long tokens[3]; + char* s = line; + for(int i = 0; i < 3; i++) { + char* end; + tokens[i] = strtoull(s, &end, 16); + if(s == end) { + printf("Error parsing line %d of %s\n", region + 1, sysfs); + tokens[0] = tokens[1] = 0; // mark invalid + break; + } + s = end; + } + + free(line); + + // required for getline() to allocate a new buffer on the next iteration + line = NULL; + len = 0; + + if(tokens[0] != tokens[1]) { + // this is a valid region + cur_region->num = region; + cur_region->start = tokens[0]; + cur_region->end = tokens[1]; + cur_region->flags = tokens[2]; + cur_region++; + valid_regions++; + } + + region++; + } + + if(valid_regions > 0) { + const size_t len = valid_regions * sizeof (struct pci_region); + *regions = malloc(len); + memcpy(*regions, _regions, len); + } + + return valid_regions; +} + + int pci_get_driver(const struct pci_device *d, char *buf, size_t buflen) { int ret; From c818c242f3181cac4e55a0026cff6de2c752807d Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 15:14:40 +0200 Subject: [PATCH 195/560] kernel/pci: fix unitialized memory --- fpga/lib/kernel/pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index f920ea6d5..0dbdf08a4 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -325,6 +325,7 @@ int pci_get_driver(const struct pci_device *d, char *buf, size_t buflen) { int ret; char sysfs[1024], syml[1024]; + memset(syml, 0, sizeof(syml)); snprintf(sysfs, sizeof(sysfs), "%s/bus/pci/devices/%04x:%02x:%02x.%x/driver", SYSFS_PATH, d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); @@ -372,6 +373,7 @@ int pci_get_iommu_group(const struct pci_device *d) { int ret; char *group, link[1024], sysfs[1024]; + memset(link, 0, sizeof(link)); snprintf(sysfs, sizeof(sysfs), "%s/bus/pci/devices/%04x:%02x:%02x.%x/iommu_group", SYSFS_PATH, d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); From 8f3833bc73a21b25ecc5e388adb678a6e845a094 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:18:05 +0200 Subject: [PATCH 196/560] ips/dma: rename pingpong to memcpy and always connect loopback --- fpga/include/villas/fpga/ips/dma.hpp | 2 +- fpga/lib/ips/dma.cpp | 8 +++++++- fpga/tests/dma.cpp | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/fpga/include/villas/fpga/ips/dma.hpp b/fpga/include/villas/fpga/ips/dma.hpp index 41a1fe115..d8b609794 100644 --- a/fpga/include/villas/fpga/ips/dma.hpp +++ b/fpga/include/villas/fpga/ips/dma.hpp @@ -55,7 +55,7 @@ public: bool readComplete() { return hasScatterGather() ? readCompleteSG() : readCompleteSimple(); } - bool pingPong(const MemoryBlock& src, const MemoryBlock& dst, size_t len); + bool memcpy(const MemoryBlock& src, const MemoryBlock& dst, size_t len); inline bool hasScatterGather() const diff --git a/fpga/lib/ips/dma.cpp b/fpga/lib/ips/dma.cpp index 3a5878441..ed9f0a888 100644 --- a/fpga/lib/ips/dma.cpp +++ b/fpga/lib/ips/dma.cpp @@ -132,8 +132,14 @@ Dma::reset() bool -Dma::pingPong(const MemoryBlock& src, const MemoryBlock& dst, size_t len) +Dma::memcpy(const MemoryBlock& src, const MemoryBlock& dst, size_t len) { + if(len == 0) + return true; + + if(not connectLoopback()) + return false; + if(this->read(dst, len) == 0) return false; diff --git a/fpga/tests/dma.cpp b/fpga/tests/dma.cpp index 63c1c0470..cb6b722f6 100644 --- a/fpga/tests/dma.cpp +++ b/fpga/tests/dma.cpp @@ -55,7 +55,7 @@ Test(fpga, dma, .description = "DMA") cr_assert(len == lenRandom, "Failed to get random data"); /* Start transfer */ - cr_assert(dma.pingPong(src.getMemoryBlock(), dst.getMemoryBlock(), len), + cr_assert(dma.memcpy(src.getMemoryBlock(), dst.getMemoryBlock(), len), "DMA ping pong failed"); /* Compare data */ From 364b13715664ee801c04701b82473a1718d66a3f Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:47:43 +0200 Subject: [PATCH 197/560] fpga/card: make pci device a class member (needed later) --- fpga/include/villas/fpga/card.hpp | 3 ++- fpga/lib/card.cpp | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index f9d78151a..d4b4b5cc2 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -98,8 +98,9 @@ public: // TODO: make this private std::string name; /**< The name of the FPGA card */ - struct pci *pci; + struct pci* pci; struct pci_device filter; /**< Filter for PCI device. */ + struct pci_device* pdev; /**< PCI device handle */ /// The VFIO container that this card is part of std::shared_ptr vfioContainer; diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index fb7369823..4581d7482 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -203,10 +203,8 @@ PCIeCard::mapMemoryBlock(const MemoryBlock& block) bool -fpga::PCIeCard::init() +PCIeCard::init() { - struct pci_device *pdev; - auto& mm = MemoryManager::get(); logger = getLogger(); From 89b5169a6ec4846f20a8794ac05db86ac4537c63 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:41:40 +0200 Subject: [PATCH 198/560] ips/pcie: parse AXI/PCI BARs and create mappings to/from PCIe address space This is used for translations that don't use VFIO which used to bridge the PCIe address space by creating direct mappings from process VA to the FPGA. When we want to communicate directly via PCIe without the involvment of the CPU/VFIO, we need the proper translations that are configured in the FPGA hardware. --- fpga/include/villas/fpga/ips/pcie.hpp | 15 +++++ fpga/lib/ips/pcie.cpp | 96 +++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/fpga/include/villas/fpga/ips/pcie.hpp b/fpga/include/villas/fpga/ips/pcie.hpp index 28b01c5aa..ebce36178 100644 --- a/fpga/include/villas/fpga/ips/pcie.hpp +++ b/fpga/include/villas/fpga/ips/pcie.hpp @@ -52,6 +52,19 @@ public: private: static constexpr char axiInterface[] = "M_AXI"; static constexpr char pcieMemory[] = "BAR0"; + + struct AxiBar { + uintptr_t base; + size_t size; + uintptr_t translation; + }; + + struct PciBar { + uintptr_t translation; + }; + + std::map axiToPcieTranslations; + std::map pcieToAxiTranslations; }; @@ -64,6 +77,8 @@ public: getCompatibleVlnvString() { return "xilinx.com:ip:axi_pcie:"; } + bool configureJson(IpCore& ip, json_t *json_ip); + IpCore* create() { return new AxiPciExpressBridge; } diff --git a/fpga/lib/ips/pcie.cpp b/fpga/lib/ips/pcie.cpp index 59174318b..3b273bbd8 100644 --- a/fpga/lib/ips/pcie.cpp +++ b/fpga/lib/ips/pcie.cpp @@ -63,6 +63,102 @@ AxiPciExpressBridge::init() card->addrSpaceIdDeviceToHost = mm.getOrCreateAddressSpace(addrSpaceNameDeviceToHost); + auto pciAddrSpaceId = mm.getPciAddressSpace(); + + struct pci_region* pci_regions = nullptr; + size_t num_regions = pci_get_regions(card->pdev, &pci_regions); + + for(size_t i = 0; i < num_regions; i++) { + const size_t region_size = pci_regions[i].end - pci_regions[i].start + 1; + + char barName[] = "BARx"; + barName[3] = '0' + pci_regions[i].num; + auto pciBar = pcieToAxiTranslations.at(barName); + + + logger->info("PCI-BAR{}: bus addr={:#x} size={:#x}", + pci_regions[i].num, pci_regions[i].start, region_size); + logger->info("PCI-BAR{}: AXI translation offset {:#x}", + i, pciBar.translation); + + mm.createMapping(pci_regions[i].start, pciBar.translation, region_size, + std::string("PCI-") + barName, + pciAddrSpaceId, card->addrSpaceIdHostToDevice); + + } + + if(pci_regions != nullptr) { + logger->debug("freeing pci regions"); + free(pci_regions); + } + + + for(auto& [barName, axiBar] : axiToPcieTranslations) { + logger->info("AXI-{}: bus addr={:#x} size={:#x}", + barName, axiBar.base, axiBar.size); + logger->info("AXI-{}: PCI translation offset: {:#x}", + barName, axiBar.translation); + + auto barXAddrSpaceName = mm.getSlaveAddrSpaceName(getInstanceName(), barName); + auto barXAddrSpaceId = mm.getOrCreateAddressSpace(barXAddrSpaceName); + + // base is already incorporated into mapping of each IP by Vivado, so + // the mapping src has to be 0 + mm.createMapping(0, axiBar.translation, axiBar.size, + std::string("AXI-") + barName, + barXAddrSpaceId, pciAddrSpaceId); + } + + return true; +} + +bool +AxiPciExpressBridgeFactory::configureJson(IpCore& ip, json_t* json_ip) +{ + auto logger = getLogger(); + auto& pcie = reinterpret_cast(ip); + + for(auto barType : std::list{"axi_bars", "pcie_bars"}) { + json_t* json_bars = json_object_get(json_ip, barType.c_str()); + if(not json_is_object(json_bars)) { + return false; + } + + json_t* json_bar; + const char* bar_name; + json_object_foreach(json_bars, bar_name, json_bar) { + unsigned int translation; + int ret = json_unpack(json_bar, "{ s: i }", "translation", &translation); + if(ret != 0) { + logger->error("Cannot parse {}/{}", barType, bar_name); + return false; + } + + if(barType == "axi_bars") { + json_int_t base, high, size; + int ret = json_unpack(json_bar, "{ s: I, s: I, s: I }", + "baseaddr", &base, + "highaddr", &high, + "size", &size); + if(ret != 0) { + logger->error("Cannot parse {}/{}", barType, bar_name); + return false; + } + + pcie.axiToPcieTranslations[bar_name] = { + .base = static_cast(base), + .size = static_cast(size), + .translation = translation + }; + + } else { + pcie.pcieToAxiTranslations[bar_name] = { + .translation = translation + }; + } + } + } + return true; } From f644a9faa8437bd80971e5f2af81c8696d01b97a Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:50:48 +0200 Subject: [PATCH 199/560] ips/pcie: move BAR0 mapping from card into PCIe IP --- fpga/lib/card.cpp | 30 ------------------------------ fpga/lib/ips/pcie.cpp | 23 ++++++++++++++++------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 4581d7482..de32310f5 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -205,7 +205,6 @@ PCIeCard::mapMemoryBlock(const MemoryBlock& block) bool PCIeCard::init() { - auto& mm = MemoryManager::get(); logger = getLogger(); logger->info("Initializing FPGA card {}", name); @@ -221,41 +220,12 @@ PCIeCard::init() VfioDevice& device = vfioContainer->attachDevice(pdev); this->vfioDevice = &device; - /* Enable memory access and PCI bus mastering for DMA */ if (not device.pciEnable()) { logger->error("Failed to enable PCI device"); return false; } - /* Map PCIe BAR */ - const void* bar0_mapped = vfioDevice->regionMap(VFIO_PCI_BAR0_REGION_INDEX); - if (bar0_mapped == MAP_FAILED) { - logger->error("Failed to mmap() BAR0"); - return false; - } - - // determine size of BAR0 region - const size_t bar0_size = vfioDevice->regionGetSize(VFIO_PCI_BAR0_REGION_INDEX); - - - /* Link mapped BAR0 to global memory graph */ - - // get the address space of the current application - const auto villasAddrSpace = mm.getProcessAddressSpace(); - - // get the address space for the PCIe proxy we use with VFIO - const auto cardPCIeAddrSpaceName = mm.getMasterAddrSpaceName(name, "PCIe"); - - // create a new address space for this FPGA card - addrSpaceIdHostToDevice = mm.getOrCreateAddressSpace(cardPCIeAddrSpaceName); - - // create a mapping from our address space to the FPGA card via vfio - mm.createMapping(reinterpret_cast(bar0_mapped), - 0, bar0_size, "VFIO_map", - villasAddrSpace, addrSpaceIdHostToDevice); - - /* Reset system? */ if (do_reset) { /* Reset / detect PCI device */ diff --git a/fpga/lib/ips/pcie.cpp b/fpga/lib/ips/pcie.cpp index 3b273bbd8..c058056ff 100644 --- a/fpga/lib/ips/pcie.cpp +++ b/fpga/lib/ips/pcie.cpp @@ -42,14 +42,23 @@ AxiPciExpressBridge::init() // Throw an exception if the is no bus master interface and thus no // address space we can use for translation -> error - const MemoryManager::AddressSpaceId myAddrSpaceid = - busMasterInterfaces.at(axiInterface); + card->addrSpaceIdHostToDevice = busMasterInterfaces.at(axiInterface); - // Create an identity mapping from the FPGA card to this IP as an entry - // point to all other IPs in the FPGA, because Vivado will generate a - // memory view for this bridge that can see all others. - MemoryManager::get().createMapping(0x00, 0x00, SIZE_MAX, "PCIeBridge", - card->addrSpaceIdHostToDevice, myAddrSpaceid); + /* Map PCIe BAR0 via VFIO */ + const void* bar0_mapped = card->vfioDevice->regionMap(VFIO_PCI_BAR0_REGION_INDEX); + if (bar0_mapped == MAP_FAILED) { + logger->error("Failed to mmap() BAR0"); + return false; + } + + // determine size of BAR0 region + const size_t bar0_size = card->vfioDevice->regionGetSize(VFIO_PCI_BAR0_REGION_INDEX); + + // create a mapping from process address space to the FPGA card via vfio + mm.createMapping(reinterpret_cast(bar0_mapped), + 0, bar0_size, "VFIO-H2D", + mm.getProcessAddressSpace(), + card->addrSpaceIdHostToDevice); /* Make PCIe (IOVA) address space available to FPGA via BAR0 */ From f823dde0f49d476b8ffd185ae98b86a6b55758fe Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:52:03 +0200 Subject: [PATCH 200/560] card: don't try to create a VFIO mapping if IOMMU is disabled In this case, VFIO cannot create DMA mappings. --- fpga/lib/card.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index de32310f5..9df391438 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -165,6 +165,11 @@ PCIeCard::lookupIp(const Vlnv& vlnv) const bool PCIeCard::mapMemoryBlock(const MemoryBlock& block) { + if(not vfioContainer->isIommuEnabled()) { + logger->warn("VFIO mapping not supported without IOMMU"); + return false; + } + auto& mm = MemoryManager::get(); const auto& addrSpaceId = block.getAddrSpaceId(); @@ -175,7 +180,6 @@ PCIeCard::mapMemoryBlock(const MemoryBlock& block) logger->debug("Create VFIO mapping for {}", addrSpaceId); } - auto translationFromProcess = mm.getTranslationFromProcess(addrSpaceId); uintptr_t processBaseAddr = translationFromProcess.getLocalAddr(0); uintptr_t iovaAddr = vfioContainer->memoryMap(processBaseAddr, @@ -188,10 +192,8 @@ PCIeCard::mapMemoryBlock(const MemoryBlock& block) return false; } - - mm.createMapping(iovaAddr, 0, block.getSize(), - "vfio", + "VFIO-D2H", this->addrSpaceIdDeviceToHost, addrSpaceId); From 7dcdfaccd9793cad630390fbdd6d442869f0702a Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:53:06 +0200 Subject: [PATCH 201/560] ips/dma: let user deal with making memory accessible to DMA It is probably too costly to do (and verify) it on every read or write. Furthermore, the user knows better how to make a certain memory available to the DMA. --- fpga/include/villas/fpga/ip.hpp | 4 ++ fpga/include/villas/fpga/ips/dma.hpp | 4 ++ fpga/lib/ips/dma.cpp | 60 ++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index f565aa4a2..1950a8ed2 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -193,6 +193,10 @@ protected: InterruptController* getInterruptController(const std::string& interruptName) const; + MemoryManager::AddressSpaceId + getMasterAddrSpaceByInterface(const std::string& masterInterfaceName) const + { return busMasterInterfaces.at(masterInterfaceName); } + protected: struct IrqPort { int num; diff --git a/fpga/include/villas/fpga/ips/dma.hpp b/fpga/include/villas/fpga/ips/dma.hpp index d8b609794..88530dbe3 100644 --- a/fpga/include/villas/fpga/ips/dma.hpp +++ b/fpga/include/villas/fpga/ips/dma.hpp @@ -57,6 +57,8 @@ public: bool memcpy(const MemoryBlock& src, const MemoryBlock& dst, size_t len); + bool makeAccesibleFromVA(const MemoryBlock& mem); + inline bool hasScatterGather() const { return hasSG; } @@ -72,6 +74,8 @@ private: bool writeCompleteSimple(); bool readCompleteSimple(); + bool isMemoryBlockAccesible(const MemoryBlock& mem, const std::string& interface); + private: static constexpr char registerMemory[] = "Reg"; diff --git a/fpga/lib/ips/dma.cpp b/fpga/lib/ips/dma.cpp index ed9f0a888..3d3bf0e70 100644 --- a/fpga/lib/ips/dma.cpp +++ b/fpga/lib/ips/dma.cpp @@ -159,17 +159,14 @@ Dma::memcpy(const MemoryBlock& src, const MemoryBlock& dst, size_t len) size_t Dma::write(const MemoryBlock& mem, size_t len) { - // make sure memory is reachable - if(not card->mapMemoryBlock(mem)) { - logger->error("Memory not accessible by DMA"); - return 0; - } - auto& mm = MemoryManager::get(); + + // user has to make sure that memory is accessible, otherwise this will throw auto translation = mm.getTranslation(busMasterInterfaces[mm2sInterface], mem.getAddrSpaceId()); const void* buf = reinterpret_cast(translation.getLocalAddr(0)); + logger->debug("Write to address: {:p}", buf); return hasScatterGather() ? writeSG(buf, len) : writeSimple(buf, len); } @@ -177,17 +174,14 @@ Dma::write(const MemoryBlock& mem, size_t len) size_t Dma::read(const MemoryBlock& mem, size_t len) { - // make sure memory is reachable - if(not card->mapMemoryBlock(mem)) { - logger->error("Memory not accessible by DMA"); - return 0; - } - auto& mm = MemoryManager::get(); + + // user has to make sure that memory is accessible, otherwise this will throw auto translation = mm.getTranslation(busMasterInterfaces[s2mmInterface], mem.getAddrSpaceId()); void* buf = reinterpret_cast(translation.getLocalAddr(0)); + logger->debug("Read from address: {:p}", buf); return hasScatterGather() ? readSG(buf, len) : readSimple(buf, len); } @@ -356,6 +350,48 @@ Dma::readCompleteSimple() } +bool +Dma::makeAccesibleFromVA(const MemoryBlock& mem) +{ + // only symmetric mapping supported currently + if(isMemoryBlockAccesible(mem, s2mmInterface) and + isMemoryBlockAccesible(mem, mm2sInterface)) { + return true; + } + + // try mapping via FPGA-card (VFIO) + if(not card->mapMemoryBlock(mem)) { + logger->error("Memory not accessible by DMA"); + return false; + } + + // sanity-check if mapping worked, this shouldn't be neccessary + if(not isMemoryBlockAccesible(mem, s2mmInterface) or + not isMemoryBlockAccesible(mem, mm2sInterface)) { + logger->error("Mapping memory via card didn't work, but reported success?!"); + return false; + } + + return true; +} + + +bool +Dma::isMemoryBlockAccesible(const MemoryBlock& mem, const std::string& interface) +{ + auto& mm = MemoryManager::get(); + + try { + mm.findPath(getMasterAddrSpaceByInterface(interface), mem.getAddrSpaceId()); + } catch(const std::out_of_range&) { + // not (yet) accessible + return false; + } + + return true; +} + + } // namespace ip } // namespace fpga } // namespace villas From 24db7ea1c0b39fc1ecfc0ff7aa1472450c6b3182 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:54:37 +0200 Subject: [PATCH 202/560] tests/dma: update to current progress --- fpga/tests/dma.cpp | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/fpga/tests/dma.cpp b/fpga/tests/dma.cpp index cb6b722f6..e7c462b26 100644 --- a/fpga/tests/dma.cpp +++ b/fpga/tests/dma.cpp @@ -26,38 +26,52 @@ Test(fpga, dma, .description = "DMA") auto dma = reinterpret_cast(*ip); - if(not dma.connectLoopback()) { + if(not dma.loopbackPossible()) { + logger->info("Loopback test not possible for {}", *ip); continue; } count++; - if(not dma.loopbackPossible()) { - logger->info("Loopback test not possible for {}", *ip); - continue; - } + + // Simple DMA can only transfer up to 4 kb due to PCIe page size burst + // limitation + size_t len = 4 * (1 << 10); // find a block RAM IP to write to auto bramIp = state.cards.front()->lookupIp(villas::fpga::Vlnv("xilinx.com:ip:axi_bram_ctrl:")); auto bram = reinterpret_cast(bramIp); cr_assert_not_null(bram, "Couldn't find BRAM"); - // Simple DMA can only transfer up to 4 kb due to PCIe page size burst - // limitation - size_t len = 4 * (1 << 10); /* Allocate memory to use with DMA */ - auto src = villas::HostRam::getAllocator().allocate(len); - auto dst = bram->getAllocator().allocate(len); + auto src = villas::HostDmaRam::getAllocator().allocate(len); + auto dst = villas::HostDmaRam::getAllocator().allocate(len); + + /* ... only works with IOMMU enabled currently */ +// auto src = bram->getAllocator().allocate(len); +// auto dst = bram->getAllocator().allocate(len); + + /* ... only works with IOMMU enabled currently */ +// auto src = villas::HostRam::getAllocator().allocate(len); +// auto dst = villas::HostRam::getAllocator().allocate(len); + + /* Make sure memory is accessible for DMA */ + cr_assert(dma.makeAccesibleFromVA(src.getMemoryBlock()), + "Source memory not accessible for DMA"); + cr_assert(dma.makeAccesibleFromVA(dst.getMemoryBlock()), + "Destination memory not accessible for DMA"); /* Get new random data */ const size_t lenRandom = read_random(&src, len); cr_assert(len == lenRandom, "Failed to get random data"); + /* Start transfer */ cr_assert(dma.memcpy(src.getMemoryBlock(), dst.getMemoryBlock(), len), "DMA ping pong failed"); + /* Compare data */ cr_assert(memcmp(&src, &dst, len) == 0, "Data not equal"); From 13fd3f3c2a825d48e216eeba8d32ef6f53b0c00a Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 15 May 2018 17:35:45 +0200 Subject: [PATCH 203/560] gpu: implement basic GPU plugin that can do DMA to and from its memory Using CUDA, memory can be allocated on the GPU and shared to peers on the PCIe bus such as the FPGA. Furthermore, the DMA on the GPU can also be used to read and write to/from other memory on the PCIe bus, such as BRAM on the FPGA. --- fpga/include/villas/plugin.hpp | 1 + fpga/lib/gpu/CMakeLists.txt | 24 +- fpga/lib/gpu/gdrcopy | 2 +- fpga/lib/gpu/gpu.cpp | 467 ++++++++++++++++++++++++++++ fpga/lib/gpu/include/villas/gpu.hpp | 80 +++++ fpga/lib/gpu/kernels.cu | 42 +++ fpga/lib/gpu/kernels.hpp | 16 + fpga/tests/CMakeLists.txt | 1 + fpga/tests/gpu.cpp | 129 ++++++++ 9 files changed, 755 insertions(+), 7 deletions(-) create mode 100644 fpga/lib/gpu/kernels.cu create mode 100644 fpga/lib/gpu/kernels.hpp create mode 100644 fpga/tests/gpu.cpp diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index 35a4912ba..fd3fd3fc8 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -40,6 +40,7 @@ public: Unknown, FpgaIp, FpgaCard, + Gpu }; Plugin(Type type, const std::string& name); diff --git a/fpga/lib/gpu/CMakeLists.txt b/fpga/lib/gpu/CMakeLists.txt index 68c3993ea..c2dd78804 100644 --- a/fpga/lib/gpu/CMakeLists.txt +++ b/fpga/lib/gpu/CMakeLists.txt @@ -1,14 +1,26 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.8) project(villas-gpu VERSION 1.0 DESCRIPTION "VILLASgpu" - LANGUAGES C CXX) + LANGUAGES C CXX CUDA) -# fail if CUDA not found -find_package(CUDA QUIET REQUIRED) +add_subdirectory(gdrcopy) -cuda_add_library(villas-gpu SHARED gpu.cpp) +add_library(villas-gpu SHARED gpu.cpp kernels.cu) + +target_compile_options(villas-gpu PRIVATE -g) + +set_source_files_properties(gpu.cpp PROPERTIES + LANGUAGE CUDA) + +target_include_directories(villas-gpu PRIVATE /opt/cuda/include) + +target_link_libraries(villas-gpu + PRIVATE villas-common gdrapi cuda) target_include_directories(villas-gpu - PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}) diff --git a/fpga/lib/gpu/gdrcopy b/fpga/lib/gpu/gdrcopy index 2b933176d..0441daa44 160000 --- a/fpga/lib/gpu/gdrcopy +++ b/fpga/lib/gpu/gdrcopy @@ -1 +1 @@ -Subproject commit 2b933176d0fd20f10bddfdf574a1d3229ca1ecdf +Subproject commit 0441daa447b80260c4e11096f03e88f7be08bfa2 diff --git a/fpga/lib/gpu/gpu.cpp b/fpga/lib/gpu/gpu.cpp index 4314fcb65..e8f7d58ec 100644 --- a/fpga/lib/gpu/gpu.cpp +++ b/fpga/lib/gpu/gpu.cpp @@ -1,7 +1,474 @@ +#include +#include +#include + +#include +#include + #include +#include +#include +#include + +#include +#include +#include + +#include "kernels.hpp" namespace villas { namespace gpu { +static GpuFactory gpuFactory; + +GpuAllocator::GpuAllocator(Gpu& gpu) : + BaseAllocator(gpu.masterPciEAddrSpaceId), + gpu(gpu) +{ + free = [&](MemoryBlock* mem) { + cudaSetDevice(gpu.gpuId); + if(cudaFree(reinterpret_cast(mem->getOffset())) != cudaSuccess) { + logger->warn("cudaFree() failed for {:#x} of size {:#x}", + mem->getOffset(), mem->getSize()); + } + + removeMemoryBlock(*mem); + }; +} + +std::string +villas::gpu::GpuAllocator::getName() const +{ + std::stringstream name; + name << "GpuAlloc" << getAddrSpaceId(); + return name.str(); +} + + +GpuFactory::GpuFactory() : + Plugin(Plugin::Type::Gpu, "GPU") +{ + logger = loggerGetOrCreate("GpuFactory"); +} + +// required to be defined here for PIMPL to compile +Gpu::~Gpu() +{ + auto& mm = MemoryManager::get(); + mm.removeAddressSpace(masterPciEAddrSpaceId); +} + + +// we use PIMPL in order to hide gdrcopy types from the public header +class Gpu::impl { +public: + gdr_t gdr; + struct pci_device pdev; +}; + +std::string Gpu::getName() const +{ + cudaDeviceProp deviceProp; + if(cudaGetDeviceProperties(&deviceProp, gpuId) != cudaSuccess) { + // logger not yet availabe + loggerGetOrCreate("Gpu")->error("Cannot retrieve properties for GPU {}", gpuId); + throw std::exception(); + } + + std::stringstream name; + name << "gpu" << gpuId << "(" << deviceProp.name << ")"; + + return name.str(); +} + +bool Gpu::registerIoMemory(const MemoryBlock& mem) +{ + auto& mm = MemoryManager::get(); + const auto pciAddrSpaceId = mm.getPciAddressSpace(); + + // Check if we need to map anything at all, maybe it's already reachable + try { + // TODO: there might already be a path through the graph, but there's no + // overlapping window, so this will fail badly! + auto translation = mm.getTranslation(masterPciEAddrSpaceId, + mem.getAddrSpaceId()); + if(translation.getSize() >= mem.getSize()) { + // there is already a sufficient path + logger->debug("Already mapped through another mapping"); + return true; + } else { + logger->warn("There's already a mapping, but too small"); + } + } catch(const std::out_of_range&) { + // not yet reachable, that's okay, proceed + } + + + // In order to register IO memory with CUDA, it has to be mapped to the VA + // space of the current process (requirement of CUDA API). Check this now. + MemoryManager::AddressSpaceId mappedBaseAddrSpaceId; + try { + auto path = mm.findPath(mm.getProcessAddressSpace(), mem.getAddrSpaceId()); + // first node in path is the mapped memory space whose virtual address + // we need to hand to CUDA + mappedBaseAddrSpaceId = path.front(); + } catch (const std::out_of_range&) { + logger->error("Memory not reachable from process, but required by CUDA"); + return false; + } + + // determine the base address of the mapped memory region needed by CUDA + const auto translationProcess = mm.getTranslationFromProcess(mappedBaseAddrSpaceId); + const uintptr_t baseAddrForProcess = translationProcess.getLocalAddr(0); + + + // Now check that the memory is also reachable via PCIe bus, otherwise GPU + // has no means to access it. + uintptr_t baseAddrOnPci; + size_t sizeOnPci; + try { + auto translationPci = mm.getTranslation(pciAddrSpaceId, + mappedBaseAddrSpaceId); + baseAddrOnPci = translationPci.getLocalAddr(0); + sizeOnPci = translationPci.getSize(); + } catch(const std::out_of_range&) { + logger->error("Memory is not reachable via PCIe bus"); + return false; + } + + if(sizeOnPci < mem.getSize()) { + logger->warn("VA mapping of IO memory is too small: {:#x} instead of {:#x} bytes", + sizeOnPci, mem.getSize()); + logger->warn("If something later on fails or behaves strangely, this might be the cause!"); + } + + + cudaSetDevice(gpuId); + + auto baseAddrVA = reinterpret_cast(baseAddrForProcess); + if(cudaHostRegister(baseAddrVA, sizeOnPci, cudaHostRegisterIoMemory) != cudaSuccess) { + logger->error("Cannot register IO memory for block {}", mem.getAddrSpaceId()); + return false; + } + + void* devicePointer = nullptr; + if(cudaHostGetDevicePointer(&devicePointer, baseAddrVA, 0) != cudaSuccess) { + logger->error("Cannot retrieve device pointer for IO memory"); + return false; + } + + mm.createMapping(reinterpret_cast(devicePointer), baseAddrOnPci, + sizeOnPci, "CudaIoMem", masterPciEAddrSpaceId, pciAddrSpaceId); + + return true; +} + +bool +Gpu::registerHostMemory(const MemoryBlock& mem) +{ + auto& mm = MemoryManager::get(); + + auto translation = mm.getTranslationFromProcess(mem.getAddrSpaceId()); + auto localBase = reinterpret_cast(translation.getLocalAddr(0)); + + int ret = cudaHostRegister(localBase, mem.getSize(), 0); + if(ret != cudaSuccess) { + logger->error("Cannot register memory block {} addr={:p} size={:#x} to CUDA: ret={}", + mem.getAddrSpaceId(), localBase, mem.getSize(), ret); + return false; + } + + void* devicePointer = nullptr; + ret = cudaHostGetDevicePointer(&devicePointer, localBase, 0); + if(ret != cudaSuccess) { + logger->error("Cannot retrieve device pointer for IO memory: ret={}", ret); + return false; + } + + mm.createMapping(reinterpret_cast(devicePointer), 0, mem.getSize(), + "CudaHostMem", masterPciEAddrSpaceId, mem.getAddrSpaceId()); + + return true; +} + +bool Gpu::makeAccessibleToPCIeAndVA(const MemoryBlock& mem) +{ + if(pImpl->gdr == nullptr) { + logger->warn("GDRcopy not available"); + return false; + } + + auto& mm = MemoryManager::get(); + + try { + auto path = mm.findPath(masterPciEAddrSpaceId, mem.getAddrSpaceId()); + // if first hop is the PCIe bus, we know that memory is off-GPU + if(path.front() == mm.getPciAddressSpace()) { + throw std::out_of_range("Memory block is outside of this GPU"); + } + + } catch (const std::out_of_range&) { + logger->error("Trying to map non-GPU memory block"); + return false; + } + + logger->debug("retrieve complete device pointer from point of view of GPU"); + // retrieve complete device pointer from point of view of GPU + auto translation = mm.getTranslation(masterPciEAddrSpaceId, + mem.getAddrSpaceId()); + CUdeviceptr devptr = translation.getLocalAddr(0); + + int ret; + + // required to set this flag before mapping + unsigned int enable = 1; + ret = cuPointerSetAttribute(&enable, CU_POINTER_ATTRIBUTE_SYNC_MEMOPS, devptr); + if(ret != CUDA_SUCCESS) { + logger->error("Cannot set pointer attributes on memory block {}: {}", + mem.getAddrSpaceId(), ret); + return false; + } + + gdr_mh_t mh; + ret = gdr_pin_buffer(pImpl->gdr, devptr, mem.getSize(), 0, 0, &mh); + if(ret != 0) { + logger->error("Cannot pin memory block {} via gdrcopy: {}", + mem.getAddrSpaceId(), ret); + return false; + } + + void* bar = nullptr; + ret = gdr_map(pImpl->gdr, mh, &bar, mem.getSize()); + if(ret != 0) { + logger->error("Cannot map memory block {} via gdrcopy: {}", + mem.getAddrSpaceId(), ret); + return false; + } + + gdr_info_t info; + ret = gdr_get_info(pImpl->gdr, mh, &info); + if(ret != 0) { + logger->error("Cannot get info for mapping of memory block {}: {}", + mem.getAddrSpaceId(), ret); + return false; + } + + const uintptr_t offset = info.va - devptr; + const uintptr_t userPtr = reinterpret_cast(bar) + offset; + + logger->debug("BAR ptr: {:p}", bar); + logger->debug("info.va: {:#x}", info.va); + logger->debug("info.mapped_size: {:#x}", info.mapped_size); + logger->debug("info.page_size: {:#x}", info.page_size); + logger->debug("offset: {:#x}", offset); + logger->debug("user pointer: {:#x}", userPtr); + + // mapping to acceses memory block from process + mm.createMapping(userPtr, 0, info.mapped_size, "GDRcopy", + mm.getProcessAddressSpace(), mem.getAddrSpaceId()); + + // retrieve bus address + uint64_t addr[8]; + ret = gdr_map_dma(pImpl->gdr, mh, 3, 0, 0, addr, 8); + + for(int i = 0; i < ret; i++) { + logger->debug("DMA addr[{}]: {:#x}", i, addr[i]); + } + + if(ret != 1) { + logger->error("Only one DMA address per block supported at the moment"); + return false; + } + + // mapping to access memory block from peer devices via PCIe + mm.createMapping(addr[0], 0, mem.getSize(), "GDRcopyDMA", + mm.getPciAddressSpace(), mem.getAddrSpaceId()); + + return true; +} + +bool +Gpu::makeAccessibleFromPCIeOrHostRam(const MemoryBlock& mem) +{ + // Check which kind of memory this is and where it resides + // There are two possibilities: + // - Host memory not managed by CUDA + // - IO memory somewhere on the PCIe bus + + auto& mm = MemoryManager::get(); + + bool isIoMemory = false; + try { + auto path = mm.findPath(mm.getPciAddressSpace(), mem.getAddrSpaceId()); + isIoMemory = true; + } catch(const std::out_of_range&) { + // not reachable via PCI -> not IO memory + } + + if(isIoMemory) { + logger->debug("Memory block {} is assumed to be IO memory", + mem.getAddrSpaceId()); + + return registerIoMemory(mem); + } else { + logger->debug("Memory block {} is assumed to be non-CUDA host memory", + mem.getAddrSpaceId()); + + return registerHostMemory(mem); + } +} + +void Gpu::memcpySync(const MemoryBlock& src, const MemoryBlock& dst, size_t size) +{ + auto& mm = MemoryManager::get(); + + auto src_translation = mm.getTranslation(masterPciEAddrSpaceId, + src.getAddrSpaceId()); + const void* src_buf = reinterpret_cast(src_translation.getLocalAddr(0)); + + auto dst_translation = mm.getTranslation(masterPciEAddrSpaceId, + dst.getAddrSpaceId()); + void* dst_buf = reinterpret_cast(dst_translation.getLocalAddr(0)); + + cudaSetDevice(gpuId); + cudaMemcpy(dst_buf, src_buf, size, cudaMemcpyDefault); +} + +void Gpu::memcpyKernel(const MemoryBlock& src, const MemoryBlock& dst, size_t size) +{ + auto& mm = MemoryManager::get(); + + auto src_translation = mm.getTranslation(masterPciEAddrSpaceId, + src.getAddrSpaceId()); + auto src_buf = reinterpret_cast(src_translation.getLocalAddr(0)); + + auto dst_translation = mm.getTranslation(masterPciEAddrSpaceId, + dst.getAddrSpaceId()); + auto dst_buf = reinterpret_cast(dst_translation.getLocalAddr(0)); + + cudaSetDevice(gpuId); + kernel_memcpy<<<1, 1>>>(dst_buf, src_buf, size); + cudaDeviceSynchronize(); +} + + +std::unique_ptr +GpuAllocator::allocateBlock(size_t size) +{ + cudaSetDevice(gpu.gpuId); + + void* addr; + if(cudaSuccess != cudaMalloc(&addr, size)) { + logger->error("cudaMalloc(..., size={}) failed", size); + throw std::bad_alloc(); + } + + auto& mm = MemoryManager::get(); + + // assemble name for this block + std::stringstream name; + name << std::showbase << std::hex << reinterpret_cast(addr); + + auto blockName = mm.getSlaveAddrSpaceName(getName(), name.str()); + auto blockAddrSpaceId = mm.getOrCreateAddressSpace(blockName); + + const auto localAddr = reinterpret_cast(addr); + std::unique_ptr + mem(new MemoryBlock(localAddr, size, blockAddrSpaceId), this->free); + + insertMemoryBlock(*mem); + + gpu.makeAccessibleToPCIeAndVA(*mem); + + return mem; +} + + +Gpu::Gpu(int gpuId) : + pImpl{std::make_unique()}, + gpuId(gpuId) +{ + logger = loggerGetOrCreate(getName()); + + pImpl->gdr = gdr_open(); + if(pImpl->gdr == nullptr) { + logger->warn("No GDRcopy support enabled, cannot open /dev/gdrdrv"); + } +} + +bool Gpu::init() +{ + auto& mm = MemoryManager::get(); + + const auto gpuPciEAddrSpaceName = mm.getMasterAddrSpaceName(getName(), "PCIe"); + masterPciEAddrSpaceId = mm.getOrCreateAddressSpace(gpuPciEAddrSpaceName); + + allocator = std::make_unique(*this); + + cudaDeviceProp deviceProp; + cudaGetDeviceProperties(&deviceProp, gpuId); + + pImpl->pdev.slot = { + deviceProp.pciDomainID, + deviceProp.pciBusID, + deviceProp.pciDeviceID, + 0}; + + struct pci_region* pci_regions = nullptr; + const size_t pci_num_regions = pci_get_regions(&pImpl->pdev, &pci_regions); + for(size_t i = 0; i < pci_num_regions; i++) { + const size_t region_size = pci_regions[i].end - pci_regions[i].start + 1; + logger->info("BAR{}: bus addr={:#x} size={:#x}", + pci_regions[i].num, pci_regions[i].start, region_size); + + char name[] = "BARx"; + name[3] = '0' + pci_regions[i].num; + + auto gpuBarXAddrSpaceName = mm.getSlaveAddrSpaceName(getName(), name); + auto gpuBarXAddrSpaceId = mm.getOrCreateAddressSpace(gpuBarXAddrSpaceName); + + mm.createMapping(pci_regions[i].start, 0, region_size, + std::string("PCI-") + name, + mm.getPciAddressSpace(), gpuBarXAddrSpaceId); + } + + free(pci_regions); + + return true; +} + + +std::list> +GpuFactory::make() +{ + int deviceCount = 0; + cudaGetDeviceCount(&deviceCount); + + std::list> gpuList; + + for(int gpuId = 0; gpuId < deviceCount; gpuId++) { + if(cudaSetDevice(gpuId) != cudaSuccess) { + logger->warn("Cannot activate GPU {}", gpuId); + continue; + } + + auto gpu = std::make_unique(gpuId); + + if(not gpu->init()) { + logger->warn("Cannot initialize GPU {}", gpuId); + continue; + } + + gpuList.emplace_back(std::move(gpu)); + } + + logger->info("Initialized {} GPUs", gpuList.size()); + for(auto& gpu : gpuList) { + logger->debug(" - {}", gpu->getName()); + } + + return gpuList; +} + } // namespace villas } // namespace gpu diff --git a/fpga/lib/gpu/include/villas/gpu.hpp b/fpga/lib/gpu/include/villas/gpu.hpp index 903dd08c2..88b316815 100644 --- a/fpga/lib/gpu/include/villas/gpu.hpp +++ b/fpga/lib/gpu/include/villas/gpu.hpp @@ -1,7 +1,87 @@ #pragma once +#include + +#include +#include +#include +#include + + namespace villas { namespace gpu { +class GpuAllocator; + +class Gpu { + friend GpuAllocator; +public: + Gpu(int gpuId); + ~Gpu(); + + bool init(); + + std::string getName() const; + + GpuAllocator& getAllocator() const + { return *allocator; } + + + bool makeAccessibleToPCIeAndVA(const MemoryBlock& mem); + + /// Make some memory block accssible for this GPU + bool makeAccessibleFromPCIeOrHostRam(const MemoryBlock& mem); + + void memcpySync(const MemoryBlock& src, const MemoryBlock& dst, size_t size); + + void memcpyKernel(const MemoryBlock& src, const MemoryBlock& dst, size_t size); + +private: + bool registerIoMemory(const MemoryBlock& mem); + bool registerHostMemory(const MemoryBlock& mem); + +private: + class impl; + std::unique_ptr pImpl; + + // master, will be used to derived slave addr spaces for allocation + MemoryManager::AddressSpaceId masterPciEAddrSpaceId; + + MemoryManager::AddressSpaceId slaveMemoryAddrSpaceId; + + SpdLogger logger; + + int gpuId; + + std::unique_ptr allocator; +}; + + +class GpuAllocator : public BaseAllocator { +public: + GpuAllocator(Gpu& gpu); + + std::string getName() const; + + std::unique_ptr + allocateBlock(size_t size); + +private: + Gpu& gpu; +}; + +class GpuFactory : public Plugin { +public: + GpuFactory(); + + std::list> + make(); + + void run(void*); + +private: + SpdLogger logger; +}; + } // namespace villas } // namespace gpu diff --git a/fpga/lib/gpu/kernels.cu b/fpga/lib/gpu/kernels.cu new file mode 100644 index 000000000..7e8b5524d --- /dev/null +++ b/fpga/lib/gpu/kernels.cu @@ -0,0 +1,42 @@ +#include + +#include +#include "kernels.hpp" + +#include "cuda_runtime.h" +#include + +namespace villas { +namespace gpu { + + +__global__ void +kernel_mailbox(volatile uint32_t *mailbox, volatile uint32_t* counter) +{ + printf("[gpu] hello!\n"); + printf("[gpu] mailbox: %p\n", mailbox); + + printf("[kernel] started\n"); + + while(1) { + if (*mailbox == 1) { + *mailbox = 0; + printf("[gpu] counter = %d\n", *counter); + break; + } + } + + printf("[gpu] quit\n"); +} + +__global__ void +kernel_memcpy(volatile uint8_t* dst, volatile uint8_t* src, size_t length) +{ + while(length > 0) { + *dst++ = *src++; + length--; + } +} + +} // namespace villas +} // namespace gpu diff --git a/fpga/lib/gpu/kernels.hpp b/fpga/lib/gpu/kernels.hpp new file mode 100644 index 000000000..986eba31e --- /dev/null +++ b/fpga/lib/gpu/kernels.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace villas { +namespace gpu { + +__global__ void +kernel_mailbox(volatile uint32_t *mailbox, volatile uint32_t* counter); + +__global__ void +kernel_memcpy(volatile uint8_t* dst, volatile uint8_t* src, size_t length); + +} // namespace villas +} // namespace gpu diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index 2e4cfe041..f36647ab3 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES logging.cpp dma.cpp fifo.cpp + gpu.cpp # hls.c # intc.c # rtds_rtt.c diff --git a/fpga/tests/gpu.cpp b/fpga/tests/gpu.cpp new file mode 100644 index 000000000..bdc3aba92 --- /dev/null +++ b/fpga/tests/gpu.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "global.hpp" + +#include +#include + + +Test(fpga, gpu_dma, .description = "GPU DMA tests") +{ + auto logger = loggerGetOrCreate("unittest:dma"); + + auto& card = state.cards.front(); + + villas::Plugin* plugin = villas::Plugin::lookup(villas::Plugin::Type::Gpu, ""); + auto gpuPlugin = dynamic_cast(plugin); + cr_assert_not_null(gpuPlugin, "No GPU plugin found"); + + auto gpus = gpuPlugin->make(); + cr_assert(gpus.size() > 0, "No GPUs found"); + + // just get first cpu + auto& gpu = gpus.front(); + + size_t count = 0; + for(auto& ip : card->ips) { + // skip non-dma IPs + if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_bram_ctrl:")) + continue; + + logger->info("Testing {}", *ip); + + auto bram = reinterpret_cast(ip.get()); + cr_assert_not_null(bram, "Couldn't find BRAM"); + + count++; + + size_t len = 4 * (1 << 10); + + /* Allocate memory to use with DMA */ + + auto bram0 = bram->getAllocator().allocate(len); + auto bram1 = bram->getAllocator().allocate(len); + + gpu->makeAccessibleFromPCIeOrHostRam(bram0.getMemoryBlock()); + gpu->makeAccessibleFromPCIeOrHostRam(bram1.getMemoryBlock()); + + auto hostRam0 = villas::HostRam::getAllocator().allocate(len); + auto hostRam1 = villas::HostRam::getAllocator().allocate(len); + + gpu->makeAccessibleFromPCIeOrHostRam(hostRam0.getMemoryBlock()); + gpu->makeAccessibleFromPCIeOrHostRam(hostRam1.getMemoryBlock()); + + auto dmaRam0 = villas::HostDmaRam::getAllocator().allocate(len); + auto dmaRam1 = villas::HostDmaRam::getAllocator().allocate(len); + + gpu->makeAccessibleFromPCIeOrHostRam(dmaRam0.getMemoryBlock()); + gpu->makeAccessibleFromPCIeOrHostRam(dmaRam1.getMemoryBlock()); + + auto gpuMem0 = gpu->getAllocator().allocate(64 << 10); + auto gpuMem1 = gpu->getAllocator().allocate(64 << 10); + + gpu->makeAccessibleToPCIeAndVA(gpuMem0.getMemoryBlock()); + gpu->makeAccessibleToPCIeAndVA(gpuMem1.getMemoryBlock()); + + +// auto& src = bram0; +// auto& dst = bram1; + +// auto& src = hostRam0; +// auto& dst = hostRam1; + + auto& src = dmaRam0; +// auto& dst = dmaRam1; + +// auto& src = gpuMem0; + auto& dst = gpuMem1; + + + std::list>> memcpyFuncs = { + {"cudaMemcpy", [&]() {gpu->memcpySync(src.getMemoryBlock(), dst.getMemoryBlock(), len);}}, + {"CUDA kernel", [&]() {gpu->memcpyKernel(src.getMemoryBlock(), dst.getMemoryBlock(), len);}}, + }; + + auto dmaIp = card->lookupIp(villas::fpga::Vlnv("xilinx.com:ip:axi_dma:")); + auto dma = dynamic_cast(dmaIp); + + if(dma != nullptr and dma->connectLoopback()) { + memcpyFuncs.push_back({ + "DMA memcpy", [&]() { + if(not dma->makeAccesibleFromVA(src.getMemoryBlock()) or + not dma->makeAccesibleFromVA(dst.getMemoryBlock())) { + return; + } + dma->memcpy(src.getMemoryBlock(), dst.getMemoryBlock(), len); + }}); + } + + for(auto& [name, memcpyFunc] : memcpyFuncs) { + logger->info("Testing {}", name); + + /* Get new random data */ + const size_t lenRandom = read_random(&src, len); + cr_assert(len == lenRandom, "Failed to get random data"); + + memcpyFunc(); + const bool success = memcmp(&src, &dst, len) == 0; + + logger->info(" {}", success ? + TXT_GREEN("Passed") : + TXT_RED("Failed")); + } + + villas::MemoryManager::get().dump(); + } + + + cr_assert(count > 0, "No BRAM found"); +} From d2384abb9df0c8517fcc5765bb342859d15b9d87 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 16 May 2018 10:58:18 +0200 Subject: [PATCH 204/560] cmake: only build GPU library if CUDA is present --- fpga/lib/CMakeLists.txt | 12 ++++++++++-- fpga/tests/CMakeLists.txt | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index fff01b8e1..5ff3a50ba 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(common) -add_subdirectory(gpu) set(SOURCES vlnv.cpp @@ -36,7 +35,16 @@ find_package(Threads) add_library(villas-fpga SHARED ${SOURCES}) target_link_libraries(villas-fpga PUBLIC villas-common) -target_link_libraries(villas-fpga PUBLIC villas-gpu) + +# GPU library is optional, check for CUDA presence +include(CheckLanguage) +check_language(CUDA) +if(CMAKE_CUDA_COMPILER) + add_subdirectory(gpu) + target_link_libraries(villas-fpga PUBLIC villas-gpu) +else() + message("No CUDA support, not building GPU library") +endif() target_compile_definitions(villas-fpga PRIVATE BUILDID=\"abc\" diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index f36647ab3..c7b762ac3 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -4,7 +4,6 @@ set(SOURCES logging.cpp dma.cpp fifo.cpp - gpu.cpp # hls.c # intc.c # rtds_rtt.c @@ -13,6 +12,10 @@ set(SOURCES graph.cpp ) +if(CMAKE_CUDA_COMPILER) + list(APPEND SOURCES gpu.cpp) +endif() + add_executable(unit-tests ${SOURCES}) find_package(Criterion REQUIRED) From c15189b74b79f9410a34a16180cd44dd3f7db5fc Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Wed, 16 May 2018 11:25:13 +0200 Subject: [PATCH 205/560] common/memory: implement freeing for LinearAllocator This is still very simple. Only really free memory, when all allocation have been deallocated so we only need to keep track of the current number of allocations. --- fpga/include/villas/memory.hpp | 1 + fpga/lib/common/memory.cpp | 28 +++++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp index b640743fb..a3d5d5dd1 100644 --- a/fpga/include/villas/memory.hpp +++ b/fpga/include/villas/memory.hpp @@ -215,6 +215,7 @@ private: size_t nextFreeAddress; ///< next chunk will be allocated here size_t memorySize; ///< total size of managed memory size_t internalOffset; ///< offset in address space (usually 0) + size_t allocationCount; ///< Number of individual allocations present }; diff --git a/fpga/lib/common/memory.cpp b/fpga/lib/common/memory.cpp index 3a44ab63e..fe15e72b1 100644 --- a/fpga/lib/common/memory.cpp +++ b/fpga/lib/common/memory.cpp @@ -49,7 +49,8 @@ LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId BaseAllocator(memoryAddrSpaceId), nextFreeAddress(0), memorySize(memorySize), - internalOffset(internalOffset) + internalOffset(internalOffset), + allocationCount(0) { // make sure to start at aligned offset, reduce size in case we need padding if(const size_t paddingBytes = getAlignmentPadding(internalOffset)) { @@ -61,12 +62,20 @@ LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId // deallocation callback free = [&](MemoryBlock* mem) { - logger->debug("freeing {:#x} bytes at local addr {:#x} (addr space {})", - mem->getSize(), mem->getOffset(), mem->getAddrSpaceId()); - logger->warn("free() not implemented"); - logger->debug("available memory: {:#x} bytes", getAvailableMemory()); + logger->debug("Deallocating memory block at local addr {:#x} (addr space {})", + mem->getOffset(), mem->getAddrSpaceId()); removeMemoryBlock(*mem); + + allocationCount--; + if(allocationCount == 0) { + logger->debug("All allocations are deallocated now, freeing memory"); + + // all allocations have been deallocated, free all memory + nextFreeAddress = 0; + } + + logger->debug("Available memory: {:#x} bytes", getAvailableMemory()); }; } @@ -75,8 +84,11 @@ std::string LinearAllocator::getName() const { std::stringstream name; - name << "LinearAlloc" << getAddrSpaceId() - << "@0x" << std::hex << internalOffset; + name << "LinearAlloc" << getAddrSpaceId(); + if(internalOffset != 0) { + name << "@0x" << std::hex << internalOffset; + } + return name.str(); } @@ -122,6 +134,8 @@ LinearAllocator::allocateBlock(size_t size) // mount block into the memory graph insertMemoryBlock(*mem); + // increase the allocation count + allocationCount++; return mem; } From 5097827757ec84b890ac6a785d3794185d4e773d Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 4 Jun 2018 12:17:23 +0200 Subject: [PATCH 206/560] fix include paths, use `` style --- fpga/include/villas/directed_graph.hpp | 2 +- fpga/include/villas/fpga/card.hpp | 19 +++++++------------ fpga/include/villas/fpga/ip.hpp | 11 +++++------ fpga/include/villas/fpga/ip_node.hpp | 5 +++-- fpga/include/villas/fpga/ips/dma.hpp | 7 ++----- fpga/include/villas/fpga/ips/fifo.hpp | 3 ++- fpga/include/villas/fpga/ips/intc.hpp | 3 +-- fpga/include/villas/fpga/ips/pcie.hpp | 7 +------ fpga/include/villas/fpga/ips/switch.hpp | 5 +---- fpga/include/villas/fpga/ips/timer.hpp | 8 ++++---- fpga/include/villas/kernel/pci.h | 3 ++- fpga/include/villas/memory.hpp | 4 ++-- fpga/include/villas/memory_manager.hpp | 4 ++-- fpga/include/villas/plugin.hpp | 4 ++-- fpga/include/villas/utils.hpp | 1 - fpga/lib/card.cpp | 10 +++++----- fpga/lib/common/memory_manager.cpp | 2 +- fpga/lib/common/plugin.cpp | 10 +++------- fpga/lib/common/utils.cpp | 2 +- fpga/lib/ip.cpp | 19 +++++++++---------- fpga/lib/ip_node.cpp | 9 +++++---- fpga/lib/ips/dma.cpp | 9 ++++----- fpga/lib/ips/fifo.cpp | 5 ++--- fpga/lib/ips/intc.cpp | 12 +++++------- fpga/lib/ips/pcie.cpp | 8 ++++---- fpga/lib/ips/switch.cpp | 3 +-- fpga/lib/ips/timer.cpp | 7 ++++--- fpga/lib/kernel/kernel.c | 6 +++--- fpga/lib/kernel/pci.c | 8 ++++---- fpga/lib/kernel/vfio.cpp | 9 ++++----- fpga/lib/list.c | 4 ++-- fpga/lib/log.c | 6 +++--- fpga/lib/log_config.c | 10 +++++----- fpga/lib/log_helper.c | 4 ++-- fpga/lib/memory.cpp | 24 ++++++++++++++++++++++++ fpga/lib/utils.c | 4 ++-- fpga/lib/vlnv.cpp | 6 +----- fpga/src/streamer.cpp | 25 +++++++++++++++++++++++++ fpga/tests/fifo.cpp | 2 ++ fpga/tests/timer.cpp | 2 +- 40 files changed, 157 insertions(+), 135 deletions(-) create mode 100644 fpga/lib/memory.cpp create mode 100644 fpga/src/streamer.cpp diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp index c2b92674a..5f8488b13 100644 --- a/fpga/include/villas/directed_graph.hpp +++ b/fpga/include/villas/directed_graph.hpp @@ -9,7 +9,7 @@ #include #include -#include "log.hpp" +#include namespace villas { diff --git a/fpga/include/villas/fpga/card.hpp b/fpga/include/villas/fpga/card.hpp index d4b4b5cc2..1abd659e6 100644 --- a/fpga/include/villas/fpga/card.hpp +++ b/fpga/include/villas/fpga/card.hpp @@ -30,24 +30,19 @@ #pragma once - -#include - -#include "common.h" -#include "kernel/pci.h" -#include "kernel/vfio.hpp" - #include #include #include +#include -#include "plugin.hpp" -#include "fpga/ip.hpp" +#include +#include +#include -#include "config.h" +#include +#include -#include "memory_manager.hpp" -#include "memory.hpp" +#include #define PCI_FILTER_DEFAULT_FPGA { \ .id = { \ diff --git a/fpga/include/villas/fpga/ip.hpp b/fpga/include/villas/fpga/ip.hpp index 1950a8ed2..0e60a8dea 100644 --- a/fpga/include/villas/fpga/ip.hpp +++ b/fpga/include/villas/fpga/ip.hpp @@ -31,17 +31,16 @@ #ifndef VILLAS_IP_HPP #define VILLAS_IP_HPP -#include "fpga/vlnv.hpp" -#include "plugin.hpp" -#include "log.hpp" - #include #include #include - #include -#include "memory_manager.hpp" +#include +#include +#include + +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ip_node.hpp b/fpga/include/villas/fpga/ip_node.hpp index 37371b7ce..890156d1d 100644 --- a/fpga/include/villas/fpga/ip_node.hpp +++ b/fpga/include/villas/fpga/ip_node.hpp @@ -35,8 +35,9 @@ #include #include -#include "ip.hpp" -#include "log.hpp" +#include + +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ips/dma.hpp b/fpga/include/villas/fpga/ips/dma.hpp index 88530dbe3..2a3af757b 100644 --- a/fpga/include/villas/fpga/ips/dma.hpp +++ b/fpga/include/villas/fpga/ips/dma.hpp @@ -26,13 +26,10 @@ #pragma once -#include -#include - #include -#include "fpga/ip_node.hpp" -#include "memory.hpp" +#include +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ips/fifo.hpp b/fpga/include/villas/fpga/ips/fifo.hpp index 82fc3156a..0cfb15f67 100644 --- a/fpga/include/villas/fpga/ips/fifo.hpp +++ b/fpga/include/villas/fpga/ips/fifo.hpp @@ -29,9 +29,10 @@ #pragma once -#include "fpga/ip_node.hpp" #include +#include + namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ips/intc.hpp b/fpga/include/villas/fpga/ips/intc.hpp index d8d3e5f86..41b007953 100644 --- a/fpga/include/villas/fpga/ips/intc.hpp +++ b/fpga/include/villas/fpga/ips/intc.hpp @@ -28,10 +28,9 @@ #pragma once -#include "fpga/ip.hpp" #include -#include "fpga/ip_node.hpp" +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ips/pcie.hpp b/fpga/include/villas/fpga/ips/pcie.hpp index ebce36178..1f69a673a 100644 --- a/fpga/include/villas/fpga/ips/pcie.hpp +++ b/fpga/include/villas/fpga/ips/pcie.hpp @@ -30,14 +30,9 @@ #pragma once -#include -#include - -#include #include -#include "fpga/ip_node.hpp" -#include "fpga/vlnv.hpp" +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ips/switch.hpp b/fpga/include/villas/fpga/ips/switch.hpp index 587ae4b70..280b73785 100644 --- a/fpga/include/villas/fpga/ips/switch.hpp +++ b/fpga/include/villas/fpga/ips/switch.hpp @@ -30,14 +30,11 @@ #pragma once -#include #include -#include #include -#include "fpga/ip_node.hpp" -#include "fpga/vlnv.hpp" +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/fpga/ips/timer.hpp b/fpga/include/villas/fpga/ips/timer.hpp index fcf4d75ea..be3d5d26c 100644 --- a/fpga/include/villas/fpga/ips/timer.hpp +++ b/fpga/include/villas/fpga/ips/timer.hpp @@ -29,12 +29,12 @@ #pragma once -#include +#include #include -#include "config.h" -#include "fpga/ip.hpp" -#include "fpga/ips/intc.hpp" +#include + +#include namespace villas { namespace fpga { diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h index 3cd9565ed..f0fae8de8 100644 --- a/fpga/include/villas/kernel/pci.h +++ b/fpga/include/villas/kernel/pci.h @@ -9,8 +9,9 @@ #pragma once +#include #include -#include "list.h" +#include #define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) #define PCI_FUNC(devfn) ((devfn) & 0x07) diff --git a/fpga/include/villas/memory.hpp b/fpga/include/villas/memory.hpp index a3d5d5dd1..ce7a9190c 100644 --- a/fpga/include/villas/memory.hpp +++ b/fpga/include/villas/memory.hpp @@ -3,8 +3,8 @@ #include #include -#include "log.hpp" -#include "memory_manager.hpp" +#include +#include namespace villas { diff --git a/fpga/include/villas/memory_manager.hpp b/fpga/include/villas/memory_manager.hpp index 555baa694..bbc0cdffd 100644 --- a/fpga/include/villas/memory_manager.hpp +++ b/fpga/include/villas/memory_manager.hpp @@ -6,8 +6,8 @@ #include #include -#include "log.hpp" -#include "directed_graph.hpp" +#include +#include namespace villas { diff --git a/fpga/include/villas/plugin.hpp b/fpga/include/villas/plugin.hpp index fd3fd3fc8..e1ff6d644 100644 --- a/fpga/include/villas/plugin.hpp +++ b/fpga/include/villas/plugin.hpp @@ -28,8 +28,8 @@ #include #include -#include "log.hpp" -#include "utils.h" +#include +#include namespace villas { diff --git a/fpga/include/villas/utils.hpp b/fpga/include/villas/utils.hpp index d368de915..e07db49aa 100644 --- a/fpga/include/villas/utils.hpp +++ b/fpga/include/villas/utils.hpp @@ -2,7 +2,6 @@ #include #include -#include namespace villas { namespace utils { diff --git a/fpga/lib/card.cpp b/fpga/lib/card.cpp index 9df391438..9effeb26a 100644 --- a/fpga/lib/card.cpp +++ b/fpga/lib/card.cpp @@ -24,13 +24,13 @@ #include #include -#include "log.hpp" +#include -#include "kernel/pci.h" -#include "kernel/vfio.hpp" +#include +#include -#include "fpga/ip.hpp" -#include "fpga/card.hpp" +#include +#include namespace villas { namespace fpga { diff --git a/fpga/lib/common/memory_manager.cpp b/fpga/lib/common/memory_manager.cpp index 0754e0c13..ad234a1d5 100644 --- a/fpga/lib/common/memory_manager.cpp +++ b/fpga/lib/common/memory_manager.cpp @@ -3,7 +3,7 @@ #include #include -#include "memory_manager.hpp" +#include using namespace villas::utils; diff --git a/fpga/lib/common/plugin.cpp b/fpga/lib/common/plugin.cpp index 1b987c254..bfe959c27 100644 --- a/fpga/lib/common/plugin.cpp +++ b/fpga/lib/common/plugin.cpp @@ -20,17 +20,13 @@ * along with this program. If not, see . *********************************************************************************/ -#include -#include - -#include #include - +#include #include #include +#include -#include "plugin.hpp" -#include "log.hpp" +#include namespace villas { diff --git a/fpga/lib/common/utils.cpp b/fpga/lib/common/utils.cpp index f0cd982f1..e32b2a516 100644 --- a/fpga/lib/common/utils.cpp +++ b/fpga/lib/common/utils.cpp @@ -1,7 +1,7 @@ #include #include -#include "utils.hpp" +#include namespace villas { namespace utils { diff --git a/fpga/lib/ip.cpp b/fpga/lib/ip.cpp index 0a21d16e3..905aef99c 100644 --- a/fpga/lib/ip.cpp +++ b/fpga/lib/ip.cpp @@ -24,18 +24,17 @@ #include #include -#include "log.hpp" -#include "utils.hpp" -#include "memory_manager.hpp" +#include +#include +#include -#include "fpga/ip.hpp" -#include "fpga/vlnv.hpp" -#include "fpga/card.hpp" +#include +#include -// needed to get VLNVs for initialization order list -#include "fpga/ips/pcie.hpp" -#include "fpga/ips/intc.hpp" -#include "fpga/ips/switch.hpp" +#include +#include +#include +#include namespace villas { diff --git a/fpga/lib/ip_node.cpp b/fpga/lib/ip_node.cpp index 2ecb3f168..d202dae14 100644 --- a/fpga/lib/ip_node.cpp +++ b/fpga/lib/ip_node.cpp @@ -2,10 +2,11 @@ #include #include -#include "utils.hpp" -#include "fpga/ip_node.hpp" -#include "fpga/ips/switch.hpp" -#include "fpga/card.hpp" +#include + +#include +#include +#include namespace villas { namespace fpga { diff --git a/fpga/lib/ips/dma.cpp b/fpga/lib/ips/dma.cpp index 3d3bf0e70..94e284d22 100644 --- a/fpga/lib/ips/dma.cpp +++ b/fpga/lib/ips/dma.cpp @@ -25,12 +25,11 @@ #include -#include "fpga/card.hpp" -#include "fpga/ips/dma.hpp" -#include "fpga/ips/intc.hpp" +#include -#include "log.hpp" -#include "memory_manager.hpp" +#include +#include +#include // max. size of a DMA transfer in simple mode #define FPGA_DMA_BOUNDARY 0x1000 diff --git a/fpga/lib/ips/fifo.cpp b/fpga/lib/ips/fifo.cpp index 2993d5fad..f0d86f59d 100644 --- a/fpga/lib/ips/fifo.cpp +++ b/fpga/lib/ips/fifo.cpp @@ -28,9 +28,8 @@ #include #include -#include "log.hpp" -#include "fpga/ips/fifo.hpp" -#include "fpga/ips/intc.hpp" +#include +#include namespace villas { diff --git a/fpga/lib/ips/intc.cpp b/fpga/lib/ips/intc.cpp index 3fbad436d..711f129f5 100644 --- a/fpga/lib/ips/intc.cpp +++ b/fpga/lib/ips/intc.cpp @@ -23,15 +23,13 @@ #include #include -#include "config.h" -#include "log.h" -#include "plugin.hpp" +#include +#include -#include "kernel/vfio.hpp" -#include "kernel/kernel.h" +#include -#include "fpga/card.hpp" -#include "fpga/ips/intc.hpp" +#include +#include namespace villas { namespace fpga { diff --git a/fpga/lib/ips/pcie.cpp b/fpga/lib/ips/pcie.cpp index c058056ff..d8294469e 100644 --- a/fpga/lib/ips/pcie.cpp +++ b/fpga/lib/ips/pcie.cpp @@ -23,11 +23,11 @@ #include #include -#include "fpga/ips/pcie.hpp" -#include "fpga/card.hpp" +#include + +#include +#include -#include "log.hpp" -#include "memory_manager.hpp" namespace villas { namespace fpga { diff --git a/fpga/lib/ips/switch.cpp b/fpga/lib/ips/switch.cpp index 3439f9fbe..4ac1be06f 100644 --- a/fpga/lib/ips/switch.cpp +++ b/fpga/lib/ips/switch.cpp @@ -26,8 +26,7 @@ #include #include -#include "log.hpp" -#include "fpga/ips/switch.hpp" +#include namespace villas { namespace fpga { diff --git a/fpga/lib/ips/timer.cpp b/fpga/lib/ips/timer.cpp index 91e36ade3..c7644f541 100644 --- a/fpga/lib/ips/timer.cpp +++ b/fpga/lib/ips/timer.cpp @@ -23,11 +23,12 @@ * along with this program. If not, see . *********************************************************************************/ +#include + #include -#include "log.hpp" -#include "fpga/ips/timer.hpp" -#include "fpga/ips/intc.hpp" +#include +#include namespace villas { namespace fpga { diff --git a/fpga/lib/kernel/kernel.c b/fpga/lib/kernel/kernel.c index a795c9f0f..8972912bf 100644 --- a/fpga/lib/kernel/kernel.c +++ b/fpga/lib/kernel/kernel.c @@ -32,9 +32,9 @@ #include #include -#include "utils.h" -#include "config.h" -#include "kernel/kernel.h" +#include +#include +#include int kernel_get_cacheline_size() { diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c index 0dbdf08a4..2c897b679 100644 --- a/fpga/lib/kernel/pci.c +++ b/fpga/lib/kernel/pci.c @@ -26,11 +26,11 @@ #include #include -#include "log.h" -#include "utils.h" +#include +#include -#include "kernel/pci.h" -#include "config.h" +#include +#include int pci_init(struct pci *p) { diff --git a/fpga/lib/kernel/vfio.cpp b/fpga/lib/kernel/vfio.cpp index 4451536ba..b9639c1de 100644 --- a/fpga/lib/kernel/vfio.cpp +++ b/fpga/lib/kernel/vfio.cpp @@ -39,11 +39,10 @@ #include #include -#include "kernel/pci.h" -#include "kernel/kernel.h" - -#include "kernel/vfio.hpp" -#include "log.hpp" +#include +#include +#include +#include static auto logger = loggerGetOrCreate("Vfio"); diff --git a/fpga/lib/list.c b/fpga/lib/list.c index 1550ee327..f990e5cbd 100644 --- a/fpga/lib/list.c +++ b/fpga/lib/list.c @@ -25,8 +25,8 @@ #include #include -#include "list.h" -#include "utils.h" +#include +#include /* Compare functions */ static int cmp_lookup(const void *a, const void *b) { diff --git a/fpga/lib/log.c b/fpga/lib/log.c index 0fe097288..9a1bff017 100644 --- a/fpga/lib/log.c +++ b/fpga/lib/log.c @@ -27,9 +27,9 @@ #include #include -#include "config.h" -#include "log.h" -#include "utils.h" +#include +#include +#include #ifdef ENABLE_OPAL_ASYNC /* Define RTLAB before including OpalPrint.h for messages to be sent diff --git a/fpga/lib/log_config.c b/fpga/lib/log_config.c index 3b6cf7cc4..5573323f6 100644 --- a/fpga/lib/log_config.c +++ b/fpga/lib/log_config.c @@ -24,12 +24,12 @@ #include #include #include +#include -#include "config.h" -#include "log.h" -#include "log_config.h" -#include "utils.h" -#include "string.h" +#include +#include +#include +#include int log_parse(struct log *l, json_t *cfg) { diff --git a/fpga/lib/log_helper.c b/fpga/lib/log_helper.c index 6f99e3696..2be0fbf67 100644 --- a/fpga/lib/log_helper.c +++ b/fpga/lib/log_helper.c @@ -24,8 +24,8 @@ #include #include -#include "utils.h" -#include "log.h" +#include +#include void debug(long class, const char *fmt, ...) { diff --git a/fpga/lib/memory.cpp b/fpga/lib/memory.cpp new file mode 100644 index 000000000..ed0656ec2 --- /dev/null +++ b/fpga/lib/memory.cpp @@ -0,0 +1,24 @@ +#include +#include + +#include + +namespace villas { + +bool +HostRam::free(void* addr, size_t length) +{ + return munmap(addr, length) == 0; +} + + +void* +HostRam::allocate(size_t length, int flags) +{ + const int mmap_flags = flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT; + const int mmap_protection = PROT_READ | PROT_WRITE; + + return mmap(nullptr, length, mmap_protection, mmap_flags, 0, 0); +} + +} // namespace villas diff --git a/fpga/lib/utils.c b/fpga/lib/utils.c index 10fc8348f..c604c891c 100644 --- a/fpga/lib/utils.c +++ b/fpga/lib/utils.c @@ -31,8 +31,8 @@ #include #include -#include "config.h" -#include "utils.h" +#include +#include pthread_t main_thread; diff --git a/fpga/lib/vlnv.cpp b/fpga/lib/vlnv.cpp index 5d65d1329..1aa80d4cd 100644 --- a/fpga/lib/vlnv.cpp +++ b/fpga/lib/vlnv.cpp @@ -23,11 +23,7 @@ #include #include -#include -#include - -#include "fpga/vlnv.hpp" -#include "fpga/ip.hpp" +#include namespace villas { namespace fpga { diff --git a/fpga/src/streamer.cpp b/fpga/src/streamer.cpp new file mode 100644 index 000000000..b261b79a5 --- /dev/null +++ b/fpga/src/streamer.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +#include +#include + +int main(int argc, const char* argv[]) +{ + (void) argc; + cxxopts::Options options(argv[0], " - example command line options"); + + return 0; +} diff --git a/fpga/tests/fifo.cpp b/fpga/tests/fifo.cpp index e3f7a250b..17cfd3dde 100644 --- a/fpga/tests/fifo.cpp +++ b/fpga/tests/fifo.cpp @@ -24,6 +24,8 @@ #include #include +#include + #include #include diff --git a/fpga/tests/timer.cpp b/fpga/tests/timer.cpp index 4317e51b6..66cc71d41 100644 --- a/fpga/tests/timer.cpp +++ b/fpga/tests/timer.cpp @@ -27,7 +27,7 @@ #include #include -#include "config.h" +#include #include "global.hpp" Test(fpga, timer, .description = "Timer Counter") From 3a99bee400b4aeada5cee6239b6aa477b32c9731 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 4 Jun 2018 12:21:45 +0200 Subject: [PATCH 207/560] scripts/non_root: also bind via pci BDF Not sure if this is really needed though. --- fpga/scripts/non_root.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/fpga/scripts/non_root.sh b/fpga/scripts/non_root.sh index a380ccf3a..daaf1b856 100755 --- a/fpga/scripts/non_root.sh +++ b/fpga/scripts/non_root.sh @@ -13,6 +13,7 @@ IOMMU_GROUP=`basename $(readlink /sys/bus/pci/devices/${PCI_BDF}/iommu_group)` # bind to vfio driver echo "${PCI_VID} ${PCI_PID}" > /sys/bus/pci/drivers/vfio-pci/new_id +echo "${PCI_BDF}" > /sys/bus/pci/drivers/vfio-pci/bind groupadd -f fpga usermod -G fpga -a svg From 63df68480f53690f58550d5fac1eddcb77be9333 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 4 Jun 2018 12:30:56 +0200 Subject: [PATCH 208/560] scripts/hwdef-parse: promote fifo to stream IP and populate all switch ports --- fpga/scripts/hwdef-parse.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py index cb617f703..09abe905a 100755 --- a/fpga/scripts/hwdef-parse.py +++ b/fpga/scripts/hwdef-parse.py @@ -35,6 +35,7 @@ whitelist = [ [ 'acs.eonerc.rwth-aachen.de', 'sysgen' ], [ 'xilinx.com', 'ip', 'axi_gpio' ], [ 'xilinx.com', 'ip', 'axi_bram_ctrl' ], + [ 'xilinx.com', 'ip', 'axis_data_fifo' ], [ 'xilinx.com', 'ip', 'axi_pcie' ] ] @@ -44,7 +45,6 @@ axi_converter_whitelist = [ [ 'xilinx.com', 'ip', 'axis_subset_converter' ], [ 'xilinx.com', 'ip', 'axis_clock_converter' ], [ 'xilinx.com', 'ip', 'axis_register_slice' ], - [ 'xilinx.com', 'ip', 'axis_data_fifo' ], [ 'xilinx.com', 'ip', 'axis_dwidth_converter' ], [ 'xilinx.com', 'ip', 'axis_register_slice' ] ] @@ -173,19 +173,32 @@ for busif in busifs: port = int(m.group(2)) + switch_ip_ports = ips[switch.get('INSTANCE')].setdefault('ports', []) + ep, busname_ep = bus_trace(root, busname, opponent[type], whitelist) if ep in ips: ports = ips[ep].setdefault('ports', []) ports.append({ 'role': opponent[type][0].lower(), - 'target': '{}:{}'.format(switch.get('INSTANCE'), port) + 'target': '{}:{}'.format(switch.get('INSTANCE'), name) }) module_ep = root.find('.//MODULE[@INSTANCE="{}"]'.format(ep)) busif_ep = module_ep.find('.//BUSINTERFACE[@BUSNAME="{}"]'.format(busname_ep)) - if busif_ep: - ports[-1]['name'] = sanitize_name(busif_ep.get('NAME')) + if not busif_ep: + print("cannot find businterface: {}".format(busname_ep)) + sys.exit(1) + + busif_name = ports[-1]['name'] = sanitize_name(busif_ep.get('NAME')) + ports[-1]['name'] = busif_name + + switch_ip_ports.append({ + 'role': type.lower(), + 'target': '{}:{}'.format(ep, busif_name), + 'name': name + }) + # set number of master/slave port pairs for switch ips[switch.get('INSTANCE')]['num_ports'] = int(switch_ports / 2) From ff20f624a60af60ee38e2c644ebb0aa275c0f24b Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 4 Jun 2018 12:32:59 +0200 Subject: [PATCH 209/560] thirdparty: add CLI11 and rang header-only libraries --- fpga/CMakeLists.txt | 2 + fpga/thirdparty/CLI11/CLI11.hpp | 3038 +++++++++++++++++++++++++++++++ fpga/thirdparty/rang/rang.hpp | 502 +++++ 3 files changed, 3542 insertions(+) create mode 100644 fpga/thirdparty/CLI11/CLI11.hpp create mode 100644 fpga/thirdparty/rang/rang.hpp diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt index 21d69bd47..917ce45b2 100644 --- a/fpga/CMakeLists.txt +++ b/fpga/CMakeLists.txt @@ -8,6 +8,8 @@ set (CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") include_directories(thirdparty/spdlog/include) +include_directories(thirdparty/CLI11) +include_directories(thirdparty/rang) add_subdirectory(lib) add_subdirectory(tests) diff --git a/fpga/thirdparty/CLI11/CLI11.hpp b/fpga/thirdparty/CLI11/CLI11.hpp new file mode 100644 index 000000000..2bb4890d6 --- /dev/null +++ b/fpga/thirdparty/CLI11/CLI11.hpp @@ -0,0 +1,3038 @@ +#pragma once + +// Distributed under the 3-Clause BSD License. See accompanying +// file LICENSE or https://github.com/CLIUtils/CLI11 for details. + +// This file was generated using MakeSingleHeader.py in CLI11/scripts +// from: v1.4.0 + +// This has the complete CLI library in one file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// From CLI/Version.hpp + +namespace CLI { + +// Note that all code in CLI11 must be in a namespace, even if it just a define. + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "1.4.0" + +} // namespace CLI + +// From CLI/StringTools.hpp + +namespace CLI { +namespace detail { + +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(""); + else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << i; + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << std::endl << std::setw(static_cast(wid)) << ""; + out << description; + } + out << std::endl; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } + +/// Verify following characters of an option +template bool valid_later_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; +} + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +inline std::vector split_up(std::string str) { + + std::vector delims = {'\'', '\"'}; + auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + trim(str); + + std::vector output; + + while(!str.empty()) { + if(str[0] == '\'') { + auto end = str.find('\'', 1); + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else if(str[0] == '\"') { + auto end = str.find('\"', 1); + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + trim(str); + } + + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(std::string leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +} // namespace detail +} // namespace CLI + +// From CLI/Error.hpp + +namespace CLI { + +// Use one of these on all error classes +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ + name(std::string name, std::string msg, ExitCodes exit_code) \ + : parent(std::move(name), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + INIError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int exit_code; + std::string name{"Error"}; + + public: + int get_exit_code() const { return exit_code; } + + std::string get_name() const { return name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), exit_code(exit_code), name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, size_t recieved) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(recieved)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(recieved)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class INIError : public ParseError { + CLI11_ERROR_DEF(ParseError, INIError) + CLI11_ERROR_SIMPLE(INIError) + static INIError Extras(std::string item) { return INIError("INI was not able to parse " + item); } + static INIError NotConfigurable(std::string item) { + return INIError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +/// @} + +} // namespace CLI + +// From CLI/TypeTools.hpp + +namespace CLI { + +// Type tools + +// We could check to see if C++14 is being used, but it does not hurt to redefine this +// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +// It is not in the std namespace anyway, so no harm done. + +template using enable_if_t = typename std::enable_if::type; + +template struct is_vector { static const bool value = false; }; + +template struct is_vector> { static bool const value = true; }; + +template struct is_bool { static const bool value = false; }; + +template <> struct is_bool { static bool const value = true; }; + +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value && std::is_signed::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// This one should not be used, since vector types print the internal type +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "VECTOR"; +} + +template ::value && !std::is_integral::value && !is_vector::value, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +// Lexical cast + +/// Signed integers / enums +template ::value && std::is_signed::value) || std::is_enum::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + long long output_ll = std::stoll(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + unsigned long long output_ll = std::stoull(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Floats +template ::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar +template ::value && !std::is_integral::value && !std::is_enum::value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + output = input; + return true; +} + +/// Non-string parsable +template ::value && !std::is_integral::value && !std::is_enum::value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + +// On GCC 4.7, thread_local is not available, so this optimization +// is turned off (avoiding multiple initialisations on multiple usages +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && __GNUC__ == 4 && (__GNUC_MINOR__ < 8) + std::istringstream is; +#else + static thread_local std::istringstream is; +#endif + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Split.hpp + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } else + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find("="); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } else + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Ini.hpp + +namespace CLI { +namespace detail { + +inline std::string inijoin(std::vector args) { + std::ostringstream s; + size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) + s << " "; + + auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); + if(it == arg.end()) + s << arg; + else if(arg.find(R"(")") == std::string::npos) + s << R"(")" << arg << R"(")"; + else + s << R"(')" << arg << R"(')"; + } + + return s.str(); +} + +struct ini_ret_t { + /// This is the full name with dots + std::string fullname; + + /// Listing of inputs + std::vector inputs; + + /// Current parent level + size_t level = 0; + + /// Return parent or empty string, based on level + /// + /// Level 0, a.b.c would return a + /// Level 1, a.b.c could return b + std::string parent() const { + std::vector plist = detail::split(fullname, '.'); + if(plist.size() > (level + 1)) + return plist[level]; + else + return ""; + } + + /// Return name + std::string name() const { + std::vector plist = detail::split(fullname, '.'); + return plist.at(plist.size() - 1); + } +}; + +/// Internal parsing function +inline std::vector parse_ini(std::istream &input) { + std::string name, line; + std::string section = "default"; + + std::vector output; + + while(getline(input, line)) { + std::vector items; + + detail::trim(line); + size_t len = line.length(); + if(len > 1 && line[0] == '[' && line[len - 1] == ']') { + section = line.substr(1, len - 2); + } else if(len > 0 && line[0] != ';') { + output.emplace_back(); + ini_ret_t &out = output.back(); + + // Find = in string, split and recombine + auto pos = line.find("="); + if(pos != std::string::npos) { + name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + items = detail::split_up(item); + } else { + name = detail::trim_copy(line); + items = {"ON"}; + } + + if(detail::to_lower(section) == "default") + out.fullname = name; + else + out.fullname = section + "." + name; + + out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); + } + } + return output; +} + +/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure +inline std::vector parse_ini(const std::string &name) { + + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return parse_ini(input); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Validators.hpp + +namespace CLI { + +/// @defgroup validator_group Validators +/// @brief Some validators that are provided +/// +/// These are simple `void(std::string&)` validators that are useful. They throw +/// a ValidationError if they fail (or the normally expected error if the cast fails) +/// @{ + +/// Check for an existing file +inline std::string ExistingFile(const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "File does not exist: " + filename; + } else if(is_dir) { + return "File is actually a directory: " + filename; + } + return std::string(); +} + +/// Check for an existing directory +inline std::string ExistingDirectory(const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "Directory does not exist: " + filename; + } else if(!is_dir) { + return "Directory is actually a file: " + filename; + } + return std::string(); +} + +/// Check for an existing path +inline std::string ExistingPath(const std::string &filename) { + struct stat buffer; + bool const exist = stat(filename.c_str(), &buffer) == 0; + if(!exist) { + return "Path does not exist: " + filename; + } + return std::string(); +} + +/// Check for a non-existing path +inline std::string NonexistentPath(const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); +} + +/// Produce a range validator function +template std::function Range(T min, T max) { + return [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(val < min || val > max) + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + + return std::string(); + }; +} + +/// Range of one value is 0 to value +template std::function Range(T max) { + return Range(static_cast(0), max); +} + +/// @} + +} // namespace CLI + +// From CLI/Option.hpp + +namespace CLI { + +using results_t = std::vector; +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr