commit 1a9fd27a31b297a7b6cbb8d5bbaa0e321c76d39a Author: alexvoste Date: Thu May 7 02:22:25 2026 +0300 push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..646648d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +demo_iso/ +limine/ +limine-tools/ +limine.conf +OS-TREE.txt +.vscode/ +linker-scripts/ +bin/ +obj/ +iso_root/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5780c0 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +

+ Cervus OS Logo +

+ + +# Cervus x86_64 Operating System + +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![Platform: x86_64](https://img.shields.io/badge/Platform-x86_64-lightgrey.svg)](https://en.wikipedia.org/wiki/X86-64) +[![Stage: Alpha](https://img.shields.io/badge/Stage-Alpha-orange.svg)]() + +**Cervus** - This is a modern 64-bit operating system written for the x86_64 architecture. It is currently under development. + +--- + +## Technical Features + +Cervus is currently in the active development phase. The kernel implements core low-level primitives required for a stable execution environment: + +### Core Architecture +- *Boot Protocol:* Utilizes the [Limine](https://github.com/limine-bootloader/limine) bootloader (Barebone profile). +- *Memory Management:* + - *PMM:* Bitmap-based Physical Memory Manager. + - *VMM:* Virtual Memory Management with 4-level paging support. +- *CPU Initialization:* Custom GDT (Global Descriptor Table) and IDT (Interrupt Descriptor Table) implementation. +- *Acceleration:* Native support for *SIMD* instructions (SSE/AVX) with proper state saving. + +## Roadmap & Progress + +| Component | Status | Description | +| :--- | :---: | :--- | +| *Bootloader* | Done | Limine Integration | +| *Graphics/PSF* | Done | Framebuffer & Text Rendering | +| *Memory (PMM/VMM)* | Done | Physical & Virtual Memory Management | +| *Interrupts (IDT)* | Done | Handling exceptions and IRQs | +| *ACPI* | Done(without rebooting) | Table parsing & SDT discovery | +| *APIC / IOAPIC* | Done | Advanced Interrupt Controllers | +| *Timers (HPET/APIC)* | Done | High Precision Event Timers | +| *SMP* | Done | Multicore Initialization | +| *Scheduler* | Done/In parallel development | Preemptive Multitasking | +| *Userspace* | In parallel development | Syscalls & Ring 3 execution | + +--- + +## Build Environment + +### Prerequisites + +To build Cervus, you need a cross-compilation toolchain and the following utilities: + +* *Compiler:* `x86_64-elf-gcc` +* *Assemblers:* `nasm` +* *Emulation:* `qemu-system-x86_64` +* *ISO Tools:* `xorriso`, `mtools` + +### Compiling and Running + +*1. Clone the repository:* + +```bash +bash +git clone https://github.com/VeoQeo/Cervus.git +cd Cervus +``` + +*2. Compile and launch in QEMU:* +```bash +./build run +``` + +*3. Deploy to hardware (Flash Drive):* +**WARNING: This will overwrite data on the target device.** +```bash +sudo ./build flash +``` + +## Contributing + +Cervus is an open-source research project. Contributions regarding bug fixes, hardware support, or documentation are welcome. Please feel free to open an Issue or submit a Pull Request. + +## License + +This project is licensed under the *GPL-3.0 License*. See the [LICENSE](LICENSE) file for details. + +--- diff --git a/TODO b/TODO new file mode 100644 index 0000000..7d0ccb8 --- /dev/null +++ b/TODO @@ -0,0 +1,23 @@ +- [x] **Bootloader**: Limine Barebone +- [x] **Графика**: Базовая поддержка фреймбуфера +- [x] **Шрифты**: Поддержка PSF шрифтов +- [x] **Порты ввода-вывода**: Базовые операции с портами +- [x] **Последовательный порт**: Отладочный вывод через COM1 +- [x] **Минимальная libc**: Базовая стандартная библиотека C +- [x] **GDT**: Глобальная таблица дескрипторов +- [x] **IDT**: Таблица дескрипторов прерываний +- [x] **SIMD**: Поддержка инструкций SSE/AVX +- [x] **PMM**: Менеджер физической памяти +- [x] **VMM**: Менеджер виртуальной памяти +- [x] **PAGING** +- [x] **ACPI**: Парсинг таблиц ACPI +- [x] **APIC**: Расширенный программируемый контроллер прерываний +- [x] **IOAPIC**: Контроллер прерываний ввода-вывода +- [x] **LAPIC**: Локальный APIC +- [x] **HPET**: Таймер высокой точности +- [x] **APIC-TIMER**: Таймер на основе APIC +- [x] **SMP**: Симметричная многопроцессорность +- [x] **Multitasking**: Многозадачность и планировщик +- [x] **Syscalls**: Системные вызовы +- [x] **Userspace**: Пространство пользователя +- [x_x(help me)] **Веселье начинается**: Дальнейшее развитие \ No newline at end of file diff --git a/build b/build new file mode 100755 index 0000000..c9e5238 Binary files /dev/null and b/build differ diff --git a/builder/Makefile b/builder/Makefile new file mode 100644 index 0000000..f56b455 --- /dev/null +++ b/builder/Makefile @@ -0,0 +1,15 @@ +CC = gcc +CFLAGS = -Wall -Wextra -O2 + +TARGET = ../build + +all: $(TARGET) + +$(TARGET): build.c + $(CC) $(CFLAGS) -o $@ $< + @echo "Build script is ready at: $(TARGET)" + +clean: + rm -f $(TARGET) + +.PHONY: all clean \ No newline at end of file diff --git a/builder/build.c b/builder/build.c new file mode 100644 index 0000000..2a606ac --- /dev/null +++ b/builder/build.c @@ -0,0 +1,2496 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMAGE_NAME "Cervus" +#define VERSION "v0.0.2" +#define QEMUFLAGS "-m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on " \ + "-drive file=cervus_disk.img,format=raw,if=ide,index=0,media=disk " + +#define LIMINE_CONF_PATH "/boot/limine/limine.conf" +#define LIMINE_CONF_BACKUP "/boot/limine/limine.conf.cervus-backup" +#define CERVUS_BOOT_DIR "/boot/cervus" +#define CERVUS_MARKER "# --- CERVUS OS ENTRY ---" + +#define WALLPAPER_SRC "wallpapers/cervus1280x720.png" +#define WALLPAPER_DST "boot():/boot/wallpapers/cervus.png" + +#define APPS_DIR "usr/apps" +#define BIN_APPS_DIR "usr/bin" +#define INSTALLER_DIR "usr/installer" +#define SYSROOT_DIR "usr/sysroot" +#define SYSROOT_INC "usr/sysroot/usr/include" +#define SYSROOT_LIB "usr/sysroot/usr/lib" +#define LIBCERVUS_DIR "usr/lib/libcervus" +#define SHELL_SRC "usr/apps/shell.c" +#define SHELL_ELF "usr/apps/shell.elf" +#define INSTALLER_SRC "usr/installer/install-on-disk.c" +#define INSTALLER_ELF "usr/installer/install-on-disk.elf" + +#define INITRAMFS_TAR "initramfs.tar" +#define INITRAMFS_ROOTFS "rootfs" + +#define BIGPATH (PATH_MAX * 2) + +#define SHORTPATH 512 + +static void path_join2(char *dst, size_t dst_size, const char *a, const char *b) { + if (dst_size == 0) return; + size_t la = strlen(a); + size_t lb = strlen(b); + if (la >= dst_size) la = dst_size - 1; + memcpy(dst, a, la); + size_t off = la; + if (off + 1 < dst_size) dst[off++] = '/'; + if (lb > dst_size - off - 1) lb = dst_size - off - 1; + memcpy(dst + off, b, lb); + dst[off + lb] = '\0'; +} + +#define COLOR_RESET "\033[0m" +#define COLOR_RED "\033[91m" +#define COLOR_GREEN "\033[92m" +#define COLOR_YELLOW "\033[93m" +#define COLOR_BLUE "\033[94m" +#define COLOR_MAGENTA "\033[95m" +#define COLOR_CYAN "\033[96m" +#define COLOR_BOLD "\033[1m" + +const char *DIRS_TO_CLEAN[] = { + "bin", "obj", "iso_root", "limine", "kernel/linker-scripts", + "demo_iso", "limine-tools", "edk2-ovmf", + INITRAMFS_ROOTFS, + NULL +}; +const char *FILES_TO_CLEAN[] = { + "Cervus.iso", "Cervus.hdd", + "kernel/.deps-obtained", + "limine.conf", + "OS-TREE.txt", "log.txt", + SHELL_ELF, INSTALLER_ELF, + SYSROOT_LIB "/libcervus.a", + SYSROOT_LIB "/crt0.o", + SYSROOT_DIR "/usr/bin/tcc", + SYSROOT_LIB "/tcc/libtcc1.a", + INITRAMFS_TAR, + "cervus_disk.img", + NULL +}; + +const char *SSE_FILES[] = { + "sse.c", "fpu.c", "fabs.c", "pow.c", "pow10.c", + "serial.c", "snprintf.c", "printf.c", NULL +}; + +struct Dependency { + const char *name; + const char *url; + const char *commit; +}; + +struct Dependency DEPENDENCIES[] = { + {"freestnd-c-hdrs", "https://codeberg.org/OSDev/freestnd-c-hdrs-0bsd.git", "5df91dd7062ad0c54f5ffd86193bb9f008677631"}, + {"cc-runtime", "https://codeberg.org/OSDev/cc-runtime.git", "dae79833b57a01b9fd3e359ee31def69f5ae899b"}, + {"limine-protocol", "https://codeberg.org/Limine/limine-protocol.git", "c4616df2572d77c60020bdefa617dd9bdcc6566a"}, + {NULL, NULL, NULL} +}; + +bool ARG_NO_CLEAN = false; +bool ARG_TREE = false; +bool ARG_STRUCTURE_ONLY = false; +bool ARG_NO_INITRAMFS = false; +bool ARG_RESET_HW_CONF = false; +bool ARG_RESET_DISK = false; +bool ARG_LIVE = false; + +char **TREE_FILES = NULL; +int TREE_FILES_COUNT = 0; +int TREE_FILES_CAP = 0; + +void print_color(const char *color, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + printf("%s", color); + vprintf(fmt, args); + printf("%s\n", COLOR_RESET); + va_end(args); +} + +void add_tree_file(const char *filename) { + if (TREE_FILES_COUNT >= TREE_FILES_CAP) { + TREE_FILES_CAP = (TREE_FILES_CAP == 0) ? 8 : TREE_FILES_CAP * 2; + TREE_FILES = realloc(TREE_FILES, TREE_FILES_CAP * sizeof(char *)); + } + TREE_FILES[TREE_FILES_COUNT++] = strdup(filename); +} + +bool should_print_content(const char *filename) { + if (TREE_FILES_COUNT == 0) return true; + for (int i = 0; i < TREE_FILES_COUNT; i++) + if (strcmp(filename, TREE_FILES[i]) == 0) return true; + return false; +} + +int cmd_run(bool verbose, const char *fmt, ...) { + char cmd[8192]; + va_list args; + va_start(args, fmt); + vsnprintf(cmd, sizeof(cmd), fmt, args); + va_end(args); + if (verbose) print_color(COLOR_BLUE, "Running: %s", cmd); + int ret = system(cmd); + return (ret != 0) ? WEXITSTATUS(ret) : 0; +} + +void ensure_dir(const char *path) { + char tmp[1024]; + snprintf(tmp, sizeof(tmp), "mkdir -p %s", path); + int r = system(tmp); + (void)r; +} + +bool file_exists(const char *path) { + return access(path, F_OK) == 0; +} + +time_t get_mtime(const char *path) { + struct stat attr; + if (stat(path, &attr) == 0) return attr.st_mtime; + return 0; +} + +void rm_rf(const char *path) { + if (file_exists(path)) { + print_color(COLOR_BLUE, "Removing %s", path); + cmd_run(false, "rm -rf %s", path); + } +} + +bool setup_dependencies(void) { + print_color(COLOR_GREEN, "Checking dependencies..."); + ensure_dir("limine-tools"); + + for (int i = 0; DEPENDENCIES[i].name != NULL; i++) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "limine-tools/%s", DEPENDENCIES[i].name); + + if (!file_exists(path)) { + print_color(COLOR_YELLOW, "Missing %s, setting up...", DEPENDENCIES[i].name); + if (cmd_run(true, "git clone %s %s", DEPENDENCIES[i].url, path) != 0) return false; + char gc[PATH_MAX + 128]; + snprintf(gc, sizeof(gc), + "git -C %s -c advice.detachedHead=false checkout %s", + path, DEPENDENCIES[i].commit); + if (system(gc) != 0) return false; + } + } + return true; +} + +static bool extract_limine_hdd_bin(void) { + const char *src = "limine/limine-bios-hdd.h"; + const char *dst = "limine/limine-bios-hdd.bin"; + + if (file_exists(dst) && file_exists(src) && get_mtime(src) <= get_mtime(dst)) { + return true; + } + if (!file_exists(src)) { + print_color(COLOR_RED, "limine-bios-hdd.h not found in limine/"); + return false; + } + + FILE *in = fopen(src, "r"); + if (!in) { + print_color(COLOR_RED, "cannot open %s", src); + return false; + } + FILE *out = fopen(dst, "wb"); + if (!out) { + fclose(in); + print_color(COLOR_RED, "cannot create %s", dst); + return false; + } + + char line[4096]; + size_t total = 0; + while (fgets(line, sizeof(line), in)) { + char *p = line; + while (*p) { + if (p[0] == '0' && p[1] == 'x') { + unsigned v = 0; + p += 2; + if (sscanf(p, "%2x", &v) == 1) { + unsigned char b = (unsigned char)v; + fwrite(&b, 1, 1, out); + total++; + p += 2; + } else { + p++; + } + } else { + p++; + } + } + } + fclose(in); + fclose(out); + print_color(COLOR_GREEN, "Extracted limine-bios-hdd.bin (%zu bytes)", total); + return true; +} + +bool build_limine(void) { + if (file_exists("limine/limine")) { + print_color(COLOR_GREEN, "Limine already built"); + extract_limine_hdd_bin(); + return true; + } + print_color(COLOR_GREEN, "Building Limine..."); + if (file_exists("limine")) rm_rf("limine"); + if (cmd_run(true, "git clone https://codeberg.org/Limine/Limine.git limine " + "--branch=v11.2.1-binary --depth=1") != 0) return false; + if (cmd_run(true, "make -C limine") != 0) return false; + if (!extract_limine_hdd_bin()) return false; + return true; +} + +void ensure_linker_script(void) { + ensure_dir("kernel/linker-scripts"); + const char *lds_path = "kernel/linker-scripts/x86_64.lds"; + if (file_exists(lds_path)) return; + + const char *script = +"OUTPUT_FORMAT(elf64-x86-64)\n" +"ENTRY(kernel_main)\n" +"PHDRS {\n" +" limine_requests PT_LOAD;\n" +" text PT_LOAD;\n" +" rodata PT_LOAD;\n" +" data PT_LOAD;\n" +"}\n" +"SECTIONS {\n" +" . = 0xffffffff80000000;\n" +" .limine_requests : {\n" +" KEEP(*(.limine_requests_start))\n" +" KEEP(*(.limine_requests))\n" +" KEEP(*(.limine_requests_end))\n" +" } :limine_requests\n" +" . = ALIGN(CONSTANT(MAXPAGESIZE));\n" +" .text : {\n" +" __start_isr_handlers = .;\n" +" KEEP(*(.isr_handlers))\n" +" __stop_isr_handlers = .;\n" +" __start_irq_handlers = .;\n" +" KEEP(*(.irq_handlers))\n" +" __stop_irq_handlers = .;\n" +" *(.text .text.*)\n" +" } :text\n" +" . = ALIGN(CONSTANT(MAXPAGESIZE));\n" +" .rodata : { *(.rodata .rodata.*) } :rodata\n" +" .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata\n" +" . = ALIGN(CONSTANT(MAXPAGESIZE));\n" +" .data : {\n" +" *(.data .data.*)\n" +" . = ALIGN(4096);\n" +" __percpu_start = .;\n" +" KEEP(*(.percpu .percpu.*))\n" +" . = ALIGN(4096);\n" +" __percpu_end = .;\n" +" } :data\n" +" .bss : { *(.bss .bss.*) *(COMMON) } :data\n" +" /DISCARD/ : { *(.eh_frame*) *(.note .note.*) }\n" +"}\n"; + + FILE *f = fopen(lds_path, "w"); + if (!f) return; + fprintf(f, "%s", script); + fclose(f); + print_color(COLOR_GREEN, "x86_64.lds created"); +} + +typedef struct { + char src[512]; + char elf[512]; + char name[256]; +} app_entry_t; + +#define MAX_APPS 64 +static app_entry_t g_apps[MAX_APPS]; +static int g_naps = 0; + +static int scan_apps(void) { + g_naps = 0; + DIR *d = opendir(APPS_DIR); + if (!d) { + print_color(COLOR_RED, "[apps] Cannot open directory '%s'", APPS_DIR); + return 0; + } + struct dirent *de; + while ((de = readdir(d)) != NULL && g_naps < MAX_APPS) { + const char *nm = de->d_name; + size_t nlen = strlen(nm); + if (nlen < 3) continue; + if (nm[0] == '.') continue; + if (strcmp(nm + nlen - 2, ".c") != 0) continue; + + app_entry_t *e = &g_apps[g_naps]; + snprintf(e->src, sizeof(e->src), "%s/%s", APPS_DIR, nm); + snprintf(e->elf, sizeof(e->elf), "%s/%.*s.elf", APPS_DIR, (int)(nlen-2), nm); + snprintf(e->name, sizeof(e->name), "%.*s", (int)(nlen-2), nm); + g_naps++; + } + closedir(d); + for (int i = 1; i < g_naps; i++) { + app_entry_t tmp = g_apps[i]; int j = i-1; + while (j >= 0 && strcmp(g_apps[j].name, tmp.name) > 0) { + g_apps[j+1] = g_apps[j]; j--; + } + g_apps[j+1] = tmp; + } + return g_naps; +} + +static bool build_one_app(const app_entry_t *e) { + if (file_exists(e->elf) && get_mtime(e->src) <= get_mtime(e->elf)) { + print_color(COLOR_GREEN, "[ELF] %s is up to date", e->elf); + return true; + } + print_color(COLOR_CYAN, "[ELF] Compiling %s -> %s", e->src, e->elf); + + int ret = cmd_run(false, + "gcc -ffreestanding -nostdlib -static -fno-stack-protector" + " -fno-pie -fno-pic" + " -mno-sse -mno-sse2 -mno-mmx -mno-avx -mno-avx2" + " -mno-red-zone" + " -O0 -g" + " -nostdinc -isystem " SYSROOT_INC + " -Wl,-Ttext-segment=0x401000" + " -o %s %s " SYSROOT_LIB "/crt0.o " SYSROOT_LIB "/libcervus.a", + e->elf, e->src); + if (ret != 0) { + print_color(COLOR_RED, "[ELF] Failed to compile %s", e->src); + return false; + } + print_color(COLOR_GREEN, "[ELF] %s built successfully", e->elf); + return true; +} + +bool build_all_apps(void) { + scan_apps(); + if (g_naps == 0) { + print_color(COLOR_YELLOW, "[apps] No .c files found in '%s'", APPS_DIR); + return true; + } + print_color(COLOR_CYAN, "[apps] Found %d app(s) in '%s'", g_naps, APPS_DIR); + for (int i = 0; i < g_naps; i++) { + if (!build_one_app(&g_apps[i])) return false; + } + return true; +} + +void clean_apps_elfs(void) { + DIR *d = opendir(APPS_DIR); + if (!d) return; + struct dirent *de; + while ((de = readdir(d)) != NULL) { + const char *nm = de->d_name; + size_t nlen = strlen(nm); + if (nlen < 5) continue; + if (strcmp(nm + nlen - 4, ".elf") != 0) continue; + char path[512]; + snprintf(path, sizeof(path), "%s/%s", APPS_DIR, nm); + remove(path); + print_color(COLOR_YELLOW, "[clean] removed %s", path); + } + closedir(d); +} + +static app_entry_t g_bin_apps[MAX_APPS]; +static int g_nbin = 0; + +static int scan_bin_apps(void) { + g_nbin = 0; + DIR *d = opendir(BIN_APPS_DIR); + if (!d) { + print_color(COLOR_YELLOW, "[bin] No '%s' directory found, skipping", BIN_APPS_DIR); + return 0; + } + struct dirent *de; + while ((de = readdir(d)) != NULL && g_nbin < MAX_APPS) { + const char *nm = de->d_name; + size_t nlen = strlen(nm); + if (nlen < 3) continue; + if (nm[0] == '.') continue; + if (strcmp(nm + nlen - 2, ".c") != 0) continue; + + app_entry_t *e = &g_bin_apps[g_nbin]; + snprintf(e->src, sizeof(e->src), "%s/%s", BIN_APPS_DIR, nm); + snprintf(e->elf, sizeof(e->elf), "%s/%.*s.elf", BIN_APPS_DIR, (int)(nlen-2), nm); + snprintf(e->name, sizeof(e->name), "%.*s", (int)(nlen-2), nm); + g_nbin++; + } + closedir(d); + for (int i = 1; i < g_nbin; i++) { + app_entry_t tmp = g_bin_apps[i]; int j = i-1; + while (j >= 0 && strcmp(g_bin_apps[j].name, tmp.name) > 0) { + g_bin_apps[j+1] = g_bin_apps[j]; j--; + } + g_bin_apps[j+1] = tmp; + } + return g_nbin; +} + +static bool build_one_bin_app(const app_entry_t *e) { + if (file_exists(e->elf) && get_mtime(e->src) <= get_mtime(e->elf)) { + print_color(COLOR_GREEN, "[bin] %s is up to date", e->elf); + return true; + } + print_color(COLOR_CYAN, "[bin] Compiling %s -> %s", e->src, e->elf); + + int ret = cmd_run(false, + "gcc -ffreestanding -nostdlib -static -fno-stack-protector" + " -fno-pie -fno-pic" + " -mno-sse -mno-sse2 -mno-mmx -mno-avx -mno-avx2" + " -mno-red-zone" + " -O0 -g" + " -nostdinc -isystem " SYSROOT_INC + " -Wl,-Ttext-segment=0x401000" + " -o %s %s " SYSROOT_LIB "/crt0.o " SYSROOT_LIB "/libcervus.a", + e->elf, e->src); + if (ret != 0) { + print_color(COLOR_RED, "[bin] Failed to compile %s", e->src); + return false; + } + print_color(COLOR_GREEN, "[bin] %s built successfully", e->elf); + return true; +} + +bool build_all_bin_apps(void) { + scan_bin_apps(); + if (g_nbin == 0) { + print_color(COLOR_YELLOW, "[bin] No .c files found in '%s'", BIN_APPS_DIR); + return true; + } + print_color(COLOR_CYAN, "[bin] Found %d program(s) in '%s'", g_nbin, BIN_APPS_DIR); + for (int i = 0; i < g_nbin; i++) { + if (!build_one_bin_app(&g_bin_apps[i])) return false; + } + return true; +} + +void clean_bin_elfs(void) { + DIR *d = opendir(BIN_APPS_DIR); + if (!d) return; + struct dirent *de; + while ((de = readdir(d)) != NULL) { + const char *nm = de->d_name; + size_t nlen = strlen(nm); + if (nlen < 5) continue; + if (strcmp(nm + nlen - 4, ".elf") != 0) continue; + char path[512]; + snprintf(path, sizeof(path), "%s/%s", BIN_APPS_DIR, nm); + remove(path); + print_color(COLOR_YELLOW, "[clean] removed %s", path); + } + closedir(d); +} + +#define LCV_CFLAGS_INT "-ffreestanding -nostdlib -static -fno-stack-protector " \ + "-mno-sse -mno-sse2 -mno-mmx -mno-avx -mno-avx2 " \ + "-mno-red-zone -fno-pie -fno-pic " \ + "-O0 -g -Wall -Wextra" + +#define LCV_CFLAGS_FLT "-ffreestanding -nostdlib -static -fno-stack-protector " \ + "-mno-red-zone -fno-pie -fno-pic " \ + "-O0 -g -Wall -Wextra" + +typedef struct { + const char *src; + const char *obj; + int is_float; +} libcervus_unit_t; + +static const libcervus_unit_t LCV_UNITS[] = { + { "libcervus.c", "libcervus.o", 0 }, + { "compat.c", "compat.o", 0 }, + { "ctype/ctype.c", "ctype/ctype.o", 0 }, + { "string/string.c", "string/string.o", 0 }, + { "memory/memory.c", "memory/memory.o", 0 }, + { "stdlib/stdlib.c", "stdlib/stdlib.o", 0 }, + { "stdlib/strtod.c", "stdlib/strtod.o", 1 }, + { "stdio/stdio.c", "stdio/stdio.o", 1 }, + { "stdio/scanf.c", "stdio/scanf.o", 1 }, + { "time/time.c", "time/time.o", 0 }, + { "signal/signal.c", "signal/signal.o", 0 }, + { "dirent/dirent.c", "dirent/dirent.o", 0 }, + { "math/abs.c", "math/abs.o", 0 }, + { "math/fabs.c", "math/fabs.o", 1 }, + { "math/isinf.c", "math/isinf.o", 0 }, + { "math/isnan.c", "math/isnan.o", 0 }, + { "math/pow.c", "math/pow.o", 1 }, + { "math/pow10.c", "math/pow10.o", 1 }, + { NULL, NULL, 0 } +}; + +static long get_mtime_safe(const char *p) { + struct stat st; + if (stat(p, &st) != 0) return 0; + return (long)st.st_mtime; +} + +static bool need_rebuild(const char *src, const char *obj) { + if (!file_exists(obj)) return true; + return get_mtime_safe(src) > get_mtime_safe(obj); +} + +bool build_libcervus(void) { + if (cmd_run(false, "command -v nasm >/dev/null 2>&1") != 0) { + print_color(COLOR_RED, + "[libcervus] 'nasm' is required to build crt0.o/setjmp.o. Install: " + "apt install nasm | dnf install nasm | pacman -S nasm"); + return false; + } + if (cmd_run(false, "command -v ar >/dev/null 2>&1") != 0) { + print_color(COLOR_RED, "[libcervus] 'ar' (binutils) is required"); + return false; + } + + print_color(COLOR_CYAN, "[libcervus] Building libcervus.a and crt0.o (native)..."); + + char incdir[BIGPATH]; + char libdir[BIGPATH]; + char cwd_buf[PATH_MAX]; + if (!getcwd(cwd_buf, sizeof(cwd_buf))) { + print_color(COLOR_RED, "[libcervus] getcwd failed"); + return false; + } + path_join2(incdir, sizeof(incdir), cwd_buf, SYSROOT_INC); + path_join2(libdir, sizeof(libdir), cwd_buf, SYSROOT_LIB); + + int rc = chdir(LIBCERVUS_DIR); + if (rc != 0) { + print_color(COLOR_RED, "[libcervus] cannot chdir to %s", LIBCERVUS_DIR); + return false; + } + + bool any_changed = false; + int built = 0; + int skipped = 0; + + for (int i = 0; LCV_UNITS[i].src; i++) { + const libcervus_unit_t *u = &LCV_UNITS[i]; + if (!need_rebuild(u->src, u->obj)) { skipped++; continue; } + const char *cflags = u->is_float ? LCV_CFLAGS_FLT : LCV_CFLAGS_INT; + if (cmd_run(false, "gcc %s -nostdinc -isystem %s -c -o %s %s", + cflags, incdir, u->obj, u->src) != 0) { + print_color(COLOR_RED, "[libcervus] failed to compile %s", u->src); + (void)!chdir(cwd_buf); + return false; + } + any_changed = true; + built++; + } + + if (need_rebuild("setjmp.asm", "setjmp.o")) { + if (cmd_run(false, "nasm -f elf64 -o setjmp.o setjmp.asm") != 0) { + print_color(COLOR_RED, "[libcervus] nasm failed on setjmp.asm"); + (void)!chdir(cwd_buf); return false; + } + any_changed = true; built++; + } else skipped++; + + if (need_rebuild("crt0.asm", "crt0.o")) { + if (cmd_run(false, "nasm -f elf64 -o crt0.o crt0.asm") != 0) { + print_color(COLOR_RED, "[libcervus] nasm failed on crt0.asm"); + (void)!chdir(cwd_buf); return false; + } + any_changed = true; built++; + } else skipped++; + + bool need_ar = any_changed || !file_exists("libcervus.a"); + if (need_ar) { + char ar_cmd[8192]; + size_t pos = 0; + pos += snprintf(ar_cmd + pos, sizeof(ar_cmd) - pos, "ar rcs libcervus.a"); + for (int i = 0; LCV_UNITS[i].src; i++) { + pos += snprintf(ar_cmd + pos, sizeof(ar_cmd) - pos, " %s", LCV_UNITS[i].obj); + } + pos += snprintf(ar_cmd + pos, sizeof(ar_cmd) - pos, " setjmp.o"); + if (cmd_run(false, "%s", ar_cmd) != 0) { + print_color(COLOR_RED, "[libcervus] ar failed"); + (void)!chdir(cwd_buf); return false; + } + } + + if (cmd_run(false, "mkdir -p %s", libdir) != 0 || + cmd_run(false, "cp libcervus.a %s/", libdir) != 0 || + cmd_run(false, "cp crt0.o %s/", libdir) != 0) + { + print_color(COLOR_RED, "[libcervus] install copy failed"); + (void)!chdir(cwd_buf); return false; + } + + (void)!chdir(cwd_buf); + print_color(COLOR_GREEN, "[libcervus] OK (compiled %d, skipped %d)", built, skipped); + return true; +} + +#define TCC_VERSION_STR "0.9.27" +#define TCC_TAR_FNAME "tcc-0.9.27.tar.bz2" +#define TCC_SRC_DIR "tcc-0.9.27" +#define TCC_DOWNLOAD_URL "https://download.savannah.gnu.org/releases/tinycc/tcc-0.9.27.tar.bz2" +#define TCC_DIR "usr/tcc" + +typedef struct { char *data; size_t len; size_t cap; } dstr_t; + +static void dstr_init(dstr_t *s) { s->data = NULL; s->len = 0; s->cap = 0; } +static void dstr_free(dstr_t *s) { free(s->data); s->data = NULL; s->len = s->cap = 0; } + +static bool dstr_reserve(dstr_t *s, size_t need) { + if (s->cap >= need) return true; + size_t nc = s->cap ? s->cap : 256; + while (nc < need) nc *= 2; + char *p = realloc(s->data, nc); + if (!p) return false; + s->data = p; s->cap = nc; + return true; +} + +static bool read_file_dstr(const char *path, dstr_t *out) { + FILE *f = fopen(path, "rb"); + if (!f) return false; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + fseek(f, 0, SEEK_SET); + if (sz < 0) { fclose(f); return false; } + if (!dstr_reserve(out, (size_t)sz + 1)) { fclose(f); return false; } + if (sz > 0 && fread(out->data, 1, (size_t)sz, f) != (size_t)sz) { + fclose(f); return false; + } + out->len = (size_t)sz; + out->data[out->len] = '\0'; + fclose(f); + return true; +} + +static bool write_file_dstr(const char *path, const dstr_t *s) { + FILE *f = fopen(path, "wb"); + if (!f) return false; + if (s->len > 0 && fwrite(s->data, 1, s->len, f) != s->len) { fclose(f); return false; } + fclose(f); + return true; +} + +static bool dstr_replace_once(dstr_t *s, const char *old_s, const char *new_s) { + size_t old_n = strlen(old_s); + size_t new_n = strlen(new_s); + if (old_n == 0 || s->len < old_n) return false; + char *p = memmem(s->data, s->len, old_s, old_n); + if (!p) return false; + size_t off = (size_t)(p - s->data); + + if (new_n > old_n) { + if (!dstr_reserve(s, s->len + (new_n - old_n) + 1)) return false; + p = s->data + off; + } + + memmove(p + new_n, p + old_n, s->len - off - old_n + 1 ); + memcpy(p, new_s, new_n); + s->len += (new_n - old_n); + return true; +} + +static bool dstr_guard_line_with(dstr_t *s, const char *marker, const char *guard_macro) { + char *p = memmem(s->data, s->len, marker, strlen(marker)); + if (!p) return false; + + char *line_start = p; + while (line_start > s->data && line_start[-1] != '\n') line_start--; + + char *prev_line_start = line_start; + if (prev_line_start > s->data) prev_line_start--; + while (prev_line_start > s->data && prev_line_start[-1] != '\n') prev_line_start--; + + char needle[160]; + snprintf(needle, sizeof(needle), "#ifndef %s", guard_macro); + size_t pll = (size_t)(line_start - prev_line_start); + if (pll >= strlen(needle) && + memcmp(prev_line_start, needle, strlen(needle)) == 0) + { + return false; + } + + char *line_end = p; + while ((size_t)(line_end - s->data) < s->len && *line_end != '\n') line_end++; + if ((size_t)(line_end - s->data) < s->len && *line_end == '\n') line_end++; + + char prefix[160], suffix[16]; + snprintf(prefix, sizeof(prefix), "#ifndef %s\n", guard_macro); + snprintf(suffix, sizeof(suffix), "#endif\n"); + size_t ins_pre = strlen(prefix), ins_suf = strlen(suffix); + size_t off_start = (size_t)(line_start - s->data); + size_t off_end = (size_t)(line_end - s->data); + + if (!dstr_reserve(s, s->len + ins_pre + ins_suf + 1)) return false; + line_start = s->data + off_start; + line_end = s->data + off_end; + + memmove(line_end + ins_pre + ins_suf, line_end, s->len - off_end + 1); + + memcpy(line_end + ins_pre, suffix, ins_suf); + + memmove(line_start + ins_pre, line_start, off_end - off_start); + + memcpy(line_start, prefix, ins_pre); + s->len += ins_pre + ins_suf; + return true; +} + +static int dstr_guard_func_impls(dstr_t *s, const char *name, const char *guard_macro) { + int count = 0; + size_t cursor = 0; + size_t name_n = strlen(name); + + while (cursor + name_n < s->len) { + char *p = memmem(s->data + cursor, s->len - cursor, name, name_n); + if (!p) break; + size_t pos = (size_t)(p - s->data); + + size_t i = pos + name_n; + while (i < s->len && (s->data[i] == ' ' || s->data[i] == '\t')) i++; + if (i >= s->len || s->data[i] != '(') { + cursor = pos + 1; continue; + } + + size_t j = i; + while (j < s->len && s->data[j] != '{' && s->data[j] != ';') j++; + if (j >= s->len) break; + if (s->data[j] == ';') { cursor = pos + 1; continue; } + + int depth = 0; + size_t end = j; + for (; end < s->len; end++) { + if (s->data[end] == '{') depth++; + else if (s->data[end] == '}') { depth--; if (depth == 0) { end++; break; } } + } + if (depth != 0) break; + if (end < s->len && s->data[end] == '\n') end++; + + size_t line_start = pos; + while (line_start > 0 && s->data[line_start - 1] != '\n') line_start--; + + size_t look = line_start > 200 ? line_start - 200 : 0; + if (memmem(s->data + look, line_start - look, + guard_macro, strlen(guard_macro))) { + cursor = end; continue; + } + + char prefix[160], suffix[16]; + snprintf(prefix, sizeof(prefix), "#ifndef %s\n", guard_macro); + snprintf(suffix, sizeof(suffix), "#endif\n"); + size_t pre_n = strlen(prefix), suf_n = strlen(suffix); + if (!dstr_reserve(s, s->len + pre_n + suf_n + 1)) return count; + + memmove(s->data + end + pre_n + suf_n, + s->data + end, + s->len - end + 1); + memcpy(s->data + end + pre_n, suffix, suf_n); + memmove(s->data + line_start + pre_n, + s->data + line_start, + end - line_start); + memcpy(s->data + line_start, prefix, pre_n); + s->len += pre_n + suf_n; + count++; + cursor = end + pre_n + suf_n; + } + return count; +} + +static bool patch_file(const char *src_dir, const char *rel, + int (*editor)(dstr_t *)) { + char path[SHORTPATH]; + path_join2(path, sizeof(path), src_dir, rel); + dstr_t s; dstr_init(&s); + if (!read_file_dstr(path, &s)) { + print_color(COLOR_RED, "[tcc-patch] cannot read %s", path); + return false; + } + int changed = editor(&s); + if (changed > 0) { + if (!write_file_dstr(path, &s)) { + print_color(COLOR_RED, "[tcc-patch] cannot write %s", path); + dstr_free(&s); return false; + } + print_color(COLOR_GREEN, "[tcc-patch] %s: %d edit(s)", rel, changed); + } else { + print_color(COLOR_YELLOW, "[tcc-patch] %s: no changes (already patched?)", rel); + } + dstr_free(&s); + return true; +} + +static int edit_tcc_h(dstr_t *s) { + int changed = 0; + if (dstr_guard_line_with(s, "#include ", "TCC_NO_DLOPEN")) changed++; + + { + const char *needle = "#include "; + char *p = memmem(s->data, s->len, needle, strlen(needle)); + if (p && !memmem(s->data, s->len, + "#include ", + strlen("#include "))) { + const char *ins = "#include \n"; + size_t off = (size_t)(p - s->data); + size_t ins_n = strlen(ins); + if (!dstr_reserve(s, s->len + ins_n + 1)) return changed; + memmove(s->data + off + ins_n, s->data + off, s->len - off + 1); + memcpy(s->data + off, ins, ins_n); + s->len += ins_n; + changed++; + } + } + + if (dstr_guard_line_with(s, "enable bound checking code", "CONFIG_TCC_BCHECK")) changed++; + return changed; +} + +static int edit_libtcc_c(dstr_t *s) { + int changed = 0; + + if (dstr_replace_once(s, + "return dlopen(filename, RTLD_GLOBAL | RTLD_LAZY);\n", + "(void)filename;\n return NULL;\n")) changed++; + if (dstr_replace_once(s, + "return dlopen(filename, RTLD_LOCAL | RTLD_LAZY);\n", + "(void)filename;\n return NULL;\n")) changed++; + if (dstr_replace_once(s, + "return dlsym(handle, sym);\n", + "(void)handle; (void)sym;\n return NULL;\n")) changed++; + if (dstr_replace_once(s, + "dlclose(handle);\n", + "(void)handle;\n")) changed++; + + if (!memmem(s->data, s->len, "Cervus: no ld.so", 16)) { + if (dstr_replace_once(s, + " s->alacarte_link = 1;", + " s->alacarte_link = 1;\n s->static_link = 1; /* Cervus: no ld.so */")) + changed++; + } + + if (dstr_replace_once(s, + " if (output_type != TCC_OUTPUT_DLL)\n" + " tcc_add_crt(s, \"crt1.o\");\n" + " tcc_add_crt(s, \"crti.o\");", + " if (output_type != TCC_OUTPUT_DLL)\n" + " tcc_add_crt(s, \"crt0.o\"); /* Cervus: only crt0 */")) + changed++; + return changed; +} + +static int edit_tccrun_c(dstr_t *s) { + int changed = 0; + + if (dstr_guard_line_with(s, "", "TCC_NO_BACKTRACE")) changed++; + + if (!memmem(s->data, s->len, "typedef void *ucontext_t;", + strlen("typedef void *ucontext_t;"))) + { + + size_t scan = s->len < 4096 ? s->len : 4096; + size_t last_inc_end = 0; + for (size_t i = 0; i < scan; ) { + if (s->data[i] == '#' && i + 8 <= s->len && + memcmp(s->data + i, "#include", 8) == 0) + { + + size_t e = i; + while (e < s->len && s->data[e] != '\n') e++; + if (e < s->len) e++; + last_inc_end = e; + i = e; continue; + } + i++; + } + if (last_inc_end > 0) { + const char *stub = "\n#ifdef TCC_NO_BACKTRACE\n" + "typedef void *ucontext_t;\n" + "#endif\n"; + size_t stub_n = strlen(stub); + if (dstr_reserve(s, s->len + stub_n + 1)) { + memmove(s->data + last_inc_end + stub_n, + s->data + last_inc_end, + s->len - last_inc_end + 1); + memcpy(s->data + last_inc_end, stub, stub_n); + s->len += stub_n; + changed++; + } + } + } + + if (dstr_guard_line_with(s, "set_exception_handler();", "TCC_NO_BACKTRACE")) changed++; + + const char *names[] = { "rt_error", "sig_error", + "set_exception_handler", "rt_get_caller_pc", NULL }; + for (int i = 0; names[i]; i++) { + int n = dstr_guard_func_impls(s, names[i], "TCC_NO_BACKTRACE"); + if (n > 0) changed++; + } + return changed; +} + +static int edit_tccelf_c(dstr_t *s) { + int changed = 0; + + if (!memmem(s->data, s->len, "Cervus: -lcervus", 16)) { + if (dstr_replace_once(s, + " if (!s1->nostdlib) {\n tcc_add_library_err(s1, \"c\");", + " if (!s1->nostdlib) {\n tcc_add_library_err(s1, \"cervus\"); /* Cervus: -lcervus */")) + changed++; + } + + if (dstr_replace_once(s, + " if (s1->output_type != TCC_OUTPUT_MEMORY)\n" + " tcc_add_crt(s1, \"crtn.o\");\n" + " }\n", + " /* Cervus: no crtn.o */\n }\n")) + changed++; + return changed; +} + +static bool write_tcc_config_h(const char *src_dir) { + char path[SHORTPATH]; + path_join2(path, sizeof(path), src_dir, "config.h"); + FILE *f = fopen(path, "w"); + if (!f) return false; + fputs( + "#ifndef _CONFIG_H\n" + "#define _CONFIG_H\n\n" + "#define TCC_VERSION \"" TCC_VERSION_STR "\"\n\n" + "#define CONFIG_TCC_SYSROOT \"\"\n" + "#define CONFIG_TCC_LIBPATHS \"/usr/lib\"\n" + "#define CONFIG_TCC_CRTPREFIX \"/usr/lib\"\n" + "#define CONFIG_TCC_ELFINTERP \"\"\n" + "#define CONFIG_TCCDIR \"/usr/lib/tcc\"\n\n" + "#define HOST_OS \"Cervus\"\n" + "#define HOST_ARCH \"x86_64\"\n\n" + "#define TCC_TARGET_X86_64 1\n\n" + "#define CONFIG_TCC_PREDEFS 1\n" + "#define CONFIG_TCC_STATIC 1\n\n" + "#define TCC_NO_DLOPEN 1\n" + "#define TCC_NO_BACKTRACE 1\n\n" + "#define CONFIG_TCC_BCHECK 0\n\n" + "#undef CONFIG_WIN32\n" + "#undef CONFIG_WIN64\n" + "#undef TCC_TARGET_PE\n\n" + "#endif /* _CONFIG_H */\n", + f); + fclose(f); + return true; +} + +static bool tcc_download(void) { + char tar_path[SHORTPATH]; + path_join2(tar_path, sizeof(tar_path), TCC_DIR, TCC_TAR_FNAME); + if (file_exists(tar_path)) return true; + print_color(COLOR_CYAN, "[tcc] Downloading %s...", TCC_TAR_FNAME); + if (cmd_run(false, "command -v wget >/dev/null 2>&1") == 0) { + if (cmd_run(true, "wget -q -O %s %s", tar_path, TCC_DOWNLOAD_URL) == 0 + && file_exists(tar_path)) return true; + } + if (cmd_run(false, "command -v curl >/dev/null 2>&1") == 0) { + if (cmd_run(true, "curl -fL -o %s %s", tar_path, TCC_DOWNLOAD_URL) == 0 + && file_exists(tar_path)) return true; + } + print_color(COLOR_RED, "[tcc] cannot download (need wget or curl)"); + return false; +} + +static bool tcc_extract_and_patch(void) { + char src_dir_full[SHORTPATH]; + path_join2(src_dir_full, sizeof(src_dir_full), TCC_DIR, TCC_SRC_DIR); + + if (!file_exists(src_dir_full)) { + print_color(COLOR_CYAN, "[tcc] Extracting %s...", TCC_TAR_FNAME); + if (cmd_run(true, "tar -xjf %s/%s -C %s", + TCC_DIR, TCC_TAR_FNAME, TCC_DIR) != 0) { + print_color(COLOR_RED, "[tcc] tar -xjf failed"); + return false; + } + } + + if (!write_tcc_config_h(src_dir_full)) { + print_color(COLOR_RED, "[tcc] cannot write config.h"); + return false; + } + + print_color(COLOR_CYAN, "[tcc] Applying Cervus patches (idempotent)..."); + if (!patch_file(src_dir_full, "tcc.h", edit_tcc_h)) return false; + if (!patch_file(src_dir_full, "libtcc.c", edit_libtcc_c)) return false; + if (!patch_file(src_dir_full, "tccrun.c", edit_tccrun_c)) return false; + if (!patch_file(src_dir_full, "tccelf.c", edit_tccelf_c)) return false; + return true; +} + +static bool tcc_build_and_install(void) { + char src_dir_full[SHORTPATH]; + path_join2(src_dir_full, sizeof(src_dir_full), TCC_DIR, TCC_SRC_DIR); + + char cwd_buf[PATH_MAX]; + if (!getcwd(cwd_buf, sizeof(cwd_buf))) { + print_color(COLOR_RED, "[tcc] getcwd failed"); + return false; + } + char incdir[BIGPATH], libdir[BIGPATH]; + path_join2(incdir, sizeof(incdir), cwd_buf, SYSROOT_INC); + path_join2(libdir, sizeof(libdir), cwd_buf, SYSROOT_LIB); + + char tcc_elf[SHORTPATH], libtcc1_a[SHORTPATH]; + path_join2(tcc_elf, sizeof(tcc_elf), TCC_DIR, "tcc.elf"); + path_join2(libtcc1_a, sizeof(libtcc1_a), TCC_DIR, "libtcc1.a"); + + char one_src[SHORTPATH]; + path_join2(one_src, sizeof(one_src), src_dir_full, "tcc.c"); + + char one_hdr[SHORTPATH], one_libtcc[SHORTPATH]; + path_join2(one_hdr, sizeof(one_hdr), src_dir_full, "tcc.h"); + path_join2(one_libtcc, sizeof(one_libtcc), src_dir_full, "libtcc.c"); + + long elf_t = get_mtime_safe(tcc_elf); + bool need_tcc = + !file_exists(tcc_elf) || + get_mtime_safe(one_src) > elf_t || + get_mtime_safe(one_hdr) > elf_t || + get_mtime_safe(one_libtcc) > elf_t; + + if (need_tcc) { + print_color(COLOR_CYAN, "[tcc] Building tcc.elf for Cervus (x86_64)..."); + char crt0[BIGPATH]; + path_join2(crt0, sizeof(crt0), libdir, "crt0.o"); + if (!file_exists(crt0)) { + print_color(COLOR_RED, "[tcc] %s missing (libcervus build failed?)", crt0); + return false; + } + int rc = cmd_run(true, + "gcc -ffreestanding -nostdlib -static -fno-stack-protector " + "-mno-red-zone -fno-pie -fno-pic -O2 -D__CERVUS__ " + "-nostdinc -isystem %s " + "-DTCC_TARGET_X86_64 -DONE_SOURCE=1 " + "-DCONFIG_TCC_PREDEFS=1 -DTCC_NO_DLOPEN=1 -DTCC_NO_BACKTRACE=1 " + "-DCONFIG_TCC_STATIC=1 " + "-I%s " + "-o %s %s/tcc.c %s " + "-nostdlib -static -L%s -lcervus", + incdir, src_dir_full, tcc_elf, src_dir_full, crt0, libdir); + if (rc != 0) { + print_color(COLOR_RED, "[tcc] tcc.elf build failed"); + return false; + } + } else { + print_color(COLOR_GREEN, "[tcc] tcc.elf up to date"); + } + + char l1_o[SHORTPATH], al_o[SHORTPATH], va_o[SHORTPATH]; + path_join2(l1_o, sizeof(l1_o), TCC_DIR, "libtcc1.o"); + path_join2(al_o, sizeof(al_o), TCC_DIR, "alloca86_64.o"); + path_join2(va_o, sizeof(va_o), TCC_DIR, "va_list.o"); + + bool need_libtcc1 = !file_exists(libtcc1_a); + if (need_libtcc1) { + print_color(COLOR_CYAN, "[tcc] Building libtcc1.a..."); + const char *l1_flags = + "-ffreestanding -nostdlib -fno-stack-protector " + "-mno-red-zone -fno-pie -fno-pic -O2 -D__CERVUS__ " + "-DTCC_TARGET_X86_64"; + + char src_path[SHORTPATH]; + char ar_cmd[2048]; + size_t arpos = (size_t)snprintf(ar_cmd, sizeof(ar_cmd), + "ar rcs %s", libtcc1_a); + + path_join2(src_path, sizeof(src_path), src_dir_full, "lib/libtcc1.c"); + if (!file_exists(src_path)) { + print_color(COLOR_RED, "[tcc] missing %s", src_path); + return false; + } + if (cmd_run(true, "gcc %s -c %s -o %s", l1_flags, src_path, l1_o) != 0) { + print_color(COLOR_RED, "[tcc] libtcc1.c compile failed"); return false; + } + arpos += (size_t)snprintf(ar_cmd + arpos, sizeof(ar_cmd) - arpos, " %s", l1_o); + + path_join2(src_path, sizeof(src_path), src_dir_full, "lib/alloca86_64.S"); + if (file_exists(src_path)) { + if (cmd_run(true, "gcc %s -c %s -o %s", l1_flags, src_path, al_o) != 0) { + print_color(COLOR_RED, "[tcc] alloca86_64.S compile failed"); return false; + } + arpos += (size_t)snprintf(ar_cmd + arpos, sizeof(ar_cmd) - arpos, " %s", al_o); + } + + path_join2(src_path, sizeof(src_path), src_dir_full, "lib/va_list.c"); + if (file_exists(src_path)) { + if (cmd_run(true, "gcc %s -c %s -o %s", l1_flags, src_path, va_o) != 0) { + print_color(COLOR_RED, "[tcc] va_list.c compile failed"); return false; + } + arpos += (size_t)snprintf(ar_cmd + arpos, sizeof(ar_cmd) - arpos, " %s", va_o); + } + + if (cmd_run(true, "%s", ar_cmd) != 0) { + print_color(COLOR_RED, "[tcc] ar failed"); return false; + } + } else { + print_color(COLOR_GREEN, "[tcc] libtcc1.a up to date"); + } + + print_color(COLOR_CYAN, "[tcc] Installing to sysroot..."); + if (cmd_run(false, "mkdir -p %s/usr/bin %s/usr/lib/tcc/include", + SYSROOT_DIR, SYSROOT_DIR) != 0) { + print_color(COLOR_RED, "[tcc] mkdir failed"); + return false; + } + if (cmd_run(false, "cp %s %s/usr/bin/tcc", tcc_elf, SYSROOT_DIR) != 0 || + cmd_run(false, "cp %s %s/usr/lib/tcc/libtcc1.a", libtcc1_a, SYSROOT_DIR) != 0 || + cmd_run(false, "cp %s/include/*.h %s/usr/lib/tcc/include/", + src_dir_full, SYSROOT_DIR) != 0) + { + print_color(COLOR_RED, "[tcc] install copy failed"); + return false; + } + + print_color(COLOR_GREEN, "[tcc] Installed to %s/usr/bin/tcc", SYSROOT_DIR); + return true; +} + +bool build_tcc(void) { + if (!tcc_download()) return false; + if (!tcc_extract_and_patch()) return false; + if (!tcc_build_and_install()) return false; + return true; +} + +void clean_tcc_build(bool deep) { + char tcc_elf[PATH_MAX], libtcc1_a[PATH_MAX]; + path_join2(tcc_elf, sizeof(tcc_elf), TCC_DIR, "tcc.elf"); + path_join2(libtcc1_a, sizeof(libtcc1_a), TCC_DIR, "libtcc1.a"); + + cmd_run(false, "rm -f %s/libtcc1.o %s/alloca86_64.o %s/va_list.o", + TCC_DIR, TCC_DIR, TCC_DIR); + if (file_exists(tcc_elf)) { remove(tcc_elf); print_color(COLOR_YELLOW, "[clean] removed %s", tcc_elf); } + if (file_exists(libtcc1_a)) { remove(libtcc1_a); print_color(COLOR_YELLOW, "[clean] removed %s", libtcc1_a); } + + cmd_run(false, "rm -f %s/usr/bin/tcc", SYSROOT_DIR); + cmd_run(false, "rm -rf %s/usr/lib/tcc", SYSROOT_DIR); + print_color(COLOR_YELLOW, "[clean] removed %s/usr/bin/tcc and %s/usr/lib/tcc/", + SYSROOT_DIR, SYSROOT_DIR); + + if (deep) { + char src_dir[SHORTPATH], tar_path[SHORTPATH]; + path_join2(src_dir, sizeof(src_dir), TCC_DIR, TCC_SRC_DIR); + path_join2(tar_path, sizeof(tar_path), TCC_DIR, TCC_TAR_FNAME); + if (file_exists(src_dir)) { rm_rf(src_dir); print_color(COLOR_YELLOW, "[clean] removed %s", src_dir); } + if (file_exists(tar_path)) { remove(tar_path); print_color(COLOR_YELLOW, "[clean] removed %s", tar_path); } + } +} + +void clean_libcervus_build(bool deep) { + char buf[PATH_MAX]; + for (int i = 0; LCV_UNITS[i].src; i++) { + path_join2(buf, sizeof(buf), LIBCERVUS_DIR, LCV_UNITS[i].obj); + if (file_exists(buf)) remove(buf); + } + path_join2(buf, sizeof(buf), LIBCERVUS_DIR, "setjmp.o"); + if (file_exists(buf)) remove(buf); + path_join2(buf, sizeof(buf), LIBCERVUS_DIR, "crt0.o"); + if (file_exists(buf)) remove(buf); + path_join2(buf, sizeof(buf), LIBCERVUS_DIR, "libcervus.a"); + if (file_exists(buf)) remove(buf); + + cmd_run(false, "rm -f %s/libcervus.a %s/crt0.o", SYSROOT_LIB, SYSROOT_LIB); + (void)deep; +} + +bool build_installer(void) { + DIR *d = opendir(INSTALLER_DIR); + if (!d) { + print_color(COLOR_YELLOW, + "[installer] directory '%s' not found, skipping", INSTALLER_DIR); + return true; + } + struct dirent *de; + int built = 0; + while ((de = readdir(d)) != NULL) { + const char *nm = de->d_name; + size_t nlen = strlen(nm); + if (nlen < 3 || nm[0] == '.') continue; + if (strcmp(nm + nlen - 2, ".c") != 0) continue; + app_entry_t e; + snprintf(e.src, sizeof(e.src), "%s/%s", INSTALLER_DIR, nm); + snprintf(e.elf, sizeof(e.elf), "%s/%.*s.elf", INSTALLER_DIR, (int)(nlen-2), nm); + snprintf(e.name, sizeof(e.name), "%.*s", (int)(nlen-2), nm); + if (!build_one_app(&e)) { closedir(d); return false; } + built++; + } + closedir(d); + if (built == 0) + print_color(COLOR_YELLOW, "[installer] no .c sources in '%s'", INSTALLER_DIR); + return true; +} + +bool build_initramfs(void) { + if (ARG_NO_INITRAMFS) { + print_color(COLOR_YELLOW, "[initramfs] skipped (--no-initramfs)"); + return true; + } + + if (!build_limine()) { + print_color(COLOR_YELLOW, "[initramfs] warning: limine not available yet, boot files will be incomplete"); + } + + bool tar_exists = file_exists(INITRAMFS_TAR); + bool any_newer = false; + if (!tar_exists) { + any_newer = true; + } else { + scan_apps(); + for (int i = 0; i < g_naps; i++) { + if (file_exists(g_apps[i].elf) && + get_mtime(g_apps[i].elf) > get_mtime(INITRAMFS_TAR)) { + any_newer = true; break; + } + } + if (!any_newer) { + scan_bin_apps(); + for (int i = 0; i < g_nbin; i++) { + if (file_exists(g_bin_apps[i].elf) && + get_mtime(g_bin_apps[i].elf) > get_mtime(INITRAMFS_TAR)) { + any_newer = true; break; + } + } + } + if (!any_newer && file_exists(SHELL_ELF) && + get_mtime(SHELL_ELF) > get_mtime(INITRAMFS_TAR)) { + any_newer = true; + } + if (!any_newer && file_exists(INSTALLER_ELF) && + get_mtime(INSTALLER_ELF) > get_mtime(INITRAMFS_TAR)) { + any_newer = true; + } + if (!any_newer && file_exists(SYSROOT_LIB "/libcervus.a") && + get_mtime(SYSROOT_LIB "/libcervus.a") > get_mtime(INITRAMFS_TAR)) { + any_newer = true; + } + } + if (tar_exists && !any_newer) { + print_color(COLOR_GREEN, "[initramfs] %s is up to date", INITRAMFS_TAR); + return true; + } + + print_color(COLOR_CYAN, "[initramfs] Building rootfs..."); + + const char *dirs[] = { + INITRAMFS_ROOTFS "/bin", + INITRAMFS_ROOTFS "/dev", + INITRAMFS_ROOTFS "/etc", + INITRAMFS_ROOTFS "/home", + INITRAMFS_ROOTFS "/tmp", + INITRAMFS_ROOTFS "/proc", + NULL + }; + for (int i = 0; dirs[i]; i++) ensure_dir(dirs[i]); + + FILE *passwd = fopen(INITRAMFS_ROOTFS "/etc/passwd", "w"); + if (passwd) { + fprintf(passwd, "root:x:0:0:root:/root:/bin/sh\n"); + fclose(passwd); + } + + FILE *hostname = fopen(INITRAMFS_ROOTFS "/etc/hostname", "w"); + if (hostname) { + fprintf(hostname, "cervus"); + fclose(hostname); + } + + FILE *motd = fopen(INITRAMFS_ROOTFS "/etc/motd", "w"); + if (motd) { + fprintf(motd, + "\n" + " $$$$$$\\ \n" + " $$ __$$\\ \n" + " $$ / \\__| $$$$$$\\ $$$$$$\\ $$\\ $$\\ $$\\ $$\\ $$$$$$$\\ \n" + " $$ | $$ __$$\\ $$ __$$\\\\$$\\ $$ |$$ | $$ |$$ _____|\n" + " $$ | $$$$$$$$ |$$ | \\__|\\$$\\$$ / $$ | $$ |\\$$$$$$\\ \n" + " $$ | $$\\ $$ ____|$$ | \\$$$ / $$ | $$ | \\____$$\\ \n" + " \\$$$$$$ |\\$$$$$$$\\ $$ | \\$ / \\$$$$$$ |$$$$$$$ |\n" + " \\______/ \\______||\\__| \\_/ \\______/ \\_______/\n" + "\n" + " Cervus OS " VERSION " (Alpha release)\n" + "\n" + " Type 'help' to see available commands.\n" + "\n" + " Your home directory is /mnt/home (persistent ext2 disk).\n" + " Files created in /mnt/ will survive reboots.\n" + " Files outside /mnt/ are in RAM and will be lost on reboot.\n" + "\n"); + fclose(motd); + } + + if (file_exists(SHELL_ELF)) { + if (cmd_run(false, "cp %s %s/bin/init", SHELL_ELF, INITRAMFS_ROOTFS) != 0) { + print_color(COLOR_RED, "[initramfs] Failed to copy shell.elf -> bin/init"); + return false; + } + cmd_run(false, "cp %s %s/bin/shell", SHELL_ELF, INITRAMFS_ROOTFS); + print_color(COLOR_GREEN, "[initramfs] shell.elf -> /bin/init + /bin/shell"); + } else { + print_color(COLOR_RED, "[initramfs] shell.elf not found - boot will drop to nothing!"); + FILE *stub = fopen(INITRAMFS_ROOTFS "/bin/.keep", "w"); + if (stub) fclose(stub); + } + + scan_bin_apps(); + print_color(COLOR_CYAN, "[initramfs] Copying %d bin program(s) -> rootfs/bin/", g_nbin); + for (int i = 0; i < g_nbin; i++) { + if (!file_exists(g_bin_apps[i].elf)) { + print_color(COLOR_YELLOW, "[initramfs] %s not built, skipping", g_bin_apps[i].src); + continue; + } + char dst[512]; + snprintf(dst, sizeof(dst), "%s/bin/%s", INITRAMFS_ROOTFS, g_bin_apps[i].name); + if (cmd_run(false, "cp %s %s", g_bin_apps[i].elf, dst) != 0) { + print_color(COLOR_RED, "[initramfs] Failed to copy %s", g_bin_apps[i].elf); + } else { + print_color(COLOR_GREEN, "[initramfs] %s -> rootfs/bin/%s", + g_bin_apps[i].elf, g_bin_apps[i].name); + } + } + + ensure_dir(INITRAMFS_ROOTFS "/apps"); + scan_apps(); + for (int i = 0; i < g_naps; i++) { + if (strcmp(g_apps[i].name, "shell") == 0) continue; + if (!file_exists(g_apps[i].elf)) continue; + char dst[512]; + snprintf(dst, sizeof(dst), "%s/apps/%s", INITRAMFS_ROOTFS, g_apps[i].name); + if (cmd_run(false, "cp %s %s", g_apps[i].elf, dst) != 0) { + print_color(COLOR_RED, "[initramfs] Failed to copy %s", g_apps[i].elf); + } else { + print_color(COLOR_GREEN, "[initramfs] %s -> rootfs/apps/%s", + g_apps[i].elf, g_apps[i].name); + } + } + + if (file_exists(INSTALLER_ELF)) { + char dst[512]; + snprintf(dst, sizeof(dst), "%s/bin/install-on-disk", INITRAMFS_ROOTFS); + if (cmd_run(false, "cp %s %s", INSTALLER_ELF, dst) == 0) + print_color(COLOR_GREEN, "[initramfs] %s -> rootfs/bin/install-on-disk", + INSTALLER_ELF); + else + print_color(COLOR_RED, "[initramfs] failed to copy installer"); + } + + if (file_exists(SYSROOT_DIR "/usr")) { + print_color(COLOR_CYAN, "[initramfs] Copying sysroot -> rootfs/usr..."); + ensure_dir(INITRAMFS_ROOTFS "/usr"); + if (cmd_run(false, "cp -r " SYSROOT_DIR "/usr/. %s/usr/", + INITRAMFS_ROOTFS) != 0) { + print_color(COLOR_RED, "[initramfs] Failed to copy sysroot"); + return false; + } + print_color(COLOR_GREEN, "[initramfs] sysroot installed into rootfs/usr/"); + } else { + print_color(COLOR_YELLOW, + "[initramfs] " SYSROOT_DIR "/usr not found - skipping sysroot " + "(did libcervus.a build fail?)"); + } + + FILE *readme = fopen(INITRAMFS_ROOTFS "/home/readme.txt", "w"); + if (readme) { + fprintf(readme, + "Cervus OS v0.0.2\n" + "================\n" + "\n" + "This is Cervus - an x86_64 OS written in C.\n" + "Bootloader: Limine | Filesystem: ext2\n" + "\n" + "Built-in shell commands:\n" + " help, cd, exit\n" + "\n" + "Programs in /bin (always available):\n" + " ls, cat, echo, pwd, clear, uname, meminfo, cpuinfo\n" + "\n" + "Programs in /apps (run from /apps dir):\n" + " hello, calc, cal, date, ps, uptime, find, stat,\n" + " hexdump, kill, wc, yes, sleep, ...\n" + "\n" + "Persistent storage:\n" + " /mnt/home - user home directory (ext2)\n" + " /mnt/usr - sysroot for libraries and headers\n" + " /mnt/tmp - temporary files (persistent)\n" + "\n" + "Source: https://github.com/VeoQeo/Cervus\n" + ); + fclose(readme); + print_color(COLOR_GREEN, "[initramfs] created /home/readme.txt"); + } + + FILE *welcome = fopen(INITRAMFS_ROOTFS "/home/welcome.txt", "w"); + if (welcome) { + fprintf(welcome, + "Welcome to Cervus Shell!\n" + "\n" + "Tips:\n" + " - Use arrow keys to move cursor within a command\n" + " - Use Up/Down to browse command history (max 20)\n" + " - Type 'ls' to list files, 'cat ' to read .txt files\n" + " - Binaries are in /bin and /apps\n" + ); + fclose(welcome); + print_color(COLOR_GREEN, "[initramfs] created /home/welcome.txt"); + } + + ensure_dir(INITRAMFS_ROOTFS "/boot"); + + struct { const char *src; const char *dst; bool required; } boot_items[] = { + { "bin/kernel", INITRAMFS_ROOTFS "/boot/kernel", true }, + { SHELL_ELF, INITRAMFS_ROOTFS "/boot/shell.elf", true }, + { "limine/limine-bios.sys", INITRAMFS_ROOTFS "/boot/limine-bios.sys", false }, + { "limine/limine-bios-hdd.bin", INITRAMFS_ROOTFS "/boot/limine-bios-hdd.bin", false }, + { "limine/BOOTX64.EFI", INITRAMFS_ROOTFS "/boot/BOOTX64.EFI", false }, + { "limine/BOOTIA32.EFI", INITRAMFS_ROOTFS "/boot/BOOTIA32.EFI", false }, + { WALLPAPER_SRC, INITRAMFS_ROOTFS "/boot/wallpaper.png", false }, + { NULL, NULL, false } + }; + for (int i = 0; boot_items[i].src; i++) { + if (!file_exists(boot_items[i].src)) { + if (boot_items[i].required) { + print_color(COLOR_RED, "[initramfs] missing required boot file: %s", boot_items[i].src); + return false; + } + print_color(COLOR_YELLOW, "[initramfs] skip (not built yet): %s", boot_items[i].src); + continue; + } + if (cmd_run(false, "cp %s %s", boot_items[i].src, boot_items[i].dst) != 0) { + print_color(COLOR_RED, "[initramfs] failed to copy %s", boot_items[i].src); + if (boot_items[i].required) return false; + } else { + print_color(COLOR_GREEN, "[initramfs] %s -> %s", + boot_items[i].src, boot_items[i].dst); + } + } + + print_color(COLOR_CYAN, "[initramfs] Packing %s...", INITRAMFS_TAR); + if (cmd_run(false, + "tar --format=ustar -cf %s -C %s .", + INITRAMFS_TAR, INITRAMFS_ROOTFS) != 0) { + print_color(COLOR_RED, "[initramfs] tar failed"); + return false; + } + + print_color(COLOR_GREEN, "[initramfs] Contents of %s:", INITRAMFS_TAR); + cmd_run(false, "tar -tvf %s", INITRAMFS_TAR); + + print_color(COLOR_GREEN, "[initramfs] %s built successfully", INITRAMFS_TAR); + return true; +} + +typedef struct { + char **paths; + int count; + int capacity; +} FileList; + +void file_list_add(FileList *list, const char *path) { + if (list->count >= list->capacity) { + list->capacity = list->capacity == 0 ? 16 : list->capacity * 2; + list->paths = realloc(list->paths, list->capacity * sizeof(char *)); + } + list->paths[list->count++] = strdup(path); +} + +void find_src_files(const char *root_dir, FileList *list) { + DIR *dir; + struct dirent *entry; + if (!(dir = opendir(root_dir))) return; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_DIR) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || + strcmp(entry->d_name, ".git") == 0) continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", root_dir, entry->d_name); + find_src_files(path, list); + } else { + const char *ext = strrchr(entry->d_name, '.'); + if (ext && (strcmp(ext, ".c") == 0 || strcmp(ext, ".asm") == 0 || + strcmp(ext, ".S") == 0 || strcmp(ext, ".psf") == 0)) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", root_dir, entry->d_name); + file_list_add(list, path); + } + } + } + closedir(dir); +} + +bool is_sse_file(const char *filename) { + for (int i = 0; SSE_FILES[i] != NULL; i++) + if (strstr(filename, SSE_FILES[i]) != NULL) return true; + return false; +} + +bool compile_kernel(void) { + print_color(COLOR_GREEN, "Compiling kernel..."); + ensure_linker_script(); + if (!setup_dependencies()) return false; + + if (!build_libcervus()) return false; + if (!build_tcc()) return false; + + if (!build_all_apps()) return false; + if (!build_all_bin_apps()) return false; + + if (!build_installer()) return false; + + ensure_dir("bin"); + ensure_dir("obj/kernel"); + ensure_dir("obj/libc"); + + const char *base_cflags = + "-g -O2 -pipe -Wall -Wextra -std=gnu11 -nostdinc -ffreestanding " + "-fno-stack-protector -fno-stack-check -fno-lto -fno-PIC " + "-ffunction-sections -fdata-sections " + "-m64 -march=x86-64 -mabi=sysv -mcmodel=kernel " + "-mno-red-zone -mgeneral-regs-only -fcf-protection=none"; + + const char *sse_base_cflags = + "-g -O2 -pipe -Wall -Wextra -std=gnu11 -nostdinc -ffreestanding " + "-fno-stack-protector -fno-stack-check -fno-lto -fno-PIC " + "-ffunction-sections -fdata-sections " + "-m64 -march=x86-64 -mabi=sysv -mcmodel=kernel " + "-mno-red-zone -fcf-protection=none"; + + const char *core_cflags = "-mno-sse -mno-sse2 -mno-mmx -mno-3dnow"; + const char *sse_cflags = "-msse -msse2 -mfpmath=sse -mno-mmx -mno-3dnow"; + + char cppflags[2048]; + snprintf(cppflags, sizeof(cppflags), + "-I kernel/src" + " -I libc/include" + " -I limine-tools/limine-protocol/include" + " -isystem limine-tools/freestnd-c-hdrs/include" + " -MMD -MP"); + + FileList sources = {0}; + if (file_exists("kernel/src")) find_src_files("kernel/src", &sources); + if (file_exists("libc/src")) find_src_files("libc/src", &sources); + + FileList objects = {0}; + bool failed = false; + + for (int i = 0; i < sources.count; i++) { + char *src = sources.paths[i]; + + char category[16] = "other"; + if (strncmp(src, "kernel/", 7) == 0) strcpy(category, "kernel"); + else if (strncmp(src, "libc/", 5) == 0) strcpy(category, "libc"); + + char obj_path[PATH_MAX]; + char flat[PATH_MAX]; + strcpy(flat, src); + for (int j = 0; flat[j]; j++) + if (flat[j] == '/' || flat[j] == '.') flat[j] = '_'; + snprintf(obj_path, sizeof(obj_path), "obj/%s/%s.o", category, flat); + file_list_add(&objects, obj_path); + + if (file_exists(obj_path) && get_mtime(src) <= get_mtime(obj_path) + && get_mtime("builder/build.c") <= get_mtime(obj_path)) continue; + + const char *ext = strrchr(src, '.'); + + if (strcmp(ext, ".psf") == 0) { + print_color(COLOR_BLUE, "Converting binary: %s", src); + char tmp[PATH_MAX]; + snprintf(tmp, sizeof(tmp), "temp_%s", strrchr(src, '/') + 1); + cmd_run(false, "cp %s %s", src, tmp); + cmd_run(false, "objcopy -I binary -O elf64-x86-64 -B i386:x86-64 " + "--rename-section .data=.rodata,alloc,load,readonly,data,contents " + "%s %s", tmp, obj_path); + remove(tmp); + char stem[256]; + strcpy(stem, strrchr(src, '/') + 1); + *strrchr(stem, '.') = '\0'; + char rsym[1024]; + snprintf(rsym, sizeof(rsym), + "--redefine-sym _binary_temp_%s_psf_start=_binary_%s_psf_start " + "--redefine-sym _binary_temp_%s_psf_end=_binary_%s_psf_end " + "--redefine-sym _binary_temp_%s_psf_size=_binary_%s_psf_size", + stem, stem, stem, stem, stem, stem); + cmd_run(false, "objcopy %s %s", rsym, obj_path); + + } else if (strcmp(ext, ".asm") == 0) { + print_color(COLOR_CYAN, "[asm] %s", src); + if (cmd_run(false, "nasm -g -F dwarf -f elf64 %s -o %s", src, obj_path) != 0) + failed = true; + + } else { + bool sse = is_sse_file(src); + char flags[1024]; + snprintf(flags, sizeof(flags), "%s %s", + sse ? sse_base_cflags : base_cflags, + sse ? sse_cflags : core_cflags); + print_color(sse ? COLOR_MAGENTA : COLOR_CYAN, "[%s] %s", category, src); + if (cmd_run(false, "gcc %s %s -c %s -o %s", + flags, cppflags, src, obj_path) != 0) + failed = true; + } + } + + if (failed) return false; + + print_color(COLOR_BLUE, "Linking kernel..."); + char ld_cmd[65536]; + snprintf(ld_cmd, sizeof(ld_cmd), + "ld -m elf_x86_64 -nostdlib -static -z max-page-size=0x1000 " + "--gc-sections -T kernel/linker-scripts/x86_64.lds -o bin/kernel"); + for (int i = 0; i < objects.count; i++) { + strcat(ld_cmd, " "); + strcat(ld_cmd, objects.paths[i]); + } + if (system(ld_cmd) != 0) return false; + + print_color(COLOR_GREEN, "Kernel linked: bin/kernel"); + return true; +} + +bool create_iso(void) { + print_color(COLOR_GREEN, "Creating ISO..."); + if (!build_limine()) return false; + if (!file_exists("bin/kernel")) { + print_color(COLOR_RED, "bin/kernel not found!"); + return false; + } + + rm_rf("iso_root"); + ensure_dir("iso_root/boot/limine"); + ensure_dir("iso_root/boot/wallpapers"); + ensure_dir("iso_root/EFI/BOOT"); + ensure_dir("demo_iso"); + + if (file_exists(WALLPAPER_SRC)) { + cmd_run(false, "cp %s iso_root/boot/wallpapers/cervus.png", WALLPAPER_SRC); + print_color(COLOR_GREEN, "Wallpaper copied"); + } else { + print_color(COLOR_YELLOW, "Warning: wallpaper not found at %s", WALLPAPER_SRC); + } + + cmd_run(false, "cp bin/kernel iso_root/boot/kernel"); + + bool has_elf = file_exists(SHELL_ELF); + if (has_elf) { + cmd_run(false, "cp %s iso_root/boot/shell.elf", SHELL_ELF); + print_color(COLOR_GREEN, "[module 0] shell.elf -> iso_root/boot/shell.elf"); + } else { + print_color(COLOR_RED, "[module 0] shell.elf not found - boot will fail!"); + } + + bool has_initramfs = !ARG_NO_INITRAMFS && file_exists(INITRAMFS_TAR); + if (has_initramfs) { + cmd_run(false, "cp %s iso_root/boot/initramfs.tar", INITRAMFS_TAR); + print_color(COLOR_GREEN, "[module 1] initramfs.tar -> iso_root/boot/initramfs.tar"); + } else { + print_color(COLOR_YELLOW, "[module 1] initramfs.tar not found - booting without rootfs"); + } + + FILE *f = fopen("limine.conf", "w"); + if (!f) { print_color(COLOR_RED, "Cannot create limine.conf"); return false; } + + if (file_exists(WALLPAPER_SRC)) + fprintf(f, "wallpaper: %s\n", WALLPAPER_DST); + + fprintf(f, "timeout: 5\n\n"); + + fprintf(f, + "/%s %s (Install / Live)\n" + " protocol: limine\n" + " path: boot():/boot/kernel\n", + IMAGE_NAME, VERSION); + + if (has_elf) { + fprintf(f, + " module_path: boot():/boot/shell.elf\n" + " module_cmdline: init\n"); + } + if (has_initramfs) { + fprintf(f, + " module_path: boot():/boot/initramfs.tar\n" + " module_cmdline: initramfs\n"); + } + + fclose(f); + + print_color(COLOR_CYAN, "--- limine.conf ---"); + cmd_run(false, "cat limine.conf"); + print_color(COLOR_CYAN, "-------------------"); + + cmd_run(false, "cp limine.conf iso_root/boot/limine/"); + cmd_run(false, "cp limine/limine-bios.sys limine/limine-bios-cd.bin " + "limine/limine-uefi-cd.bin iso_root/boot/limine/"); + cmd_run(false, "cp limine/BOOTX64.EFI limine/BOOTIA32.EFI iso_root/EFI/BOOT/"); + + char timestamp[64]; + time_t t = time(NULL); + strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", localtime(&t)); + + char iso_name[PATH_MAX]; + snprintf(iso_name, sizeof(iso_name), + "demo_iso/%s.%s.%s.iso", IMAGE_NAME, VERSION, timestamp); + + char xorriso[PATH_MAX + 1024]; + snprintf(xorriso, sizeof(xorriso), + "xorriso -as mkisofs -R -r -J" + " -b boot/limine/limine-bios-cd.bin" + " -no-emul-boot -boot-load-size 4 -boot-info-table" + " -hfsplus -apm-block-size 2048" + " --efi-boot boot/limine/limine-uefi-cd.bin" + " -efi-boot-part --efi-boot-image --protective-msdos-label" + " iso_root -o %s", iso_name); + + if (cmd_run(true, xorriso) != 0) return false; + cmd_run(true, "./limine/limine bios-install %s", iso_name); + + char link[PATH_MAX]; + snprintf(link, sizeof(link), "demo_iso/%s.latest.iso", IMAGE_NAME); + unlink(link); + (void)!symlink(strrchr(iso_name, '/') + 1, link); + + rm_rf("iso_root"); + + print_color(COLOR_GREEN, "ISO ready: %s", iso_name); + return true; +} + +void check_sudo(void) { + if (geteuid() != 0) { + print_color(COLOR_RED, "This command requires root. Run with sudo."); + exit(1); + } +} + +void list_iso_files(FileList *list) { + DIR *dir = opendir("demo_iso"); + if (!dir) return; + struct dirent *e; + while ((e = readdir(dir)) != NULL) { + if (strstr(e->d_name, ".iso")) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "demo_iso/%s", e->d_name); + file_list_add(list, path); + } + } + closedir(dir); +} + +void list_usb_devices(FileList *list) { + DIR *dir = opendir("/sys/block"); + if (!dir) return; + struct dirent *e; + while ((e = readdir(dir)) != NULL) { + if (e->d_name[0] == '.') continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/sys/block/%s/removable", e->d_name); + FILE *f = fopen(path, "r"); + if (f) { + int removable = 0; + if (fscanf(f, "%d", &removable) == 1 && removable) { + char dev[PATH_MAX]; + snprintf(dev, sizeof(dev), "/dev/%s", e->d_name); + file_list_add(list, dev); + } + fclose(f); + } + } + closedir(dir); +} + +void flash_iso(void) { + check_sudo(); + FileList isos = {0}, devs = {0}; + list_iso_files(&isos); + if (isos.count == 0) { print_color(COLOR_RED, "No ISO found in demo_iso/"); return; } + + printf("\n%s--- SELECT ISO ---%s\n", COLOR_CYAN, COLOR_RESET); + for (int i = 0; i < isos.count; i++) printf("[%d] %s\n", i + 1, isos.paths[i]); + int ic; printf("Choice: "); + if (scanf("%d", &ic) < 1 || ic < 1 || ic > isos.count) return; + + list_usb_devices(&devs); + if (devs.count == 0) { print_color(COLOR_RED, "No removable USB devices found!"); return; } + + printf("\n%s--- SELECT DEVICE ---%s\n", COLOR_RED, COLOR_RESET); + for (int i = 0; i < devs.count; i++) { + char mpath[PATH_MAX], model[256] = "Unknown"; + snprintf(mpath, sizeof(mpath), "/sys/block/%s/device/model", devs.paths[i] + 5); + FILE *mf = fopen(mpath, "r"); + if (mf) { if (fgets(model, sizeof(model), mf)) model[strcspn(model, "\n")] = 0; fclose(mf); } + printf("[%d] %s (%s)\n", i + 1, devs.paths[i], model); + } + int dc; printf("Choice: "); + if (scanf("%d", &dc) < 1 || dc < 1 || dc > devs.count) return; + + printf("\n%sWARNING: ALL DATA ON %s WILL BE ERASED!%s\n", + COLOR_BOLD, devs.paths[dc - 1], COLOR_RESET); + printf("Type 'YES' to confirm: "); + char confirm[10]; + if (scanf("%9s", confirm) != 1) { printf("Aborted.\n"); return; } + if (strcmp(confirm, "YES") != 0) { printf("Aborted.\n"); return; } + + print_color(COLOR_YELLOW, "Flashing %s -> %s ...", isos.paths[ic - 1], devs.paths[dc - 1]); + cmd_run(true, "dd if=%s of=%s bs=4M status=progress oflag=sync", + isos.paths[ic - 1], devs.paths[dc - 1]); + print_color(COLOR_GREEN, "Done!"); +} + +bool is_unreadable_file(const char *filename) { + const char *skip_names[] = { "TODO", "LICENSE", "build", NULL }; + for (int i = 0; skip_names[i]; i++) + if (strcmp(filename, skip_names[i]) == 0) return true; + + const char *ext = strrchr(filename, '.'); + if (!ext) return false; + + const char *bin_exts[] = { + ".psf", ".jpg", ".png", ".jpeg", ".iso", ".hdd", ".img", + ".bin", ".elf", ".o", ".txt", ".md", ".gitignore", ".json", + ".tar", + NULL + }; + for (int i = 0; bin_exts[i]; i++) + if (strcasecmp(ext, bin_exts[i]) == 0) return true; + return false; +} + +void generate_tree_recursive(const char *base_dir, FILE *out, int level) { + struct dirent **namelist; + int n = scandir(base_dir, &namelist, NULL, alphasort); + if (n < 0) return; + + const char *skip_dirs[] = { + "wallpapers", "demo_iso", ".vscode", "builder", + INITRAMFS_ROOTFS, + NULL + }; + + for (int i = 0; i < n; i++) { + const char *name = namelist[i]->d_name; + + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || + strcmp(name, ".git") == 0 || strcmp(name, "obj") == 0 || + strcmp(name, "bin") == 0 || strcmp(name, "limine") == 0) { + free(namelist[i]); continue; + } + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", base_dir, name); + struct stat st; + stat(path, &st); + + if (S_ISDIR(st.st_mode)) { + bool skip = false; + for (int j = 0; skip_dirs[j]; j++) + if (strcmp(name, skip_dirs[j]) == 0) { skip = true; break; } + if (skip) { free(namelist[i]); continue; } + } + + if (!S_ISDIR(st.st_mode) && is_unreadable_file(name)) { + free(namelist[i]); continue; + } + + for (int j = 0; j < level; j++) fprintf(out, "| "); + + if (S_ISDIR(st.st_mode)) { + fprintf(out, "+-- %s/\n", name); + generate_tree_recursive(path, out, level + 1); + } else { + fprintf(out, "+-- %s (%ld bytes)\n", name, st.st_size); + if (!ARG_STRUCTURE_ONLY && should_print_content(name)) { + FILE *src = fopen(path, "r"); + if (src) { + char line[4096]; int ln = 1; + while (fgets(line, sizeof(line), src) && ln <= 5000) { + for (int j = 0; j <= level; j++) fprintf(out, "| "); + fprintf(out, "| %4d: %s", ln++, line); + } + fclose(src); + } + } + } + free(namelist[i]); + } + free(namelist); +} + +void do_generate_tree(void) { + FILE *f = fopen("OS-TREE.txt", "w"); + if (!f) return; + time_t t = time(NULL); + fprintf(f, "OS Tree - %s %s | %s", IMAGE_NAME, VERSION, ctime(&t)); + generate_tree_recursive(".", f, 0); + fclose(f); + print_color(COLOR_GREEN, "Tree generated to OS-TREE.txt"); +} + +static bool limine_conf_has_cervus(const char *path) { + FILE *f = fopen(path, "r"); + if (!f) return false; + char line[1024]; + while (fgets(line, sizeof(line), f)) { + if (strstr(line, CERVUS_MARKER)) { fclose(f); return true; } + } + fclose(f); + return false; +} + +static bool __attribute__((unused)) copy_file_raw(const char *src, const char *dst) { + FILE *in = fopen(src, "r"); + if (!in) return false; + FILE *out = fopen(dst, "w"); + if (!out) { fclose(in); return false; } + char buf[4096]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), in)) > 0) + fwrite(buf, 1, n, out); + fclose(in); + fclose(out); + return true; +} + +static bool ask_yes_no(const char *question) { + printf("%s%s [y/n]: %s", COLOR_YELLOW, question, COLOR_RESET); + fflush(stdout); + char buf[16]; + if (!fgets(buf, sizeof(buf), stdin)) return false; + return (buf[0] == 'y' || buf[0] == 'Y'); +} + +void reset_hardware_conf(void) { + check_sudo(); + + if (!file_exists(LIMINE_CONF_BACKUP)) { + print_color(COLOR_RED, "No backup found at %s", LIMINE_CONF_BACKUP); + print_color(COLOR_RED, "Nothing to restore. Was hardwaretest ever run?"); + return; + } + + print_color(COLOR_CYAN, "Restoring original Limine configuration..."); + + if (cmd_run(true, "cp %s %s", LIMINE_CONF_BACKUP, LIMINE_CONF_PATH) != 0) { + print_color(COLOR_RED, "Failed to restore %s", LIMINE_CONF_PATH); + return; + } + + print_color(COLOR_GREEN, "Original limine.conf restored from backup"); + + if (file_exists(CERVUS_BOOT_DIR)) { + print_color(COLOR_CYAN, "Removing %s ...", CERVUS_BOOT_DIR); + cmd_run(true, "rm -rf %s", CERVUS_BOOT_DIR); + } + + print_color(COLOR_GREEN, "Hardware configuration reset complete"); + print_color(COLOR_CYAN, "--- Current limine.conf ---"); + cmd_run(false, "cat %s", LIMINE_CONF_PATH); + print_color(COLOR_CYAN, "---------------------------"); + + if (ask_yes_no("Reboot now?")) { + print_color(COLOR_YELLOW, "Rebooting..."); + cmd_run(false, "reboot"); + } +} + +static void write_cervus_entries(FILE *f, bool has_elf, bool has_initramfs) { + if (file_exists(WALLPAPER_SRC)) + fprintf(f, "wallpaper: boot():/cervus/wallpaper.png\n"); + + fprintf(f, + "/%s %s (Install / Live)\n" + " protocol: limine\n" + " path: boot():/cervus/kernel\n", + IMAGE_NAME, VERSION); + if (has_elf) { + fprintf(f, + " module_path: boot():/cervus/shell.elf\n" + " module_cmdline: init\n"); + } + if (has_initramfs) { + fprintf(f, + " module_path: boot():/cervus/initramfs.tar\n" + " module_cmdline: initramfs\n"); + } + + fprintf(f, + "/%s %s (Installed - boot from disk)\n" + " protocol: limine\n" + " path: boot():/cervus/kernel\n", + IMAGE_NAME, VERSION); + if (has_elf) { + fprintf(f, + " module_path: boot():/cervus/shell.elf\n" + " module_cmdline: init\n"); + } +} + +void hardware_test(void) { + check_sudo(); + + if (!file_exists(LIMINE_CONF_PATH)) { + print_color(COLOR_RED, "Limine config not found at %s", LIMINE_CONF_PATH); + print_color(COLOR_RED, "Is Limine installed as your bootloader?"); + print_color(COLOR_YELLOW, "Expected path: %s", LIMINE_CONF_PATH); + print_color(COLOR_YELLOW, "Install Limine first: https://github.com/limine-bootloader/limine"); + return; + } + + print_color(COLOR_GREEN, "=== Cervus Hardware Test Setup ==="); + print_color(COLOR_CYAN, "Limine config found: %s", LIMINE_CONF_PATH); + + print_color(COLOR_CYAN, "Step 1: Building Cervus..."); + + if (!compile_kernel()) { + print_color(COLOR_RED, "Kernel compilation failed"); + return; + } + + if (!build_initramfs()) { + print_color(COLOR_RED, "initramfs build failed"); + return; + } + + if (!file_exists("bin/kernel")) { + print_color(COLOR_RED, "bin/kernel not found after build!"); + return; + } + + print_color(COLOR_CYAN, "Step 2: Checking Limine configuration..."); + + bool already_has_cervus = limine_conf_has_cervus(LIMINE_CONF_PATH); + + if (already_has_cervus) { + print_color(COLOR_GREEN, "Cervus entry already present in limine.conf"); + print_color(COLOR_CYAN, "Updating boot files only..."); + } else { + print_color(COLOR_CYAN, "No Cervus entry found, will add one"); + + if (!file_exists(LIMINE_CONF_BACKUP)) { + print_color(COLOR_YELLOW, "Step 2a: Backing up original limine.conf..."); + if (cmd_run(true, "cp %s %s", LIMINE_CONF_PATH, LIMINE_CONF_BACKUP) != 0) { + print_color(COLOR_RED, "Failed to backup limine.conf!"); + return; + } + print_color(COLOR_GREEN, "Backup saved: %s", LIMINE_CONF_BACKUP); + print_color(COLOR_YELLOW, "To restore later: ./build --resethardwareconf"); + } else { + print_color(COLOR_GREEN, "Backup already exists: %s", LIMINE_CONF_BACKUP); + } + } + + print_color(COLOR_CYAN, "Step 3: Copying Cervus files to /boot..."); + + cmd_run(true, "mkdir -p %s", CERVUS_BOOT_DIR); + + if (cmd_run(true, "cp bin/kernel %s/kernel", CERVUS_BOOT_DIR) != 0) { + print_color(COLOR_RED, "Failed to copy kernel"); + return; + } + print_color(COLOR_GREEN, " kernel -> %s/kernel", CERVUS_BOOT_DIR); + + bool has_elf = file_exists(SHELL_ELF); + if (has_elf) { + cmd_run(true, "cp %s %s/shell.elf", SHELL_ELF, CERVUS_BOOT_DIR); + print_color(COLOR_GREEN, " shell.elf -> %s/shell.elf", CERVUS_BOOT_DIR); + } + + bool has_initramfs = file_exists(INITRAMFS_TAR); + if (has_initramfs) { + cmd_run(true, "cp %s %s/initramfs.tar", INITRAMFS_TAR, CERVUS_BOOT_DIR); + print_color(COLOR_GREEN, " initramfs.tar -> %s/initramfs.tar", CERVUS_BOOT_DIR); + } + + if (file_exists(WALLPAPER_SRC)) { + cmd_run(true, "cp %s %s/wallpaper.png", WALLPAPER_SRC, CERVUS_BOOT_DIR); + print_color(COLOR_GREEN, " wallpaper -> %s/wallpaper.png", CERVUS_BOOT_DIR); + } + + if (!already_has_cervus) { + print_color(COLOR_CYAN, "Step 4: Adding Cervus entries to limine.conf..."); + + FILE *f = fopen(LIMINE_CONF_PATH, "a"); + if (!f) { + print_color(COLOR_RED, "Cannot open %s for writing", LIMINE_CONF_PATH); + return; + } + + fprintf(f, "\n%s\n", CERVUS_MARKER); + write_cervus_entries(f, has_elf, has_initramfs); + fprintf(f, "%s\n", "# --- END CERVUS ---"); + + fclose(f); + print_color(COLOR_GREEN, "Cervus boot entries added to limine.conf"); + } else { + print_color(COLOR_CYAN, "Step 4: Updating Cervus entries in limine.conf..."); + + FILE *orig = fopen(LIMINE_CONF_PATH, "r"); + if (!orig) { + print_color(COLOR_RED, "Cannot read %s", LIMINE_CONF_PATH); + return; + } + + char tmppath[PATH_MAX]; + snprintf(tmppath, sizeof(tmppath), "%s.tmp", LIMINE_CONF_PATH); + FILE *tmp = fopen(tmppath, "w"); + if (!tmp) { fclose(orig); print_color(COLOR_RED, "Cannot create temp file"); return; } + + char line[1024]; + bool in_cervus = false; + while (fgets(line, sizeof(line), orig)) { + if (strstr(line, CERVUS_MARKER)) { + in_cervus = true; + continue; + } + if (in_cervus && strstr(line, "# --- END CERVUS ---")) { + in_cervus = false; + continue; + } + if (!in_cervus) { + fputs(line, tmp); + } + } + fclose(orig); + + fprintf(tmp, "\n%s\n", CERVUS_MARKER); + write_cervus_entries(tmp, has_elf, has_initramfs); + fprintf(tmp, "%s\n", "# --- END CERVUS ---"); + fclose(tmp); + + cmd_run(false, "mv %s %s", tmppath, LIMINE_CONF_PATH); + print_color(COLOR_GREEN, "Cervus entries updated in limine.conf"); + } + + print_color(COLOR_GREEN, "\n=== Setup Complete ==="); + print_color(COLOR_CYAN, "--- Current limine.conf ---"); + cmd_run(false, "cat %s", LIMINE_CONF_PATH); + print_color(COLOR_CYAN, "---------------------------"); + + print_color(COLOR_GREEN, "\nCervus will appear in your Limine boot menu."); + print_color(COLOR_YELLOW, "First boot: select '%s %s (Install / Live)'", IMAGE_NAME, VERSION); + print_color(COLOR_YELLOW, "After install: select '%s %s (Installed - boot from disk)'", IMAGE_NAME, VERSION); + print_color(COLOR_YELLOW, "To restore original config: sudo ./build --resethardwareconf"); + + if (ask_yes_no("\nReboot now to test on real hardware?")) { + print_color(COLOR_YELLOW, "Rebooting..."); + cmd_run(false, "reboot"); + } else { + print_color(COLOR_GREEN, "OK. Reboot manually when ready."); + } +} + +static const char* find_ovmf(void) { + const char *ovmf_paths[] = { + "/usr/share/edk2/x64/OVMF.4m.fd", + "/usr/share/edk2/x64/OVMF_CODE.4m.fd", + "/usr/share/edk2/ovmf/OVMF.fd", + "/usr/share/edk2/ovmf/OVMF_CODE.fd", + "/usr/share/ovmf/x64/OVMF.fd", + "/usr/share/ovmf/x64/OVMF_CODE.fd", + "/usr/share/ovmf/OVMF.fd", + "/usr/share/OVMF/OVMF.fd", + "/usr/share/OVMF/OVMF_CODE.fd", + "/usr/share/qemu/OVMF.fd", + NULL + }; + for (int i = 0; ovmf_paths[i]; i++) { + if (file_exists(ovmf_paths[i])) return ovmf_paths[i]; + } + return NULL; +} + +static void print_help(void) { + printf("%sCervus OS build system%s\n\n", COLOR_BOLD, COLOR_RESET); + printf("Usage: ./build [command] [options]\n\n"); + printf("Commands:\n"); + printf(" run Build kernel + initramfs + ISO, then launch QEMU (BIOS, with disk)\n"); + printf(" run-uefi Build kernel + initramfs + ISO, then launch QEMU (UEFI, with disk)\n"); + printf(" run-installed Launch QEMU from cervus_disk.img only (BIOS, no ISO, real hardware sim)\n"); + printf(" run-installed-uefi Launch QEMU from cervus_disk.img only (UEFI, no ISO)\n"); + printf(" run-fresh Wipe disk and run installer from ISO again (BIOS)\n"); + printf(" run-fresh-uefi Wipe disk and run installer from ISO again (UEFI)\n"); + printf(" hardwaretest Build & install Cervus into Limine boot menu (requires sudo)\n"); + printf(" flash Flash latest ISO to USB device (requires sudo)\n"); + printf(" clean Remove all build artifacts\n"); + printf(" cleaniso Remove only ISO images in demo_iso/\n"); + printf(" gitclean Same as clean (for git commit prep)\n"); + printf(" help Show this message\n\n"); + printf("Subcomponent build (debug):\n"); + printf(" _libcervus Build only libcervus.a + crt0.o into sysroot\n"); + printf(" _tcc Build only TCC (and libcervus first) into sysroot\n\n"); + printf("Options:\n"); + printf(" --tree [files] Generate OS-TREE.txt (optional: only list files)\n"); + printf(" --structure-only Generate tree without file contents\n"); + printf(" --no-clean Keep obj/ and bin/ after run\n"); + printf(" --no-initramfs Skip initramfs.tar creation\n"); + printf(" --reset-disk Re-create empty disk image\n"); + printf(" --live Run without disk (Live Mode, BIOS only)\n"); + printf(" --resethardwareconf Restore original Limine config (requires sudo)\n\n"); + printf("Boot entries in Limine menu:\n"); + printf(" Install / Live - first boot, runs installer, uses initramfs\n"); + printf(" Installed - boot from disk - after install, no initramfs, uses ext2 disk\n\n"); + printf("Hardware test workflow:\n"); + printf(" sudo ./build hardwaretest # build, install to /boot, add boot entries\n"); + printf(" sudo ./build --resethardwareconf # restore original bootloader config\n\n"); + printf("UEFI Requirements:\n"); + printf(" Install OVMF: sudo apt install ovmf (Debian/Ubuntu) or equivalent\n\n"); + printf("Examples:\n"); + printf(" ./build run # normal boot with disk (BIOS)\n"); + printf(" ./build run-uefi # normal boot with disk (UEFI)\n"); + printf(" ./build run --live # boot without disk (Live Mode, BIOS)\n"); + printf(" ./build run-fresh-uefi # fresh disk, boot installer (UEFI)\n"); + printf(" ./build run --reset-disk # fresh disk (BIOS)\n"); + printf(" ./build run --tree\n"); + printf(" ./build --tree kernel.c vfs.c\n"); +} + +int main(int argc, char **argv) { + char *command = NULL; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--tree") == 0) { + ARG_TREE = true; + int j = i + 1; + while (j < argc && argv[j][0] != '-') add_tree_file(argv[j++]); + i = j - 1; + } else if (strcmp(argv[i], "--structure-only") == 0) { + ARG_STRUCTURE_ONLY = true; + } else if (strcmp(argv[i], "--no-clean") == 0) { + ARG_NO_CLEAN = true; + } else if (strcmp(argv[i], "--no-initramfs") == 0) { + ARG_NO_INITRAMFS = true; + } else if (strcmp(argv[i], "--resethardwareconf") == 0) { + ARG_RESET_HW_CONF = true; + } else if (strcmp(argv[i], "--reset-disk") == 0) { + ARG_RESET_DISK = true; + } else if (strcmp(argv[i], "--live") == 0) { + ARG_LIVE = true; + } else if (argv[i][0] != '-') { + command = argv[i]; + } + } + + if (ARG_TREE && !command) { do_generate_tree(); return 0; } + + if (ARG_RESET_HW_CONF) { + reset_hardware_conf(); + return 0; + } + + if (!command || strcmp(command, "help") == 0) { + print_help(); + return 0; + } + + if (strcmp(command, "clean") == 0 || strcmp(command, "gitclean") == 0) { + for (int i = 0; DIRS_TO_CLEAN[i]; i++) rm_rf(DIRS_TO_CLEAN[i]); + clean_apps_elfs(); + clean_bin_elfs(); + for (int i = 0; FILES_TO_CLEAN[i]; i++) + if (file_exists(FILES_TO_CLEAN[i])) remove(FILES_TO_CLEAN[i]); + clean_libcervus_build(true); + clean_tcc_build(true); + cmd_run(false, "rm -f " INSTALLER_DIR "/*.elf 2>/dev/null"); + cmd_run(false, "rm -f temp_* 2>/dev/null"); + print_color(COLOR_GREEN, "Cleanup complete"); + return 0; + } + + if (strcmp(command, "cleaniso") == 0) { + rm_rf("demo_iso"); + ensure_dir("demo_iso"); + return 0; + } + + if (strcmp(command, "flash") == 0) { + flash_iso(); + return 0; + } + + if (strcmp(command, "hardwaretest") == 0) { + hardware_test(); + return 0; + } + + if (strcmp(command, "_libcervus") == 0) { + return build_libcervus() ? 0 : 1; + } + if (strcmp(command, "_tcc") == 0) { + if (!build_libcervus()) return 1; + return build_tcc() ? 0 : 1; + } + + if (strcmp(command, "run-installed-uefi") == 0) { + if (!file_exists("cervus_disk.img")) { + print_color(COLOR_RED, "cervus_disk.img not found. Run './build run' first."); + return 1; + } + const char *ovmf = find_ovmf(); + if (!ovmf) { + print_color(COLOR_RED, "OVMF not found. Install: sudo apt install ovmf"); + return 1; + } + print_color(COLOR_GREEN, "Starting QEMU with UEFI firmware from disk only..."); + print_color(COLOR_GREEN, "Using OVMF: %s", ovmf); + cmd_run(false, + "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" + " -bios %s" + " -drive file=cervus_disk.img,format=raw,if=ide" + " -serial stdio" + " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" + " 2>&1 | tee log.txt", + ovmf); + return 0; + } + + if (strcmp(command, "run-installed") == 0) { + if (!file_exists("cervus_disk.img")) { + print_color(COLOR_RED, "cervus_disk.img not found. " + "Run './build run' first to build and install Cervus."); + return 1; + } + print_color(COLOR_CYAN, "Starting QEMU from disk only (no ISO, simulates real hardware)..."); + cmd_run(false, + "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" + " -drive file=cervus_disk.img,format=raw,if=ide" + " -serial stdio" + " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" + " 2>&1 | tee log.txt"); + return 0; + } + + if (strcmp(command, "run-fresh") == 0) { + ARG_RESET_DISK = true; + command = "run"; + } + + if (strcmp(command, "run-fresh-uefi") == 0) { + ARG_RESET_DISK = true; + command = "run-uefi"; + } + + if (strcmp(command, "run") == 0 || strcmp(command, "run-uefi") == 0) { + bool use_uefi = (strcmp(command, "run-uefi") == 0); + + if (!compile_kernel()) { + print_color(COLOR_RED, "Kernel compilation failed"); + return 1; + } + + if (!build_initramfs()) { + print_color(COLOR_RED, "initramfs build failed"); + return 1; + } + + if (!create_iso()) { + print_color(COLOR_RED, "ISO creation failed"); + return 1; + } + + if (ARG_TREE) do_generate_tree(); + + char iso_path[PATH_MAX]; + snprintf(iso_path, sizeof(iso_path), "demo_iso/%s.latest.iso", IMAGE_NAME); + + if (use_uefi) { + const char *ovmf = find_ovmf(); + if (!ovmf) { + print_color(COLOR_RED, "OVMF not found. Install: sudo apt install ovmf"); + return 1; + } + print_color(COLOR_GREEN, "Using OVMF: %s", ovmf); + } + + if (ARG_LIVE && !use_uefi) { + print_color(COLOR_CYAN, "Starting QEMU in Live Mode (no disk, BIOS)..."); + cmd_run(false, + "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" + " -cdrom %s -boot d" + " -serial stdio" + " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" + " 2>&1 | tee log.txt", + iso_path); + } else if (ARG_LIVE && use_uefi) { + print_color(COLOR_RED, "Live mode (--live) is not supported in UEFI mode yet"); + return 1; + } else { + if (!file_exists("cervus_disk.img") || ARG_RESET_DISK) { + if (ARG_RESET_DISK && file_exists("cervus_disk.img")) { + print_color(COLOR_YELLOW, "[disk] --reset-disk: removing old disk image"); + remove("cervus_disk.img"); + } + print_color(COLOR_CYAN, "Creating empty disk image (256MB)..."); + cmd_run(false, "dd if=/dev/zero of=cervus_disk.img bs=1M count=256 2>/dev/null"); + print_color(COLOR_GREEN, "Disk created (OS installer will format on first boot)"); + } else { + print_color(COLOR_GREEN, "Using existing cervus_disk.img (persistent data)"); + } + + if (use_uefi) { + const char *ovmf = find_ovmf(); + print_color(COLOR_GREEN, "Starting QEMU with UEFI firmware (IDE mode)..."); + cmd_run(false, + "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" + " -bios %s" + " -cdrom %s -boot d" + " -serial stdio" + " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" + " -drive file=cervus_disk.img,format=raw,if=ide,index=0,media=disk" + " 2>&1 | tee log.txt", + ovmf, iso_path); + } else { + print_color(COLOR_GREEN, "Starting QEMU with BIOS..."); + cmd_run(false, + "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" + " -cdrom %s -boot d" + " -serial stdio" + " %s" + " 2>&1 | tee log.txt", + iso_path, QEMUFLAGS); + } + } + + if (!ARG_NO_CLEAN) { + print_color(COLOR_CYAN, "[post-run] Cleaning build artifacts..."); + rm_rf("obj"); + rm_rf("bin"); + rm_rf(INITRAMFS_ROOTFS); + if (file_exists(INITRAMFS_TAR)) { + remove(INITRAMFS_TAR); + print_color(COLOR_GREEN, "[post-run] removed %s", INITRAMFS_TAR); + } + clean_apps_elfs(); + clean_bin_elfs(); + clean_libcervus_build(false); + clean_tcc_build(false); + print_color(COLOR_GREEN, "[post-run] Done."); + } + return 0; + } + + print_color(COLOR_RED, "Unknown command: %s (try: ./build help)", command); + return 1; +} \ No newline at end of file diff --git a/kernel/include/acpi/acpi.h b/kernel/include/acpi/acpi.h new file mode 100644 index 0000000..cdf54c8 --- /dev/null +++ b/kernel/include/acpi/acpi.h @@ -0,0 +1,199 @@ +#ifndef ACPI_H +#define ACPI_H + +#include +#include +#include +#include + +typedef struct { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + uint32_t creator_id; + uint32_t creator_revision; +} __attribute__((packed)) acpi_sdt_header_t; + +typedef struct { + char signature[8]; + uint8_t checksum; + char oem_id[6]; + uint8_t revision; + uint32_t rsdt_address; +} __attribute__((packed)) acpi_rsdp_t; + +typedef struct { + acpi_rsdp_t rsdp_v1; + uint32_t length; + uint64_t xsdt_address; + uint8_t extended_checksum; + uint8_t reserved[3]; +} __attribute__((packed)) acpi_rsdp2_t; + +typedef struct { + acpi_sdt_header_t header; + uint32_t sdt_pointers[]; +} __attribute__((packed)) acpi_rsdt_t; + +typedef struct { + acpi_sdt_header_t header; + uint64_t sdt_pointers[]; +} __attribute__((packed)) acpi_xsdt_t; + +typedef struct { + uint8_t address_space_id; + uint8_t register_bit_width; + uint8_t register_bit_offset; + uint8_t access_size; + uint64_t address; +} __attribute__((packed)) acpi_gas_t; + +typedef struct { + acpi_sdt_header_t header; + uint32_t firmware_ctrl; + uint32_t dsdt; + uint8_t reserved; + uint8_t preferred_power_management_profile; + uint16_t sci_interrupt; + uint32_t smi_command_port; + uint8_t acpi_enable; + uint8_t acpi_disable; + uint8_t s4bios_req; + uint8_t pstate_control; + uint32_t pm1a_event_block; + uint32_t pm1b_event_block; + uint32_t pm1a_control_block; + uint32_t pm1b_control_block; + uint32_t pm2_control_block; + uint32_t pm_timer_block; + uint32_t gpe0_block; + uint32_t gpe1_block; + uint8_t pm1_event_length; + uint8_t pm1_control_length; + uint8_t pm2_control_length; + uint8_t pm_timer_length; + uint8_t gpe0_length; + uint8_t gpe1_length; + uint8_t gpe1_base; + uint8_t cstate_control; + uint16_t worst_c2_latency; + uint16_t worst_c3_latency; + uint16_t flush_size; + uint16_t flush_stride; + uint8_t duty_offset; + uint8_t duty_width; + uint8_t day_alarm; + uint8_t month_alarm; + uint8_t century; + uint16_t boot_architecture_flags; + uint8_t reserved2; + uint32_t flags; + acpi_gas_t reset_reg; + uint8_t reset_value; + uint16_t arm_boot_architecture_flags; + uint8_t fadt_minor_version; + uint64_t x_firmware_ctrl; + uint64_t x_dsdt; + acpi_gas_t x_pm1a_event_block; + acpi_gas_t x_pm1b_event_block; + acpi_gas_t x_pm1a_control_block; + acpi_gas_t x_pm1b_control_block; + acpi_gas_t x_pm2_control_block; + acpi_gas_t x_pm_timer_block; + acpi_gas_t x_gpe0_block; + acpi_gas_t x_gpe1_block; +} __attribute__((packed)) acpi_fadt_t; + +typedef struct { + acpi_sdt_header_t header; + uint32_t local_apic_address; + uint32_t flags; + uint8_t entries[]; +} __attribute__((packed)) acpi_madt_t; + +#define MADT_ENTRY_LAPIC 0 +#define MADT_ENTRY_IOAPIC 1 +#define MADT_ENTRY_ISO 2 +#define MADT_ENTRY_NMI 4 +#define MADT_ENTRY_LAPIC_ADDR 5 +#define MADT_ENTRY_IOAPIC_MMIO 6 + +typedef struct { + uint8_t type; + uint8_t length; +} __attribute__((packed)) madt_entry_header_t; + +typedef struct { + madt_entry_header_t header; + uint8_t processor_id; + uint8_t apic_id; + uint32_t flags; +} __attribute__((packed)) madt_lapic_entry_t; + +typedef struct { + madt_entry_header_t header; + uint8_t ioapic_id; + uint8_t reserved; + uint32_t ioapic_address; + uint32_t global_system_interrupt_base; +} __attribute__((packed)) madt_ioapic_entry_t; + +typedef struct { + madt_entry_header_t header; + uint8_t bus; + uint8_t source; + uint32_t global_system_interrupt; + uint16_t flags; +} __attribute__((packed)) madt_iso_entry_t; + +typedef struct { + acpi_sdt_header_t header; + uint8_t hardware_rev_id; + uint8_t comparator_count : 5; + uint8_t counter_size : 1; + uint8_t reserved : 1; + uint8_t legacy_replacement : 1; + uint16_t pci_vendor_id; + uint8_t address_space_id; + uint8_t register_bit_width; + uint8_t register_bit_offset; + uint8_t reserved2; + uint64_t address; + uint8_t hpet_number; + uint16_t minimum_tick; + uint8_t page_protection; +} __attribute__((packed)) acpi_hpet_t; + +typedef struct { + acpi_sdt_header_t header; + uint64_t reserved; +} __attribute__((packed)) acpi_mcfg_t; + +typedef struct { + uint64_t base_address; + uint16_t pci_segment_group; + uint8_t start_pci_bus; + uint8_t end_pci_bus; + uint32_t reserved; +} __attribute__((packed)) mcfg_entry_t; + +typedef struct { + acpi_sdt_header_t header; + uint8_t definition_block[]; +} __attribute__((packed)) acpi_ssdt_t; + +void acpi_init(void); +bool acpi_is_available(void); +void* acpi_find_table(const char* signature, uint64_t index); +void acpi_print_tables(void); + +extern volatile struct limine_rsdp_request rsdp_request; + +void acpi_shutdown(void); +void acpi_reboot(void); + +#endif \ No newline at end of file diff --git a/kernel/include/apic/apic.h b/kernel/include/apic/apic.h new file mode 100644 index 0000000..6dc141a --- /dev/null +++ b/kernel/include/apic/apic.h @@ -0,0 +1,113 @@ +#ifndef APIC_H +#define APIC_H + +#include +#include +#include "../acpi/acpi.h" + +#define LAPIC_ID 0x0020 +#define LAPIC_VERSION 0x0030 +#define LAPIC_TPR 0x0080 +#define LAPIC_EOI 0x00B0 +#define LAPIC_SIVR 0x00F0 +#define LAPIC_ISR0 0x0100 +#define LAPIC_TIMER 0x0320 +#define LAPIC_THERMAL 0x0330 +#define LAPIC_PERFORMANCE 0x0340 +#define LAPIC_LINT0 0x0350 +#define LAPIC_LINT1 0x0360 +#define LAPIC_ERROR 0x0370 +#define LAPIC_TIMER_ICR 0x0380 +#define LAPIC_TIMER_CCR 0x0390 +#define LAPIC_TIMER_DCR 0x03E0 + +#define LAPIC_ENABLE (1 << 8) +#define LAPIC_SPURIOUS_VECTOR 0xFF + +#define LAPIC_TIMER_MASKED (1 << 16) +#define LAPIC_TIMER_PERIODIC (1 << 17) +#define LAPIC_TIMER_DIV1 0x0 +#define LAPIC_TIMER_DIV2 0x1 +#define LAPIC_TIMER_DIV4 0x2 +#define LAPIC_TIMER_DIV8 0x3 +#define LAPIC_TIMER_DIV16 0x4 +#define LAPIC_TIMER_DIV32 0x5 +#define LAPIC_TIMER_DIV64 0x6 +#define LAPIC_TIMER_DIV128 0x7 + +#define IOAPIC_ID 0x00 +#define IOAPIC_VERSION 0x01 +#define IOAPIC_ARB 0x02 +#define IOAPIC_REDIR_START 0x10 + +#define IOAPIC_INT_MASKED (1 << 16) +#define IOAPIC_TRIGGER_LEVEL (1 << 15) +#define IOAPIC_POLARITY_LOW (1 << 13) +#define IOAPIC_DELIVERY_FIXED 0x0 +#define IOAPIC_DELIVERY_NMI 0x4 + +#define HPET_CAPABILITIES 0x000 +#define HPET_PERIOD 0x004 +#define HPET_CONFIG 0x010 +#define HPET_INTERRUPT_STATUS 0x020 +#define HPET_MAIN_COUNTER 0x0F0 +#define HPET_TIMER0_CONFIG 0x100 +#define HPET_TIMER0_COMPARATOR 0x108 + +#define HPET_ENABLE_CNF (1ULL << 0) +#define HPET_LEGACY_CNF (1ULL << 1) + +#define HPET_TN_INT_ENABLE_CNF (1 << 2) +#define HPET_TN_INT_TYPE_CNF (1 << 3) +#define HPET_TN_PERIODIC_CNF (1 << 4) +#define HPET_TN_32BIT_CNF (1 << 8) +#define HPET_TN_FSB_ENABLE_CNF (1 << 14) +#define HPET_TN_FSB_INT_DEL_CNF (1 << 15) + +void apic_init(void); +bool apic_is_available(void); +bool hpet_is_available(void); +bool hpet_init(void); + +void lapic_write(uint32_t reg, uint32_t value); +uint32_t lapic_read(uint32_t reg); +void lapic_eoi(void); +void lapic_enable(void); +uint32_t lapic_get_id(void); +void lapic_timer_init(uint32_t vector, uint32_t count, bool periodic, uint8_t divisor); +void lapic_timer_stop(void); +uint32_t lapic_timer_get_current(void); +void lapic_send_ipi(uint32_t target_lapic_id, uint8_t vector); +void lapic_send_ipi_to_all_but_self(uint8_t vector); +void lapic_send_nmi_to_all_but_self(void); + +void ioapic_write(uintptr_t base, uint32_t reg, uint32_t value); +uint32_t ioapic_read(uintptr_t base, uint32_t reg); +uint32_t ioapic_get_max_redirects(uintptr_t base); +void ioapic_redirect_irq(uint8_t irq, uint8_t vector, uint32_t flags); +void ioapic_mask_irq(uint8_t irq); +void ioapic_unmask_irq(uint8_t irq); + +void apic_setup_irq(uint8_t irq, uint8_t vector, bool mask, uint32_t flags); +void apic_timer_calibrate(void); + +uint64_t hpet_read_counter(void); +uint64_t hpet_get_frequency(void); +uint64_t hpet_elapsed_ns(void); +void hpet_sleep_ns(uint64_t nanoseconds); +void hpet_sleep_us(uint64_t microseconds); +void hpet_sleep_ms(uint64_t milliseconds); + +extern uintptr_t lapic_base; +extern uintptr_t ioapic_base; +extern uintptr_t hpet_base; +extern uint32_t hpet_period; +extern uint64_t g_hpet_boot_counter; + +void ipi_reschedule_all(void); +void ipi_reschedule_cpu(uint32_t lapic_id); +void ipi_reschedule_single(uint32_t target_lapic_id); +void ipi_tlb_shootdown_broadcast(const uintptr_t* addrs, size_t count); +void ipi_tlb_shootdown_single(uint32_t target_lapic_id, uintptr_t addr); + +#endif \ No newline at end of file diff --git a/kernel/include/drivers/ata.h b/kernel/include/drivers/ata.h new file mode 100644 index 0000000..2ba906c --- /dev/null +++ b/kernel/include/drivers/ata.h @@ -0,0 +1,77 @@ +#ifndef ATA_H +#define ATA_H + +#include +#include +#include + +#define ATA_PRIMARY_IO 0x1F0 +#define ATA_PRIMARY_CTRL 0x3F6 +#define ATA_SECONDARY_IO 0x170 +#define ATA_SECONDARY_CTRL 0x376 + +#define ATA_REG_DATA 0x00 +#define ATA_REG_ERROR 0x01 +#define ATA_REG_FEATURES 0x01 +#define ATA_REG_SECCOUNT 0x02 +#define ATA_REG_LBA_LO 0x03 +#define ATA_REG_LBA_MID 0x04 +#define ATA_REG_LBA_HI 0x05 +#define ATA_REG_DRIVE 0x06 +#define ATA_REG_STATUS 0x07 +#define ATA_REG_COMMAND 0x07 + +#define ATA_REG_ALT_STATUS 0x00 +#define ATA_REG_DEV_CTRL 0x00 + +#define ATA_SR_BSY 0x80 +#define ATA_SR_DRDY 0x40 +#define ATA_SR_DF 0x20 +#define ATA_SR_DSC 0x10 +#define ATA_SR_DRQ 0x08 +#define ATA_SR_CORR 0x04 +#define ATA_SR_IDX 0x02 +#define ATA_SR_ERR 0x01 + +#define ATA_CMD_READ_PIO 0x20 +#define ATA_CMD_READ_PIO_EXT 0x24 +#define ATA_CMD_WRITE_PIO 0x30 +#define ATA_CMD_WRITE_PIO_EXT 0x34 +#define ATA_CMD_CACHE_FLUSH 0xE7 +#define ATA_CMD_CACHE_FLUSH_EXT 0xEA +#define ATA_CMD_IDENTIFY 0xEC +#define ATA_CMD_IDENTIFY_PACKET 0xA1 + +#define ATA_DRIVE_MASTER 0xA0 +#define ATA_DRIVE_SLAVE 0xB0 + +#define ATA_LBA_BIT 0x40 + +#define ATA_MAX_DRIVES 4 + +#define ATA_SECTOR_SIZE 512 + +typedef struct { + bool present; + bool is_atapi; + bool lba48; + uint16_t io_base; + uint16_t ctrl_base; + uint8_t drive_select; + uint8_t irq; + uint64_t sectors; + uint64_t size_bytes; + char model[41]; + char serial[21]; + char firmware[9]; + uint16_t identify[256]; +} ata_drive_t; + +void ata_init(void); +ata_drive_t *ata_get_drive(int index); +int ata_get_drive_count(void); +int ata_read_sectors(ata_drive_t *drive, uint64_t lba, uint32_t count, void *buffer); +int ata_write_sectors(ata_drive_t *drive, uint64_t lba, uint32_t count, const void *buffer); +int ata_flush(ata_drive_t *drive); + +#endif \ No newline at end of file diff --git a/kernel/include/drivers/blkdev.h b/kernel/include/drivers/blkdev.h new file mode 100644 index 0000000..9e08251 --- /dev/null +++ b/kernel/include/drivers/blkdev.h @@ -0,0 +1,38 @@ +#ifndef BLKDEV_H +#define BLKDEV_H + +#include +#include +#include + +#define BLKDEV_MAX 8 +#define BLKDEV_NAME_MAX 32 +#define BLKDEV_SECTOR_SIZE 512 + +typedef struct blkdev blkdev_t; + +typedef struct blkdev_ops { + int (*read_sectors) (blkdev_t *dev, uint64_t lba, uint32_t count, void *buf); + int (*write_sectors)(blkdev_t *dev, uint64_t lba, uint32_t count, const void *buf); + int (*flush) (blkdev_t *dev); +} blkdev_ops_t; + +struct blkdev { + char name[BLKDEV_NAME_MAX]; + bool present; + uint64_t sector_count; + uint64_t size_bytes; + uint32_t sector_size; + const blkdev_ops_t *ops; + void *priv; +}; + +int blkdev_register(blkdev_t *dev); +blkdev_t *blkdev_get_by_name(const char *name); +blkdev_t *blkdev_get(int index); +int blkdev_count(void); +void blkdev_init(void); +int blkdev_read(blkdev_t *dev, uint64_t offset, void *buf, size_t len); +int blkdev_write(blkdev_t *dev, uint64_t offset, const void *buf, size_t len); + +#endif \ No newline at end of file diff --git a/kernel/include/drivers/disk.h b/kernel/include/drivers/disk.h new file mode 100644 index 0000000..6b2a926 --- /dev/null +++ b/kernel/include/drivers/disk.h @@ -0,0 +1,11 @@ +#ifndef DISK_H +#define DISK_H + +#include "../drivers/blkdev.h" + +void disk_init(void); +int disk_mount(const char *devname, const char *path); +int disk_umount(const char *path); +int disk_format(const char *devname, const char *label); + +#endif \ No newline at end of file diff --git a/kernel/include/drivers/partition.h b/kernel/include/drivers/partition.h new file mode 100644 index 0000000..94213ee --- /dev/null +++ b/kernel/include/drivers/partition.h @@ -0,0 +1,52 @@ +#ifndef PARTITION_H +#define PARTITION_H + +#include +#include +#include "blkdev.h" + +#define MBR_SIGNATURE 0xAA55 +#define MBR_SIGNATURE_OFF 510 +#define MBR_PARTITION_OFF 446 +#define MBR_MAX_PARTITIONS 4 + +#define MBR_TYPE_EMPTY 0x00 +#define MBR_TYPE_FAT12 0x01 +#define MBR_TYPE_FAT16_S 0x04 +#define MBR_TYPE_EXTENDED 0x05 +#define MBR_TYPE_FAT16 0x06 +#define MBR_TYPE_FAT32_CHS 0x0B +#define MBR_TYPE_FAT32_LBA 0x0C +#define MBR_TYPE_FAT16_LBA 0x0E +#define MBR_TYPE_LINUX 0x83 +#define MBR_TYPE_ESP 0xEF + +typedef struct __attribute__((packed)) { + uint8_t boot_flag; + uint8_t chs_start[3]; + uint8_t type; + uint8_t chs_end[3]; + uint32_t lba_start; + uint32_t sector_count; +} mbr_partition_t; + +typedef struct __attribute__((packed)) { + uint8_t bootstrap[440]; + uint32_t disk_signature; + uint16_t reserved; + mbr_partition_t partitions[4]; + uint16_t signature; +} mbr_t; + +_Static_assert(sizeof(mbr_t) == 512, "mbr size must be 512"); + +int partition_scan(blkdev_t *disk); + +int partition_write_mbr(blkdev_t *disk, const mbr_partition_t parts[4], + uint32_t disk_signature); + +int partition_read_mbr(blkdev_t *disk, mbr_t *out); + +blkdev_t *partition_get(const char *name); + +#endif \ No newline at end of file diff --git a/kernel/include/drivers/ps2.h b/kernel/include/drivers/ps2.h new file mode 100644 index 0000000..13ad328 --- /dev/null +++ b/kernel/include/drivers/ps2.h @@ -0,0 +1,91 @@ +#ifndef PS2_H +#define PS2_H + +#include +#include +#include + +#define PS2_DATA_PORT 0x60 +#define PS2_STATUS_PORT 0x64 +#define PS2_CMD_PORT 0x64 + +#define PS2_STATUS_OUTPUT_FULL (1 << 0) +#define PS2_STATUS_INPUT_FULL (1 << 1) + +#define PS2_CMD_READ_CONFIG 0x20 +#define PS2_CMD_WRITE_CONFIG 0x60 +#define PS2_CMD_DISABLE_PORT2 0xA7 +#define PS2_CMD_ENABLE_PORT2 0xA8 +#define PS2_CMD_TEST_PORT2 0xA9 +#define PS2_CMD_SELF_TEST 0xAA +#define PS2_CMD_TEST_PORT1 0xAB +#define PS2_CMD_DISABLE_PORT1 0xAD +#define PS2_CMD_ENABLE_PORT1 0xAE +#define PS2_CMD_WRITE_PORT2 0xD4 + +#define PS2_CFG_PORT1_IRQ (1 << 0) +#define PS2_CFG_PORT2_IRQ (1 << 1) +#define PS2_CFG_PORT1_XLAT (1 << 6) + +#define PS2_KEY_RELEASE_BIT 0x80 + +#define SC_LSHIFT 0x2A +#define SC_RSHIFT 0x36 +#define SC_CAPS 0x3A +#define SC_LCTRL 0x1D +#define SC_LALT 0x38 +#define SC_ESCAPE 0x01 +#define SC_F1 0x3B +#define SC_F2 0x3C +#define SC_F3 0x3D +#define SC_F4 0x3E +#define SC_F5 0x3F +#define SC_F6 0x40 +#define SC_F7 0x41 +#define SC_F8 0x42 +#define SC_F9 0x43 +#define SC_F10 0x44 +#define SC_F11 0x57 +#define SC_F12 0x58 + +#define MOUSE_BTN_LEFT (1 << 0) +#define MOUSE_BTN_RIGHT (1 << 1) +#define MOUSE_BTN_MIDDLE (1 << 2) +#define MOUSE_X_SIGN (1 << 4) +#define MOUSE_Y_SIGN (1 << 5) +#define MOUSE_X_OVERFLOW (1 << 6) +#define MOUSE_Y_OVERFLOW (1 << 7) + +typedef enum { + MOUSE_SCROLL_NONE = 0, + MOUSE_SCROLL_UP, + MOUSE_SCROLL_DOWN, +} mouse_scroll_t; + +typedef struct { + int32_t x, y; + bool btn_left, btn_right, btn_middle; + mouse_scroll_t scroll; +} mouse_state_t; + +typedef struct { + bool shift, caps_lock, ctrl, alt; +} kb_state_t; + +#define KB_BUF_SIZE 64 + +typedef struct { + char buf[KB_BUF_SIZE]; + uint8_t head, tail; +} kb_buf_t; + +bool ps2_init(void); +const kb_state_t* ps2_kb_get_state(void); +const mouse_state_t* ps2_mouse_get_state(void); +bool kb_buf_empty(void); +char kb_buf_getc(void); +bool kb_buf_try_getc(char *out); +bool kb_buf_has_ctrlc(void); +void kb_buf_consume_ctrlc(void); + +#endif \ No newline at end of file diff --git a/kernel/include/drivers/timer.h b/kernel/include/drivers/timer.h new file mode 100644 index 0000000..681d59c --- /dev/null +++ b/kernel/include/drivers/timer.h @@ -0,0 +1,17 @@ +#ifndef TIMER_H +#define TIMER_H + +#include +#include + +bool timer_init(void); + +uint64_t timer_get_ticks(void); + +void timer_sleep_ms(uint64_t milliseconds); +void timer_sleep_us(uint64_t microseconds); +void timer_sleep_ns(uint64_t nanoseconds); + +extern volatile uint32_t g_ctrlc_pending; + +#endif \ No newline at end of file diff --git a/kernel/include/elf/elf.h b/kernel/include/elf/elf.h new file mode 100644 index 0000000..e034602 --- /dev/null +++ b/kernel/include/elf/elf.h @@ -0,0 +1,118 @@ +#ifndef ELF_H +#define ELF_H + +#include +#include +#include +#include "../memory/vmm.h" +#include "../memory/paging.h" + +#define ELF_MAGIC 0x464C457F + +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_OSABI 7 +#define EI_NIDENT 16 + +#define ELFCLASS64 2 +#define ELFDATA2LSB 1 +#define EV_CURRENT 1 + +#define ET_EXEC 2 +#define ET_DYN 3 + +#define EM_X86_64 62 + +#define PT_NULL 0 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define PT_NOTE 4 +#define PT_PHDR 6 +#define PT_TLS 7 + +#define PF_X (1 << 0) +#define PF_W (1 << 1) +#define PF_R (1 << 2) + +#define SHT_NULL 0 +#define SHT_PROGBITS 1 +#define SHT_SYMTAB 2 +#define SHT_STRTAB 3 +#define SHT_RELA 4 +#define SHT_NOBITS 8 + +typedef struct { + uint8_t e_ident[EI_NIDENT]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint64_t e_entry; + uint64_t e_phoff; + uint64_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +} __attribute__((packed)) elf64_ehdr_t; + +typedef struct { + uint32_t p_type; + uint32_t p_flags; + uint64_t p_offset; + uint64_t p_vaddr; + uint64_t p_paddr; + uint64_t p_filesz; + uint64_t p_memsz; + uint64_t p_align; +} __attribute__((packed)) elf64_phdr_t; + +typedef struct { + uint32_t sh_name; + uint32_t sh_type; + uint64_t sh_flags; + uint64_t sh_addr; + uint64_t sh_offset; + uint64_t sh_size; + uint32_t sh_link; + uint32_t sh_info; + uint64_t sh_addralign; + uint64_t sh_entsize; +} __attribute__((packed)) elf64_shdr_t; + +typedef enum { + ELF_OK = 0, + ELF_ERR_NULL, + ELF_ERR_TOO_SMALL, + ELF_ERR_BAD_MAGIC, + ELF_ERR_NOT_64, + ELF_ERR_NOT_LE, + ELF_ERR_BAD_VERSION, + ELF_ERR_NOT_EXEC, + ELF_ERR_WRONG_ARCH, + ELF_ERR_NO_LOAD, + ELF_ERR_MAP_FAIL, + ELF_ERR_NO_MEM, +} elf_error_t; + +typedef struct { + uintptr_t entry; + vmm_pagemap_t* pagemap; + uintptr_t load_base; + uintptr_t stack_top; + size_t stack_size; + elf_error_t error; + uintptr_t load_end; +} elf_load_result_t; + +elf_load_result_t elf_load(const void* data, size_t size, size_t stack_sz); + +void elf_unload(elf_load_result_t* result); + +const char* elf_strerror(elf_error_t err); + +#endif \ No newline at end of file diff --git a/kernel/include/fs/devfs.h b/kernel/include/fs/devfs.h new file mode 100644 index 0000000..45d9521 --- /dev/null +++ b/kernel/include/fs/devfs.h @@ -0,0 +1,9 @@ +#ifndef DEVFS_H +#define DEVFS_H + +#include "vfs.h" + +vnode_t *devfs_create_root(void); +void devfs_register(const char *name, vnode_t *node); + +#endif \ No newline at end of file diff --git a/kernel/include/fs/ext2.h b/kernel/include/fs/ext2.h new file mode 100644 index 0000000..a5fd312 --- /dev/null +++ b/kernel/include/fs/ext2.h @@ -0,0 +1,161 @@ +#ifndef EXT2_H +#define EXT2_H + +#include +#include +#include +#include "../drivers/blkdev.h" +#include "vfs.h" + +#define EXT2_SUPER_MAGIC 0xEF53 +#define EXT2_SUPER_OFFSET 1024 +#define EXT2_ROOT_INO 2 +#define EXT2_GOOD_OLD_REV 0 +#define EXT2_DYNAMIC_REV 1 +#define EXT2_VALID_FS 1 +#define EXT2_ERROR_FS 2 + +#define EXT2_S_IFSOCK 0xC000 +#define EXT2_S_IFLNK 0xA000 +#define EXT2_S_IFREG 0x8000 +#define EXT2_S_IFBLK 0x6000 +#define EXT2_S_IFDIR 0x4000 +#define EXT2_S_IFCHR 0x2000 +#define EXT2_S_IFIFO 0x1000 + +#define EXT2_FT_UNKNOWN 0 +#define EXT2_FT_REG_FILE 1 +#define EXT2_FT_DIR 2 +#define EXT2_FT_CHRDEV 3 +#define EXT2_FT_BLKDEV 4 +#define EXT2_FT_FIFO 5 +#define EXT2_FT_SOCK 6 +#define EXT2_FT_SYMLINK 7 + +#define EXT2_NDIR_BLOCKS 12 +#define EXT2_IND_BLOCK 12 +#define EXT2_DIND_BLOCK 13 +#define EXT2_TIND_BLOCK 14 +#define EXT2_N_BLOCKS 15 +#define EXT2_NAME_LEN 255 + +typedef struct __attribute__((packed)) { + uint32_t s_inodes_count; + uint32_t s_blocks_count; + uint32_t s_r_blocks_count; + uint32_t s_free_blocks_count; + uint32_t s_free_inodes_count; + uint32_t s_first_data_block; + uint32_t s_log_block_size; + uint32_t s_log_frag_size; + uint32_t s_blocks_per_group; + uint32_t s_frags_per_group; + uint32_t s_inodes_per_group; + uint32_t s_mtime; + uint32_t s_wtime; + uint16_t s_mnt_count; + uint16_t s_max_mnt_count; + uint16_t s_magic; + uint16_t s_state; + uint16_t s_errors; + uint16_t s_minor_rev_level; + uint32_t s_lastcheck; + uint32_t s_checkinterval; + uint32_t s_creator_os; + uint32_t s_rev_level; + uint16_t s_def_resuid; + uint16_t s_def_resgid; + uint32_t s_first_ino; + uint16_t s_inode_size; + uint16_t s_block_group_nr; + uint32_t s_feature_compat; + uint32_t s_feature_incompat; + uint32_t s_feature_ro_compat; + uint8_t s_uuid[16]; + char s_volume_name[16]; + char s_last_mounted[64]; + uint32_t s_algo_bitmap; + uint8_t s_prealloc_blocks; + uint8_t s_prealloc_dir_blocks; + uint16_t s_padding1; + uint8_t s_journal_uuid[16]; + uint32_t s_journal_inum; + uint32_t s_journal_dev; + uint32_t s_last_orphan; + uint32_t s_hash_seed[4]; + uint8_t s_def_hash_version; + uint8_t s_reserved_pad[3]; + uint32_t s_default_mount_options; + uint32_t s_first_meta_bg; + uint8_t s_reserved[760]; +} ext2_superblock_t; + +_Static_assert(sizeof(ext2_superblock_t) == 1024, "ext2 superblock size"); + +typedef struct __attribute__((packed)) { + uint32_t bg_block_bitmap; + uint32_t bg_inode_bitmap; + uint32_t bg_inode_table; + uint16_t bg_free_blocks_count; + uint16_t bg_free_inodes_count; + uint16_t bg_used_dirs_count; + uint16_t bg_pad; + uint8_t bg_reserved[12]; +} ext2_group_desc_t; + +_Static_assert(sizeof(ext2_group_desc_t) == 32, "ext2 group desc size"); + +typedef struct __attribute__((packed)) { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_ctime; + uint32_t i_mtime; + uint32_t i_dtime; + uint16_t i_gid; + uint16_t i_links_count; + uint32_t i_blocks; + uint32_t i_flags; + uint32_t i_osd1; + uint32_t i_block[EXT2_N_BLOCKS]; + uint32_t i_generation; + uint32_t i_file_acl; + uint32_t i_dir_acl; + uint32_t i_faddr; + uint8_t i_osd2[12]; +} ext2_inode_t; + +_Static_assert(sizeof(ext2_inode_t) == 128, "ext2 inode size"); + +typedef struct __attribute__((packed)) { + uint32_t inode; + uint16_t rec_len; + uint8_t name_len; + uint8_t file_type; + char name[EXT2_NAME_LEN]; +} ext2_dir_entry_t; + +typedef struct { + blkdev_t *dev; + ext2_superblock_t sb; + ext2_group_desc_t *gdt; + uint32_t block_size; + uint32_t groups_count; + uint32_t inodes_per_block; + uint32_t inode_size; + uint32_t ptrs_per_block; + bool dirty; +} ext2_t; + +typedef struct { + ext2_t *fs; + uint32_t ino; +} ext2_vdata_t; + +int ext2_format(blkdev_t *dev, const char *label); +vnode_t *ext2_mount(blkdev_t *dev); +void ext2_unmount(ext2_t *fs); +void ext2_sync(ext2_t *fs); + +#endif \ No newline at end of file diff --git a/kernel/include/fs/fat32.h b/kernel/include/fs/fat32.h new file mode 100644 index 0000000..e5ca24b --- /dev/null +++ b/kernel/include/fs/fat32.h @@ -0,0 +1,146 @@ +#ifndef FAT32_H +#define FAT32_H + +#include +#include +#include +#include "../drivers/blkdev.h" +#include "vfs.h" + +#define FAT32_SECTOR_SIZE 512 +#define FAT32_EOC 0x0FFFFFFF +#define FAT32_EOC_MIN 0x0FFFFFF8 +#define FAT32_FREE_CLUSTER 0x00000000 +#define FAT32_BAD_CLUSTER 0x0FFFFFF7 +#define FAT32_CLUSTER_MASK 0x0FFFFFFF + +#define FAT32_FSINFO_LEAD_SIG 0x41615252 +#define FAT32_FSINFO_STRUCT_SIG 0x61417272 +#define FAT32_FSINFO_TRAIL_SIG 0xAA550000 + +#define FAT_ATTR_READ_ONLY 0x01 +#define FAT_ATTR_HIDDEN 0x02 +#define FAT_ATTR_SYSTEM 0x04 +#define FAT_ATTR_VOLUME_ID 0x08 +#define FAT_ATTR_DIRECTORY 0x10 +#define FAT_ATTR_ARCHIVE 0x20 +#define FAT_ATTR_LONG_NAME 0x0F + +typedef struct __attribute__((packed)) { + uint8_t bs_jmp[3]; + uint8_t bs_oem[8]; + uint16_t bpb_bytes_per_sector; + uint8_t bpb_sectors_per_cluster; + uint16_t bpb_reserved_sectors; + uint8_t bpb_num_fats; + uint16_t bpb_root_entries; + uint16_t bpb_total_sectors_16; + uint8_t bpb_media; + uint16_t bpb_fat_size_16; + uint16_t bpb_sectors_per_track; + uint16_t bpb_num_heads; + uint32_t bpb_hidden_sectors; + uint32_t bpb_total_sectors_32; + uint32_t bpb_fat_size_32; + uint16_t bpb_ext_flags; + uint16_t bpb_fs_version; + uint32_t bpb_root_cluster; + uint16_t bpb_fs_info; + uint16_t bpb_backup_boot; + uint8_t bpb_reserved[12]; + uint8_t bs_drive_num; + uint8_t bs_reserved1; + uint8_t bs_boot_sig; + uint32_t bs_vol_id; + uint8_t bs_vol_label[11]; + uint8_t bs_fs_type[8]; + uint8_t bs_boot_code[420]; + uint16_t bs_signature; +} fat32_bpb_t; + +_Static_assert(sizeof(fat32_bpb_t) == 512, "fat32 bpb size"); + +typedef struct __attribute__((packed)) { + uint32_t lead_sig; + uint8_t reserved1[480]; + uint32_t struct_sig; + uint32_t free_count; + uint32_t next_free; + uint8_t reserved2[12]; + uint32_t trail_sig; +} fat32_fsinfo_t; + +_Static_assert(sizeof(fat32_fsinfo_t) == 512, "fat32 fsinfo size"); + +typedef struct __attribute__((packed)) { + uint8_t name[11]; + uint8_t attr; + uint8_t nt_reserved; + uint8_t create_time_tenth; + uint16_t create_time; + uint16_t create_date; + uint16_t access_date; + uint16_t cluster_hi; + uint16_t modify_time; + uint16_t modify_date; + uint16_t cluster_lo; + uint32_t file_size; +} fat32_dirent_t; + +_Static_assert(sizeof(fat32_dirent_t) == 32, "fat32 dirent size"); + +typedef struct __attribute__((packed)) { + uint8_t order; + uint8_t name1[10]; + uint8_t attr; + uint8_t type; + uint8_t checksum; + uint8_t name2[12]; + uint16_t cluster_lo; + uint8_t name3[4]; +} fat32_lfn_t; + +_Static_assert(sizeof(fat32_lfn_t) == 32, "fat32 lfn size"); + +typedef struct { + blkdev_t *dev; + uint32_t bytes_per_sector; + uint32_t sectors_per_cluster; + uint32_t bytes_per_cluster; + uint32_t reserved_sectors; + uint32_t num_fats; + uint32_t fat_size_sectors; + uint32_t total_sectors; + uint32_t root_cluster; + uint32_t first_fat_sector; + uint32_t first_data_sector; + uint32_t total_clusters; + uint32_t fsinfo_sector; + uint32_t next_free_hint; + uint32_t free_count; + bool dirty; + bool readonly; + uint8_t *shared_buf; +} fat32_t; + +typedef struct { + fat32_t *fs; + uint32_t first_cluster; + uint32_t dir_cluster; + uint32_t dir_entry_offset; + uint32_t file_size; + uint8_t attr; + + uint32_t cached_cluster; + uint32_t cached_index; + bool size_dirty; + + uint8_t *io_buf; +} fat32_vdata_t; + +int fat32_format(blkdev_t *dev, const char *label); +vnode_t *fat32_mount(blkdev_t *dev); +void fat32_unmount(fat32_t *fs); +int fat32_sync(fat32_t *fs); + +#endif \ No newline at end of file diff --git a/kernel/include/fs/initramfs.h b/kernel/include/fs/initramfs.h new file mode 100644 index 0000000..2bbd104 --- /dev/null +++ b/kernel/include/fs/initramfs.h @@ -0,0 +1,9 @@ +#ifndef INITRAMFS_H +#define INITRAMFS_H + +#include +#include + +int initramfs_mount(const void *data, size_t size); + +#endif \ No newline at end of file diff --git a/kernel/include/fs/ramfs.h b/kernel/include/fs/ramfs.h new file mode 100644 index 0000000..9624ac7 --- /dev/null +++ b/kernel/include/fs/ramfs.h @@ -0,0 +1,11 @@ +#ifndef RAMFS_H +#define RAMFS_H + +#include "vfs.h" + +#define RAMFS_MAX_CHILDREN 64 +#define RAMFS_MAX_FILE_SIZE (4 * 1024 * 1024) + +vnode_t *ramfs_create_root(void); + +#endif \ No newline at end of file diff --git a/kernel/include/fs/vfs.h b/kernel/include/fs/vfs.h new file mode 100644 index 0000000..422976f --- /dev/null +++ b/kernel/include/fs/vfs.h @@ -0,0 +1,181 @@ +#ifndef VFS_H +#define VFS_H + +#include +#include +#include +#include "../syscall/errno.h" + +#define VFS_MAX_PATH 512 +#define VFS_MAX_NAME 256 +#define VFS_MAX_MOUNTS 16 +#define VFS_MAX_OPEN_FILES 512 + +#define O_RDONLY 0x000 +#define O_WRONLY 0x001 +#define O_RDWR 0x002 +#define O_ACCMODE 0x003 +#define O_CREAT 0x040 +#define O_TRUNC 0x200 +#define O_APPEND 0x400 +#define O_DIRECTORY 0x10000 +#define O_NONBLOCK 0x800 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +#define FD_CLOEXEC (1 << 0) +#define TASK_MAX_FDS 256 + +typedef enum { + VFS_NODE_FILE = 0, + VFS_NODE_DIR = 1, + VFS_NODE_CHARDEV = 2, + VFS_NODE_BLKDEV = 3, + VFS_NODE_SYMLINK = 4, + VFS_NODE_PIPE = 5, +} vnode_type_t; + +typedef struct { + uint64_t st_ino; + vnode_type_t st_type; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + uint64_t st_size; + uint64_t st_blocks; +} vfs_stat_t; + +typedef struct { + uint64_t d_ino; + uint8_t d_type; + char d_name[VFS_MAX_NAME]; +} vfs_dirent_t; + +typedef struct vnode vnode_t; +typedef struct vfs_mount vfs_mount_t; + +typedef struct vnode_ops { + int64_t (*read) (vnode_t *node, void *buf, size_t len, uint64_t offset); + int64_t (*write) (vnode_t *node, const void *buf, size_t len, uint64_t offset); + int (*truncate)(vnode_t *node, uint64_t new_size); + int (*lookup) (vnode_t *dir, const char *name, vnode_t **out); + int (*readdir) (vnode_t *dir, uint64_t index, vfs_dirent_t *out); + int (*mkdir) (vnode_t *dir, const char *name, uint32_t mode); + int (*create) (vnode_t *dir, const char *name, uint32_t mode, vnode_t **out); + int (*unlink) (vnode_t *dir, const char *name); + int (*rename) (vnode_t *src_dir, const char *src_name, + vnode_t *dst_dir, const char *dst_name); + int (*stat) (vnode_t *node, vfs_stat_t *out); + void (*ref) (vnode_t *node); + void (*unref) (vnode_t *node); + int64_t (*ioctl) (vnode_t *node, uint64_t req, void *arg); +} vnode_ops_t; + +struct vnode { + vnode_type_t type; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint64_t size; + uint64_t ino; + const vnode_ops_t *ops; + void *fs_data; + volatile int refcount; + vfs_mount_t *mounted; +}; + +struct vfs_mount { + char path[VFS_MAX_PATH]; + char device[32]; + char fstype[16]; + vnode_t *root; + bool used; + void *fs_priv; + void (*unmount)(void *fs_priv); + void (*sync)(void *fs_priv); +}; + +typedef struct { + vnode_t *vnode; + uint64_t offset; + int flags; + volatile int refcount; +} vfs_file_t; + +typedef struct { + vfs_file_t *file; + int fd_flags; +} fd_entry_t; + +typedef struct fd_table fd_table_t; + +struct fd_table { + fd_entry_t entries[TASK_MAX_FDS]; +}; + +void vfs_init (void); +int vfs_mount (const char *path, vnode_t *fs_root); + +int vfs_mount_fs(const char *path, vnode_t *fs_root, + void *fs_priv, void (*unmount_fn)(void *), + void (*sync_fn)(void *)); + +int vfs_umount (const char *path); +int vfs_lookup (const char *path, vnode_t **out); + +int vfs_open (const char *path, int flags, uint32_t mode, vfs_file_t **out); +void vfs_close (vfs_file_t *file); +int64_t vfs_read (vfs_file_t *file, void *buf, size_t len); +int64_t vfs_write (vfs_file_t *file, const void *buf, size_t len); +int64_t vfs_seek (vfs_file_t *file, int64_t offset, int whence); +int vfs_stat (const char *path, vfs_stat_t *out); +int vfs_fstat (vfs_file_t *file, vfs_stat_t *out); +int64_t vfs_ioctl (vfs_file_t *file, uint64_t req, void *arg); +int vfs_readdir(vfs_file_t *file, vfs_dirent_t *out); +int vfs_mkdir (const char *path, uint32_t mode); + +void vnode_ref (vnode_t *node); +void vnode_unref (vnode_t *node); +vfs_file_t *vfs_file_alloc(void); +void vfs_file_free (vfs_file_t *file); + +fd_table_t *fd_table_create (void); +fd_table_t *fd_table_clone (const fd_table_t *src); +void fd_table_cloexec(fd_table_t *table); +void fd_table_destroy(fd_table_t *table); + +int fd_alloc (fd_table_t *table, vfs_file_t *file, int min_fd); +vfs_file_t *fd_get (const fd_table_t *table, int fd); +int fd_close (fd_table_t *table, int fd); +int fd_dup2 (fd_table_t *table, int oldfd, int newfd); +int fd_set_flags(fd_table_t *table, int fd, int flags); +int fd_get_flags(const fd_table_t *table, int fd); +void vfs_sync_all(void); +int vfs_init_stdio(void *task_ptr); + +typedef struct { + char path[VFS_MAX_PATH]; + char device[32]; + char fstype[16]; + uint32_t flags; +} vfs_mount_info_t; + +int vfs_set_mount_info(const char *path, const char *device, const char *fstype); +int vfs_list_mounts(vfs_mount_info_t *out, int max); + +typedef struct { + uint64_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; + uint32_t f_flag; + uint32_t f_namemax; +} vfs_statvfs_t; + +int vfs_statvfs(const char *path, vfs_statvfs_t *out); + +#endif \ No newline at end of file diff --git a/kernel/include/gdt/gdt.h b/kernel/include/gdt/gdt.h new file mode 100644 index 0000000..5b0a00d --- /dev/null +++ b/kernel/include/gdt/gdt.h @@ -0,0 +1,78 @@ +#ifndef GDT_H +#define GDT_H + +#include + +#define GDT_CODE_SEGMENT 0x08 +#define GDT_DATA_SEGMENT 0x10 +#define GDT_USER_DATA_SEGMENT 0x18 +#define GDT_USER_CODE_SEGMENT 0x20 + +#define GDT_USER_DATA_RPL3 (GDT_USER_DATA_SEGMENT | 3) +#define GDT_USER_CODE_RPL3 (GDT_USER_CODE_SEGMENT | 3) + +#define GDT_STAR_SYSRET_BASE GDT_DATA_SEGMENT +#define GDT_STAR_SYSCALL_CS GDT_CODE_SEGMENT + +#define TSS_SELECTOR_BASE 0x28 +#define KERNEL_STACK_SIZE (4096 * 8) +#define KERNEL_STACK_PAGES (KERNEL_STACK_SIZE / 0x1000) + +#define GDT_LIMIT_LOW(limit) (limit & 0xFFFF) +#define GDT_BASE_LOW(base) (base & 0xFFFF) +#define GDT_BASE_MIDDLE(base) ((base >> 16) & 0xFF) +#define GDT_FLAGS_HI_LIMIT(limit, flags) \ + (((limit >> 16) & 0xF) | ((flags << 4) & 0xF0)) +#define GDT_BASE_HIGH(base) ((base >> 24) & 0xFF) + +#define GDT_ENTRY(base, limit, access, flags) \ + {GDT_LIMIT_LOW(limit), \ + GDT_BASE_LOW(base), \ + GDT_BASE_MIDDLE(base), \ + access, \ + GDT_FLAGS_HI_LIMIT(limit, flags), \ + GDT_BASE_HIGH(base)} + +typedef struct { + uint32_t reserved0; + uint64_t rsp0; + uint64_t rsp1; + uint64_t rsp2; + uint64_t reserved1; + uint64_t ist[7]; + uint64_t reserved2; + uint32_t iobase; +} __attribute__((packed)) tss_t; + +typedef struct { + uint16_t limit_low; + uint16_t base_low; + uint8_t base_middle; + uint8_t access; + uint8_t limit_high_and_flags; + uint8_t base_high; +} __attribute__((packed)) gdt_entry_t; + +typedef struct { + uint16_t limit_low; + uint16_t base_low; + uint8_t base_middle; + uint8_t access; + uint8_t limit_high_and_flags; + uint8_t base_high; + uint32_t base_higher; + uint32_t zero; +} __attribute__((packed)) tss_entry_t; + +typedef struct { + uint16_t size; + gdt_entry_t *pointer; +} __attribute__((packed)) gdt_pointer_t; + +extern gdt_pointer_t gdtr; + +extern void load_tss(uint16_t sel); +void gdt_init(void); +void gdt_load(void); + +#endif \ No newline at end of file diff --git a/kernel/include/graphics/fb/fb.h b/kernel/include/graphics/fb/fb.h new file mode 100644 index 0000000..b61f3e9 --- /dev/null +++ b/kernel/include/graphics/fb/fb.h @@ -0,0 +1,58 @@ +#ifndef FB_H +#define FB_H + +#include +#include +#include + +#define RGB(r, g, b) (((r) << 16) | ((g) << 8) | (b)) + +#define COLOR_BLACK RGB( 0, 0, 0) +#define COLOR_WHITE RGB(255, 255, 255) +#define COLOR_RED RGB(255, 0, 0) +#define COLOR_GREEN RGB( 0, 255, 0) +#define COLOR_BLUE RGB( 0, 0, 255) +#define COLOR_CYAN RGB( 0, 255, 255) +#define COLOR_MAGENTA RGB(255, 0, 255) +#define COLOR_YELLOW RGB(255, 255, 0) +#define COLOR_ORANGE RGB(255, 165, 0) +#define COLOR_GRAY RGB(128, 128, 128) +#define COLOR_DARKGRAY RGB( 64, 64, 64) +#define COLOR_BROWN RGB(165, 42, 42) + +struct psf_header { + uint32_t magic; + uint32_t version; + uint32_t headersize; + uint32_t flags; + uint32_t numglyph; + uint32_t bytesperglyph; + uint32_t height; + uint32_t width; +} __attribute__((packed)); + +extern uint8_t _binary_font_psf_start[]; +extern uint8_t _binary_font_psf_end[]; + +static inline const uint8_t* get_font_data(void) { + return (const uint8_t*)&_binary_font_psf_start; +} +static inline size_t get_font_data_size(void) { + return _binary_font_psf_end - _binary_font_psf_start; +} + +static inline const struct psf_header* get_psf_header(void) { + return (const struct psf_header*)&_binary_font_psf_start; +} + +int psf_validate(void); +void fb_draw_pixel(struct limine_framebuffer *fb, uint32_t x, uint32_t y, uint32_t color); +void fb_fill_rect(struct limine_framebuffer *fb, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color); +void fb_clear(struct limine_framebuffer *fb, uint32_t color); +void fb_draw_char(struct limine_framebuffer *fb, char c, uint32_t x, uint32_t y, uint32_t color); +void fb_draw_string(struct limine_framebuffer *fb, const char *str, uint32_t x, uint32_t y, uint32_t color); +void fb_init_backbuffer(struct limine_framebuffer *fb); +void fb_flush(struct limine_framebuffer *fb); +void fb_flush_lines(struct limine_framebuffer *fb, uint32_t y_start, uint32_t y_end); + +#endif diff --git a/kernel/include/interrupts/idt.h b/kernel/include/interrupts/idt.h new file mode 100644 index 0000000..18f37e9 --- /dev/null +++ b/kernel/include/interrupts/idt.h @@ -0,0 +1,47 @@ +#ifndef IDT_H +#define IDT_H + +#include +#include + +#define IDT_MAX_DESCRIPTORS 256 + +#define IS_FLAG_SETTED(x, flag) ((x) | (flag)) +#define FLAG_SET(x, flag) ((x) |= (flag)) +#define FLAG_UNSET(x, flag) ((x) &= ~(flag)) + +typedef enum { + IDT_FLAG_GATE_TASK = 0x5, + IDT_FLAG_GATE_16BIT_INT = 0x6, + IDT_FLAG_GATE_16BIT_TRAP = 0x7, + IDT_FLAG_GATE_32BIT_INT = 0xE, + IDT_FLAG_GATE_32BIT_TRAP = 0xF, + + IDT_FLAG_RING0 = (0 << 5), + IDT_FLAG_RING1 = (1 << 5), + IDT_FLAG_RING2 = (2 << 5), + IDT_FLAG_RING3 = (3 << 5), + + IDT_FLAG_PRESENT = 0x80, + +} IDT_FLAGS; + +typedef struct { + uint16_t base_low; + uint16_t kernel_cs; + uint8_t ist; + uint8_t attributes; + uint16_t base_mid; + uint32_t base_high; + uint32_t reserved; +}__attribute__((packed)) idt_entry_t; + +typedef struct { + uint16_t limit; + idt_entry_t *base; +}__attribute__((packed)) idtr_t; +extern idtr_t idtr; +void setup_interrupt_descriptor_table(uint64_t kernel_code_segment); +void idt_load(void); + +#endif diff --git a/kernel/include/interrupts/interrupts.h b/kernel/include/interrupts/interrupts.h new file mode 100644 index 0000000..42c9268 --- /dev/null +++ b/kernel/include/interrupts/interrupts.h @@ -0,0 +1,77 @@ +#ifndef INTERRUPTS_H +#define INTERRUPTS_H + +#include + +struct int_frame_t { + uint64_t ds; + + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + + uint64_t rbp; + + uint64_t rdi; + uint64_t rsi; + + uint64_t rdx; + uint64_t rcx; + uint64_t rbx; + uint64_t rax; + + uint64_t interrupt; + uint64_t error; + + uint64_t rip; + uint64_t cs; + uint64_t rflags; + uint64_t rsp; + uint64_t ss; +} __attribute__((packed)); + +typedef void (*int_handler_f)(struct int_frame_t* frame); + +typedef struct { + uint64_t vector; + int_handler_f handler; +} int_desc_t; + +#define __CHECK_HANDLER(fn) \ + _Static_assert( \ + __builtin_types_compatible_p( \ + typeof(fn), void (*)(struct int_frame_t *)), \ + "Invalid interrupt handler signature") + +#define __CONCAT(a, b) a##b +#define __UNIQUE_NAME(base) __CONCAT(base, __COUNTER__) + +#define DEFINE_ISR(_vector, _name) \ + static void _name(struct int_frame_t *frame); \ + static const int_desc_t __UNIQUE_NAME(__isr_desc_##_name) \ + __attribute__((used, section(".isr_handlers"))) = { \ + .vector = (_vector), \ + .handler = _name, \ + }; \ + static void _name(struct int_frame_t *frame) + + +#define DEFINE_IRQ(_vector, _name) \ + static void _name(struct int_frame_t *frame); \ + static const int_desc_t __UNIQUE_NAME(__irq_desc_##_name) \ + __attribute__((used, section(".irq_handlers"))) = { \ + .vector = (_vector), \ + .handler = _name, \ + }; \ + static void _name(struct int_frame_t *frame) + +#define IPI_RESCHEDULE_VECTOR 0x40 +#define IPI_TLB_SHOOTDOWN 0x41 +void init_interrupt_system(); + +#endif diff --git a/kernel/include/interrupts/irq.h b/kernel/include/interrupts/irq.h new file mode 100644 index 0000000..30fba98 --- /dev/null +++ b/kernel/include/interrupts/irq.h @@ -0,0 +1,31 @@ +#ifndef IRQ_H +#define IRQ_H + +#include +#include "interrupts.h" + +#define IRQ_INTERRUPTS_COUNT 224 + +static const char* irq_default_names[] __attribute__((unused)) = { + "IRQ0 timer", + "IRQ1 keyboard", + "IRQ2 cascade", + "IRQ3 COM2", + "IRQ4 COM1", + "IRQ5 LPT2", + "IRQ6 floppy", + "IRQ7 LPT1", + "IRQ8 RTC", + "IRQ9 ACPI", + "IRQ10 reserved", + "IRQ11 reserved", + "IRQ12 mouse", + "IRQ13 FPU", + "IRQ14 ATA1", + "IRQ15 ATA2" +}; + +void irq_common_handler(struct int_frame_t* regs); +void setup_defined_irq_handlers(void); + +#endif diff --git a/kernel/include/interrupts/isr.h b/kernel/include/interrupts/isr.h new file mode 100644 index 0000000..04d5d93 --- /dev/null +++ b/kernel/include/interrupts/isr.h @@ -0,0 +1,81 @@ +#ifndef ISR_H +#define ISR_H + +#include "interrupts.h" + +#define ISR_EXCEPTION_COUNT 32 + +enum { + EXCEPTION_DIVIDE_ERROR = 0, + EXCEPTION_DEBUG, + EXCEPTION_NMI, + EXCEPTION_BREAKPOINT, + EXCEPTION_OVERFLOW, + EXCEPTION_BOUND_RANGE, + EXCEPTION_INVALID_OPCODE, + EXCEPTION_DEVICE_NOT_AVAILABLE, + EXCEPTION_DOUBLE_FAULT, + EXCEPTION_COPROCESSOR_SEGMENT_OVERRUN, + EXCEPTION_INVALID_TSS, + EXCEPTION_SEGMENT_NOT_PRESENT, + EXCEPTION_STACK_SEGMENT_FAULT, + EXCEPTION_GENERAL_PROTECTION_FAULT, + EXCEPTION_PAGE_FAULT, + EXCEPTION_RESERVED15, + EXCEPTION_X87_FPU_ERROR, + EXCEPTION_ALIGNMENT_CHECK, + EXCEPTION_MACHINE_CHECK, + EXCEPTION_SIMD_FPU_EXCEPTION, + EXCEPTION_VIRTUALIZATION_EXCEPTION, + EXCEPTION_RESERVED21, + EXCEPTION_RESERVED22, + EXCEPTION_RESERVED23, + EXCEPTION_RESERVED24, + EXCEPTION_RESERVED25, + EXCEPTION_RESERVED26, + EXCEPTION_RESERVED27, + EXCEPTION_RESERVED28, + EXCEPTION_RESERVED29, + EXCEPTION_SECURITY_EXCEPTION = 30, + EXCEPTION_RESERVED31 +}; + +static const char* exception_names[] __attribute__((unused)) = { + "Divide Error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "Bound Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Segment Fault", + "General Protection Fault", + "Page Fault", + "Reserved", + "x87 Floating-Point Exception", + "Alignment Check", + "Machine Check", + "SIMD Floating-Point Exception", + "Virtualization Exception", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Security Exception", + "Reserved" +}; + +void isr_common_handler(struct int_frame_t* regs); +void setup_defined_isr_handlers(void); + +#endif diff --git a/kernel/include/io/ports.h b/kernel/include/io/ports.h new file mode 100644 index 0000000..15d2836 --- /dev/null +++ b/kernel/include/io/ports.h @@ -0,0 +1,40 @@ +#ifndef PORTS_H +#define PORTS_H + +#include + +static inline uint8_t inb(uint16_t port) { + uint8_t result; + asm volatile ("inb %1, %0" : "=a"(result) : "Nd"(port)); + return result; +} + +static inline void outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" : : "a"(data), "Nd"(port)); +} + +static inline uint16_t inw(uint16_t port) { + uint16_t result; + asm volatile ("inw %1, %0" : "=a"(result) : "Nd"(port)); + return result; +} + +static inline void outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" : : "a"(data), "Nd"(port)); +} + +static inline uint32_t inl(uint16_t port) { + uint32_t result; + asm volatile ("inl %1, %0" : "=a"(result) : "Nd"(port)); + return result; +} + +static inline void outl(uint16_t port, uint32_t data) { + asm volatile ("outl %0, %1" : : "a"(data), "Nd"(port)); +} + +static inline void io_wait(void) { + outb(0x80, 0); +} + +#endif \ No newline at end of file diff --git a/kernel/include/io/serial.h b/kernel/include/io/serial.h new file mode 100644 index 0000000..cd09b41 --- /dev/null +++ b/kernel/include/io/serial.h @@ -0,0 +1,51 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include +#include +#include +#include + +#define COM1 0x3F8 +#define COM2 0x2F8 +#define COM3 0x3E8 +#define COM4 0x2E8 + +#define SERIAL_DATA_PORT(base) (base) +#define SERIAL_FIFO_COMMAND_PORT(base) (base + 2) +#define SERIAL_LINE_COMMAND_PORT(base) (base + 3) +#define SERIAL_MODEM_COMMAND_PORT(base) (base + 4) +#define SERIAL_LINE_STATUS_PORT(base) (base + 5) + +#define SERIAL_LSR_DATA_READY 0x01 +#define SERIAL_LSR_OVERRUN_ERROR 0x02 +#define SERIAL_LSR_PARITY_ERROR 0x04 +#define SERIAL_LSR_FRAMING_ERROR 0x08 +#define SERIAL_LSR_BREAK_INDICATOR 0x10 +#define SERIAL_LSR_TRANSMIT_HOLDING_EMPTY 0x20 +#define SERIAL_LSR_TRANSMIT_EMPTY 0x40 +#define SERIAL_LSR_FIFO_ERROR 0x80 + +void serial_initialize(uint16_t port, uint32_t baud_rate); + +int serial_received_port(uint16_t port); +char serial_read_port(uint16_t port); +int serial_is_transmit_empty_port(uint16_t port); +void serial_write_port(uint16_t port, char c); +void serial_writestring_port(uint16_t port, const char* str); +void serial_printf_port(uint16_t port, const char* format, ...); + +int serial_received(void); +char serial_read(void); +int serial_is_transmit_empty(void); +void serial_write(char c); +void serial_writestring(const char* str); +void serial_writebuf(const char* buf, size_t len); +void serial_printf(const char* format, ...); + +uint16_t serial_get_default_port(void); + +void serial_set_default_port(uint16_t port); +void serial_force_unlock(void); + +#endif \ No newline at end of file diff --git a/kernel/include/memory/paging.h b/kernel/include/memory/paging.h new file mode 100644 index 0000000..440a902 --- /dev/null +++ b/kernel/include/memory/paging.h @@ -0,0 +1,44 @@ +#ifndef PAGING_H +#define PAGING_H + +#include +#include +#include +#include "vmm.h" +#include "../include/interrupts/interrupts.h" + +#define PAGING_PRESENT VMM_PRESENT +#define PAGING_WRITE VMM_WRITE +#define PAGING_USER VMM_USER +#define PAGING_NOEXEC VMM_NOEXEC + +#define PAGING_KERNEL (PAGING_PRESENT | PAGING_WRITE) +#define PAGING_USER_RW (PAGING_PRESENT | PAGING_WRITE | PAGING_USER) +#define PAGING_USER_RO (PAGING_PRESENT | PAGING_USER) +#define PAGING_USER_NOEXEC (PAGING_PRESENT | PAGING_USER | PAGING_NOEXEC) + +#define PAGING_LARGE_PAGE_SIZE 0x200000 +#define PAGING_HUGE_PAGE_SIZE 0x40000000 + +typedef struct { + uintptr_t virtual_start; + uintptr_t virtual_end; + uintptr_t physical_start; + uint64_t flags; + size_t page_count; + bool allocated; +} paging_region_t; + +void paging_init(void); +bool paging_map_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t phys_start, size_t page_count, uint64_t flags); +bool paging_unmap_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, size_t page_count); +bool paging_change_flags(vmm_pagemap_t* pagemap, uintptr_t virt_start, size_t page_count, uint64_t new_flags); +paging_region_t* paging_create_region(vmm_pagemap_t* pagemap, uintptr_t virt_start, size_t size, uint64_t flags); +bool paging_destroy_region(vmm_pagemap_t* pagemap, paging_region_t* region); +void* paging_alloc_pages(vmm_pagemap_t* pagemap, size_t page_count, uint64_t flags, uintptr_t preferred_virt); +void paging_free_pages(vmm_pagemap_t* pagemap, void* virt_addr, size_t page_count); +bool paging_reserve_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t virt_end); +bool paging_is_range_free(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t virt_end); +void paging_print_stats(vmm_pagemap_t* pagemap); +void paging_dump_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t virt_end); +#endif \ No newline at end of file diff --git a/kernel/include/memory/pmm.h b/kernel/include/memory/pmm.h new file mode 100644 index 0000000..c9acdd5 --- /dev/null +++ b/kernel/include/memory/pmm.h @@ -0,0 +1,89 @@ +#ifndef PMM_H +#define PMM_H + +#include +#include +#include +#include + +#define PAGE_SIZE 4096UL +#define PAGE_SHIFT 12 +#define PMM_PAGE_ALIGN(x) (((x) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) + +#define PMM_MAX_ORDER 10 +#define PMM_MAX_ORDER_NR (PMM_MAX_ORDER + 1) + +#define SLAB_MIN_SIZE 8 +#define SLAB_MAX_SIZE 4096 +#define SLAB_NUM_CACHES 10 + +#define PMM_FREE_MIN_PHYS 0x100000ULL + +typedef struct pmm_block { + struct pmm_block *next; + struct pmm_block *prev; + int order; +} pmm_block_t; + +typedef struct { + pmm_block_t head; + size_t count; +} pmm_free_list_t; + +typedef struct { + uintptr_t hhdm_offset; + uintptr_t mem_start; + uintptr_t mem_end; + size_t total_pages; + size_t usable_pages; + size_t free_pages; + pmm_free_list_t orders[PMM_MAX_ORDER_NR]; +} pmm_buddy_state_t; + +typedef struct slab { + struct slab *next; + struct slab *prev; + void *freelist; + uint16_t obj_size; + uint16_t total; + uint16_t used; + uint16_t _pad; +} slab_t; + +typedef struct { + size_t obj_size; + slab_t *partial; + slab_t *full; + size_t total_allocs; + size_t total_frees; +} slab_cache_t; + +void pmm_init(struct limine_memmap_response *memmap, + struct limine_hhdm_response *hhdm); + +void *pmm_alloc(size_t pages); +void *pmm_alloc_zero(size_t pages); +void *pmm_alloc_aligned(size_t pages, size_t alignment); +void pmm_free(void *addr, size_t pages); +void pmm_free_single(void *addr); + +void *pmm_phys_to_virt(uintptr_t phys); +uintptr_t pmm_virt_to_phys(void *vaddr); +uint64_t pmm_get_hhdm_offset(void); + +size_t pmm_get_total_pages(void); +size_t pmm_get_usable_pages(void); +size_t pmm_get_free_pages(void); +size_t pmm_get_used_pages(void); +void pmm_print_stats(void); + +void slab_init(void); +void *kmalloc(size_t size); +void *kzalloc(size_t size); +void *krealloc(void *ptr, size_t new_size); +void kfree(void *ptr); +void slab_print_stats(void); + +extern slab_cache_t g_caches[SLAB_NUM_CACHES]; + +#endif \ No newline at end of file diff --git a/kernel/include/memory/vmm.h b/kernel/include/memory/vmm.h new file mode 100644 index 0000000..b22662d --- /dev/null +++ b/kernel/include/memory/vmm.h @@ -0,0 +1,41 @@ +#ifndef VMM_H +#define VMM_H + +#include +#include +#include + +#define VMM_PRESENT (1ULL << 0) +#define VMM_WRITE (1ULL << 1) +#define VMM_USER (1ULL << 2) +#define VMM_PWT (1ULL << 3) +#define VMM_PCD (1ULL << 4) +#define VMM_ACCESSED (1ULL << 5) +#define VMM_DIRTY (1ULL << 6) +#define VMM_PSE (1ULL << 7) +#define VMM_GLOBAL (1ULL << 8) +#define VMM_NOEXEC (1ULL << 63) + +typedef uint64_t vmm_pte_t; + +typedef struct { + vmm_pte_t* pml4; +} vmm_pagemap_t; + +extern uintptr_t kernel_pml4_phys; + +void vmm_init(void); +vmm_pagemap_t* vmm_create_pagemap(void); +void vmm_switch_pagemap(vmm_pagemap_t* map); +bool vmm_map_page(vmm_pagemap_t* map, uintptr_t virt, uintptr_t phys, uint64_t flags); +void vmm_unmap_page(vmm_pagemap_t* map, uintptr_t virt); +void vmm_unmap_page_noflush(vmm_pagemap_t* map, uintptr_t virt); +bool vmm_virt_to_phys(vmm_pagemap_t* map, uintptr_t virt, uintptr_t* phys_out); +bool vmm_get_page_flags(vmm_pagemap_t* map, uintptr_t virt, uint64_t* flags_out); +vmm_pagemap_t* vmm_get_kernel_pagemap(void); +vmm_pagemap_t* vmm_clone_pagemap(vmm_pagemap_t* src); +void vmm_free_pagemap(vmm_pagemap_t* map); +void vmm_sync_kernel_mappings(vmm_pagemap_t* map); +void vmm_test(void); + +#endif \ No newline at end of file diff --git a/kernel/include/panic/panic.h b/kernel/include/panic/panic.h new file mode 100644 index 0000000..e175b01 --- /dev/null +++ b/kernel/include/panic/panic.h @@ -0,0 +1,19 @@ +#ifndef PANIC_H +#define PANIC_H + +#include +#include "../interrupts/interrupts.h" + +__attribute__((noreturn)) +void kernel_panic(const char *msg); + +__attribute__((noreturn)) +void kernel_panic_regs(const char *msg, struct int_frame_t *regs); + +#define KPANIC(msg) \ + kernel_panic("[" __FILE__ ":" STRINGIFY(__LINE__) "] " msg) + +#define STRINGIFY_IMPL(x) #x +#define STRINGIFY(x) STRINGIFY_IMPL(x) + +#endif \ No newline at end of file diff --git a/kernel/include/sched/capabilities.h b/kernel/include/sched/capabilities.h new file mode 100644 index 0000000..f009750 --- /dev/null +++ b/kernel/include/sched/capabilities.h @@ -0,0 +1,49 @@ +#ifndef CAPABILITIES_H +#define CAPABILITIES_H + +#include +#include + +#define UID_ROOT 0 +#define GID_ROOT 0 +#define UID_NOBODY 65534 +#define GID_NOBODY 65534 + +#define CAP_IOPORT (1ULL << 0) +#define CAP_RAWMEM (1ULL << 1) +#define CAP_DMA (1ULL << 2) +#define CAP_IRQ (1ULL << 3) +#define CAP_KILL_ANY (1ULL << 4) +#define CAP_SET_PRIO (1ULL << 5) +#define CAP_TASK_SPAWN (1ULL << 6) +#define CAP_TASK_INFO (1ULL << 7) +#define CAP_MMAP_EXEC (1ULL << 8) +#define CAP_MMAP_PHYS (1ULL << 9) +#define CAP_FS_ROOT (1ULL << 10) +#define CAP_FS_OWNER (1ULL << 11) +#define CAP_NET_RAW (1ULL << 12) +#define CAP_NET_BIND (1ULL << 13) +#define CAP_SYSADMIN (1ULL << 14) +#define CAP_REBOOT (1ULL << 15) +#define CAP_MODULE (1ULL << 16) +#define CAP_SETUID (1ULL << 17) +#define CAP_AUDIT (1ULL << 18) +#define CAP_PTRACE (1ULL << 19) +#define CAP_DBG_SERIAL (1ULL << 20) +#define CAP_ALL (~0ULL) +#define CAP_BASIC_SET (CAP_MMAP_EXEC | CAP_TASK_SPAWN) +#define CAP_SERVICE_SET (CAP_BASIC_SET | CAP_TASK_INFO | CAP_SET_PRIO | CAP_NET_BIND) + +static inline bool cap_has(uint64_t caps, uint64_t cap) { + return (caps & cap) == cap; +} + +static inline uint64_t cap_drop(uint64_t caps, uint64_t cap) { + return caps & ~cap; +} + +static inline uint64_t cap_initial(uint32_t uid) { + return (uid == UID_ROOT) ? CAP_ALL : CAP_BASIC_SET; +} + +#endif \ No newline at end of file diff --git a/kernel/include/sched/sched.h b/kernel/include/sched/sched.h new file mode 100644 index 0000000..fe4af64 --- /dev/null +++ b/kernel/include/sched/sched.h @@ -0,0 +1,183 @@ +#ifndef TASK_H +#define TASK_H + +#include +#include +#include +#include "capabilities.h" +#include "../memory/vmm.h" +#include "../smp/smp.h" + +#ifndef __ATOMIC_RELAXED +#define __ATOMIC_RELAXED 0 +#define __ATOMIC_ACQUIRE 2 +#define __ATOMIC_RELEASE 3 +#endif + +typedef struct { volatile bool _val; } atomic_bool; + +static inline void atomic_init_bool(atomic_bool *a, bool v) { + __atomic_store_n(&a->_val, v, __ATOMIC_RELAXED); +} +static inline bool atomic_load_bool_acq(const atomic_bool *a) { + return __atomic_load_n(&a->_val, __ATOMIC_ACQUIRE); +} +static inline void atomic_store_bool_rel(atomic_bool *a, bool v) { + __atomic_store_n(&a->_val, v, __ATOMIC_RELEASE); +} + +static inline bool atomic_cas_bool(atomic_bool *a, bool *expected, bool desired) { + return __atomic_compare_exchange_n( + &a->_val, expected, desired, + false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); +} + +typedef struct fd_table fd_table_t; + +typedef struct { + uint8_t data[512] __attribute__((aligned(16))); +} fpu_state_t; + +typedef enum { + TASK_RUNNING = 0, + TASK_READY, + TASK_BLOCKED, + TASK_ZOMBIE, + TASK_DEAD, +} task_state_t; + +typedef enum { + TASK_TYPE_KERNEL = 0, + TASK_TYPE_USER = 1, +} task_type_t; + +#define MAX_PRIORITY 31 +#define DEFAULT_PRIORITY 16 +#define TASK_DEFAULT_TIMESLICE 10 + +typedef struct task { + uint64_t rsp; + uint64_t rip; + uint64_t rbp_save; + uint64_t cr3; + int priority; + bool runnable; + uint8_t is_userspace; + uint8_t _pad0[2]; + uint32_t cpu_id; + char name[32]; + + uint32_t time_slice; + uint32_t time_slice_init; + uint8_t _pad1[4]; + uint64_t total_runtime; + + fpu_state_t* fpu_state; + bool fpu_used; + uint8_t _pad2[3]; + uint32_t last_cpu; + uint64_t cpu_affinity; + + void (*entry)(void*); + void *arg; + + uintptr_t stack_base; + uint64_t user_rsp; + + task_state_t state; + uint8_t _pad3[4]; + + struct task* next; + + uint32_t pid; + uint32_t ppid; + uint32_t uid; + uint32_t gid; + uint64_t capabilities; + + struct task* parent; + struct task* children; + struct task* sibling; + + int exit_code; + volatile bool pending_kill; + uint8_t _pad4[3]; + + uintptr_t brk_start; + uintptr_t brk_current; + uintptr_t brk_max; + vmm_pagemap_t* pagemap; + + uint32_t flags; + uint32_t wait_for_pid; + + uint64_t wakeup_time_ns; + + uint64_t user_saved_rip; + uint64_t user_saved_rbp; + uint64_t user_saved_rbx; + uint64_t user_saved_r12; + uint64_t user_saved_r13; + uint64_t user_saved_r14; + uint64_t user_saved_r15; + uint64_t user_saved_r11; + + fd_table_t *fd_table; + + atomic_bool on_cpu; + +} task_t; + +#define TASK_FLAG_TRACE (1 << 0) +#define TASK_FLAG_VFORK (1 << 1) +#define TASK_FLAG_FORK (1 << 2) +#define TASK_FLAG_STARTED (1 << 3) +#define TASK_FLAG_OWN_PAGEMAP (1 << 4) +#define TASK_FLAG_STACK_DEFERRED (1 << 5) +#define TASK_FLAG_DESTROYED (1U << 31) + +_Static_assert(offsetof(task_t, rsp) == 0, "task_t: rsp"); +_Static_assert(offsetof(task_t, entry) == 120, "task_t: entry — update TASK_ENTRY_OFFSET"); +_Static_assert(offsetof(task_t, arg) == 128, "task_t: arg — update TASK_ARG_OFFSET"); +_Static_assert(offsetof(task_t, stack_base) == 136, "task_t: stack_base"); +_Static_assert(offsetof(task_t, user_rsp) == 144, "task_t: user_rsp — update TASK_USER_RSP_OFFSET"); +_Static_assert(offsetof(task_t, user_saved_rip) == 272, "task_t: user_saved_rip"); +_Static_assert(offsetof(task_t, user_saved_rbp) == 280, "task_t: user_saved_rbp — update TASK_USER_SAVED_RBP_OFFSET"); + +extern task_t* ready_queues[MAX_PRIORITY + 1]; +extern task_t* current_task[MAX_CPUS]; + +void sched_init(void); +void sched_reschedule(void); +void sched_print_stats(void); +void task_yield(void); + +task_t* task_create(const char* name, void (*entry)(void*), void* arg, int priority); + +task_t* task_create_user(const char* name, uintptr_t entry, uintptr_t user_rsp, uint64_t cr3, int priority, vmm_pagemap_t* pagemap, uint32_t uid, uint32_t gid); + +__attribute__((noreturn)) void task_exit(void); +void task_kill(task_t* task); +void task_destroy(task_t* task); +task_t* task_fork(task_t* parent); +task_t* task_find_by_pid(uint32_t pid); +uint32_t task_alloc_pid(void); +void task_reparent(task_t* child, task_t* new_parent); + +#include "spinlock.h" +extern spinlock_t children_lock; +void task_wakeup_waiters(uint32_t pid); +void task_unblock(task_t* t); +void sched_wakeup_sleepers(uint64_t now_ns); +task_t* task_find_foreground(void); +extern volatile uint32_t g_foreground_pid; +void task_set_foreground(uint32_t pid); + +extern void context_switch(task_t* old, task_t* next, task_t** current_task_slot, uint64_t new_cr3); +extern void first_task_start(task_t* task); +extern void task_trampoline(void); +extern void task_trampoline_user(void); +extern void task_trampoline_fork(void); +extern void fpu_save(fpu_state_t* state); +extern void fpu_restore(fpu_state_t* state); +#endif diff --git a/kernel/include/sched/spinlock.h b/kernel/include/sched/spinlock.h new file mode 100644 index 0000000..bfd5129 --- /dev/null +++ b/kernel/include/sched/spinlock.h @@ -0,0 +1,44 @@ +#ifndef SPINLOCK_H +#define SPINLOCK_H + +#include + +typedef struct { + volatile uint32_t ticket; + volatile uint32_t serving; +} spinlock_t; + +#define SPINLOCK_INIT { .ticket = 0, .serving = 0 } + +static inline void spinlock_acquire(spinlock_t* lock) { + uint32_t my_ticket = __atomic_fetch_add(&lock->ticket, 1, __ATOMIC_RELAXED); + while (__atomic_load_n(&lock->serving, __ATOMIC_ACQUIRE) != my_ticket) + asm volatile("pause"); +} + +static inline void spinlock_release(spinlock_t* lock) { + __atomic_fetch_add(&lock->serving, 1, __ATOMIC_RELEASE); +} + +static inline int spinlock_try_acquire(spinlock_t* lock) { + uint32_t serving = __atomic_load_n(&lock->serving, __ATOMIC_RELAXED); + uint32_t ticket = __atomic_load_n(&lock->ticket, __ATOMIC_RELAXED); + if (serving != ticket) return 0; + uint32_t expected = serving; + return __atomic_compare_exchange_n(&lock->ticket, &expected, serving + 1, + 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); +} + +static inline uint64_t spinlock_acquire_irqsave(spinlock_t* lock) { + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(lock); + return flags; +} + +static inline void spinlock_release_irqrestore(spinlock_t* lock, uint64_t flags) { + spinlock_release(lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + +#endif \ No newline at end of file diff --git a/kernel/include/smp/percpu.h b/kernel/include/smp/percpu.h new file mode 100644 index 0000000..bab9f86 --- /dev/null +++ b/kernel/include/smp/percpu.h @@ -0,0 +1,55 @@ +#ifndef PERCPU_H +#define PERCPU_H + +#include +#include "../include/smp/smp.h" + +#define PERCPU_SECTION __attribute__((section(".percpu"))) + +typedef struct { + uint64_t syscall_kernel_rsp; + uint64_t syscall_user_rsp; + uint32_t cpu_id; + uint32_t _pad; + void* current_task; + uint64_t some_counter; + bool need_resched; + uint8_t _pad2[7]; + uint64_t user_saved_rbp; + uint64_t user_saved_rbx; + + uint64_t user_saved_r12; + uint64_t user_saved_r13; + uint64_t user_saved_r14; + uint64_t user_saved_r15; + uint64_t user_saved_r11; + uint64_t user_saved_rip; + uint8_t _pad3[8]; + void* deferred_free_task; + uint64_t sched_stack_top; +} __attribute__((aligned(64))) percpu_t; + +_Static_assert(__builtin_offsetof(percpu_t, syscall_kernel_rsp) == 0, "percpu: kernel_rsp"); +_Static_assert(__builtin_offsetof(percpu_t, syscall_user_rsp) == 8, "percpu: user_rsp"); +_Static_assert(__builtin_offsetof(percpu_t, current_task) == 24, "percpu: current_task"); +_Static_assert(__builtin_offsetof(percpu_t, need_resched) == 40, "percpu: need_resched"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_rbp) == 48, "percpu: saved_rbp"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_rbx) == 56, "percpu: saved_rbx"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_r12) == 64, "percpu: saved_r12"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_r13) == 72, "percpu: saved_r13"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_r14) == 80, "percpu: saved_r14"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_r15) == 88, "percpu: saved_r15"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_r11) == 96, "percpu: saved_r11"); +_Static_assert(__builtin_offsetof(percpu_t, user_saved_rip) == 104, "percpu: saved_rip"); + +extern percpu_t percpu; +extern percpu_t* percpu_regions[MAX_CPUS]; +extern bool g_has_fsgsbase; + +percpu_t* get_percpu(void); +percpu_t* get_percpu_mut(void); +void init_percpu_regions(void); +void set_percpu_base(percpu_t* base); +#define current_cpu_id() (get_percpu()->cpu_id) + +#endif diff --git a/kernel/include/smp/smp.h b/kernel/include/smp/smp.h new file mode 100644 index 0000000..50cce88 --- /dev/null +++ b/kernel/include/smp/smp.h @@ -0,0 +1,60 @@ +#ifndef SMP_H +#define SMP_H + +#include +#include +#include +#include + +#define AP_STACK_SIZE 16384 +#define MAX_CPUS 256 + +#define MAX_TLB_ADDRESSES 32 + +typedef struct { + volatile bool pending; + uintptr_t addresses[MAX_TLB_ADDRESSES]; + size_t count; +} tlb_shootdown_t; + +extern tlb_shootdown_t tlb_shootdown_queue[MAX_CPUS]; +typedef enum { + CPU_UNINITIALIZED = 0, + CPU_BOOTED, + CPU_ONLINE, + CPU_OFFLINE, + CPU_FAULTED +} cpu_state_t; + +typedef struct { + uint32_t lapic_id; + uint32_t processor_id; + uint32_t acpi_id; + cpu_state_t state; + bool is_bsp; + uint64_t stack_top; + uint32_t cpu_index; + uint16_t tss_selector; +} cpu_info_t; + +typedef struct { + uint32_t cpu_count; + uint32_t online_count; + uint32_t bsp_lapic_id; + uint64_t lapic_base; + cpu_info_t cpus[256]; +} smp_info_t; + +void smp_init(struct limine_mp_response* mp_response); +void smp_boot_aps(struct limine_mp_response* mp_response); +smp_info_t* smp_get_info(void); +cpu_info_t* smp_get_current_cpu(void); +uint32_t smp_get_cpu_count(void); +uint32_t smp_get_online_count(void); +bool smp_is_bsp(void); +void smp_print_info(void); +void smp_print_info_fb(void); +void smp_wait_for_ready(void); +void ap_entry_point(struct limine_mp_info* cpu_info); +void sched_notify_ready(void); +#endif \ No newline at end of file diff --git a/kernel/include/sse/fpu.h b/kernel/include/sse/fpu.h new file mode 100644 index 0000000..17b8ec0 --- /dev/null +++ b/kernel/include/sse/fpu.h @@ -0,0 +1,17 @@ +#ifndef FPU_H +#define FPU_H + +#include +#include + +void fpu_init(void); +bool fpu_detect(void); +void fpu_set_control_word(uint16_t cw); +uint16_t fpu_get_control_word(void); +void fpu_set_status_word(uint16_t sw); +uint16_t fpu_get_status_word(void); +void fpu_set_tag_word(uint16_t tw); +uint16_t fpu_get_tag_word(void); +void fpu_reset(void); + +#endif \ No newline at end of file diff --git a/kernel/include/sse/sse.h b/kernel/include/sse/sse.h new file mode 100644 index 0000000..3bd11e5 --- /dev/null +++ b/kernel/include/sse/sse.h @@ -0,0 +1,31 @@ +#ifndef SSE_H +#define SSE_H + +#include +#include +#include + +#define MXCSR_DEFAULT 0x1F80 +#define MXCSR_FLUSH_TO_ZERO (1 << 15) +#define MXCSR_DENORMALS_ARE_ZERO (1 << 6) + +bool sse_supported(void); +bool sse2_supported(void); +bool sse3_supported(void); +bool ssse3_supported(void); +bool sse4_1_supported(void); +bool sse4_2_supported(void); +bool avx_supported(void); +bool avx2_supported(void); +void sse_init(void); +void sse_set_mxcsr(uint32_t mxcsr); +uint32_t sse_get_mxcsr(void); +void sse_memcpy_fast(void* dest, const void* src, size_t n); +void sse_memset_fast(void* dest, int value, size_t n); +bool mmx_supported(void); +void mmx_enter(void); +void mmx_exit(void); +void print_simd_cpuid(void); +void enable_fsgsbase(void); + +#endif \ No newline at end of file diff --git a/kernel/include/syscall/errno.h b/kernel/include/syscall/errno.h new file mode 100644 index 0000000..2b049cc --- /dev/null +++ b/kernel/include/syscall/errno.h @@ -0,0 +1,66 @@ +#ifndef ERRNO_H +#define ERRNO_H + +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define EINVAL 22 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define ERANGE 34 +#define EDEADLK 35 +#define ENAMETOOLONG 36 +#define ENOLCK 37 +#define ENOSYS 38 +#define ENOTEMPTY 39 +#define ELOOP 40 +#define ENOMSG 42 +#define EIDRM 43 +#define ENOSTR 60 +#define ENODATA 61 +#define ETIME 62 +#define ENOSR 63 +#define EREMOTE 66 +#define ENOLINK 67 +#define EPROTO 71 +#define EMULTIHOP 72 +#define EBADMSG 74 +#define EOVERFLOW 75 +#define EILSEQ 84 +#define EUSERS 87 +#define ENOTSOCK 88 +#define EOPNOTSUPP 95 +#define EADDRINUSE 98 +#define ECONNREFUSED 111 +#define ETIMEDOUT 110 + +#define ECERVUS_BASE 200 +#define ECAPABILITY 200 +#define ETASKDEAD 201 +#define EBADCR3 202 + +#endif \ No newline at end of file diff --git a/kernel/include/syscall/syscall.h b/kernel/include/syscall/syscall.h new file mode 100644 index 0000000..d8c3899 --- /dev/null +++ b/kernel/include/syscall/syscall.h @@ -0,0 +1,12 @@ +#ifndef SYSCALL_H +#define SYSCALL_H + +#include +#include "syscall_nums.h" +#include "../sched/capabilities.h" + +void syscall_init(void); + +int64_t syscall_handler_c(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t user_rip); + +#endif \ No newline at end of file diff --git a/kernel/include/syscall/syscall_nums.h b/kernel/include/syscall/syscall_nums.h new file mode 100644 index 0000000..ac526d5 --- /dev/null +++ b/kernel/include/syscall/syscall_nums.h @@ -0,0 +1,142 @@ +#ifndef SYSCALL_NUMS_H +#define SYSCALL_NUMS_H + +#define SYS_EXIT 0 +#define SYS_EXIT_GROUP 1 +#define SYS_GETPID 2 +#define SYS_GETPPID 3 +#define SYS_FORK 4 +#define SYS_WAIT 5 +#define SYS_EXECVE 14 +#define SYS_YIELD 6 +#define SYS_GETUID 7 +#define SYS_GETGID 8 +#define SYS_SETUID 9 +#define SYS_SETGID 10 +#define SYS_CAP_GET 11 +#define SYS_CAP_DROP 12 +#define SYS_TASK_INFO 13 + +#define SYS_READ 20 +#define SYS_WRITE 21 +#define SYS_OPEN 22 +#define SYS_CLOSE 23 +#define SYS_SEEK 24 +#define SYS_STAT 25 +#define SYS_FSTAT 26 +#define SYS_IOCTL 27 +#define SYS_DUP 28 +#define SYS_DUP2 29 +#define SYS_PIPE 30 +#define SYS_FCNTL 31 +#define SYS_READDIR 32 + +#define SYS_MMAP 40 +#define SYS_MUNMAP 41 +#define SYS_MPROTECT 42 +#define SYS_BRK 43 + +#define SYS_CLOCK_GET 60 +#define SYS_SLEEP_NS 61 +#define SYS_UPTIME 62 +#define SYS_MEMINFO 63 + +#define SYS_FUTEX_WAIT 80 +#define SYS_FUTEX_WAKE 81 + +#define SYS_CERVUS_BASE 512 + +#define SYS_DBG_PRINT 512 +#define SYS_DBG_DUMP 513 +#define SYS_TASK_SPAWN 514 +#define SYS_TASK_KILL 515 +#define SYS_SHMEM_CREATE 516 +#define SYS_SHMEM_MAP 517 +#define SYS_SHMEM_UNMAP 518 +#define SYS_IPC_SEND 519 +#define SYS_IPC_RECV 520 +#define SYS_IOPORT_READ 521 +#define SYS_IOPORT_WRITE 522 +#define SYS_SHUTDOWN 523 +#define SYS_REBOOT 524 + +#define SYS_DISK_MOUNT 530 +#define SYS_DISK_UMOUNT 531 +#define SYS_DISK_FORMAT 532 +#define SYS_DISK_INFO 533 +#define SYS_UNLINK 534 +#define SYS_RMDIR 535 +#define SYS_MKDIR 536 +#define SYS_RENAME 537 + +#define SYS_DISK_READ_RAW 540 +#define SYS_DISK_WRITE_RAW 541 +#define SYS_DISK_PARTITION 542 +#define SYS_DISK_MKFS_FAT32 543 +#define SYS_DISK_LIST_PARTS 544 + +#define SYS_DISK_BIOS_INSTALL 545 +#define SYS_LIST_MOUNTS 546 +#define SYS_STATVFS 547 + +#define SYSCALL_TABLE_SIZE 548 + +#define PROT_NONE 0x0 +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define PROT_EXEC 0x4 + +#define MAP_PRIVATE 0x02 +#define MAP_ANONYMOUS 0x20 +#define MAP_FIXED 0x10 +#define MAP_FAILED ((void*)-1) + +#define WNOHANG 0x1 + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 + +typedef struct { + uint32_t pid; + uint32_t ppid; + uint32_t uid; + uint32_t gid; + uint64_t capabilities; + char name[32]; + uint32_t state; + uint32_t priority; + uint64_t total_runtime_ns; +} cervus_task_info_t; + +typedef struct { + int64_t tv_sec; + int64_t tv_nsec; +} cervus_timespec_t; + +typedef struct { + uint64_t total_bytes; + uint64_t free_bytes; + uint64_t used_bytes; + uint64_t usable_bytes; + uint64_t page_size; +} cervus_meminfo_t; + +typedef struct __attribute__((packed)) { + uint8_t boot_flag; + uint8_t type; + uint32_t lba_start; + uint32_t sector_count; +} cervus_mbr_part_t; + +typedef struct __attribute__((packed)) { + char disk_name[32]; + char part_name[32]; + uint32_t part_num; + uint8_t type; + uint8_t bootable; + uint64_t lba_start; + uint64_t sector_count; + uint64_t size_bytes; +} cervus_part_info_t; + +#endif \ No newline at end of file diff --git a/kernel/src/acpi/acpi.c b/kernel/src/acpi/acpi.c new file mode 100644 index 0000000..0f561f3 --- /dev/null +++ b/kernel/src/acpi/acpi.c @@ -0,0 +1,397 @@ +#include "../../include/acpi/acpi.h" +#include "../../include/io/serial.h" +#include "../../include/io/ports.h" +#include "../../include/memory/pmm.h" +#include + +static acpi_rsdp2_t *rsdp; +static acpi_xsdt_t *xsdt; +static acpi_rsdt_t *rsdt; + +static uint8_t s5_slp_typa = 0; +static uint8_t s5_slp_typb = 0; +static bool s5_parsed = false; + +static inline void *phys_to_virt(uintptr_t phys) { + return (void *)(phys + pmm_get_hhdm_offset()); +} + +static bool checksum(void *base, size_t len) { + uint8_t sum = 0; + uint8_t *b = base; + for (size_t i = 0; i < len; i++) + sum += b[i]; + return sum == 0; +} + +static bool parse_s5_from_aml(const uint8_t *aml, size_t aml_len) { + if (!aml || aml_len < 40) return false; + + for (size_t i = 0; i + 20 < aml_len; i++) { + if (aml[i] == '_' && aml[i+1] == 'S' && + aml[i+2] == '5' && aml[i+3] == '_') { + + size_t pos = i + 4; + + for (size_t j = pos; j < pos + 4 && j < aml_len; j++) { + if (aml[j] == 0x12) { + size_t pkg = j + 1; + + if (pkg >= aml_len) continue; + uint8_t lead = aml[pkg]; + size_t pkg_len_bytes = 1; + if (lead & 0xC0) { + pkg_len_bytes = (lead >> 6) + 1; + } + pkg += pkg_len_bytes; + + if (pkg >= aml_len) continue; + + pkg++; + + if (pkg >= aml_len) continue; + + if (aml[pkg] == 0x0A) { + pkg++; + if (pkg >= aml_len) continue; + s5_slp_typa = aml[pkg]; + pkg++; + } else if (aml[pkg] == 0x00) { + s5_slp_typa = 0; + pkg++; + } else if (aml[pkg] == 0x01) { + s5_slp_typa = 1; + pkg++; + } else { + s5_slp_typa = aml[pkg]; + pkg++; + } + + if (pkg >= aml_len) continue; + + if (aml[pkg] == 0x0A) { + pkg++; + if (pkg >= aml_len) continue; + s5_slp_typb = aml[pkg]; + } else if (aml[pkg] == 0x00) { + s5_slp_typb = 0; + } else if (aml[pkg] == 0x01) { + s5_slp_typb = 1; + } else { + s5_slp_typb = aml[pkg]; + } + + serial_printf("ACPI: parsed S5 from AML: SLP_TYPa=%u SLP_TYPb=%u\n", + s5_slp_typa, s5_slp_typb); + return true; + } + } + } + } + return false; +} + +static void parse_s5(void) { + if (s5_parsed) return; + s5_parsed = true; + + acpi_fadt_t *fadt = (acpi_fadt_t *)acpi_find_table("FACP", 0); + if (!fadt) { + serial_writestring("ACPI: no FADT, cannot parse S5\n"); + return; + } + + uintptr_t dsdt_phys = 0; + if (fadt->header.length >= 148 && fadt->x_dsdt) + dsdt_phys = (uintptr_t)fadt->x_dsdt; + if (!dsdt_phys) + dsdt_phys = (uintptr_t)fadt->dsdt; + + if (!dsdt_phys) { + serial_writestring("ACPI: no DSDT address in FADT\n"); + return; + } + + acpi_sdt_header_t *dsdt = phys_to_virt(dsdt_phys); + + if (memcmp(dsdt->signature, "DSDT", 4) != 0) { + serial_writestring("ACPI: invalid DSDT signature\n"); + return; + } + + serial_printf("ACPI: DSDT at phys=0x%llx length=%u\n", + (unsigned long long)dsdt_phys, dsdt->length); + + const uint8_t *aml = (const uint8_t *)dsdt + sizeof(acpi_sdt_header_t); + size_t aml_len = dsdt->length - sizeof(acpi_sdt_header_t); + + if (parse_s5_from_aml(aml, aml_len)) + return; + + for (uint64_t idx = 0; ; idx++) { + acpi_sdt_header_t *ssdt = acpi_find_table("SSDT", idx); + if (!ssdt) break; + + const uint8_t *s_aml = (const uint8_t *)ssdt + sizeof(acpi_sdt_header_t); + size_t s_len = ssdt->length - sizeof(acpi_sdt_header_t); + + if (parse_s5_from_aml(s_aml, s_len)) + return; + } + + serial_writestring("ACPI: S5 object not found in DSDT/SSDT, using defaults\n"); + s5_slp_typa = 5; + s5_slp_typb = 0; +} + +static void acpi_enable_mode(acpi_fadt_t *fadt) { + if (!fadt->smi_command_port || !fadt->acpi_enable) + return; + + uint16_t pm1a_val = inw(fadt->pm1a_control_block); + if (pm1a_val & 1) { + serial_writestring("ACPI: already in ACPI mode\n"); + return; + } + + serial_writestring("ACPI: enabling ACPI mode...\n"); + outb((uint16_t)fadt->smi_command_port, fadt->acpi_enable); + + for (int i = 0; i < 3000; i++) { + pm1a_val = inw(fadt->pm1a_control_block); + if (pm1a_val & 1) { + serial_writestring("ACPI: ACPI mode enabled\n"); + return; + } + for (volatile int j = 0; j < 10000; j++) + asm volatile("pause"); + } + serial_writestring("ACPI: WARNING - timeout enabling ACPI mode\n"); +} + +void acpi_init(void) { + if (!rsdp_request.response) { + serial_writestring("ACPI: no RSDP\n"); + return; + } + + rsdp = (acpi_rsdp2_t *)rsdp_request.response->address; + + if (!checksum(&rsdp->rsdp_v1, sizeof(acpi_rsdp_t))) { + serial_writestring("ACPI: bad RSDP checksum\n"); + return; + } + + if (rsdp->rsdp_v1.revision >= 2 && rsdp->xsdt_address) { + xsdt = phys_to_virt(rsdp->xsdt_address); + if (!checksum(xsdt, xsdt->header.length)) + xsdt = NULL; + } + + if (!xsdt && rsdp->rsdp_v1.rsdt_address) { + rsdt = phys_to_virt(rsdp->rsdp_v1.rsdt_address); + if (!checksum(rsdt, rsdt->header.length)) + rsdt = NULL; + } + + if (!xsdt && !rsdt) + return; + + parse_s5(); +} + +void *acpi_find_table(const char *sig, uint64_t index) { + uint64_t count = 0; + + if (xsdt) { + size_t n = (xsdt->header.length - sizeof(acpi_sdt_header_t)) / 8; + for (size_t i = 0; i < n; i++) { + acpi_sdt_header_t *h = phys_to_virt(xsdt->sdt_pointers[i]); + if (!memcmp(h->signature, sig, 4)) { + if (count++ == index) + return h; + } + } + } else if (rsdt) { + size_t n = (rsdt->header.length - sizeof(acpi_sdt_header_t)) / 4; + for (size_t i = 0; i < n; i++) { + acpi_sdt_header_t *h = phys_to_virt(rsdt->sdt_pointers[i]); + if (!memcmp(h->signature, sig, 4)) { + if (count++ == index) + return h; + } + } + } + + return NULL; +} + +void acpi_print_tables(void) { + serial_writestring("ACPI tables:\n"); + + for (uint64_t i = 0;; i++) { + acpi_sdt_header_t *h = acpi_find_table("APIC", i); + if (!h) break; + serial_writestring(" - APIC (MADT)\n"); + } + + for (uint64_t i = 0;; i++) { + acpi_sdt_header_t *h = acpi_find_table("HPET", i); + if (!h) break; + serial_writestring(" - HPET\n"); + } + + for (uint64_t i = 0;; i++) { + acpi_sdt_header_t *h = acpi_find_table("MCFG", i); + if (!h) break; + serial_writestring(" - MCFG (PCIe)\n"); + } + + for (uint64_t i = 0;; i++) { + acpi_sdt_header_t *h = acpi_find_table("FACP", i); + if (!h) break; + serial_writestring(" - FACP (FADT)\n"); + } +} + +static void acpi_write_gas(const acpi_gas_t *gas, uint64_t value) { + if (gas->address == 0) return; + + switch (gas->address_space_id) { + case 0x00: + { + volatile uint8_t *mmio = phys_to_virt(gas->address); + switch (gas->register_bit_width) { + case 8: *(volatile uint8_t *)mmio = (uint8_t)value; break; + case 16: *(volatile uint16_t *)mmio = (uint16_t)value; break; + case 32: *(volatile uint32_t *)mmio = (uint32_t)value; break; + case 64: *(volatile uint64_t *)mmio = value; break; + } + break; + } + case 0x01: + { + uint16_t port = (uint16_t)gas->address; + switch (gas->register_bit_width) { + case 8: outb(port, (uint8_t)value); break; + case 16: outw(port, (uint16_t)value); break; + case 32: outl(port, (uint32_t)value); break; + } + break; + } + } +} + +void acpi_shutdown(void) { + acpi_fadt_t *fadt = (acpi_fadt_t *)acpi_find_table("FACP", 0); + if (!fadt) { + serial_writestring("ACPI shutdown: FADT not found\n"); + goto fallback; + } + + acpi_enable_mode(fadt); + + parse_s5(); + + serial_printf("ACPI shutdown: SLP_TYPa=%u SLP_TYPb=%u\n", + s5_slp_typa, s5_slp_typb); + serial_printf("ACPI shutdown: PM1a_CNT=0x%x PM1b_CNT=0x%x\n", + fadt->pm1a_control_block, fadt->pm1b_control_block); + + asm volatile("cli"); + + uint16_t pm1a_value = (s5_slp_typa << 10) | (1 << 13); + outw(fadt->pm1a_control_block, pm1a_value); + + if (fadt->pm1b_control_block) { + uint16_t pm1b_value = (s5_slp_typb << 10) | (1 << 13); + outw(fadt->pm1b_control_block, pm1b_value); + } + + for (volatile int i = 0; i < 100000; i++) + asm volatile("pause"); + + if (fadt->header.length >= 244) { + if (fadt->x_pm1a_control_block.address) { + serial_writestring("ACPI shutdown: trying extended PM1a...\n"); + acpi_write_gas(&fadt->x_pm1a_control_block, pm1a_value); + } + if (fadt->x_pm1b_control_block.address) { + uint16_t pm1b_value = (s5_slp_typb << 10) | (1 << 13); + acpi_write_gas(&fadt->x_pm1b_control_block, pm1b_value); + } + + for (volatile int i = 0; i < 100000; i++) + asm volatile("pause"); + } + +fallback: + serial_writestring("ACPI shutdown: trying QEMU/Bochs port 0x604...\n"); + outw(0x604, 0x2000); + + for (volatile int i = 0; i < 100000; i++) + asm volatile("pause"); + + serial_writestring("ACPI shutdown: trying VirtualBox port 0x4004...\n"); + outw(0x4004, 0x3400); + + for (volatile int i = 0; i < 100000; i++) + asm volatile("pause"); + + serial_writestring("ACPI shutdown: trying legacy APM...\n"); + outw(0xB004, 0x2000); + + serial_writestring("ACPI shutdown: all methods failed, halting CPU\n"); + for (;;) + asm volatile("cli; hlt"); +} + +void acpi_reboot(void) { + acpi_fadt_t *fadt = (acpi_fadt_t *)acpi_find_table("FACP", 0); + asm volatile("cli"); + + if (fadt && fadt->header.length >= 129) { + if ((fadt->flags & (1 << 10)) && fadt->reset_reg.address) { + serial_printf("ACPI reboot: using FADT reset register " + "(space=%u addr=0x%llx val=0x%x)\n", + fadt->reset_reg.address_space_id, + (unsigned long long)fadt->reset_reg.address, + fadt->reset_value); + + acpi_write_gas(&fadt->reset_reg, fadt->reset_value); + + for (volatile int i = 0; i < 100000; i++) + asm volatile("pause"); + } + } + + serial_writestring("ACPI reboot: trying 8042 keyboard controller reset...\n"); + + for (int i = 0; i < 10000; i++) { + if (!(inb(0x64) & 0x02)) + break; + for (volatile int j = 0; j < 100; j++) + asm volatile("pause"); + } + + outb(0x64, 0xFE); + + for (volatile int i = 0; i < 100000; i++) + asm volatile("pause"); + + serial_writestring("ACPI reboot: triggering triple fault...\n"); + + struct { + uint16_t limit; + uint64_t base; + } __attribute__((packed)) null_idt = { 0, 0 }; + + asm volatile( + "lidt %0\n" + "int $3\n" + :: "m"(null_idt) + ); + + for (;;) + asm volatile("cli; hlt"); +} \ No newline at end of file diff --git a/kernel/src/apic/apic.c b/kernel/src/apic/apic.c new file mode 100644 index 0000000..f6b63e4 --- /dev/null +++ b/kernel/src/apic/apic.c @@ -0,0 +1,212 @@ +#include "../../include/apic/apic.h" +#include "../../include/io/serial.h" +#include "../../include/io/ports.h" +#include "../../include/acpi/acpi.h" +#include "../../include/memory/pmm.h" +#include "../../include/memory/vmm.h" +#include +#include + +uintptr_t lapic_base = 0; +uintptr_t ioapic_base = 0; +uintptr_t hpet_base = 0; +uint32_t hpet_period = 0; +static acpi_madt_t* madt = NULL; +static acpi_hpet_t* hpet_table = NULL; + +uint64_t g_hpet_boot_counter = 0; + +static inline uintptr_t phys_to_virt(uintptr_t phys) { + return phys + pmm_get_hhdm_offset(); +} + +static bool map_mmio_region(uintptr_t phys_base, size_t size) { + vmm_pagemap_t* kernel_pagemap = vmm_get_kernel_pagemap(); + if (!kernel_pagemap) return false; + + for (uintptr_t offset = 0; offset < size; offset += 0x1000) { + uintptr_t phys_addr = phys_base + offset; + uintptr_t virt_addr = phys_to_virt(phys_addr); + + uintptr_t mapped_phys; + if (vmm_virt_to_phys(kernel_pagemap, virt_addr, &mapped_phys)) { + if (mapped_phys != phys_addr) return false; + continue; + } + + if (!vmm_map_page(kernel_pagemap, virt_addr, phys_addr, + VMM_PRESENT | VMM_WRITE | VMM_NOEXEC)) { + return false; + } + } + + return true; +} + +bool hpet_init(void) { + hpet_table = (acpi_hpet_t*)acpi_find_table("HPET", 0); + if (!hpet_table) return false; + + uintptr_t phys_base = hpet_table->address; + if (!map_mmio_region(phys_base, 0x1000)) return false; + + hpet_base = phys_to_virt(phys_base); + + volatile uint32_t* hpet_regs = (volatile uint32_t*)hpet_base; + hpet_period = hpet_regs[HPET_PERIOD / 4]; + if (hpet_period == 0) return false; + + uint64_t config = *(volatile uint64_t*)(hpet_base + HPET_CONFIG); + config |= HPET_ENABLE_CNF; + if (hpet_table->legacy_replacement) config |= HPET_LEGACY_CNF; + *(volatile uint64_t*)(hpet_base + HPET_CONFIG) = config; + + for (volatile int _i = 0; _i < 1000; _i++) asm volatile("pause"); + g_hpet_boot_counter = hpet_read_counter(); + + return true; +} + +bool hpet_is_available(void) { + return hpet_base != 0 && hpet_period != 0; +} + +uint64_t hpet_read_counter(void) { + if (!hpet_base) return 0; + + if (hpet_table->counter_size) { + return *(volatile uint64_t*)(hpet_base + HPET_MAIN_COUNTER); + } else { + return *(volatile uint32_t*)(hpet_base + HPET_MAIN_COUNTER); + } +} + +uint64_t hpet_get_frequency(void) { + if (!hpet_period) return 0; + return 1000000000000000ULL / hpet_period; +} + +uint64_t hpet_elapsed_ns(void) { + if (!hpet_is_available()) return 0; + uint64_t delta = hpet_read_counter() - g_hpet_boot_counter; + return (delta * (uint64_t)hpet_period) / 1000000ULL; +} + +void hpet_sleep_ns(uint64_t nanoseconds) { + if (!hpet_base || !hpet_period) return; + + uint64_t ticks_needed = (nanoseconds * 1000000ULL) / hpet_period; + if (ticks_needed == 0) ticks_needed = 1; + + uint64_t start = hpet_read_counter(); + uint64_t target = start + ticks_needed; + + if (target > start) { + while (hpet_read_counter() < target) asm volatile("pause"); + } else { + while (hpet_read_counter() > start) asm volatile("pause"); + while (hpet_read_counter() < (target - 0xFFFFFFFFFFFFFFFFULL)) asm volatile("pause"); + } +} + +void hpet_sleep_us(uint64_t microseconds) { + hpet_sleep_ns(microseconds * 1000ULL); +} + +void hpet_sleep_ms(uint64_t milliseconds) { + hpet_sleep_ns(milliseconds * 1000000ULL); +} + +static void parse_madt(void) { + madt = (acpi_madt_t*)acpi_find_table("APIC", 0); + if (!madt) return; + + if (!map_mmio_region(madt->local_apic_address, 0x1000)) return; + lapic_base = phys_to_virt(madt->local_apic_address); + + uint8_t* entries = madt->entries; + uint32_t length = madt->header.length; + uint8_t* end = (uint8_t*)madt + length; + + while (entries < end) { + madt_entry_header_t* header = (madt_entry_header_t*)entries; + + switch (header->type) { + case MADT_ENTRY_LAPIC: { + madt_lapic_entry_t* lapic_entry = (madt_lapic_entry_t*)entries; + (void)lapic_entry; + break; + } + + case MADT_ENTRY_IOAPIC: { + madt_ioapic_entry_t* ioapic_entry = (madt_ioapic_entry_t*)entries; + if (!map_mmio_region(ioapic_entry->ioapic_address, 0x1000)) break; + ioapic_base = phys_to_virt(ioapic_entry->ioapic_address); + break; + } + + default: + break; + } + + entries += header->length; + } +} + +bool apic_is_available(void) { + return madt != NULL; +} + +void apic_init(void) { + parse_madt(); + if (!madt) return; + if (!lapic_base) return; + + hpet_init(); + lapic_enable(); + + if (ioapic_base) { + uint32_t max_redirects = ioapic_get_max_redirects(ioapic_base); + for (uint32_t i = 0; i < max_redirects; i++) { + ioapic_mask_irq(i); + } + } +} + +void apic_setup_irq(uint8_t irq, uint8_t vector, bool mask, uint32_t flags) { + if (!ioapic_base) return; + + uint32_t redir_flags = IOAPIC_DELIVERY_FIXED | flags; + if (mask) redir_flags |= IOAPIC_INT_MASKED; + + ioapic_redirect_irq(irq, vector, redir_flags); +} + +void apic_timer_calibrate(void) { + if (!hpet_is_available()) return; + + uint64_t measurement_time_ns = 10000000ULL; + uint64_t hpet_ticks_needed = (measurement_time_ns * 1000000ULL) / hpet_period; + + lapic_write(LAPIC_TIMER_DCR, 0x3); + lapic_write(LAPIC_TIMER_ICR, 0xFFFFFFFF); + lapic_write(LAPIC_TIMER, LAPIC_TIMER_MASKED | 0xFF); + lapic_write(LAPIC_TIMER, 0xFF); + + uint64_t hpet_start = hpet_read_counter(); + uint64_t hpet_target = hpet_start + hpet_ticks_needed; + + while (hpet_read_counter() < hpet_target) asm volatile("pause"); + + lapic_write(LAPIC_TIMER, LAPIC_TIMER_MASKED | 0xFF); + uint32_t remaining = lapic_read(LAPIC_TIMER_CCR); + + if (remaining == 0 || remaining == 0xFFFFFFFF) return; + + uint32_t ticks_elapsed = 0xFFFFFFFF - remaining; + uint32_t ticks_per_10ms = (uint32_t)((ticks_elapsed * 10000000ULL) / measurement_time_ns); + + if (ticks_per_10ms == 0) return; + + lapic_timer_init(0x20, ticks_per_10ms, true, 0x3); +} \ No newline at end of file diff --git a/kernel/src/apic/ioapic.c b/kernel/src/apic/ioapic.c new file mode 100644 index 0000000..c0917e6 --- /dev/null +++ b/kernel/src/apic/ioapic.c @@ -0,0 +1,79 @@ +#include "../../include/apic/apic.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include + +static void ioapic_write_internal(uintptr_t base, uint32_t reg, uint32_t value) { + if (!base) { + serial_printf("IOAPIC: Attempt to write to unmapped IOAPIC (reg: 0x%x)\n", reg); + return; + } + + volatile uint32_t* ioregsel = (volatile uint32_t*)base; + volatile uint32_t* iowin = (volatile uint32_t*)(base + 0x10); + + *ioregsel = reg; + *iowin = value; +} + +static uint32_t ioapic_read_internal(uintptr_t base, uint32_t reg) { + if (!base) { + serial_printf("IOAPIC: Attempt to read from unmapped IOAPIC (reg: 0x%x)\n", reg); + return 0; + } + + volatile uint32_t* ioregsel = (volatile uint32_t*)base; + volatile uint32_t* iowin = (volatile uint32_t*)(base + 0x10); + + *ioregsel = reg; + return *iowin; +} + +void ioapic_write(uintptr_t base, uint32_t reg, uint32_t value) { + ioapic_write_internal(base, reg, value); +} + +uint32_t ioapic_read(uintptr_t base, uint32_t reg) { + return ioapic_read_internal(base, reg); +} + +uint32_t ioapic_get_max_redirects(uintptr_t base) { + uint32_t version = ioapic_read(base, IOAPIC_VERSION); + return ((version >> 16) & 0xFF) + 1; +} + +void ioapic_redirect_irq(uint8_t irq, uint8_t vector, uint32_t flags) { + if (!ioapic_base) return; + + uint32_t max_redirects = ioapic_get_max_redirects(ioapic_base); + if (irq >= max_redirects) { + serial_printf("IOAPIC: IRQ %u out of range (max %u)\n", irq, max_redirects); + return; + } + + uint32_t low = vector | flags; + uint32_t high = 0; + + uint32_t redir_reg = IOAPIC_REDIR_START + irq * 2; + + ioapic_write(ioapic_base, redir_reg, low); + ioapic_write(ioapic_base, redir_reg + 1, high); + + serial_printf("IOAPIC: IRQ %u redirected to vector 0x%x\n", irq, vector); +} + +void ioapic_mask_irq(uint8_t irq) { + if (!ioapic_base) return; + + uint32_t redir_reg = IOAPIC_REDIR_START + irq * 2; + uint32_t current = ioapic_read(ioapic_base, redir_reg); + ioapic_write(ioapic_base, redir_reg, current | IOAPIC_INT_MASKED); +} + +void ioapic_unmask_irq(uint8_t irq) { + if (!ioapic_base) return; + + uint32_t redir_reg = IOAPIC_REDIR_START + irq * 2; + uint32_t current = ioapic_read(ioapic_base, redir_reg); + ioapic_write(ioapic_base, redir_reg, current & ~IOAPIC_INT_MASKED); +} \ No newline at end of file diff --git a/kernel/src/apic/lapic.c b/kernel/src/apic/lapic.c new file mode 100644 index 0000000..56bc211 --- /dev/null +++ b/kernel/src/apic/lapic.c @@ -0,0 +1,207 @@ +#include "../../include/apic/apic.h" +#include "../../include/io/serial.h" +#include "../../include/io/ports.h" +#include "../../include/memory/pmm.h" +#include "../../include/smp/smp.h" +#include "../../include/interrupts/interrupts.h" +#include + +static inline uintptr_t phys_to_virt(uintptr_t phys) { + return phys + pmm_get_hhdm_offset(); +} + +void lapic_write(uint32_t reg, uint32_t value) { + if (!lapic_base) { + serial_printf("LAPIC: Attempt to write to unmapped LAPIC (reg: 0x%x)\n", reg); + return; + } + + if (reg & 0x3) { + serial_printf("LAPIC: Unaligned register access: 0x%x\n", reg); + return; + } + + volatile uint32_t* addr = (volatile uint32_t*)(lapic_base + reg); + *addr = value; + + (void)*addr; +} + +uint32_t lapic_read(uint32_t reg) { + if (!lapic_base) { + serial_printf("LAPIC: Attempt to read from unmapped LAPIC (reg: 0x%x)\n", reg); + return 0; + } + + if (reg & 0x3) { + serial_printf("LAPIC: Unaligned register access: 0x%x\n", reg); + return 0; + } + + volatile uint32_t* addr = (volatile uint32_t*)(lapic_base + reg); + return *addr; +} + +void lapic_enable(void) { + if (!lapic_base) return; + + lapic_write(LAPIC_SIVR, lapic_read(LAPIC_SIVR) | LAPIC_ENABLE | LAPIC_SPURIOUS_VECTOR); + serial_printf("LAPIC enabled, ID: 0x%x\n", lapic_get_id()); +} + +uint32_t lapic_get_id(void) { + return (lapic_read(LAPIC_ID) >> 24) & 0xFF; +} + +void lapic_eoi(void) { + lapic_write(LAPIC_EOI, 0); +} + +void lapic_timer_init(uint32_t vector, uint32_t count, bool periodic, uint8_t divisor) { + serial_printf("Initializing LAPIC timer: vector=0x%x, count=%u, periodic=%d, divisor=%u\n", + vector, count, periodic, divisor); + + if (count == 0) { + serial_writestring("Warning: APIC timer count is 0, timer will not generate interrupts\n"); + } + + lapic_write(LAPIC_TIMER, LAPIC_TIMER_MASKED); + + lapic_write(LAPIC_TIMER_DCR, divisor & 0x7); + + lapic_write(LAPIC_TIMER_ICR, count); + + uint32_t timer_config = vector & 0xFF; + if (periodic) { + timer_config |= LAPIC_TIMER_PERIODIC; + } + + lapic_write(LAPIC_TIMER, timer_config); + + uint32_t current = lapic_read(LAPIC_TIMER_CCR); + serial_printf("LAPIC timer started: current count: %u\n", current); +} + +void lapic_timer_stop(void) { + lapic_write(LAPIC_TIMER, LAPIC_TIMER_MASKED); +} + +uint32_t lapic_timer_get_current(void) { + return lapic_read(LAPIC_TIMER_CCR); +} + +void lapic_send_ipi(uint32_t target_lapic_id, uint8_t vector) +{ + uint32_t icr_low = vector | (0 << 8) | (1 << 14); + uint32_t icr_high = target_lapic_id << 24; + + lapic_write(0x310, icr_high); + lapic_write(0x300, icr_low); + + while (lapic_read(0x300) & (1 << 12)) + asm volatile ("pause"); + + serial_printf("Sent IPI vector 0x%02x to LAPIC %u\n", vector, target_lapic_id); +} + +void lapic_send_ipi_to_all_but_self(uint8_t vector) +{ + uint32_t icr_low = vector | (0 << 8) | (1 << 14) | (3 << 18); + + lapic_write(0x310, 0); + lapic_write(0x300, icr_low); + + while (lapic_read(0x300) & (1 << 12)) + asm volatile ("pause"); + + serial_printf("Broadcast IPI (all but self) vector 0x%02x sent\n", vector); +} + +void lapic_send_nmi_to_all_but_self(void) +{ + uint32_t icr_low = 0x02 | (0x4 << 8) | (1 << 14) | (3 << 18); + + lapic_write(0x310, 0); + lapic_write(0x300, icr_low); + + for (int _w = 0; _w < 10000 && (lapic_read(0x300) & (1 << 12)); _w++) + asm volatile ("pause"); + + serial_printf("NMI broadcast (all but self) sent\n"); +} + +void ipi_reschedule_all(void) { + lapic_send_ipi_to_all_but_self(IPI_RESCHEDULE_VECTOR); +} + +void ipi_reschedule_cpu(uint32_t lapic_id) { + lapic_send_ipi(lapic_id, IPI_RESCHEDULE_VECTOR); +} + +void ipi_reschedule_single(uint32_t target_lapic_id) { + uint32_t icr_high = target_lapic_id << 24; + uint32_t icr_low = IPI_RESCHEDULE_VECTOR | (0 << 8) | (1 << 14); + + lapic_write(0x310, icr_high); + lapic_write(0x300, icr_low); + + while (lapic_read(0x300) & (1 << 12)) + asm volatile ("pause"); + + serial_printf("Reschedule IPI sent to LAPIC %u\n", target_lapic_id); +} + +void ipi_tlb_shootdown_broadcast(const uintptr_t* addrs, size_t count) { + if (count > MAX_TLB_ADDRESSES) count = MAX_TLB_ADDRESSES; + + uint32_t my_lapic = lapic_get_id(); + smp_info_t* info = smp_get_info(); + + for (uint32_t i = 0; i < info->cpu_count; i++) { + uint32_t target_lapic = info->cpus[i].lapic_id; + if (target_lapic == my_lapic) continue; + + tlb_shootdown_t* q = &tlb_shootdown_queue[target_lapic]; + q->count = count; + for (size_t j = 0; j < count; j++) + q->addresses[j] = addrs[j]; + __atomic_store_n(&q->pending, true, __ATOMIC_RELEASE); + } + + asm volatile ("lock addl $0, (%%rsp)" ::: "memory", "cc"); + + uint32_t icr_low = IPI_TLB_SHOOTDOWN + | (0 << 8) + | (1 << 14) + | (3 << 18); + + lapic_write(0x310, 0); + lapic_write(0x300, icr_low); + + while (lapic_read(0x300) & (1 << 12)) + asm volatile ("pause"); + + serial_printf("TLB shootdown broadcast sent for %zu addresses\n", count); +} + +void ipi_tlb_shootdown_single(uint32_t target_lapic_id, uintptr_t addr) { + tlb_shootdown_t* q = &tlb_shootdown_queue[target_lapic_id]; + + q->addresses[0] = addr; + q->count = 1; + __atomic_store_n(&q->pending, true, __ATOMIC_RELEASE); + + asm volatile ("lock addl $0, (%%rsp)" ::: "memory", "cc"); + + uint32_t icr_high = target_lapic_id << 24; + uint32_t icr_low = IPI_TLB_SHOOTDOWN | (0 << 8) | (1 << 14); + + lapic_write(0x310, icr_high); + lapic_write(0x300, icr_low); + + while (lapic_read(0x300) & (1 << 12)) + asm volatile ("pause"); + + serial_printf("TLB shootdown sent to LAPIC %u for virt 0x%llx\n", + target_lapic_id, addr); +} \ No newline at end of file diff --git a/kernel/src/drivers/ata.c b/kernel/src/drivers/ata.c new file mode 100644 index 0000000..0ecc4f9 --- /dev/null +++ b/kernel/src/drivers/ata.c @@ -0,0 +1,371 @@ +#include "../../include/drivers/ata.h" +#include "../../include/io/ports.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include "../../include/sched/spinlock.h" +#include "../../include/syscall/errno.h" +#include + +static ata_drive_t g_drives[ATA_MAX_DRIVES]; +static int g_drive_count = 0; +static spinlock_t g_ata_lock = SPINLOCK_INIT; + +static void ata_io_wait(uint16_t ctrl) { + inb(ctrl); inb(ctrl); inb(ctrl); inb(ctrl); +} + +static inline void ata_cpu_relax(void) { + asm volatile("pause" ::: "memory"); +} + +static void ata_soft_reset(uint16_t ctrl) { + outb(ctrl, 0x04); + ata_io_wait(ctrl); + outb(ctrl, 0x00); + ata_io_wait(ctrl); + + for (int i = 0; i < 2000000; i++) { + uint8_t s = inb(ctrl); + if (!(s & ATA_SR_BSY)) return; + ata_io_wait(ctrl); + ata_cpu_relax(); + } +} + +static int ata_wait_ready(uint16_t io, uint16_t ctrl, int timeout_us) { + (void)io; + ata_io_wait(ctrl); + for (int i = 0; i < timeout_us; i++) { + uint8_t s = inb(ctrl + ATA_REG_ALT_STATUS); + if (!(s & ATA_SR_BSY)) { + if (s & ATA_SR_ERR) return -EIO; + if (s & ATA_SR_DF) return -EIO; + return 0; + } + ata_io_wait(ctrl); + ata_cpu_relax(); + } + return -ETIMEDOUT; +} + +static int ata_wait_drq(uint16_t io, uint16_t ctrl, int timeout_us) { + (void)io; + ata_io_wait(ctrl); + for (int i = 0; i < timeout_us; i++) { + uint8_t s = inb(ctrl + ATA_REG_ALT_STATUS); + if (s & ATA_SR_ERR) return -EIO; + if (s & ATA_SR_DF) return -EIO; + if (!(s & ATA_SR_BSY) && (s & ATA_SR_DRQ)) return 0; + ata_io_wait(ctrl); + ata_cpu_relax(); + } + return -ETIMEDOUT; +} + +static void ata_fix_string(char *dst, const uint16_t *src, int words) { + for (int i = 0; i < words; i++) { + dst[i * 2 + 0] = (char)(src[i] >> 8); + dst[i * 2 + 1] = (char)(src[i] & 0xFF); + } + dst[words * 2] = '\0'; + for (int i = words * 2 - 1; i >= 0 && dst[i] == ' '; i--) + dst[i] = '\0'; +} + +static bool ata_identify_drive(uint16_t io, uint16_t ctrl, uint8_t drv_sel, ata_drive_t *out) { + memset(out, 0, sizeof(*out)); + out->io_base = io; + out->ctrl_base = ctrl; + out->drive_select = drv_sel; + + outb(io + ATA_REG_DRIVE, drv_sel); + ata_io_wait(ctrl); + + outb(io + ATA_REG_SECCOUNT, 0); + outb(io + ATA_REG_LBA_LO, 0); + outb(io + ATA_REG_LBA_MID, 0); + outb(io + ATA_REG_LBA_HI, 0); + + outb(io + ATA_REG_COMMAND, ATA_CMD_IDENTIFY); + ata_io_wait(ctrl); + + uint8_t status = inb(io + ATA_REG_STATUS); + if (status == 0) return false; + + for (int i = 0; i < 1000000; i++) { + status = inb(io + ATA_REG_STATUS); + if (!(status & ATA_SR_BSY)) break; + ata_cpu_relax(); + } + if (status & ATA_SR_BSY) return false; + + uint8_t lm = inb(io + ATA_REG_LBA_MID); + uint8_t lh = inb(io + ATA_REG_LBA_HI); + if (lm == 0x14 && lh == 0xEB) { + out->is_atapi = true; + return false; + } + if (lm == 0x3C && lh == 0xC3) { + + } + if (lm != 0 || lh != 0) { + return false; + } + + for (int i = 0; i < 1000000; i++) { + status = inb(io + ATA_REG_STATUS); + if (status & ATA_SR_ERR) return false; + if (status & ATA_SR_DRQ) break; + ata_cpu_relax(); + } + if (!(status & ATA_SR_DRQ)) return false; + + for (int i = 0; i < 256; i++) + out->identify[i] = inw(io + ATA_REG_DATA); + + ata_fix_string(out->model, &out->identify[27], 20); + ata_fix_string(out->serial, &out->identify[10], 10); + ata_fix_string(out->firmware, &out->identify[23], 4); + + if (out->identify[83] & (1 << 10)) { + out->lba48 = true; + out->sectors = (uint64_t)out->identify[100] + | ((uint64_t)out->identify[101] << 16) + | ((uint64_t)out->identify[102] << 32) + | ((uint64_t)out->identify[103] << 48); + } else { + out->lba48 = false; + out->sectors = (uint64_t)out->identify[60] + | ((uint64_t)out->identify[61] << 16); + } + out->size_bytes = out->sectors * ATA_SECTOR_SIZE; + out->present = true; + return true; +} + +void ata_init(void) { + serial_writestring("[ATA] probing drives...\n"); + g_drive_count = 0; + + struct { uint16_t io; uint16_t ctrl; uint8_t irq; } channels[] = { + { ATA_PRIMARY_IO, ATA_PRIMARY_CTRL, 14 }, + { ATA_SECONDARY_IO, ATA_SECONDARY_CTRL, 15 }, + }; + + for (int ch = 0; ch < 2; ch++) { + outb(channels[ch].ctrl, 0x02); + ata_soft_reset(channels[ch].ctrl); + + uint8_t drvs[] = { ATA_DRIVE_MASTER, ATA_DRIVE_SLAVE }; + for (int d = 0; d < 2; d++) { + int idx = ch * 2 + d; + ata_drive_t *drv = &g_drives[idx]; + if (ata_identify_drive(channels[ch].io, channels[ch].ctrl, + drvs[d], drv)) + { + drv->irq = channels[ch].irq; + g_drive_count++; + serial_printf("[ATA] drive %d: '%s' %llu sectors (%llu MB) %s\n", + idx, drv->model, drv->sectors, + drv->size_bytes / (1024 * 1024), + drv->lba48 ? "LBA48" : "LBA28"); + } + } + } + + if (g_drive_count == 0) + serial_writestring("[ATA] no drives found\n"); + else + serial_printf("[ATA] %d drive(s) detected\n", g_drive_count); +} + +ata_drive_t *ata_get_drive(int index) { + if (index < 0 || index >= ATA_MAX_DRIVES) return NULL; + if (!g_drives[index].present) return NULL; + return &g_drives[index]; +} + +int ata_get_drive_count(void) { + return g_drive_count; +} + +#define ATA_IO_TIMEOUT 20000000 +#define ATA_WRITE_TIMEOUT 40000000 +#define ATA_RETRY_COUNT 3 + +static int ata_read_sectors_once(ata_drive_t *drive, uint64_t lba, + uint32_t count, void *buffer) +{ + uint16_t io = drive->io_base; + uint16_t ctrl = drive->ctrl_base; + uint16_t *buf = (uint16_t *)buffer; + int ret = 0; + + outb(io + ATA_REG_DRIVE, drive->drive_select); + ata_io_wait(ctrl); + (void)inb(io + ATA_REG_STATUS); + ret = ata_wait_ready(io, ctrl, ATA_IO_TIMEOUT); + if (ret < 0) return ret; + + if (drive->lba48 && (lba > 0x0FFFFFFF || count > 256)) { + outb(io + ATA_REG_DRIVE, (drive->drive_select & 0xF0) | ATA_LBA_BIT); + ata_io_wait(ctrl); + + outb(io + ATA_REG_SECCOUNT, (uint8_t)(count >> 8)); + outb(io + ATA_REG_LBA_LO, (uint8_t)(lba >> 24)); + outb(io + ATA_REG_LBA_MID, (uint8_t)(lba >> 32)); + outb(io + ATA_REG_LBA_HI, (uint8_t)(lba >> 40)); + outb(io + ATA_REG_SECCOUNT, (uint8_t)(count)); + outb(io + ATA_REG_LBA_LO, (uint8_t)(lba)); + outb(io + ATA_REG_LBA_MID, (uint8_t)(lba >> 8)); + outb(io + ATA_REG_LBA_HI, (uint8_t)(lba >> 16)); + + outb(io + ATA_REG_COMMAND, ATA_CMD_READ_PIO_EXT); + } else { + outb(io + ATA_REG_DRIVE, + (drive->drive_select & 0xF0) | ATA_LBA_BIT | ((lba >> 24) & 0x0F)); + ata_io_wait(ctrl); + outb(io + ATA_REG_SECCOUNT, (uint8_t)count); + outb(io + ATA_REG_LBA_LO, (uint8_t)(lba)); + outb(io + ATA_REG_LBA_MID, (uint8_t)(lba >> 8)); + outb(io + ATA_REG_LBA_HI, (uint8_t)(lba >> 16)); + + outb(io + ATA_REG_COMMAND, ATA_CMD_READ_PIO); + } + + ata_io_wait(ctrl); + + for (uint32_t s = 0; s < count; s++) { + ret = ata_wait_drq(io, ctrl, ATA_IO_TIMEOUT); + if (ret < 0) return ret; + + for (int i = 0; i < 256; i++) + *buf++ = inw(io + ATA_REG_DATA); + + ata_io_wait(ctrl); + } + return 0; +} + +int ata_read_sectors(ata_drive_t *drive, uint64_t lba, + uint32_t count, void *buffer) +{ + if (!drive || !drive->present || !buffer) return -EINVAL; + if (count == 0) return 0; + if (lba + count > drive->sectors) return -EINVAL; + + uint64_t flags = spinlock_acquire_irqsave(&g_ata_lock); + + int ret = -EIO; + for (int attempt = 0; attempt < ATA_RETRY_COUNT; attempt++) { + ret = ata_read_sectors_once(drive, lba, count, buffer); + if (ret == 0) break; + serial_printf("[ATA] read lba=%llu count=%u attempt %d failed: %d\n", + lba, count, attempt + 1, ret); + ata_soft_reset(drive->ctrl_base); + for (volatile int k = 0; k < 100000; k++) ata_cpu_relax(); + } + + spinlock_release_irqrestore(&g_ata_lock, flags); + return ret; +} + +static int ata_write_sectors_once(ata_drive_t *drive, uint64_t lba, + uint32_t count, const void *buffer) +{ + uint16_t io = drive->io_base; + uint16_t ctrl = drive->ctrl_base; + const uint16_t *buf = (const uint16_t *)buffer; + int ret = 0; + + outb(io + ATA_REG_DRIVE, drive->drive_select); + ata_io_wait(ctrl); + (void)inb(io + ATA_REG_STATUS); + ret = ata_wait_ready(io, ctrl, ATA_IO_TIMEOUT); + if (ret < 0) return ret; + + if (drive->lba48 && (lba > 0x0FFFFFFF || count > 256)) { + outb(io + ATA_REG_DRIVE, (drive->drive_select & 0xF0) | ATA_LBA_BIT); + ata_io_wait(ctrl); + + outb(io + ATA_REG_SECCOUNT, (uint8_t)(count >> 8)); + outb(io + ATA_REG_LBA_LO, (uint8_t)(lba >> 24)); + outb(io + ATA_REG_LBA_MID, (uint8_t)(lba >> 32)); + outb(io + ATA_REG_LBA_HI, (uint8_t)(lba >> 40)); + + outb(io + ATA_REG_SECCOUNT, (uint8_t)(count)); + outb(io + ATA_REG_LBA_LO, (uint8_t)(lba)); + outb(io + ATA_REG_LBA_MID, (uint8_t)(lba >> 8)); + outb(io + ATA_REG_LBA_HI, (uint8_t)(lba >> 16)); + + outb(io + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO_EXT); + } else { + outb(io + ATA_REG_DRIVE, + (drive->drive_select & 0xF0) | ATA_LBA_BIT | ((lba >> 24) & 0x0F)); + ata_io_wait(ctrl); + outb(io + ATA_REG_SECCOUNT, (uint8_t)count); + outb(io + ATA_REG_LBA_LO, (uint8_t)(lba)); + outb(io + ATA_REG_LBA_MID, (uint8_t)(lba >> 8)); + outb(io + ATA_REG_LBA_HI, (uint8_t)(lba >> 16)); + + outb(io + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO); + } + + ata_io_wait(ctrl); + + for (uint32_t s = 0; s < count; s++) { + ret = ata_wait_drq(io, ctrl, ATA_WRITE_TIMEOUT); + if (ret < 0) return ret; + + for (int i = 0; i < 256; i++) + outw(io + ATA_REG_DATA, *buf++); + + ata_io_wait(ctrl); + } + + ret = ata_wait_ready(io, ctrl, ATA_IO_TIMEOUT); + if (ret < 0) return ret; + + return 0; +} + +int ata_write_sectors(ata_drive_t *drive, uint64_t lba, + uint32_t count, const void *buffer) +{ + if (!drive || !drive->present || !buffer) return -EINVAL; + if (count == 0) return 0; + if (lba + count > drive->sectors) return -EINVAL; + + uint64_t flags = spinlock_acquire_irqsave(&g_ata_lock); + + int ret = -EIO; + for (int attempt = 0; attempt < ATA_RETRY_COUNT; attempt++) { + ret = ata_write_sectors_once(drive, lba, count, buffer); + if (ret == 0) break; + serial_printf("[ATA] write lba=%llu count=%u attempt %d failed: %d\n", + lba, count, attempt + 1, ret); + ata_soft_reset(drive->ctrl_base); + for (volatile int k = 0; k < 100000; k++) ata_cpu_relax(); + } + + spinlock_release_irqrestore(&g_ata_lock, flags); + return ret; +} + +int ata_flush(ata_drive_t *drive) { + if (!drive || !drive->present) return -EINVAL; + + uint64_t flags = spinlock_acquire_irqsave(&g_ata_lock); + + uint16_t io = drive->io_base; + uint16_t ctrl = drive->ctrl_base; + + outb(io + ATA_REG_DRIVE, drive->drive_select); + outb(io + ATA_REG_COMMAND, + drive->lba48 ? ATA_CMD_CACHE_FLUSH_EXT : ATA_CMD_CACHE_FLUSH); + + int ret = ata_wait_ready(io, ctrl, ATA_IO_TIMEOUT); + + spinlock_release_irqrestore(&g_ata_lock, flags); + return ret; +} \ No newline at end of file diff --git a/kernel/src/drivers/blkdev.c b/kernel/src/drivers/blkdev.c new file mode 100644 index 0000000..6ec2e75 --- /dev/null +++ b/kernel/src/drivers/blkdev.c @@ -0,0 +1,95 @@ +#include "../../include/drivers/blkdev.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include "../../include/syscall/errno.h" +#include + +static blkdev_t *g_blkdevs[BLKDEV_MAX]; +static int g_blkdev_count = 0; + +void blkdev_init(void) { + memset(g_blkdevs, 0, sizeof(g_blkdevs)); + g_blkdev_count = 0; + serial_writestring("[blkdev] initialized\n"); +} + +int blkdev_register(blkdev_t *dev) { + if (!dev || g_blkdev_count >= BLKDEV_MAX) return -ENOMEM; + int idx = g_blkdev_count; + g_blkdevs[idx] = dev; + g_blkdev_count++; + serial_printf("[blkdev] registered '%s' (%llu sectors, %llu MB)\n", + dev->name, dev->sector_count, + dev->size_bytes / (1024 * 1024)); + return idx; +} + +blkdev_t *blkdev_get_by_name(const char *name) { + for (int i = 0; i < g_blkdev_count; i++) { + if (g_blkdevs[i] && strcmp(g_blkdevs[i]->name, name) == 0) + return g_blkdevs[i]; + } + return NULL; +} + +blkdev_t *blkdev_get(int index) { + if (index < 0 || index >= g_blkdev_count) return NULL; + return g_blkdevs[index]; +} + +int blkdev_count(void) { + return g_blkdev_count; +} + +int blkdev_read(blkdev_t *dev, uint64_t offset, void *buf, size_t len) { + if (!dev || !dev->ops || !dev->ops->read_sectors) return -EIO; + if (len == 0) return 0; + + uint32_t sec_size = dev->sector_size ? dev->sector_size : BLKDEV_SECTOR_SIZE; + uint64_t start_lba = offset / sec_size; + uint64_t end_byte = offset + len; + uint64_t end_lba = (end_byte + sec_size - 1) / sec_size; + uint32_t nsectors = (uint32_t)(end_lba - start_lba); + + uint8_t *tmp = kmalloc((size_t)nsectors * sec_size); + if (!tmp) return -ENOMEM; + + int ret = dev->ops->read_sectors(dev, start_lba, nsectors, tmp); + if (ret < 0) { + kfree(tmp); + return ret; + } + + uint64_t off_in_buf = offset - start_lba * sec_size; + memcpy(buf, tmp + off_in_buf, len); + kfree(tmp); + return 0; +} + +int blkdev_write(blkdev_t *dev, uint64_t offset, const void *buf, size_t len) { + if (!dev || !dev->ops || !dev->ops->write_sectors) return -EIO; + if (len == 0) return 0; + + uint32_t sec_size = dev->sector_size ? dev->sector_size : BLKDEV_SECTOR_SIZE; + uint64_t start_lba = offset / sec_size; + uint64_t end_byte = offset + len; + uint64_t end_lba = (end_byte + sec_size - 1) / sec_size; + uint32_t nsectors = (uint32_t)(end_lba - start_lba); + + uint8_t *tmp = kmalloc((size_t)nsectors * sec_size); + if (!tmp) return -ENOMEM; + + int ret = dev->ops->read_sectors(dev, start_lba, nsectors, tmp); + if (ret < 0) { + kfree(tmp); + return ret; + } + + uint64_t off_in_buf = offset - start_lba * sec_size; + memcpy(tmp + off_in_buf, buf, len); + + ret = dev->ops->write_sectors(dev, start_lba, nsectors, tmp); + kfree(tmp); + + return ret; +} \ No newline at end of file diff --git a/kernel/src/drivers/disk.c b/kernel/src/drivers/disk.c new file mode 100644 index 0000000..915bd54 --- /dev/null +++ b/kernel/src/drivers/disk.c @@ -0,0 +1,164 @@ +#include "../../include/drivers/disk.h" +#include "../../include/drivers/ata.h" +#include "../../include/drivers/blkdev.h" +#include "../../include/drivers/partition.h" +#include "../../include/fs/ext2.h" +#include "../../include/fs/fat32.h" +#include "../../include/fs/vfs.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include "../../include/syscall/errno.h" +#include +#include + +static int ata_blk_read(blkdev_t *dev, uint64_t lba, uint32_t count, void *buf) { + ata_drive_t *drv = (ata_drive_t *)dev->priv; + return ata_read_sectors(drv, lba, count, buf); +} +static int ata_blk_write(blkdev_t *dev, uint64_t lba, uint32_t count, const void *buf) { + ata_drive_t *drv = (ata_drive_t *)dev->priv; + return ata_write_sectors(drv, lba, count, buf); +} +static int ata_blk_flush(blkdev_t *dev) { + ata_drive_t *drv = (ata_drive_t *)dev->priv; + return ata_flush(drv); +} + +static const blkdev_ops_t ata_blkdev_ops = { + .read_sectors = ata_blk_read, + .write_sectors = ata_blk_write, + .flush = ata_blk_flush, +}; + +static blkdev_t g_ata_blkdevs[ATA_MAX_DRIVES]; +extern void devfs_register(const char *name, vnode_t *node); + +static int64_t blk_vnode_read(vnode_t *node, void *buf, size_t len, uint64_t offset) { + blkdev_t *dev = (blkdev_t *)node->fs_data; + if (!dev) return -EIO; + int r = blkdev_read(dev, offset, buf, len); + return (r < 0) ? r : (int64_t)len; +} +static int64_t blk_vnode_write(vnode_t *node, const void *buf, size_t len, uint64_t offset) { + blkdev_t *dev = (blkdev_t *)node->fs_data; + if (!dev) return -EIO; + int r = blkdev_write(dev, offset, buf, len); + return (r < 0) ? r : (int64_t)len; +} +static int blk_vnode_stat(vnode_t *node, vfs_stat_t *out) { + blkdev_t *dev = (blkdev_t *)node->fs_data; + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = VFS_NODE_BLKDEV; + out->st_mode = 0660; + out->st_size = dev ? dev->size_bytes : 0; + return 0; +} +static void blk_vnode_ref(vnode_t *n) { (void)n; } +static void blk_vnode_unref(vnode_t *n) { (void)n; } + +static const vnode_ops_t blk_vnode_ops = { + .read = blk_vnode_read, .write = blk_vnode_write, + .stat = blk_vnode_stat, .ref = blk_vnode_ref, .unref = blk_vnode_unref, +}; + +static vnode_t g_blk_vnodes[ATA_MAX_DRIVES]; +static uint64_t g_blk_ino_base = 200; + +void disk_init(void) { + serial_writestring("[disk] initializing...\n"); + blkdev_init(); + ata_init(); + int count = 0; + const char *names[] = { "hda", "hdb", "hdc", "hdd" }; + for (int i = 0; i < ATA_MAX_DRIVES; i++) { + ata_drive_t *drv = ata_get_drive(i); + if (!drv) continue; + blkdev_t *bdev = &g_ata_blkdevs[count]; + memset(bdev, 0, sizeof(*bdev)); + strncpy(bdev->name, names[i], BLKDEV_NAME_MAX - 1); + bdev->present = true; + bdev->sector_count = drv->sectors; + bdev->size_bytes = drv->size_bytes; + bdev->sector_size = ATA_SECTOR_SIZE; + bdev->ops = &ata_blkdev_ops; + bdev->priv = drv; + blkdev_register(bdev); + vnode_t *vn = &g_blk_vnodes[count]; + memset(vn, 0, sizeof(*vn)); + vn->type = VFS_NODE_BLKDEV; + vn->mode = 0660; + vn->ino = g_blk_ino_base + (uint64_t)count; + vn->ops = &blk_vnode_ops; + vn->fs_data = bdev; + vn->size = drv->size_bytes; + vn->refcount = 1; + devfs_register(names[i], vn); + serial_printf("[disk] /dev/%s -> %s (%llu MB)\n", + names[i], drv->model, drv->size_bytes / (1024 * 1024)); + printf("[disk] /dev/%s -> %s (%llu MB)\n", + names[i], drv->model, drv->size_bytes / (1024 * 1024)); + + partition_scan(bdev); + count++; + } + if (count == 0) serial_writestring("[disk] no disks available\n"); + else { serial_printf("[disk] %d disk(s) ready\n", count); printf("[disk] %d disk(s) ready\n", count); } +} + +static const char *strip_dev_prefix(const char *name) { + if (strncmp(name, "/dev/", 5) == 0) return name + 5; + return name; +} + +int disk_format(const char *devname, const char *label) { + const char *raw = strip_dev_prefix(devname); + blkdev_t *dev = blkdev_get_by_name(raw); + if (!dev) return -ENODEV; + return ext2_format(dev, label ? label : raw); +} + +static int detect_fs_type(blkdev_t *dev) { + uint8_t sec[512]; + if (dev->ops->read_sectors(dev, 0, 1, sec) < 0) return -1; + if (sec[510] == 0x55 && sec[511] == (uint8_t)0xAA) { + if (memcmp(sec + 82, "FAT32", 5) == 0) return 1; + if (memcmp(sec + 54, "FAT", 3) == 0) return 1; + } + uint16_t magic = 0; + if (blkdev_read(dev, EXT2_SUPER_OFFSET + 56, &magic, sizeof(magic)) == 0 + && magic == EXT2_SUPER_MAGIC) return 2; + return 0; +} + +int disk_mount(const char *devname, const char *path) { + const char *raw = strip_dev_prefix(devname); + blkdev_t *dev = blkdev_get_by_name(raw); + if (!dev) return -ENODEV; + + int t = detect_fs_type(dev); + vnode_t *root = NULL; + if (t == 1) { + root = fat32_mount(dev); + serial_printf("[disk] mounting FAT32 %s -> %s\n", raw, path); + } else if (t == 2) { + root = ext2_mount(dev); + serial_printf("[disk] mounting ext2 %s -> %s\n", raw, path); + } else { + serial_printf("[disk] %s: no recognizable FS\n", raw); + return -EINVAL; + } + if (!root) return -EIO; + int r = vfs_mount(path, root); + if (r < 0) { vnode_unref(root); return r; } + + const char *fsname = (t == 1) ? "fat32" : "ext2"; + int si = vfs_set_mount_info(path, raw, fsname); + serial_printf("[disk_mount] set_mount_info path='%s' dev='%s' fs='%s' -> %d\n", + path, raw, fsname, si); + return 0; +} + +int disk_umount(const char *path) { + return vfs_umount(path); +} \ No newline at end of file diff --git a/kernel/src/drivers/partition.c b/kernel/src/drivers/partition.c new file mode 100644 index 0000000..33fac01 --- /dev/null +++ b/kernel/src/drivers/partition.c @@ -0,0 +1,204 @@ +#include "../../include/drivers/partition.h" +#include "../../include/drivers/blkdev.h" +#include "../../include/fs/vfs.h" +#include "../../include/io/serial.h" +#include "../../include/syscall/errno.h" +#include "../../include/memory/pmm.h" +#include +#include + +#define MAX_PARTITIONS 16 + +typedef struct { + blkdev_t base; + blkdev_t *parent; + uint64_t offset_sectors; + uint64_t count_sectors; + uint8_t type; + uint8_t bootable; + uint32_t partnum; +} partition_blkdev_t; + +static partition_blkdev_t g_partitions[MAX_PARTITIONS]; +static int g_partition_count = 0; + +extern void devfs_register(const char *name, vnode_t *node); + +static int64_t part_vnode_read(vnode_t *node, void *buf, size_t len, uint64_t offset) { + blkdev_t *dev = (blkdev_t *)node->fs_data; + if (!dev) return -EIO; + int r = blkdev_read(dev, offset, buf, len); + return (r < 0) ? r : (int64_t)len; +} +static int64_t part_vnode_write(vnode_t *node, const void *buf, size_t len, uint64_t offset) { + blkdev_t *dev = (blkdev_t *)node->fs_data; + if (!dev) return -EIO; + int r = blkdev_write(dev, offset, buf, len); + return (r < 0) ? r : (int64_t)len; +} +static int part_vnode_stat(vnode_t *node, vfs_stat_t *out) { + blkdev_t *dev = (blkdev_t *)node->fs_data; + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = VFS_NODE_BLKDEV; + out->st_mode = 0660; + out->st_size = dev ? dev->size_bytes : 0; + return 0; +} +static void part_vnode_ref(vnode_t *n) { (void)n; } +static void part_vnode_unref(vnode_t *n) { (void)n; } + +static const vnode_ops_t part_vnode_ops = { + .read = part_vnode_read, .write = part_vnode_write, + .stat = part_vnode_stat, .ref = part_vnode_ref, .unref = part_vnode_unref, +}; + +static vnode_t g_part_vnodes[MAX_PARTITIONS]; +static uint64_t g_part_ino_base = 300; + +static int part_read_sectors(blkdev_t *dev, uint64_t lba, uint32_t count, void *buf) { + partition_blkdev_t *p = (partition_blkdev_t *)dev->priv; + if (!p || !p->parent) return -EIO; + if (lba + count > p->count_sectors) return -EINVAL; + return p->parent->ops->read_sectors(p->parent, p->offset_sectors + lba, count, buf); +} + +static int part_write_sectors(blkdev_t *dev, uint64_t lba, uint32_t count, const void *buf) { + partition_blkdev_t *p = (partition_blkdev_t *)dev->priv; + if (!p || !p->parent) return -EIO; + if (lba + count > p->count_sectors) return -EINVAL; + return p->parent->ops->write_sectors(p->parent, p->offset_sectors + lba, count, buf); +} + +static int part_flush(blkdev_t *dev) { + partition_blkdev_t *p = (partition_blkdev_t *)dev->priv; + if (!p || !p->parent) return -EIO; + return p->parent->ops->flush ? p->parent->ops->flush(p->parent) : 0; +} + +static const blkdev_ops_t part_blkdev_ops = { + .read_sectors = part_read_sectors, + .write_sectors = part_write_sectors, + .flush = part_flush, +}; + +int partition_read_mbr(blkdev_t *disk, mbr_t *out) { + if (!disk || !out) return -EINVAL; + uint8_t sector[512]; + int r = disk->ops->read_sectors(disk, 0, 1, sector); + if (r < 0) return r; + memcpy(out, sector, 512); + return 0; +} + +int partition_write_mbr(blkdev_t *disk, const mbr_partition_t parts[4], + uint32_t disk_signature) +{ + if (!disk || !parts) return -EINVAL; + uint8_t sector[512]; + int r = disk->ops->read_sectors(disk, 0, 1, sector); + if (r < 0) return r; + + mbr_t *mbr = (mbr_t *)sector; + mbr->disk_signature = disk_signature; + mbr->reserved = 0; + for (int i = 0; i < 4; i++) mbr->partitions[i] = parts[i]; + mbr->signature = MBR_SIGNATURE; + + return disk->ops->write_sectors(disk, 0, 1, sector); +} + +static const char *part_type_name(uint8_t t) { + switch (t) { + case MBR_TYPE_EMPTY: return "empty"; + case MBR_TYPE_FAT12: return "FAT12"; + case MBR_TYPE_FAT16_S: return "FAT16 <32M"; + case MBR_TYPE_FAT16: return "FAT16"; + case MBR_TYPE_EXTENDED: return "Extended"; + case MBR_TYPE_FAT32_CHS: return "FAT32 CHS"; + case MBR_TYPE_FAT32_LBA: return "FAT32 LBA"; + case MBR_TYPE_FAT16_LBA: return "FAT16 LBA"; + case MBR_TYPE_LINUX: return "Linux"; + case MBR_TYPE_ESP: return "EFI System"; + default: return "unknown"; + } +} + +int partition_scan(blkdev_t *disk) { + if (!disk || !disk->present) return -ENODEV; + + mbr_t mbr; + int r = partition_read_mbr(disk, &mbr); + if (r < 0) { + serial_printf("[part] %s: cannot read MBR: %d\n", disk->name, r); + return r; + } + + if (mbr.signature != MBR_SIGNATURE) { + serial_printf("[part] %s: no MBR signature (raw disk / unpartitioned)\n", + disk->name); + return 0; + } + + int found = 0; + for (int i = 0; i < 4; i++) { + mbr_partition_t *p = &mbr.partitions[i]; + if (p->type == 0 || p->sector_count == 0) continue; + if (g_partition_count >= MAX_PARTITIONS) break; + + partition_blkdev_t *pb = &g_partitions[g_partition_count]; + memset(pb, 0, sizeof(*pb)); + + pb->parent = disk; + pb->offset_sectors = p->lba_start; + pb->count_sectors = p->sector_count; + pb->type = p->type; + pb->bootable = (p->boot_flag == 0x80) ? 1 : 0; + pb->partnum = (uint32_t)(i + 1); + + snprintf(pb->base.name, BLKDEV_NAME_MAX, "%s%u", disk->name, pb->partnum); + pb->base.present = true; + pb->base.sector_count = pb->count_sectors; + pb->base.size_bytes = pb->count_sectors * (uint64_t)disk->sector_size; + pb->base.sector_size = disk->sector_size; + pb->base.ops = &part_blkdev_ops; + pb->base.priv = pb; + + blkdev_register(&pb->base); + + vnode_t *vn = &g_part_vnodes[g_partition_count]; + memset(vn, 0, sizeof(*vn)); + vn->type = VFS_NODE_BLKDEV; + vn->mode = 0660; + vn->ino = g_part_ino_base + (uint64_t)g_partition_count; + vn->ops = &part_vnode_ops; + vn->fs_data = &pb->base; + vn->size = pb->base.size_bytes; + vn->refcount = 1; + devfs_register(pb->base.name, vn); + + serial_printf("[part] /dev/%s: type=0x%02x (%s) lba=%u sectors=%u %s\n", + pb->base.name, pb->type, part_type_name(pb->type), + p->lba_start, p->sector_count, + pb->bootable ? "[bootable]" : ""); + printf("[part] /dev/%s: 0x%02x (%s) %u MB %s\n", + pb->base.name, pb->type, part_type_name(pb->type), + (unsigned)(pb->base.size_bytes / (1024 * 1024)), + pb->bootable ? "*" : ""); + + g_partition_count++; + found++; + } + + if (found == 0) { + serial_printf("[part] %s: MBR present but no valid partitions\n", disk->name); + } + return found; +} + +blkdev_t *partition_get(const char *name) { + for (int i = 0; i < g_partition_count; i++) { + if (strcmp(g_partitions[i].base.name, name) == 0) return &g_partitions[i].base; + } + return NULL; +} \ No newline at end of file diff --git a/kernel/src/drivers/ps2.c b/kernel/src/drivers/ps2.c new file mode 100644 index 0000000..6d98683 --- /dev/null +++ b/kernel/src/drivers/ps2.c @@ -0,0 +1,505 @@ +#include "../../include/drivers/ps2.h" +#include "../../include/sched/sched.h" +#include "../../include/fs/devfs.h" +#include "../../include/drivers/timer.h" +#include "../../include/interrupts/interrupts.h" +#include "../../include/apic/apic.h" +#include "../../include/io/ports.h" +#include "../../include/io/serial.h" +#include "../../include/graphics/fb/fb.h" +#include +#include + +#define KB_IRQ_VECTOR 0x21 +#define MOUSE_IRQ_VECTOR 0x2C +#define KB_IRQ_LINE 1 +#define MOUSE_IRQ_LINE 12 + +typedef enum { MOUSE_MODE_RELATIVE = 0, MOUSE_MODE_ABSOLUTE } mouse_mode_t; + +static volatile kb_state_t kb_state; +static volatile mouse_state_t mouse_state; +static volatile mouse_mode_t mouse_mode = MOUSE_MODE_RELATIVE; +static volatile int32_t mouse_screen_w = 1024; +static volatile int32_t mouse_screen_h = 768; + +static volatile uint8_t mouse_packet[4]; +static volatile uint8_t mouse_packet_idx = 0; +static volatile bool mouse_has_scroll = false; +static volatile uint32_t mouse_lost_sync = 0; + +static volatile kb_buf_t kb_buf; + +static const char sc_lower[89] = { + 0, '\x1b','1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', '\n', 0, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', 0, '*', + 0, ' ', 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const char sc_upper[89] = { + 0, '\x1b','!', '@', '#', '$', '%', '^', + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '{', '}', '\n', 0, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', + '"', '~', 0, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', 0, '*', + 0, ' ', 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + +static void ps2_wait_read(void) { + for (int i = 0; i < 100000; i++) { + if (inb(PS2_STATUS_PORT) & PS2_STATUS_OUTPUT_FULL) return; + io_wait(); + } +} + +static void ps2_wait_write(void) { + for (int i = 0; i < 100000; i++) { + if (!(inb(PS2_STATUS_PORT) & PS2_STATUS_INPUT_FULL)) return; + io_wait(); + } +} + +static void ps2_send_cmd(uint8_t cmd) { ps2_wait_write(); outb(PS2_CMD_PORT, cmd); } +static void ps2_send_data(uint8_t data) { ps2_wait_write(); outb(PS2_DATA_PORT, data); } +static uint8_t ps2_recv_data(void) { ps2_wait_read(); return inb(PS2_DATA_PORT); } + +static void ps2_flush_output(void) { + for (int i = 0; i < 32; i++) { + if (!(inb(PS2_STATUS_PORT) & PS2_STATUS_OUTPUT_FULL)) break; + inb(PS2_DATA_PORT); + io_wait(); + } +} + +static bool ps2_mouse_wait_ack(void) { + for (int i = 0; i < 100000; i++) { + if (inb(PS2_STATUS_PORT) & PS2_STATUS_OUTPUT_FULL) { + uint8_t r = inb(PS2_DATA_PORT); + if (r == 0xFA) return true; + if (r == 0xFE || r == 0xFC) return false; + } + io_wait(); + } + serial_writestring("[PS2] mouse_wait_ack: TIMEOUT\n"); + return false; +} + +static bool ps2_mouse_cmd(uint8_t cmd) { + ps2_send_cmd(PS2_CMD_WRITE_PORT2); + ps2_send_data(cmd); + return ps2_mouse_wait_ack(); +} + +static bool ps2_mouse_cmd_param(uint8_t cmd, uint8_t param) { + return ps2_mouse_cmd(cmd) && ps2_mouse_cmd(param); +} + +static uint8_t ps2_mouse_read_byte(void) { + for (int i = 0; i < 100000; i++) { + if (inb(PS2_STATUS_PORT) & PS2_STATUS_OUTPUT_FULL) + return inb(PS2_DATA_PORT); + io_wait(); + } + return 0xFF; +} + +static uint8_t ps2_mouse_get_id(void) { + ps2_mouse_cmd(0xF2); + return ps2_mouse_read_byte(); +} + +static bool ps2_mouse_init_scroll(void) { + ps2_mouse_cmd_param(0xF3, 200); + ps2_mouse_cmd_param(0xF3, 100); + ps2_mouse_cmd_param(0xF3, 80); + uint8_t id = ps2_mouse_get_id(); + serial_printf("[PS2] Mouse ID after IntelliMouse knock: 0x%02x\n", id); + return (id == 0x03); +} + +static bool ps2_detect_absolute(void) { + uint8_t id = ps2_mouse_get_id(); + serial_printf("[PS2] Mouse base device ID: 0x%02x\n", id); + + if (!ps2_mouse_cmd(0xE9)) { + serial_writestring("[PS2] Status request failed\n"); + return false; + } + + uint8_t status = ps2_mouse_read_byte(); + uint8_t resolution = ps2_mouse_read_byte(); + uint8_t sample_rate = ps2_mouse_read_byte(); + serial_printf("[PS2] Mouse status: flags=0x%02x res=0x%02x rate=%d\n", + status, resolution, sample_rate); + + if (resolution == 0x08) { + serial_writestring("[PS2] Detected: QEMU absolute tablet mode\n"); + return true; + } + + ps2_mouse_cmd_param(0xF3, 200); ps2_mouse_cmd_param(0xF3, 100); ps2_mouse_cmd_param(0xF3, 80); + ps2_mouse_cmd_param(0xF3, 200); ps2_mouse_cmd_param(0xF3, 100); ps2_mouse_cmd_param(0xF3, 80); + + id = ps2_mouse_get_id(); + serial_printf("[PS2] Mouse ID after abs knock: 0x%02x\n", id); + if (id == 0x08) { + serial_writestring("[PS2] Detected: QEMU absolute mode (ID=0x08)\n"); + return true; + } + + return false; +} + +static char scancode_to_char(uint8_t sc) { + if (sc >= sizeof(sc_lower) / sizeof(sc_lower[0])) return 0; + bool use_upper = kb_state.shift; + if ((sc >= 0x10 && sc <= 0x19) || + (sc >= 0x1E && sc <= 0x26) || + (sc >= 0x2C && sc <= 0x32)) + use_upper = kb_state.caps_lock ^ kb_state.shift; + return use_upper ? sc_upper[sc] : sc_lower[sc]; +} + +void kb_buf_push(char c) { + uint8_t next = (kb_buf.tail + 1) % KB_BUF_SIZE; + if (next != kb_buf.head) { + kb_buf.buf[kb_buf.tail] = c; + kb_buf.tail = next; + } +} + +extern uint32_t cursor_x; +extern uint32_t cursor_y; +extern uint32_t bg_color; +extern struct limine_framebuffer *global_framebuffer; +extern void get_cursor_position(uint32_t *x, uint32_t *y); +extern uint32_t get_screen_width(void); + +/*static void mouse_print_status(void) { + const mouse_state_t *m = (const mouse_state_t *)&mouse_state; + const char *scroll_str = "NONE"; + if (m->scroll == MOUSE_SCROLL_UP) scroll_str = "UP "; + if (m->scroll == MOUSE_SCROLL_DOWN) scroll_str = "DOWN"; + + serial_printf("[MOUSE] X:%5d Y:%5d L:%d M:%d R:%d Scroll:%s\n", + m->x, m->y, + (int)m->btn_left, (int)m->btn_middle, (int)m->btn_right, + scroll_str); + + uint32_t saved_x, saved_y; + get_cursor_position(&saved_x, &saved_y); + + if (global_framebuffer) + fb_fill_rect(global_framebuffer, 0, 0, global_framebuffer->width, 16, bg_color); + + cursor_x = 0; cursor_y = 0; + printf("[MOUSE] X:%-5d Y:%-5d [L:%d M:%d R:%d] Wheel:%s", + m->x, m->y, + (int)m->btn_left, (int)m->btn_middle, (int)m->btn_right, + scroll_str); + + cursor_x = saved_x; + cursor_y = saved_y; +}*/ + +DEFINE_IRQ(KB_IRQ_VECTOR, ps2_kb_handler) +{ + (void)frame; + static bool e0_prefix = false; + uint8_t sc = inb(PS2_DATA_PORT); + bool released = (sc & PS2_KEY_RELEASE_BIT) != 0; + uint8_t key = sc & ~PS2_KEY_RELEASE_BIT; + + if (sc == 0xE0) { e0_prefix = true; lapic_eoi(); return; } + + if (e0_prefix) { + e0_prefix = false; + if (!released) { + switch (key) { + case 0x48: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('A'); break; + case 0x50: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('B'); break; + case 0x4D: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('C'); break; + case 0x4B: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('D'); break; + case 0x47: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('H'); break; + case 0x4F: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('F'); break; + case 0x53: kb_buf_push('\x1b'); kb_buf_push('['); kb_buf_push('3'); kb_buf_push('~'); break; + default: break; + } + } + lapic_eoi(); + return; + } + + if (key == SC_LSHIFT || key == SC_RSHIFT) { kb_state.shift = !released; lapic_eoi(); return; } + if (key == SC_LCTRL) { kb_state.ctrl = !released; lapic_eoi(); return; } + if (key == SC_LALT) { kb_state.alt = !released; lapic_eoi(); return; } + if (key == SC_CAPS && !released) { kb_state.caps_lock = !kb_state.caps_lock; lapic_eoi(); return; } + if (released) { lapic_eoi(); return; } + + if (kb_state.ctrl) { + char base = scancode_to_char(key); + serial_printf("[PS2] ctrl+key sc=0x%02x base='%c'(0x%02x)\n", (unsigned)key, (base>=0x20&&base<0x7f)?base:'?', (unsigned)(uint8_t)base); + if (base >= 'a' && base <= 'z') { + uint8_t ctrl_char = (uint8_t)(base - 'a' + 1); + serial_printf("[PS2] ctrl char generated: 0x%02x\n", ctrl_char); + if (ctrl_char == 0x03) { + g_ctrlc_pending = 1; + lapic_eoi(); + return; + } + kb_buf_push((char)ctrl_char); + lapic_eoi(); + return; + } + if (base >= 'A' && base <= 'Z') { + kb_buf_push((char)(base - 'A' + 1)); + lapic_eoi(); + return; + } + } + + char c = scancode_to_char(key); + if (c != 0) + kb_buf_push(c); + + lapic_eoi(); +} + +DEFINE_IRQ(MOUSE_IRQ_VECTOR, ps2_mouse_handler) +{ + (void)frame; + uint8_t data = inb(PS2_DATA_PORT); + uint8_t idx = mouse_packet_idx; + + if (idx == 0 && !(data & 0x08)) { + mouse_lost_sync++; + lapic_eoi(); + return; + } + + mouse_packet[idx] = data; + mouse_packet_idx = (uint8_t)(idx + 1); + + uint8_t pkt_size = mouse_has_scroll ? 4 : 3; + if (mouse_packet_idx < pkt_size) { lapic_eoi(); return; } + mouse_packet_idx = 0; + + uint8_t flags = mouse_packet[0]; + if ((flags & MOUSE_X_OVERFLOW) || (flags & MOUSE_Y_OVERFLOW)) { lapic_eoi(); return; } + + mouse_state.btn_left = (flags & MOUSE_BTN_LEFT) != 0; + mouse_state.btn_right = (flags & MOUSE_BTN_RIGHT) != 0; + mouse_state.btn_middle = (flags & MOUSE_BTN_MIDDLE) != 0; + + if (mouse_mode == MOUSE_MODE_ABSOLUTE) { + uint16_t abs_x = (uint16_t)mouse_packet[1] | ((flags & 0x10) ? 0x100 : 0); + uint16_t abs_y = (uint16_t)mouse_packet[2] | ((flags & 0x20) ? 0x100 : 0); + mouse_state.x = (int32_t)((uint32_t)abs_x * (uint32_t)mouse_screen_w / 0x1FF); + mouse_state.y = (int32_t)((uint32_t)abs_y * (uint32_t)mouse_screen_h / 0x1FF); + } else { + int32_t dx = mouse_packet[1]; + int32_t dy = mouse_packet[2]; + if (flags & MOUSE_X_SIGN) dx |= 0xFFFFFF00; + if (flags & MOUSE_Y_SIGN) dy |= 0xFFFFFF00; + mouse_state.x += dx; + mouse_state.y -= dy; + } + + if (mouse_state.x < 0) mouse_state.x = 0; + if (mouse_state.y < 0) mouse_state.y = 0; + if (mouse_state.x >= mouse_screen_w) mouse_state.x = mouse_screen_w - 1; + if (mouse_state.y >= mouse_screen_h) mouse_state.y = mouse_screen_h - 1; + + if (mouse_has_scroll) { + uint8_t z_raw = mouse_packet[3] & 0x0F; + int8_t z = (z_raw & 0x08) ? (int8_t)(z_raw | 0xF0) : (int8_t)z_raw; + if (z > 0) mouse_state.scroll = MOUSE_SCROLL_DOWN; + else if (z < 0) mouse_state.scroll = MOUSE_SCROLL_UP; + else mouse_state.scroll = MOUSE_SCROLL_NONE; + } + + //mouse_print_status(); + lapic_eoi(); +} + +bool ps2_init(void) { + serial_writestring("[PS2] Initializing PS/2 driver...\n"); + + if (!apic_is_available()) { + serial_writestring("[PS2] ERROR: APIC not available\n"); + return false; + } + + serial_writestring("[PS2] Initializing PS/2 controller...\n"); + ps2_send_cmd(PS2_CMD_DISABLE_PORT1); + ps2_send_cmd(PS2_CMD_DISABLE_PORT2); + ps2_flush_output(); + + ps2_send_cmd(PS2_CMD_READ_CONFIG); + uint8_t cfg = ps2_recv_data(); + cfg &= ~(PS2_CFG_PORT1_IRQ | PS2_CFG_PORT2_IRQ | PS2_CFG_PORT1_XLAT); + ps2_send_cmd(PS2_CMD_WRITE_CONFIG); + ps2_send_data(cfg); + + ps2_send_cmd(PS2_CMD_SELF_TEST); + uint8_t result = ps2_recv_data(); + if (result != 0x55) { serial_printf("[PS2] Self-test FAILED (0x%02x)\n", result); return false; } + serial_writestring("[PS2] Self-test PASSED\n"); + ps2_send_cmd(PS2_CMD_WRITE_CONFIG); + ps2_send_data(cfg); + + ps2_send_cmd(PS2_CMD_TEST_PORT1); + bool kb_ok = (ps2_recv_data() == 0x00); + serial_printf("[PS2] Port 1 (keyboard): %s\n", kb_ok ? "OK" : "FAIL"); + + ps2_send_cmd(PS2_CMD_TEST_PORT2); + bool mouse_ok = (ps2_recv_data() == 0x00); + serial_printf("[PS2] Port 2 (mouse): %s\n", mouse_ok ? "OK" : "FAIL"); + + if (kb_ok) ps2_send_cmd(PS2_CMD_ENABLE_PORT1); + if (mouse_ok) ps2_send_cmd(PS2_CMD_ENABLE_PORT2); + + if (kb_ok) { + ps2_send_data(0xFF); + for (int i = 0; i < 200000; i++) { + if (inb(PS2_STATUS_PORT) & PS2_STATUS_OUTPUT_FULL) + if (inb(PS2_DATA_PORT) == 0xAA) break; + io_wait(); + } + ps2_flush_output(); + ps2_send_data(0xF0); ps2_flush_output(); + ps2_send_data(0x01); ps2_flush_output(); + ps2_send_data(0xF4); ps2_flush_output(); + serial_writestring("[PS2] Keyboard ready (scancode set 1)\n"); + } + + if (mouse_ok) { + ps2_mouse_cmd(0xFF); + for (int i = 0; i < 200000; i++) { + if (inb(PS2_STATUS_PORT) & PS2_STATUS_OUTPUT_FULL) inb(PS2_DATA_PORT); + io_wait(); + } + ps2_flush_output(); + ps2_mouse_cmd(0xF6); + + bool is_abs = ps2_detect_absolute(); + if (is_abs) { + mouse_mode = MOUSE_MODE_ABSOLUTE; + mouse_has_scroll = false; + serial_writestring("[PS2] Mouse mode: ABSOLUTE (QEMU tablet)\n"); + } else { + mouse_mode = MOUSE_MODE_RELATIVE; + mouse_has_scroll = ps2_mouse_init_scroll(); + serial_printf("[PS2] Mouse mode: RELATIVE, scroll=%s\n", + mouse_has_scroll ? "YES" : "NO"); + } + ps2_mouse_cmd_param(0xF3, 100); + ps2_mouse_cmd(0xF4); + serial_writestring("[PS2] Mouse: data reporting enabled\n"); + } + + ps2_send_cmd(PS2_CMD_READ_CONFIG); + cfg = ps2_recv_data(); + if (kb_ok) cfg |= PS2_CFG_PORT1_IRQ; + if (mouse_ok) cfg |= PS2_CFG_PORT2_IRQ; + ps2_send_cmd(PS2_CMD_WRITE_CONFIG); + ps2_send_data(cfg); + + if (kb_ok) { + apic_setup_irq(KB_IRQ_LINE, KB_IRQ_VECTOR, false, 0); + serial_printf("[PS2] Keyboard IRQ%d -> vector 0x%02x\n", KB_IRQ_LINE, KB_IRQ_VECTOR); + } + + if (mouse_ok) { + apic_setup_irq(MOUSE_IRQ_LINE, MOUSE_IRQ_VECTOR, false, 0); + serial_printf("[PS2] Mouse IRQ%d -> vector 0x%02x\n", MOUSE_IRQ_LINE, MOUSE_IRQ_VECTOR); + } + + kb_state = (kb_state_t){0}; + kb_buf = (kb_buf_t){0}; + + mouse_state = (mouse_state_t){0}; + mouse_packet_idx = 0; + mouse_lost_sync = 0; + + if (global_framebuffer) { + mouse_screen_w = (int32_t)global_framebuffer->width; + mouse_screen_h = (int32_t)global_framebuffer->height; + mouse_state.x = mouse_screen_w / 2; + mouse_state.y = mouse_screen_h / 2; + } + + serial_printf("[PS2] Screen: %dx%d, cursor start: %d,%d\n", + (int)mouse_screen_w, (int)mouse_screen_h, + (int)mouse_state.x, (int)mouse_state.y); + serial_writestring("[PS2] Driver ready.\n"); + return true; +} + +const kb_state_t* ps2_kb_get_state(void) { + return (const kb_state_t*)&kb_state; +} +const mouse_state_t* ps2_mouse_get_state(void) { + return (const mouse_state_t*)&mouse_state; +} + +bool kb_buf_empty(void) { + return kb_buf.head == kb_buf.tail; +} + +bool kb_buf_try_getc(char *out) { + if (kb_buf_empty()) return false; + *out = kb_buf.buf[kb_buf.head]; + kb_buf.head = (kb_buf.head + 1) % KB_BUF_SIZE; + return true; +} + +bool kb_buf_has_ctrlc(void) { + uint8_t h = kb_buf.head; + while (h != kb_buf.tail) { + if (kb_buf.buf[h] == 0x03) return true; + h = (h + 1) % KB_BUF_SIZE; + } + return false; +} + +void kb_buf_consume_ctrlc(void) { + uint8_t h = kb_buf.head, t = kb_buf.tail; + uint8_t pos = h; + bool found = false; + while (pos != t) { + if (kb_buf.buf[pos] == 0x03) { found = true; break; } + pos = (pos + 1) % KB_BUF_SIZE; + } + if (!found) return; + uint8_t cur = pos; + uint8_t nxt = (cur + 1) % KB_BUF_SIZE; + while (nxt != t) { + kb_buf.buf[cur] = kb_buf.buf[nxt]; + cur = nxt; + nxt = (nxt + 1) % KB_BUF_SIZE; + } + kb_buf.tail = cur; +} + +char kb_buf_getc(void) { + char c; + while (!kb_buf_try_getc(&c)) { + //wait + } + return c; +} \ No newline at end of file diff --git a/kernel/src/drivers/timer.c b/kernel/src/drivers/timer.c new file mode 100644 index 0000000..f6e3c5c --- /dev/null +++ b/kernel/src/drivers/timer.c @@ -0,0 +1,145 @@ +#include "../include/drivers/timer.h" +#include "../include/apic/apic.h" +#include "../include/io/serial.h" +#include "../include/interrupts/interrupts.h" +#include "../include/io/ports.h" +#include "../include/sched/sched.h" +#include "../include/smp/percpu.h" +#include "../include/memory/pmm.h" +#include "../include/gdt/gdt.h" +#include + +static volatile uint64_t ticks = 0; +volatile uint32_t g_ctrlc_pending = 0; +extern task_t* task_find_foreground(void); +extern void task_kill(task_t* target); +extern void sched_wakeup_sleepers(uint64_t now_ns); + +DEFINE_IRQ(0x20, timer_handler) +{ + ticks++; + lapic_eoi(); + + uint32_t cpu = lapic_get_id(); + task_t* current = current_task[cpu]; + percpu_t* pc = get_percpu(); + + if (cpu == 0 && g_ctrlc_pending) { + g_ctrlc_pending = 0; + task_t *fg = task_find_foreground(); + if (fg) { + task_kill(fg); + } else { + extern void kb_buf_push(char c); + extern bool kb_buf_has_ctrlc(void); + if (!kb_buf_has_ctrlc()) + kb_buf_push(0x03); + } + } + + if (cpu == 0 && hpet_is_available()) { + sched_wakeup_sleepers(hpet_elapsed_ns()); + } + + if (!current) return; + + if (current->pending_kill && pc) { + current->time_slice = 0; + pc->need_resched = true; + (void)frame; + return; + } + + if (current->time_slice > 0) + current->time_slice--; + + if (current->time_slice == 0 && pc) + pc->need_resched = true; + + (void)frame; +} + +bool timer_init(void) { + serial_writestring("Initializing timer subsystem...\n"); + + if (!apic_is_available()) { + serial_writestring("ERROR: APIC not available\n"); + return false; + } + + apic_timer_calibrate(); + + outb(0x21, 0xFF); + outb(0xA1, 0xFF); + + serial_writestring("Timer subsystem initialized\n"); + + if (hpet_is_available()) { + serial_printf("HPET frequency: %llu Hz\n", hpet_get_frequency()); + } else { + serial_writestring("HPET not available\n"); + } + + serial_writestring("Checking APIC Timer Status\n"); + + uint32_t timer_config = lapic_read(LAPIC_TIMER); + uint32_t initial_count = lapic_read(LAPIC_TIMER_ICR); + uint32_t current_count = lapic_read(LAPIC_TIMER_CCR); + + serial_printf("APIC Timer Config: 0x%x\n", timer_config); + serial_printf(" Vector: 0x%x\n", timer_config & 0xFF); + serial_printf(" Masked: %s\n", (timer_config & LAPIC_TIMER_MASKED) ? "YES" : "NO"); + serial_printf(" Periodic: %s\n", (timer_config & LAPIC_TIMER_PERIODIC) ? "YES" : "NO"); + serial_printf("Initial Count: %u\n", initial_count); + serial_printf("Current Count: %u\n", current_count); + + if (timer_config & LAPIC_TIMER_MASKED) { + serial_writestring("WARNING: APIC timer is masked! Unmasking...\n"); + lapic_write(LAPIC_TIMER, (timer_config & ~LAPIC_TIMER_MASKED)); + } + + uint64_t rflags; + asm volatile("pushfq; pop %0" : "=r"(rflags)); + serial_printf("RFLAGS: 0x%llx (IF=%s)\n", rflags, (rflags & 0x200) ? "ON" : "OFF"); + + if (!(rflags & 0x200)) { + serial_writestring("WARNING: Interrupts are DISABLED! Enabling...\n"); + asm volatile("sti"); + } + + return true; +} + +uint64_t timer_get_ticks(void) { + return ticks; +} + +void timer_sleep_ms(uint64_t milliseconds) { + if (hpet_is_available()) { + hpet_sleep_ms(milliseconds); + } else { + volatile uint64_t i; + for (i = 0; i < milliseconds * 1000; i++) { + asm volatile("pause"); + } + } +} + +void timer_sleep_us(uint64_t microseconds) { + if (hpet_is_available()) { + hpet_sleep_us(microseconds); + } else { + volatile uint64_t i; + for (i = 0; i < microseconds; i++) { + asm volatile("pause"); + } + } +} + +void timer_sleep_ns(uint64_t nanoseconds) { + if (hpet_is_available()) { + hpet_sleep_ns(nanoseconds); + } else { + timer_sleep_us(nanoseconds / 1000); + } +} diff --git a/kernel/src/elf/elf.c b/kernel/src/elf/elf.c new file mode 100644 index 0000000..ff82b85 --- /dev/null +++ b/kernel/src/elf/elf.c @@ -0,0 +1,374 @@ +#include "../../include/elf/elf.h" +#include "../../include/memory/vmm.h" +#include "../../include/memory/pmm.h" +#include "../../include/memory/paging.h" +#include "../../include/io/serial.h" +#include +#include + +#define ELF_PIE_BASE 0x0000000000400000ULL +#define ELF_USER_STACK_TOP 0x00007FFFFFFFE000ULL +#define ELF_DEFAULT_STACK (64 * 1024) + +#define PML4_KERNEL_START 256 +#define PML4_ENTRIES 512 + +#ifndef SHF_WRITE +#define SHF_WRITE 0x1 +#endif +#ifndef SHF_ALLOC +#define SHF_ALLOC 0x2 +#endif +#ifndef SHF_EXECINSTR +#define SHF_EXECINSTR 0x4 +#endif + +static inline uintptr_t page_align_down(uintptr_t addr) { + return addr & ~(uintptr_t)(PAGE_SIZE - 1); +} +static inline uintptr_t page_align_up(uintptr_t addr) { + return (addr + PAGE_SIZE - 1) & ~(uintptr_t)(PAGE_SIZE - 1); +} +static uint64_t phdr_flags_to_vmm(uint32_t pf) { + uint64_t flags = VMM_PRESENT | VMM_USER; + if (pf & PF_W) flags |= VMM_WRITE; + if (!(pf & PF_X)) flags |= VMM_NOEXEC; + return flags; +} + +static elf_error_t elf_validate(const elf64_ehdr_t* hdr, size_t size) { + if (size < sizeof(elf64_ehdr_t)) return ELF_ERR_TOO_SMALL; + + uint32_t magic; + memcpy(&magic, hdr->e_ident, 4); + if (magic != ELF_MAGIC) return ELF_ERR_BAD_MAGIC; + if (hdr->e_ident[EI_CLASS] != ELFCLASS64) return ELF_ERR_NOT_64; + if (hdr->e_ident[EI_DATA] != ELFDATA2LSB) return ELF_ERR_NOT_LE; + if (hdr->e_ident[EI_VERSION] != EV_CURRENT || + hdr->e_version != EV_CURRENT) return ELF_ERR_BAD_VERSION; + if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) return ELF_ERR_NOT_EXEC; + if (hdr->e_machine != EM_X86_64) return ELF_ERR_WRONG_ARCH; + if (hdr->e_phnum == 0) return ELF_ERR_NO_LOAD; + + uint64_t pht_end = (uint64_t)hdr->e_phoff + + (uint64_t)hdr->e_phentsize * hdr->e_phnum; + if (pht_end > size) return ELF_ERR_TOO_SMALL; + + if (hdr->e_shnum && hdr->e_shoff) { + uint64_t sht_end = (uint64_t)hdr->e_shoff + + (uint64_t)hdr->e_shentsize * hdr->e_shnum; + if (sht_end > size) return ELF_ERR_TOO_SMALL; + } + return ELF_OK; +} + +static bool ensure_page_mapped(vmm_pagemap_t* map, uintptr_t virt, uint64_t flags) { + uint64_t pf = 0; + if (vmm_get_page_flags(map, virt, &pf) && (pf & VMM_PRESENT)) { + return true; + } + void* page = pmm_alloc_zero(1); + if (!page) return false; + if (!vmm_map_page(map, virt, pmm_virt_to_phys(page), flags)) { + pmm_free(page, 1); + return false; + } + return true; +} + +static elf_error_t load_segment(vmm_pagemap_t* map, + const uint8_t* data, + size_t file_size, + const elf64_phdr_t* phdr, + uintptr_t load_bias) +{ + if (phdr->p_memsz == 0) return ELF_OK; + + uintptr_t virt_start = phdr->p_vaddr + load_bias; + uintptr_t virt_end = virt_start + phdr->p_memsz; + uintptr_t page_start = page_align_down(virt_start); + uintptr_t page_end = page_align_up(virt_end); + size_t page_count = (page_end - page_start) / PAGE_SIZE; + + if (phdr->p_filesz > 0 && phdr->p_offset + phdr->p_filesz > file_size) { + serial_printf("[ELF] Segment data out of file bounds\n"); + return ELF_ERR_TOO_SMALL; + } + + uint64_t vmm_flags = phdr_flags_to_vmm(phdr->p_flags); + + for (size_t i = 0; i < page_count; i++) { + uintptr_t virt = page_start + i * PAGE_SIZE; + + uint64_t cur_flags = 0; + bool already_mapped = + vmm_get_page_flags(map, virt, &cur_flags) && (cur_flags & VMM_PRESENT); + + if (!already_mapped) { + void* page = pmm_alloc_zero(1); + if (!page) { + serial_printf("[ELF] pmm_alloc_zero(1) failed at page %zu for vaddr 0x%llx\n", + i, virt_start); + return ELF_ERR_NO_MEM; + } + if (!vmm_map_page(map, virt, pmm_virt_to_phys(page), vmm_flags)) { + serial_printf("[ELF] vmm_map_page failed: virt=0x%llx\n", virt); + pmm_free(page, 1); + return ELF_ERR_MAP_FAIL; + } + } + + if (phdr->p_filesz > 0) { + uintptr_t pv_start = virt; + uintptr_t pv_end = pv_start + PAGE_SIZE; + uintptr_t data_end = virt_start + phdr->p_filesz; + uintptr_t cp_start = (pv_start > virt_start) ? pv_start : virt_start; + uintptr_t cp_end = (pv_end < data_end) ? pv_end : data_end; + + if (cp_start < cp_end) { + uintptr_t phys = 0; + if (!vmm_virt_to_phys(map, cp_start, &phys)) { + return ELF_ERR_MAP_FAIL; + } + phys &= ~(uintptr_t)0xFFF; + size_t dst_off = cp_start - pv_start; + size_t src_off = cp_start - virt_start; + memcpy((uint8_t*)pmm_phys_to_virt(phys) + dst_off, + data + phdr->p_offset + src_off, + cp_end - cp_start); + } + } + } + + serial_printf("[ELF] Segment loaded: virt=0x%llx-0x%llx flags=%s%s%s " + "phys= pages=%zu\n", + virt_start, virt_end, + (phdr->p_flags & PF_R) ? "R" : "-", + (phdr->p_flags & PF_W) ? "W" : "-", + (phdr->p_flags & PF_X) ? "X" : "-", + page_count); + return ELF_OK; +} + +static uintptr_t cover_orphan_sections(vmm_pagemap_t* map, + const elf64_ehdr_t* ehdr, + const uint8_t* bytes, + size_t file_size, + uintptr_t load_bias, + uintptr_t cur_load_end) +{ + if (!ehdr->e_shnum || !ehdr->e_shoff) { + serial_printf("[ELF] no section headers; skipping orphan-section pass\n"); + return cur_load_end; + } + if (ehdr->e_shentsize < sizeof(elf64_shdr_t)) { + serial_printf("[ELF] e_shentsize=%u too small; skipping orphan-section pass\n", + (unsigned)ehdr->e_shentsize); + return cur_load_end; + } + uint64_t sht_end = (uint64_t)ehdr->e_shoff + + (uint64_t)ehdr->e_shentsize * ehdr->e_shnum; + if (sht_end > file_size) { + serial_printf("[ELF] section table out of file bounds; skip\n"); + return cur_load_end; + } + + serial_printf("[ELF] section scan: e_shnum=%u\n", (unsigned)ehdr->e_shnum); + + uintptr_t new_end = cur_load_end; + + for (uint16_t i = 0; i < ehdr->e_shnum; i++) { + const elf64_shdr_t* sh = (const elf64_shdr_t*) + (bytes + ehdr->e_shoff + (uint64_t)ehdr->e_shentsize * i); + + if (!(sh->sh_flags & SHF_ALLOC)) continue; + if (sh->sh_size == 0) continue; + if (sh->sh_addr == 0) continue; + + uintptr_t s_start = (uintptr_t)sh->sh_addr + load_bias; + uintptr_t s_end = s_start + sh->sh_size; + uintptr_t p_start = page_align_down(s_start); + uintptr_t p_end = page_align_up(s_end); + + uint64_t flags = VMM_PRESENT | VMM_USER; + if (sh->sh_flags & SHF_WRITE) flags |= VMM_WRITE; + if (!(sh->sh_flags & SHF_EXECINSTR)) flags |= VMM_NOEXEC; + + size_t added_pages = 0; + for (uintptr_t p = p_start; p < p_end; p += PAGE_SIZE) { + uint64_t pf = 0; + if (vmm_get_page_flags(map, p, &pf) && (pf & VMM_PRESENT)) + continue; + if (!ensure_page_mapped(map, p, flags)) { + serial_printf("[ELF] orphan-section: alloc/map failed at 0x%llx\n", + (unsigned long long)p); + return new_end; + } + added_pages++; + } + + if (added_pages > 0) { + serial_printf("[ELF] ORPHAN section #%u type=%u: virt=0x%llx-0x%llx " + "added=%zu pages flags=%s%s%s\n", + (unsigned)i, (unsigned)sh->sh_type, + (unsigned long long)s_start, + (unsigned long long)s_end, + added_pages, + (sh->sh_flags & SHF_ALLOC) ? "A" : "-", + (sh->sh_flags & SHF_WRITE) ? "W" : "-", + (sh->sh_flags & SHF_EXECINSTR) ? "X" : "-"); + + if (sh->sh_type != SHT_NOBITS && sh->sh_size > 0) { + if (sh->sh_offset + sh->sh_size <= file_size) { + for (uintptr_t p = p_start; p < p_end; p += PAGE_SIZE) { + uintptr_t pv_end = p + PAGE_SIZE; + uintptr_t cp_start = (p > s_start) ? p : s_start; + uintptr_t cp_end = (pv_end < s_end) ? pv_end : s_end; + if (cp_start >= cp_end) continue; + uintptr_t phys = 0; + if (!vmm_virt_to_phys(map, cp_start, &phys)) continue; + phys &= ~(uintptr_t)0xFFF; + size_t dst_off = cp_start - p; + size_t src_off = cp_start - s_start; + memcpy((uint8_t*)pmm_phys_to_virt(phys) + dst_off, + bytes + sh->sh_offset + src_off, + cp_end - cp_start); + } + } + } + } + + if (p_end > new_end) new_end = p_end; + } + return new_end; +} + +static uintptr_t alloc_user_stack(vmm_pagemap_t* map, size_t stack_size) { + size_t page_count = page_align_up(stack_size) / PAGE_SIZE; + uintptr_t stack_bottom = ELF_USER_STACK_TOP - page_count * PAGE_SIZE; + uint64_t flags = VMM_PRESENT | VMM_WRITE | VMM_USER | VMM_NOEXEC; + + for (size_t i = 0; i < page_count; i++) { + void* page = pmm_alloc_zero(1); + if (!page) { + serial_printf("[ELF] Stack alloc failed at page %zu\n", i); + for (size_t j = 0; j < i; j++) + vmm_unmap_page_noflush(map, stack_bottom + j * PAGE_SIZE); + return 0; + } + if (!vmm_map_page(map, stack_bottom + i * PAGE_SIZE, + pmm_virt_to_phys(page), flags)) { + serial_printf("[ELF] Stack map failed at page %zu\n", i); + pmm_free(page, 1); + for (size_t j = 0; j < i; j++) + vmm_unmap_page_noflush(map, stack_bottom + j * PAGE_SIZE); + return 0; + } + } + + serial_printf("[ELF] Stack: virt=0x%llx-0x%llx (%zu KiB)\n", + stack_bottom, ELF_USER_STACK_TOP, + (page_count * PAGE_SIZE) / 1024); + + return (ELF_USER_STACK_TOP - 8) & ~(uintptr_t)0xF; +} + +elf_load_result_t elf_load(const void* data, size_t size, size_t stack_sz) { + elf_load_result_t result = {0}; + + if (!data) { result.error = ELF_ERR_NULL; return result; } + + const elf64_ehdr_t* ehdr = (const elf64_ehdr_t*)data; + result.error = elf_validate(ehdr, size); + if (result.error != ELF_OK) { + serial_printf("[ELF] Validation failed: %s\n", elf_strerror(result.error)); + return result; + } + + uintptr_t load_bias = (ehdr->e_type == ET_DYN) ? ELF_PIE_BASE : 0; + if (load_bias) serial_printf("[ELF] PIE binary, bias=0x%llx\n", load_bias); + + vmm_pagemap_t* map = vmm_create_pagemap(); + if (!map) { + serial_printf("[ELF] vmm_create_pagemap() failed\n"); + result.error = ELF_ERR_NO_MEM; + return result; + } + result.pagemap = map; + + serial_printf("[ELF] Kernel PML4[256..511] inherited via vmm_create_pagemap\n"); + + const uint8_t* bytes = (const uint8_t*)data; + const elf64_phdr_t* phdrs = (const elf64_phdr_t*)(bytes + ehdr->e_phoff); + bool has_load = false; + uintptr_t max_vaddr = 0; + + for (uint16_t i = 0; i < ehdr->e_phnum; i++) { + const elf64_phdr_t* ph = &phdrs[i]; + if (ph->p_type != PT_LOAD) continue; + has_load = true; + + serial_printf("[ELF] PT_LOAD[%u]: off=0x%llx vaddr=0x%llx " + "filesz=0x%llx memsz=0x%llx flags=0x%x\n", + i, ph->p_offset, ph->p_vaddr, + ph->p_filesz, ph->p_memsz, ph->p_flags); + + elf_error_t err = load_segment(map, bytes, size, ph, load_bias); + if (err != ELF_OK) { + result.error = err; + return result; + } + + uintptr_t seg_end = ph->p_vaddr + load_bias + ph->p_memsz; + if (seg_end > max_vaddr) max_vaddr = seg_end; + } + + if (!has_load) { result.error = ELF_ERR_NO_LOAD; return result; } + + uintptr_t load_end = page_align_up(max_vaddr); + + load_end = cover_orphan_sections(map, ehdr, bytes, size, load_bias, load_end); + + result.entry = ehdr->e_entry + load_bias; + result.load_base = load_bias; + result.load_end = load_end; + + serial_printf("[ELF] Entry point: 0x%llx load_end (brk_start): 0x%llx\n", + result.entry, result.load_end); + + if (stack_sz == 0) stack_sz = ELF_DEFAULT_STACK; + result.stack_size = stack_sz; + result.stack_top = alloc_user_stack(map, stack_sz); + if (result.stack_top == 0) { result.error = ELF_ERR_NO_MEM; return result; } + + serial_printf("[ELF] Load complete. entry=0x%llx stack_top=0x%llx\n", + result.entry, result.stack_top); + return result; +} + +void elf_unload(elf_load_result_t* result) { + if (!result) return; + if (result->pagemap) { + vmm_free_pagemap(result->pagemap); + result->pagemap = NULL; + } + serial_printf("[ELF] Process unloaded.\n"); +} + +const char* elf_strerror(elf_error_t err) { + switch (err) { + case ELF_OK: return "OK"; + case ELF_ERR_NULL: return "NULL pointer"; + case ELF_ERR_TOO_SMALL: return "file too small / data out of bounds"; + case ELF_ERR_BAD_MAGIC: return "not an ELF file (bad magic)"; + case ELF_ERR_NOT_64: return "not ELF64"; + case ELF_ERR_NOT_LE: return "not little-endian"; + case ELF_ERR_BAD_VERSION: return "unsupported ELF version"; + case ELF_ERR_NOT_EXEC: return "not an executable or shared object"; + case ELF_ERR_WRONG_ARCH: return "not x86_64"; + case ELF_ERR_NO_LOAD: return "no PT_LOAD segments"; + case ELF_ERR_MAP_FAIL: return "vmm_map_page failed"; + case ELF_ERR_NO_MEM: return "out of memory"; + default: return "unknown error"; + } +} \ No newline at end of file diff --git a/kernel/src/font.psf b/kernel/src/font.psf new file mode 100644 index 0000000..13a4489 Binary files /dev/null and b/kernel/src/font.psf differ diff --git a/kernel/src/fs/devfs.c b/kernel/src/fs/devfs.c new file mode 100644 index 0000000..2cdf02a --- /dev/null +++ b/kernel/src/fs/devfs.c @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include "../../include/fs/devfs.h" +#include "../../include/fs/vfs.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include "../../include/drivers/ps2.h" +#include "../../include/sched/sched.h" +#include "../../include/apic/apic.h" +#include "../../include/smp/percpu.h" + +extern void draw_cursor(void); +extern void erase_cursor(void); + +extern uint32_t get_screen_width(void); +extern uint32_t get_screen_height(void); +extern uint32_t get_cursor_row(void); +extern uint32_t get_cursor_col(void); + +#define TIOCGWINSZ 0x5413 +#define TIOCGCURSOR 0x5480 +#define TCGETS 0x5401 +#define TCSETS 0x5402 +#define TCSETSW 0x5403 +#define TCSETSF 0x5404 +#define TIOCSNONBLOCK 0x5481 + +static int tty_nonblock = 0; + +#define T_ICANON 0x0002 +#define T_ECHO 0x0008 +#define T_ISIG 0x0001 + +struct cervus_winsize { + uint16_t ws_row; + uint16_t ws_col; + uint16_t ws_xpixel; + uint16_t ws_ypixel; +}; + +struct cervus_cursor_pos { + uint32_t row; + uint32_t col; +}; + +struct cervus_termios { + uint32_t c_iflag; + uint32_t c_oflag; + uint32_t c_cflag; + uint32_t c_lflag; + uint8_t c_cc[32]; +}; + +static struct cervus_termios g_tty_termios = { + .c_iflag = 0, + .c_oflag = 0, + .c_cflag = 0, + .c_lflag = T_ICANON | T_ECHO | T_ISIG, + .c_cc = {0}, +}; + +static inline int tty_is_canonical(void) { + return (g_tty_termios.c_lflag & T_ICANON) != 0; +} +static inline int tty_has_isig(void) { + return (g_tty_termios.c_lflag & T_ISIG) != 0; +} + +static inline task_t* devfs_cur_task(void) { + percpu_t* pc = get_percpu(); + return pc ? (task_t*)pc->current_task : NULL; +} + +static int64_t tty_read(vnode_t *node, void *buf, size_t len, uint64_t offset) { + (void)node; (void)offset; + if (len == 0) return 0; + + char *dst = buf; + + uint64_t freq = hpet_is_available() ? hpet_get_frequency() : 0; + uint64_t half_tick = freq ? freq / 2 : 0; + uint64_t next_blink = half_tick ? (hpet_read_counter() + half_tick) : 0; + int cursor_on = 1; + int canonical = tty_is_canonical(); + int isig = tty_has_isig(); + + if (tty_nonblock && kb_buf_empty()) return -EAGAIN; + + draw_cursor(); + + while (kb_buf_empty()) { + task_t *me = devfs_cur_task(); + if (me && me->pending_kill) { + erase_cursor(); + return -EINTR; + } + + if (half_tick) { + uint64_t now = hpet_read_counter(); + if (now >= next_blink) { + if (cursor_on) { erase_cursor(); cursor_on = 0; } + else { draw_cursor(); cursor_on = 1; } + next_blink = now + half_tick; + } + } + task_yield(); + } + + erase_cursor(); + + task_t *me = devfs_cur_task(); + if (me && me->pending_kill) { + kb_buf_consume_ctrlc(); + return -EINTR; + } + + char first = kb_buf_getc(); + if (isig && canonical && first == 0x03) { + return -EINTR; + } + + dst[0] = first; + size_t got = 1; + + if (!canonical) { + while (got < len) { + char c; + if (!kb_buf_try_getc(&c)) break; + dst[got++] = c; + } + return (int64_t)got; + } + + while (got < len) { + char c; + if (!kb_buf_try_getc(&c)) break; + if (isig && c == 0x03) break; + dst[got++] = c; + if (c == '\n') break; + } + return (int64_t)got; +} + +static int64_t tty_write(vnode_t *node, const void *buf, size_t len, uint64_t offset) { + (void)node; (void)offset; + serial_writebuf((const char *)buf, len); + const unsigned char *p = (const unsigned char *)buf; + for (size_t i = 0; i < len; i++) putchar((int)p[i]); + return (int64_t)len; +} + +static int64_t tty_ioctl(vnode_t *node, uint64_t req, void *arg) { + (void)node; + + if (req == TIOCGWINSZ) { + if (!arg) return -EFAULT; + struct cervus_winsize *ws = (struct cervus_winsize *)arg; + uint32_t w = get_screen_width(); + uint32_t h = get_screen_height(); + ws->ws_col = (uint16_t)(w / 8); + ws->ws_row = (uint16_t)(h / 16); + ws->ws_xpixel = (uint16_t)w; + ws->ws_ypixel = (uint16_t)h; + return 0; + } + + if (req == TIOCGCURSOR) { + if (!arg) return -EFAULT; + struct cervus_cursor_pos *cp = (struct cervus_cursor_pos *)arg; + cp->row = get_cursor_row(); + cp->col = get_cursor_col(); + return 0; + } + + if (req == TIOCSNONBLOCK) { + if (!arg) return -EFAULT; + tty_nonblock = *((int *)arg) ? 1 : 0; + return 0; + } + + if (req == TCGETS) { + if (!arg) return -EFAULT; + memcpy(arg, &g_tty_termios, sizeof(g_tty_termios)); + return 0; + } + + if (req == TCSETS || req == TCSETSW || req == TCSETSF) { + if (!arg) return -EFAULT; + memcpy(&g_tty_termios, arg, sizeof(g_tty_termios)); + return 0; + } + + return -ENOTTY; +} + +static void devfs_ref(vnode_t *node) { + (void)node; +} + +static void devfs_unref(vnode_t *node) { + (void)node; +} + +static int devfs_stat(vnode_t *node, vfs_stat_t *out) { + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = node->type; + out->st_mode = node->mode; + return 0; +} + +static const vnode_ops_t tty_ops = { + .read = tty_read, + .write = tty_write, + .ioctl = tty_ioctl, + .stat = devfs_stat, + .ref = devfs_ref, + .unref = devfs_unref, +}; + +static int64_t null_read(vnode_t *n, void *buf, size_t len, uint64_t off) { + (void)n; (void)buf; (void)len; (void)off; + return 0; +} + +static int64_t null_write(vnode_t *n, const void *buf, size_t len, uint64_t off) { + (void)n; (void)buf; (void)off; + return (int64_t)len; +} + +static const vnode_ops_t null_ops = { + .read = null_read, + .write = null_write, + .stat = devfs_stat, + .ref = devfs_ref, + .unref = devfs_unref, +}; + +static int64_t zero_read(vnode_t *n, void *buf, size_t len, uint64_t off) { + (void)n; (void)off; + memset(buf, 0, len); + return (int64_t)len; +} + +static const vnode_ops_t zero_ops = { + .read = zero_read, + .write = null_write, + .stat = devfs_stat, + .ref = devfs_ref, + .unref = devfs_unref, +}; + +#define DEVFS_MAX_ENTRIES 32 + +typedef struct { + char name[VFS_MAX_NAME]; + vnode_t *node; +} devfs_entry_t; + +typedef struct { + devfs_entry_t entries[DEVFS_MAX_ENTRIES]; + int count; +} devfs_dir_data_t; + +static devfs_dir_data_t g_devdir; +static vnode_t g_devroot; +static vnode_t g_tty_node; +static vnode_t g_null_node; +static vnode_t g_zero_node; + +static uint64_t g_devfs_ino = 100; + +static int devfs_dir_lookup(vnode_t *dir, const char *name, vnode_t **out) { + (void)dir; + for (int i = 0; i < g_devdir.count; i++) { + if (strcmp(g_devdir.entries[i].name, name) == 0) { + vnode_ref(g_devdir.entries[i].node); + *out = g_devdir.entries[i].node; + return 0; + } + } + return -ENOENT; +} + +static int devfs_dir_readdir(vnode_t *dir, uint64_t index, vfs_dirent_t *out) { + (void)dir; + if ((int64_t)index >= g_devdir.count) return -ENOENT; + devfs_entry_t *e = &g_devdir.entries[index]; + out->d_ino = e->node->ino; + out->d_type = (uint8_t)e->node->type; + strncpy(out->d_name, e->name, VFS_MAX_NAME - 1); + out->d_name[VFS_MAX_NAME - 1] = '\0'; + return 0; +} + +static const vnode_ops_t devfs_dir_ops = { + .lookup = devfs_dir_lookup, + .readdir = devfs_dir_readdir, + .stat = devfs_stat, + .ref = devfs_ref, + .unref = devfs_unref, +}; + +void devfs_register(const char *name, vnode_t *node) { + if (g_devdir.count >= DEVFS_MAX_ENTRIES) return; + devfs_entry_t *e = &g_devdir.entries[g_devdir.count++]; + strncpy(e->name, name, VFS_MAX_NAME - 1); + e->name[VFS_MAX_NAME - 1] = '\0'; + e->node = node; +} + +vnode_t *devfs_create_root(void) { + memset(&g_devdir, 0, sizeof(g_devdir)); + memset(&g_devroot, 0, sizeof(g_devroot)); + memset(&g_tty_node, 0, sizeof(g_tty_node)); + memset(&g_null_node, 0, sizeof(g_null_node)); + memset(&g_zero_node, 0, sizeof(g_zero_node)); + + g_devroot.type = VFS_NODE_DIR; + g_devroot.mode = 0755; + g_devroot.ino = g_devfs_ino++; + g_devroot.ops = &devfs_dir_ops; + g_devroot.fs_data = &g_devdir; + g_devroot.refcount = 1; + + g_tty_node.type = VFS_NODE_CHARDEV; + g_tty_node.mode = 0666; + g_tty_node.ino = g_devfs_ino++; + g_tty_node.ops = &tty_ops; + g_tty_node.refcount = 1; + + g_null_node.type = VFS_NODE_CHARDEV; + g_null_node.mode = 0666; + g_null_node.ino = g_devfs_ino++; + g_null_node.ops = &null_ops; + g_null_node.refcount = 1; + + g_zero_node.type = VFS_NODE_CHARDEV; + g_zero_node.mode = 0666; + g_zero_node.ino = g_devfs_ino++; + g_zero_node.ops = &zero_ops; + g_zero_node.refcount = 1; + + devfs_register("tty", &g_tty_node); + devfs_register("null", &g_null_node); + devfs_register("zero", &g_zero_node); + + serial_writestring("[devfs] /dev/tty, /dev/null, /dev/zero registered\n"); + return &g_devroot; +} \ No newline at end of file diff --git a/kernel/src/fs/ext2.c b/kernel/src/fs/ext2.c new file mode 100644 index 0000000..f97f197 --- /dev/null +++ b/kernel/src/fs/ext2.c @@ -0,0 +1,935 @@ +#include "../../include/fs/ext2.h" +#include "../../include/fs/vfs.h" +#include "../../include/drivers/blkdev.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include "../../include/syscall/errno.h" +#include +#include + +static int block_read(ext2_t *fs, uint32_t block, void *buf) { + return blkdev_read(fs->dev, (uint64_t)block * fs->block_size, buf, fs->block_size); +} + +static int block_write(ext2_t *fs, uint32_t block, const void *buf) { + return blkdev_write(fs->dev, (uint64_t)block * fs->block_size, buf, fs->block_size); +} + +static int sb_flush(ext2_t *fs) { + return blkdev_write(fs->dev, EXT2_SUPER_OFFSET, &fs->sb, sizeof(fs->sb)); +} + +static int gdt_flush(ext2_t *fs) { + uint32_t gdt_block = (fs->block_size == 1024) ? 2 : 1; + return blkdev_write(fs->dev, (uint64_t)gdt_block * fs->block_size, + fs->gdt, fs->groups_count * sizeof(ext2_group_desc_t)); +} + +static int inode_read(ext2_t *fs, uint32_t ino, ext2_inode_t *out) { + if (ino == 0) return -EINVAL; + uint32_t idx = ino - 1; + uint32_t group = idx / fs->sb.s_inodes_per_group; + uint32_t local = idx % fs->sb.s_inodes_per_group; + if (group >= fs->groups_count) return -EINVAL; + uint64_t off = (uint64_t)fs->gdt[group].bg_inode_table * fs->block_size + + (uint64_t)local * fs->inode_size; + return blkdev_read(fs->dev, off, out, sizeof(*out)); +} + +static int inode_write(ext2_t *fs, uint32_t ino, const ext2_inode_t *in) { + if (ino == 0) return -EINVAL; + uint32_t idx = ino - 1; + uint32_t group = idx / fs->sb.s_inodes_per_group; + uint32_t local = idx % fs->sb.s_inodes_per_group; + if (group >= fs->groups_count) return -EINVAL; + uint64_t off = (uint64_t)fs->gdt[group].bg_inode_table * fs->block_size + + (uint64_t)local * fs->inode_size; + return blkdev_write(fs->dev, off, in, sizeof(*in)); +} + +static bool bmp_test(const uint8_t *bmp, uint32_t bit) { + return (bmp[bit / 8] >> (bit % 8)) & 1; +} +static void bmp_set(uint8_t *bmp, uint32_t bit) { + bmp[bit / 8] |= (uint8_t)(1 << (bit % 8)); +} +static void bmp_clear(uint8_t *bmp, uint32_t bit) { + bmp[bit / 8] &= (uint8_t)~(1 << (bit % 8)); +} + +static int32_t alloc_inode(ext2_t *fs) { + uint8_t *bmp = kmalloc(fs->block_size); + if (!bmp) return -ENOMEM; + for (uint32_t g = 0; g < fs->groups_count; g++) { + if (fs->gdt[g].bg_free_inodes_count == 0) continue; + block_read(fs, fs->gdt[g].bg_inode_bitmap, bmp); + for (uint32_t i = 0; i < fs->sb.s_inodes_per_group; i++) { + if (!bmp_test(bmp, i)) { + bmp_set(bmp, i); + block_write(fs, fs->gdt[g].bg_inode_bitmap, bmp); + fs->gdt[g].bg_free_inodes_count--; + fs->sb.s_free_inodes_count--; + fs->dirty = true; + kfree(bmp); + return (int32_t)(g * fs->sb.s_inodes_per_group + i + 1); + } + } + } + kfree(bmp); + return -ENOSPC; +} + +static void free_inode(ext2_t *fs, uint32_t ino) { + uint32_t idx = ino - 1; + uint32_t group = idx / fs->sb.s_inodes_per_group; + uint32_t local = idx % fs->sb.s_inodes_per_group; + uint8_t *bmp = kmalloc(fs->block_size); + if (!bmp) return; + block_read(fs, fs->gdt[group].bg_inode_bitmap, bmp); + bmp_clear(bmp, local); + block_write(fs, fs->gdt[group].bg_inode_bitmap, bmp); + fs->gdt[group].bg_free_inodes_count++; + fs->sb.s_free_inodes_count++; + fs->dirty = true; + kfree(bmp); +} + +static int32_t alloc_block(ext2_t *fs) { + uint8_t *bmp = kmalloc(fs->block_size); + if (!bmp) return -ENOMEM; + for (uint32_t g = 0; g < fs->groups_count; g++) { + if (fs->gdt[g].bg_free_blocks_count == 0) continue; + block_read(fs, fs->gdt[g].bg_block_bitmap, bmp); + for (uint32_t i = 0; i < fs->sb.s_blocks_per_group; i++) { + uint32_t abs_block = g * fs->sb.s_blocks_per_group + i + fs->sb.s_first_data_block; + if (abs_block >= fs->sb.s_blocks_count) break; + if (!bmp_test(bmp, i)) { + bmp_set(bmp, i); + block_write(fs, fs->gdt[g].bg_block_bitmap, bmp); + fs->gdt[g].bg_free_blocks_count--; + fs->sb.s_free_blocks_count--; + fs->dirty = true; + kfree(bmp); + return (int32_t)abs_block; + } + } + } + kfree(bmp); + return -ENOSPC; +} + +static void free_block(ext2_t *fs, uint32_t blk) { + if (blk < fs->sb.s_first_data_block) return; + uint32_t adj = blk - fs->sb.s_first_data_block; + uint32_t group = adj / fs->sb.s_blocks_per_group; + uint32_t local = adj % fs->sb.s_blocks_per_group; + if (group >= fs->groups_count) return; + uint8_t *bmp = kmalloc(fs->block_size); + if (!bmp) return; + block_read(fs, fs->gdt[group].bg_block_bitmap, bmp); + bmp_clear(bmp, local); + block_write(fs, fs->gdt[group].bg_block_bitmap, bmp); + fs->gdt[group].bg_free_blocks_count++; + fs->sb.s_free_blocks_count++; + fs->dirty = true; + kfree(bmp); +} + +static int32_t get_block_num(ext2_t *fs, ext2_inode_t *di, uint32_t file_block) { + if (file_block < EXT2_NDIR_BLOCKS) + return (int32_t)di->i_block[file_block]; + uint32_t ppb = fs->ptrs_per_block; + file_block -= EXT2_NDIR_BLOCKS; + if (file_block < ppb) { + if (di->i_block[EXT2_IND_BLOCK] == 0) return 0; + uint32_t *ind = kmalloc(fs->block_size); + if (!ind) return -ENOMEM; + block_read(fs, di->i_block[EXT2_IND_BLOCK], ind); + int32_t ret = (int32_t)ind[file_block]; + kfree(ind); + return ret; + } + file_block -= ppb; + if (file_block < ppb * ppb) { + if (di->i_block[EXT2_DIND_BLOCK] == 0) return 0; + uint32_t *dind = kmalloc(fs->block_size); + if (!dind) return -ENOMEM; + block_read(fs, di->i_block[EXT2_DIND_BLOCK], dind); + uint32_t i1 = file_block / ppb; + uint32_t i2 = file_block % ppb; + if (dind[i1] == 0) { kfree(dind); return 0; } + uint32_t ib = dind[i1]; + kfree(dind); + uint32_t *ind = kmalloc(fs->block_size); + if (!ind) return -ENOMEM; + block_read(fs, ib, ind); + int32_t ret = (int32_t)ind[i2]; + kfree(ind); + return ret; + } + return -EFBIG; +} + +static int set_block_num(ext2_t *fs, ext2_inode_t *di, uint32_t file_block, uint32_t disk_block) { + if (file_block < EXT2_NDIR_BLOCKS) { + di->i_block[file_block] = disk_block; + return 0; + } + uint32_t ppb = fs->ptrs_per_block; + file_block -= EXT2_NDIR_BLOCKS; + if (file_block < ppb) { + if (di->i_block[EXT2_IND_BLOCK] == 0) { + int32_t nb = alloc_block(fs); + if (nb < 0) return nb; + di->i_block[EXT2_IND_BLOCK] = (uint32_t)nb; + uint8_t *z = kzalloc(fs->block_size); + block_write(fs, (uint32_t)nb, z); + kfree(z); + } + uint32_t *ind = kmalloc(fs->block_size); + if (!ind) return -ENOMEM; + block_read(fs, di->i_block[EXT2_IND_BLOCK], ind); + ind[file_block] = disk_block; + block_write(fs, di->i_block[EXT2_IND_BLOCK], ind); + kfree(ind); + return 0; + } + file_block -= ppb; + if (file_block < ppb * ppb) { + if (di->i_block[EXT2_DIND_BLOCK] == 0) { + int32_t nb = alloc_block(fs); + if (nb < 0) return nb; + di->i_block[EXT2_DIND_BLOCK] = (uint32_t)nb; + uint8_t *z = kzalloc(fs->block_size); + block_write(fs, (uint32_t)nb, z); + kfree(z); + } + uint32_t *dind = kmalloc(fs->block_size); + if (!dind) return -ENOMEM; + block_read(fs, di->i_block[EXT2_DIND_BLOCK], dind); + uint32_t i1 = file_block / ppb; + uint32_t i2 = file_block % ppb; + if (dind[i1] == 0) { + int32_t nb = alloc_block(fs); + if (nb < 0) { kfree(dind); return nb; } + dind[i1] = (uint32_t)nb; + block_write(fs, di->i_block[EXT2_DIND_BLOCK], dind); + uint8_t *z = kzalloc(fs->block_size); + block_write(fs, (uint32_t)nb, z); + kfree(z); + } + uint32_t ib = dind[i1]; + kfree(dind); + uint32_t *ind = kmalloc(fs->block_size); + if (!ind) return -ENOMEM; + block_read(fs, ib, ind); + ind[i2] = disk_block; + block_write(fs, ib, ind); + kfree(ind); + return 0; + } + return -EFBIG; +} + +static const vnode_ops_t ext2_file_ops; +static const vnode_ops_t ext2_dir_ops; + +static vnode_t *ext2_make_vnode(ext2_t *fs, uint32_t ino, ext2_inode_t *di) { + vnode_t *v = kzalloc(sizeof(vnode_t)); + if (!v) return NULL; + ext2_vdata_t *vd = kzalloc(sizeof(ext2_vdata_t)); + if (!vd) { kfree(v); return NULL; } + vd->fs = fs; + vd->ino = ino; + if ((di->i_mode & 0xF000) == EXT2_S_IFDIR) { + v->type = VFS_NODE_DIR; + v->ops = &ext2_dir_ops; + } else { + v->type = VFS_NODE_FILE; + v->ops = &ext2_file_ops; + } + v->mode = di->i_mode & 0x0FFF; + v->uid = di->i_uid; + v->gid = di->i_gid; + v->ino = ino; + v->size = di->i_size; + v->fs_data = vd; + v->refcount = 1; + return v; +} + +static int64_t ext2_file_read(vnode_t *node, void *buf, size_t len, uint64_t offset) { + ext2_vdata_t *vd = node->fs_data; + ext2_t *fs = vd->fs; + ext2_inode_t di; + int r = inode_read(fs, vd->ino, &di); + if (r < 0) return r; + if (offset >= di.i_size) return 0; + if (offset + len > di.i_size) len = di.i_size - (size_t)offset; + if (len == 0) return 0; + uint8_t *dst = (uint8_t *)buf; + size_t done = 0; + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + while (done < len) { + uint32_t co = (uint32_t)(offset + done); + uint32_t fb = co / fs->block_size; + uint32_t bo = co % fs->block_size; + int32_t db = get_block_num(fs, &di, fb); + if (db <= 0) { + if (db < 0) { kfree(bb); return db; } + memset(bb, 0, fs->block_size); + } else { + r = block_read(fs, (uint32_t)db, bb); + if (r < 0) { kfree(bb); return r; } + } + size_t ch = fs->block_size - bo; + if (ch > len - done) ch = len - done; + memcpy(dst + done, bb + bo, ch); + done += ch; + } + kfree(bb); + return (int64_t)done; +} + +static int64_t ext2_file_write(vnode_t *node, const void *buf, size_t len, uint64_t offset) { + ext2_vdata_t *vd = node->fs_data; + ext2_t *fs = vd->fs; + ext2_inode_t di; + int r = inode_read(fs, vd->ino, &di); + if (r < 0) return r; + const uint8_t *src = (const uint8_t *)buf; + size_t done = 0; + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + while (done < len) { + uint32_t co = (uint32_t)(offset + done); + uint32_t fb = co / fs->block_size; + uint32_t bo = co % fs->block_size; + int32_t db = get_block_num(fs, &di, fb); + if (db == 0) { + int32_t nb = alloc_block(fs); + if (nb < 0) { kfree(bb); return (done > 0) ? (int64_t)done : nb; } + set_block_num(fs, &di, fb, (uint32_t)nb); + db = nb; + memset(bb, 0, fs->block_size); + di.i_blocks += fs->block_size / 512; + } else if (db < 0) { + kfree(bb); return db; + } else { + if (bo != 0 || (len - done) < fs->block_size) + block_read(fs, (uint32_t)db, bb); + } + size_t ch = fs->block_size - bo; + if (ch > len - done) ch = len - done; + memcpy(bb + bo, src + done, ch); + block_write(fs, (uint32_t)db, bb); + done += ch; + } + uint32_t ne = (uint32_t)(offset + done); + if (ne > di.i_size) { di.i_size = ne; node->size = ne; } + inode_write(fs, vd->ino, &di); + fs->dirty = true; + kfree(bb); + return (int64_t)done; +} + +static void free_all_blocks(ext2_t *fs, ext2_inode_t *di) { + for (int i = 0; i < EXT2_NDIR_BLOCKS; i++) { + if (di->i_block[i]) { free_block(fs, di->i_block[i]); di->i_block[i] = 0; } + } + if (di->i_block[EXT2_IND_BLOCK]) { + uint32_t *ind = kmalloc(fs->block_size); + if (ind) { + block_read(fs, di->i_block[EXT2_IND_BLOCK], ind); + for (uint32_t i = 0; i < fs->ptrs_per_block; i++) + if (ind[i]) free_block(fs, ind[i]); + kfree(ind); + } + free_block(fs, di->i_block[EXT2_IND_BLOCK]); + di->i_block[EXT2_IND_BLOCK] = 0; + } + if (di->i_block[EXT2_DIND_BLOCK]) { + uint32_t *dind = kmalloc(fs->block_size); + if (dind) { + block_read(fs, di->i_block[EXT2_DIND_BLOCK], dind); + for (uint32_t i = 0; i < fs->ptrs_per_block; i++) { + if (dind[i]) { + uint32_t *ind = kmalloc(fs->block_size); + if (ind) { + block_read(fs, dind[i], ind); + for (uint32_t j = 0; j < fs->ptrs_per_block; j++) + if (ind[j]) free_block(fs, ind[j]); + kfree(ind); + } + free_block(fs, dind[i]); + } + } + kfree(dind); + } + free_block(fs, di->i_block[EXT2_DIND_BLOCK]); + di->i_block[EXT2_DIND_BLOCK] = 0; + } + di->i_blocks = 0; +} + +static int ext2_file_truncate(vnode_t *node, uint64_t new_size) { + ext2_vdata_t *vd = node->fs_data; + ext2_t *fs = vd->fs; + ext2_inode_t di; + int r = inode_read(fs, vd->ino, &di); + if (r < 0) return r; + if (new_size == 0) free_all_blocks(fs, &di); + di.i_size = (uint32_t)new_size; + node->size = new_size; + inode_write(fs, vd->ino, &di); + fs->dirty = true; + return 0; +} + +static int ext2_stat(vnode_t *node, vfs_stat_t *out) { + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = node->type; + out->st_mode = node->mode; + out->st_uid = node->uid; + out->st_gid = node->gid; + out->st_size = node->size; + out->st_blocks = (node->size + 511) / 512; + return 0; +} + +static void ext2_vnode_ref(vnode_t *node) { (void)node; } +static void ext2_vnode_unref(vnode_t *node) { + if (node->fs_data) kfree(node->fs_data); + kfree(node); +} + +static const vnode_ops_t ext2_file_ops = { + .read = ext2_file_read, + .write = ext2_file_write, + .truncate = ext2_file_truncate, + .stat = ext2_stat, + .ref = ext2_vnode_ref, + .unref = ext2_vnode_unref, +}; + +static int ext2_dir_lookup(vnode_t *dir, const char *name, vnode_t **out) { + ext2_vdata_t *vd = dir->fs_data; + ext2_t *fs = vd->fs; + ext2_inode_t di; + int r = inode_read(fs, vd->ino, &di); + if (r < 0) return r; + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + size_t nl = strlen(name); + uint32_t ds = di.i_size; + uint32_t pos = 0, fb = 0; + while (pos < ds) { + int32_t db = get_block_num(fs, &di, fb); + if (db <= 0) { fb++; pos += fs->block_size; continue; } + block_read(fs, (uint32_t)db, bb); + uint32_t off = 0; + while (off < fs->block_size && (pos + off) < ds) { + ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off); + if (de->rec_len == 0) break; + if (de->inode != 0 && de->name_len == nl && memcmp(de->name, name, nl) == 0) { + ext2_inode_t ci; + inode_read(fs, de->inode, &ci); + *out = ext2_make_vnode(fs, de->inode, &ci); + kfree(bb); + return *out ? 0 : -ENOMEM; + } + off += de->rec_len; + } + fb++; pos += fs->block_size; + } + kfree(bb); + return -ENOENT; +} + +static int ext2_dir_readdir(vnode_t *dir, uint64_t index, vfs_dirent_t *out) { + ext2_vdata_t *vd = dir->fs_data; + ext2_t *fs = vd->fs; + ext2_inode_t di; + int r = inode_read(fs, vd->ino, &di); + if (r < 0) return r; + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + uint32_t ds = di.i_size, pos = 0, fb = 0; + uint64_t cur = 0; + while (pos < ds) { + int32_t db = get_block_num(fs, &di, fb); + if (db <= 0) { fb++; pos += fs->block_size; continue; } + block_read(fs, (uint32_t)db, bb); + uint32_t off = 0; + while (off < fs->block_size && (pos + off) < ds) { + ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off); + if (de->rec_len == 0) break; + if (de->inode != 0 && de->name_len > 0) { + bool skip = (de->name_len == 1 && de->name[0] == '.') || + (de->name_len == 2 && de->name[0] == '.' && de->name[1] == '.'); + if (!skip) { + if (cur == index) { + out->d_ino = de->inode; + out->d_type = (de->file_type == EXT2_FT_DIR) ? VFS_NODE_DIR : VFS_NODE_FILE; + size_t n = de->name_len; + if (n >= VFS_MAX_NAME) n = VFS_MAX_NAME - 1; + memcpy(out->d_name, de->name, n); + out->d_name[n] = '\0'; + kfree(bb); + return 0; + } + cur++; + } + } + off += de->rec_len; + } + fb++; pos += fs->block_size; + } + kfree(bb); + return -ENOENT; +} + +static int ext2_dir_add_entry(ext2_t *fs, uint32_t dir_ino, uint32_t child_ino, + uint8_t file_type, const char *name) { + if (!name || !name[0]) return -EINVAL; + ext2_inode_t di; + int r = inode_read(fs, dir_ino, &di); + if (r < 0) return r; + uint8_t nl = (uint8_t)strlen(name); + uint16_t need = (uint16_t)((8 + nl + 3) & ~3); + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + uint32_t nb = (di.i_size + fs->block_size - 1) / fs->block_size; + for (uint32_t fb = 0; fb < nb; fb++) { + int32_t db = get_block_num(fs, &di, fb); + if (db <= 0) continue; + block_read(fs, (uint32_t)db, bb); + uint32_t off = 0; + while (off < fs->block_size) { + ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off); + if (de->rec_len == 0) break; + if (de->inode == 0 && de->rec_len >= need) { + de->inode = child_ino; + de->name_len = nl; + de->file_type = file_type; + memcpy(de->name, name, nl); + block_write(fs, (uint32_t)db, bb); + kfree(bb); + return 0; + } + uint16_t actual = (uint16_t)((8 + de->name_len + 3) & ~3); + uint16_t slack = de->rec_len - actual; + if (slack >= need) { + de->rec_len = actual; + ext2_dir_entry_t *ne = (ext2_dir_entry_t *)(bb + off + actual); + ne->inode = child_ino; + ne->rec_len = slack; + ne->name_len = nl; + ne->file_type = file_type; + memcpy(ne->name, name, nl); + block_write(fs, (uint32_t)db, bb); + kfree(bb); + return 0; + } + off += de->rec_len; + } + } + int32_t new_blk = alloc_block(fs); + if (new_blk < 0) { kfree(bb); return (int)new_blk; } + set_block_num(fs, &di, nb, (uint32_t)new_blk); + di.i_size += fs->block_size; + di.i_blocks += fs->block_size / 512; + memset(bb, 0, fs->block_size); + ext2_dir_entry_t *de = (ext2_dir_entry_t *)bb; + de->inode = child_ino; + de->rec_len = (uint16_t)fs->block_size; + de->name_len = nl; + de->file_type = file_type; + memcpy(de->name, name, nl); + block_write(fs, (uint32_t)new_blk, bb); + inode_write(fs, dir_ino, &di); + kfree(bb); + return 0; +} + +static int ext2_dir_mkdir(vnode_t *dir, const char *name, uint32_t mode) { + if (!name || !name[0]) return -EINVAL; + ext2_vdata_t *vd = dir->fs_data; + ext2_t *fs = vd->fs; + vnode_t *ex = NULL; + if (ext2_dir_lookup(dir, name, &ex) == 0) { vnode_unref(ex); return -EEXIST; } + int32_t ino = alloc_inode(fs); + if (ino < 0) return (int)ino; + ext2_inode_t ndi; + memset(&ndi, 0, sizeof(ndi)); + ndi.i_mode = EXT2_S_IFDIR | (uint16_t)(mode ? mode : 0755); + ndi.i_links_count = 2; + int32_t blk = alloc_block(fs); + if (blk < 0) { free_inode(fs, (uint32_t)ino); return (int)blk; } + ndi.i_block[0] = (uint32_t)blk; + ndi.i_size = fs->block_size; + ndi.i_blocks = fs->block_size / 512; + uint8_t *bb = kzalloc(fs->block_size); + if (!bb) { free_block(fs, (uint32_t)blk); free_inode(fs, (uint32_t)ino); return -ENOMEM; } + ext2_dir_entry_t *dot = (ext2_dir_entry_t *)bb; + dot->inode = (uint32_t)ino; dot->rec_len = 12; dot->name_len = 1; + dot->file_type = EXT2_FT_DIR; dot->name[0] = '.'; + ext2_dir_entry_t *dotdot = (ext2_dir_entry_t *)(bb + 12); + dotdot->inode = vd->ino; dotdot->rec_len = (uint16_t)(fs->block_size - 12); + dotdot->name_len = 2; dotdot->file_type = EXT2_FT_DIR; + dotdot->name[0] = '.'; dotdot->name[1] = '.'; + block_write(fs, (uint32_t)blk, bb); + kfree(bb); + inode_write(fs, (uint32_t)ino, &ndi); + int r = ext2_dir_add_entry(fs, vd->ino, (uint32_t)ino, EXT2_FT_DIR, name); + if (r < 0) { free_block(fs, (uint32_t)blk); free_inode(fs, (uint32_t)ino); return r; } + ext2_inode_t pdi; + inode_read(fs, vd->ino, &pdi); + pdi.i_links_count++; + inode_write(fs, vd->ino, &pdi); + uint32_t grp = ((uint32_t)ino - 1) / fs->sb.s_inodes_per_group; + fs->gdt[grp].bg_used_dirs_count++; + fs->dirty = true; + dir->size++; + return 0; +} + +static int ext2_dir_create(vnode_t *dir, const char *name, uint32_t mode, vnode_t **out) { + if (!name || !name[0]) return -EINVAL; + ext2_vdata_t *vd = dir->fs_data; + ext2_t *fs = vd->fs; + if (ext2_dir_lookup(dir, name, out) == 0) return 0; + int32_t ino = alloc_inode(fs); + if (ino < 0) return (int)ino; + ext2_inode_t ndi; + memset(&ndi, 0, sizeof(ndi)); + ndi.i_mode = EXT2_S_IFREG | (uint16_t)(mode ? mode : 0644); + ndi.i_links_count = 1; + inode_write(fs, (uint32_t)ino, &ndi); + int r = ext2_dir_add_entry(fs, vd->ino, (uint32_t)ino, EXT2_FT_REG_FILE, name); + if (r < 0) { free_inode(fs, (uint32_t)ino); return r; } + *out = ext2_make_vnode(fs, (uint32_t)ino, &ndi); + if (!*out) return -ENOMEM; + fs->dirty = true; + dir->size++; + return 0; +} + +static int ext2_dir_unlink(vnode_t *dir, const char *name) { + ext2_vdata_t *vd = dir->fs_data; + ext2_t *fs = vd->fs; + ext2_inode_t di; + int r = inode_read(fs, vd->ino, &di); + if (r < 0) return r; + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + size_t nl = strlen(name); + uint32_t nblocks = (di.i_size + fs->block_size - 1) / fs->block_size; + for (uint32_t fb = 0; fb < nblocks; fb++) { + int32_t db = get_block_num(fs, &di, fb); + if (db <= 0) continue; + block_read(fs, (uint32_t)db, bb); + uint32_t off = 0; + ext2_dir_entry_t *prev = NULL; + while (off < fs->block_size) { + ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off); + if (de->rec_len == 0) break; + if (de->inode != 0 && de->name_len == nl && memcmp(de->name, name, nl) == 0) { + uint32_t cino = de->inode; + ext2_inode_t ci; + inode_read(fs, cino, &ci); + bool is_dir = ((ci.i_mode & 0xF000) == EXT2_S_IFDIR); + free_all_blocks(fs, &ci); + free_inode(fs, cino); + if (prev) prev->rec_len += de->rec_len; + else de->inode = 0; + block_write(fs, (uint32_t)db, bb); + if (is_dir) { + ext2_inode_t pdi; + inode_read(fs, vd->ino, &pdi); + if (pdi.i_links_count > 1) pdi.i_links_count--; + inode_write(fs, vd->ino, &pdi); + uint32_t grp = (cino - 1) / fs->sb.s_inodes_per_group; + if (fs->gdt[grp].bg_used_dirs_count > 0) + fs->gdt[grp].bg_used_dirs_count--; + } + dir->size--; + fs->dirty = true; + kfree(bb); + return 0; + } + prev = de; + off += de->rec_len; + } + } + kfree(bb); + return -ENOENT; +} + +static int ext2_dir_rename(vnode_t *src_dir, const char *src_name, + vnode_t *dst_dir, const char *dst_name) { + ext2_vdata_t *svd = src_dir->fs_data; + ext2_t *fs = svd->fs; + vnode_t *child = NULL; + int r = ext2_dir_lookup(src_dir, src_name, &child); + if (r < 0) return r; + uint8_t ft = (child->type == VFS_NODE_DIR) ? EXT2_FT_DIR : EXT2_FT_REG_FILE; + uint32_t cino = (uint32_t)child->ino; + vnode_unref(child); + vnode_t *ex = NULL; + if (ext2_dir_lookup(dst_dir, dst_name, &ex) == 0) { + vnode_unref(ex); + ext2_dir_unlink(dst_dir, dst_name); + } + ext2_vdata_t *dvd = dst_dir->fs_data; + r = ext2_dir_add_entry(fs, dvd->ino, cino, ft, dst_name); + if (r < 0) return r; + ext2_inode_t sdi; + inode_read(fs, svd->ino, &sdi); + uint8_t *bb = kmalloc(fs->block_size); + if (!bb) return -ENOMEM; + size_t snl = strlen(src_name); + uint32_t nblocks = (sdi.i_size + fs->block_size - 1) / fs->block_size; + for (uint32_t fb = 0; fb < nblocks; fb++) { + int32_t db = get_block_num(fs, &sdi, fb); + if (db <= 0) continue; + block_read(fs, (uint32_t)db, bb); + uint32_t off = 0; + ext2_dir_entry_t *prev = NULL; + while (off < fs->block_size) { + ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off); + if (de->rec_len == 0) break; + if (de->inode == cino && de->name_len == snl && + memcmp(de->name, src_name, snl) == 0) { + if (prev) prev->rec_len += de->rec_len; + else de->inode = 0; + block_write(fs, (uint32_t)db, bb); + kfree(bb); + fs->dirty = true; + return 0; + } + prev = de; + off += de->rec_len; + } + } + kfree(bb); + fs->dirty = true; + return 0; +} + +static const vnode_ops_t ext2_dir_ops = { + .lookup = ext2_dir_lookup, + .readdir = ext2_dir_readdir, + .mkdir = ext2_dir_mkdir, + .create = ext2_dir_create, + .unlink = ext2_dir_unlink, + .rename = ext2_dir_rename, + .stat = ext2_stat, + .ref = ext2_vnode_ref, + .unref = ext2_vnode_unref, +}; + +int ext2_format(blkdev_t *dev, const char *label) { + if (!dev) return -EINVAL; + uint64_t disk_bytes = dev->size_bytes; + if (disk_bytes < 64 * 1024) return -ENOSPC; + uint32_t block_size = 1024; + uint32_t total_blocks = (uint32_t)(disk_bytes / block_size); + uint32_t blocks_per_group = block_size * 8; + uint32_t inodes_per_group = (total_blocks < 8192) ? 128 : 256; + uint32_t groups_count = (total_blocks + blocks_per_group - 1) / blocks_per_group; + if (groups_count == 0) groups_count = 1; + uint32_t total_inodes = inodes_per_group * groups_count; + uint32_t inode_size = 128; + uint32_t it_blocks_pg = (inodes_per_group * inode_size + block_size - 1) / block_size; + uint32_t first_data_block = 1; + uint8_t *zb = kzalloc(block_size); + if (!zb) return -ENOMEM; + for (uint32_t b = 0; b < total_blocks && b < 16; b++) + blkdev_write(dev, (uint64_t)b * block_size, zb, block_size); + ext2_superblock_t sb; + memset(&sb, 0, sizeof(sb)); + sb.s_inodes_count = total_inodes; + sb.s_blocks_count = total_blocks; + sb.s_r_blocks_count = total_blocks / 20; + sb.s_free_blocks_count = total_blocks; + sb.s_free_inodes_count = total_inodes; + sb.s_first_data_block = first_data_block; + sb.s_log_block_size = 0; + sb.s_blocks_per_group = blocks_per_group; + sb.s_frags_per_group = blocks_per_group; + sb.s_inodes_per_group = inodes_per_group; + sb.s_magic = EXT2_SUPER_MAGIC; + sb.s_state = EXT2_VALID_FS; + sb.s_errors = 1; + sb.s_rev_level = EXT2_DYNAMIC_REV; + sb.s_first_ino = 11; + sb.s_inode_size = (uint16_t)inode_size; + sb.s_max_mnt_count = 20; + if (label) strncpy(sb.s_volume_name, label, 15); + uint32_t gdt_block = first_data_block + 1; + uint32_t gdt_blocks = (groups_count * sizeof(ext2_group_desc_t) + block_size - 1) / block_size; + ext2_group_desc_t *gdt = kzalloc(gdt_blocks * block_size); + if (!gdt) { kfree(zb); return -ENOMEM; } + uint32_t used_total = first_data_block; + printf(" ext2: "); + uint32_t last_pct_ext2 = 999; + uint32_t spinner_ext2 = 0; + for (uint32_t g = 0; g < groups_count; g++) { + uint32_t gs = g * blocks_per_group + first_data_block; + uint32_t cb = (g == 0) ? (gdt_block + gdt_blocks) : gs; + gdt[g].bg_block_bitmap = cb++; + gdt[g].bg_inode_bitmap = cb++; + gdt[g].bg_inode_table = cb; + cb += it_blocks_pg; + uint32_t meta = cb - gs; + if (g == 0) meta = cb - first_data_block; + uint32_t gb = blocks_per_group; + if (g == groups_count - 1) gb = total_blocks - gs; + gdt[g].bg_free_blocks_count = (gb > meta) ? (uint16_t)(gb - meta) : 0; + gdt[g].bg_free_inodes_count = (uint16_t)inodes_per_group; + used_total += meta; + for (uint32_t t = 0; t < it_blocks_pg; t++) + blkdev_write(dev, (uint64_t)(gdt[g].bg_inode_table + t) * block_size, zb, block_size); + uint8_t *bbmp = kzalloc(block_size); + for (uint32_t i = 0; i < meta; i++) bmp_set(bbmp, i); + if (g == 0) + for (uint32_t i = 0; i < (gdt_block + gdt_blocks - first_data_block); i++) + bmp_set(bbmp, i); + for (uint32_t i = gb; i < blocks_per_group; i++) bmp_set(bbmp, i); + blkdev_write(dev, (uint64_t)gdt[g].bg_block_bitmap * block_size, bbmp, block_size); + kfree(bbmp); + uint8_t *ibmp = kzalloc(block_size); + if (g == 0) { + for (uint32_t i = 0; i < 11; i++) { bmp_set(ibmp, i); gdt[g].bg_free_inodes_count--; } + } + for (uint32_t i = inodes_per_group; i < block_size * 8; i++) bmp_set(ibmp, i); + blkdev_write(dev, (uint64_t)gdt[g].bg_inode_bitmap * block_size, ibmp, block_size); + kfree(ibmp); + + uint32_t pct = ((g + 1) * 100) / groups_count; + if (pct != last_pct_ext2) { + static const char glyphs[4] = { '|', '/', '-', '\\' }; + printf("\r \r %c ext2: %u%% (group %u/%u)", + glyphs[spinner_ext2 & 3], pct, g + 1, groups_count); + spinner_ext2++; + last_pct_ext2 = pct; + } + } + printf("\r \r ext2: done\n"); + sb.s_free_blocks_count = total_blocks - used_total; + sb.s_free_inodes_count = total_inodes - 11; + int32_t root_blk = -1; + { + uint8_t *bbmp = kmalloc(block_size); + blkdev_read(dev, (uint64_t)gdt[0].bg_block_bitmap * block_size, bbmp, block_size); + for (uint32_t i = 0; i < blocks_per_group; i++) { + if (!bmp_test(bbmp, i)) { + bmp_set(bbmp, i); + blkdev_write(dev, (uint64_t)gdt[0].bg_block_bitmap * block_size, bbmp, block_size); + root_blk = (int32_t)(i + first_data_block); + gdt[0].bg_free_blocks_count--; + sb.s_free_blocks_count--; + break; + } + } + kfree(bbmp); + } + if (root_blk < 0) { kfree(gdt); kfree(zb); return -ENOSPC; } + ext2_inode_t ri; + memset(&ri, 0, sizeof(ri)); + ri.i_mode = EXT2_S_IFDIR | 0755; + ri.i_links_count = 2; + ri.i_block[0] = (uint32_t)root_blk; + ri.i_size = block_size; + ri.i_blocks = block_size / 512; + uint64_t roff = (uint64_t)gdt[0].bg_inode_table * block_size + (uint64_t)(EXT2_ROOT_INO - 1) * inode_size; + blkdev_write(dev, roff, &ri, sizeof(ri)); + uint8_t *rdb = kzalloc(block_size); + ext2_dir_entry_t *dot = (ext2_dir_entry_t *)rdb; + dot->inode = EXT2_ROOT_INO; dot->rec_len = 12; dot->name_len = 1; + dot->file_type = EXT2_FT_DIR; dot->name[0] = '.'; + ext2_dir_entry_t *dotdot = (ext2_dir_entry_t *)(rdb + 12); + dotdot->inode = EXT2_ROOT_INO; dotdot->rec_len = (uint16_t)(block_size - 12); + dotdot->name_len = 2; dotdot->file_type = EXT2_FT_DIR; + dotdot->name[0] = '.'; dotdot->name[1] = '.'; + blkdev_write(dev, (uint64_t)root_blk * block_size, rdb, block_size); + kfree(rdb); + gdt[0].bg_used_dirs_count = 1; + blkdev_write(dev, EXT2_SUPER_OFFSET, &sb, sizeof(sb)); + blkdev_write(dev, (uint64_t)gdt_block * block_size, gdt, gdt_blocks * block_size); + kfree(gdt); + kfree(zb); + serial_printf("[ext2] formatted: %u blocks (%u KiB), %u inodes, bs=%u, label='%s'\n", + total_blocks, total_blocks * block_size / 1024, total_inodes, block_size, + label ? label : ""); + return 0; +} + +vnode_t *ext2_mount(blkdev_t *dev) { + if (!dev) return NULL; + ext2_t *fs = kzalloc(sizeof(ext2_t)); + if (!fs) return NULL; + fs->dev = dev; + if (blkdev_read(dev, EXT2_SUPER_OFFSET, &fs->sb, sizeof(fs->sb)) < 0) { kfree(fs); return NULL; } + if (fs->sb.s_magic != EXT2_SUPER_MAGIC) { + serial_printf("[ext2] bad magic: 0x%x (expected 0x%x)\n", fs->sb.s_magic, EXT2_SUPER_MAGIC); + kfree(fs); + return NULL; + } + fs->block_size = 1024 << fs->sb.s_log_block_size; + fs->inode_size = (fs->sb.s_rev_level >= EXT2_DYNAMIC_REV) ? fs->sb.s_inode_size : 128; + fs->groups_count = (fs->sb.s_blocks_count + fs->sb.s_blocks_per_group - 1) / fs->sb.s_blocks_per_group; + fs->inodes_per_block = fs->block_size / fs->inode_size; + fs->ptrs_per_block = fs->block_size / 4; + uint32_t gdt_block = (fs->block_size == 1024) ? 2 : 1; + uint32_t gdt_sz = fs->groups_count * sizeof(ext2_group_desc_t); + uint32_t gdt_blocks = (gdt_sz + fs->block_size - 1) / fs->block_size; + fs->gdt = kmalloc(gdt_blocks * fs->block_size); + if (!fs->gdt) { kfree(fs); return NULL; } + if (blkdev_read(dev, (uint64_t)gdt_block * fs->block_size, fs->gdt, gdt_blocks * fs->block_size) < 0) { + kfree(fs->gdt); kfree(fs); return NULL; + } + ext2_inode_t root_di; + if (inode_read(fs, EXT2_ROOT_INO, &root_di) < 0) { kfree(fs->gdt); kfree(fs); return NULL; } + vnode_t *root = ext2_make_vnode(fs, EXT2_ROOT_INO, &root_di); + if (!root) { kfree(fs->gdt); kfree(fs); return NULL; } + serial_printf("[ext2] mounted '%s': %u blocks (%u free), %u inodes (%u free), bs=%u\n", + fs->sb.s_volume_name, fs->sb.s_blocks_count, fs->sb.s_free_blocks_count, + fs->sb.s_inodes_count, fs->sb.s_free_inodes_count, fs->block_size); + return root; +} + +void ext2_sync(ext2_t *fs) { + if (!fs || !fs->dirty) return; + sb_flush(fs); + gdt_flush(fs); + if (fs->dev->ops && fs->dev->ops->flush) fs->dev->ops->flush(fs->dev); + fs->dirty = false; +} + +void ext2_unmount(ext2_t *fs) { + if (!fs) return; + ext2_sync(fs); + if (fs->gdt) kfree(fs->gdt); + kfree(fs); +} + +int ext2_statvfs(vnode_t *root, vfs_statvfs_t *out) { + if (!root || !root->fs_data || !out) return -EINVAL; + ext2_vdata_t *vd = (ext2_vdata_t *)root->fs_data; + ext2_t *fs = vd->fs; + if (!fs) return -EINVAL; + + out->f_bsize = fs->block_size; + out->f_blocks = fs->sb.s_blocks_count; + out->f_bfree = fs->sb.s_free_blocks_count; + out->f_bavail = fs->sb.s_free_blocks_count; + out->f_files = fs->sb.s_inodes_count; + out->f_ffree = fs->sb.s_free_inodes_count; + out->f_flag = 0; + out->f_namemax = 255; + return 0; +} \ No newline at end of file diff --git a/kernel/src/fs/fat32.c b/kernel/src/fs/fat32.c new file mode 100644 index 0000000..67e42c3 --- /dev/null +++ b/kernel/src/fs/fat32.c @@ -0,0 +1,1198 @@ +#include "../../include/fs/fat32.h" +#include "../../include/fs/vfs.h" +#include "../../include/drivers/blkdev.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include "../../include/syscall/errno.h" +#include +#include +#include + +#define MAX_CLUSTER_BYTES 32768 + +static int read_sector(fat32_t *fs, uint32_t lba, void *buf) { + return fs->dev->ops->read_sectors(fs->dev, lba, 1, buf); +} +static int write_sector(fat32_t *fs, uint32_t lba, const void *buf) { + if (fs->readonly) return -EROFS; + return fs->dev->ops->write_sectors(fs->dev, lba, 1, buf); +} + +static uint32_t cluster_to_lba(fat32_t *fs, uint32_t cluster) { + return fs->first_data_sector + (cluster - 2) * fs->sectors_per_cluster; +} + +static uint32_t fat_read_entry(fat32_t *fs, uint32_t cluster) { + uint32_t fat_offset = cluster * 4; + uint32_t fat_sector = fs->first_fat_sector + (fat_offset / fs->bytes_per_sector); + uint32_t ent_offset = fat_offset % fs->bytes_per_sector; + + uint8_t sector[FAT32_SECTOR_SIZE]; + if (read_sector(fs, fat_sector, sector) < 0) return FAT32_EOC; + + uint32_t val; + memcpy(&val, sector + ent_offset, 4); + return val & FAT32_CLUSTER_MASK; +} + +static int fat_write_entry(fat32_t *fs, uint32_t cluster, uint32_t value) { + if (fs->readonly) return -EROFS; + uint32_t fat_offset = cluster * 4; + uint32_t ent_offset = fat_offset % fs->bytes_per_sector; + + for (uint32_t i = 0; i < fs->num_fats; i++) { + uint32_t fat_sector = fs->first_fat_sector + i * fs->fat_size_sectors + + (fat_offset / fs->bytes_per_sector); + uint8_t sector[FAT32_SECTOR_SIZE]; + int r = read_sector(fs, fat_sector, sector); + if (r < 0) return r; + uint32_t existing; + memcpy(&existing, sector + ent_offset, 4); + uint32_t reserved = existing & 0xF0000000; + uint32_t merged = (value & FAT32_CLUSTER_MASK) | reserved; + memcpy(sector + ent_offset, &merged, 4); + r = write_sector(fs, fat_sector, sector); + if (r < 0) return r; + } + fs->dirty = true; + return 0; +} + +static bool fat32_is_eoc(uint32_t cluster) { + return (cluster & FAT32_CLUSTER_MASK) >= FAT32_EOC_MIN; +} + +static int read_cluster(fat32_t *fs, uint32_t cluster, void *buf) { + uint32_t lba = cluster_to_lba(fs, cluster); + return fs->dev->ops->read_sectors(fs->dev, lba, fs->sectors_per_cluster, buf); +} +static int write_cluster(fat32_t *fs, uint32_t cluster, const void *buf) { + if (fs->readonly) return -EROFS; + uint32_t lba = cluster_to_lba(fs, cluster); + return fs->dev->ops->write_sectors(fs->dev, lba, fs->sectors_per_cluster, buf); +} + +static uint32_t allocate_cluster(fat32_t *fs) { + if (fs->readonly) return 0; + uint32_t start = fs->next_free_hint; + if (start < 2) start = 2; + for (uint32_t i = 0; i < fs->total_clusters; i++) { + uint32_t c = 2 + ((start - 2 + i) % fs->total_clusters); + uint32_t v = fat_read_entry(fs, c); + if (v == FAT32_FREE_CLUSTER) { + if (fat_write_entry(fs, c, FAT32_EOC) < 0) return 0; + fs->next_free_hint = c + 1; + if (fs->free_count != 0xFFFFFFFF) fs->free_count--; + + uint8_t *zero = (uint8_t *)malloc(fs->bytes_per_cluster); + if (zero) { + memset(zero, 0, fs->bytes_per_cluster); + write_cluster(fs, c, zero); + free(zero); + } + return c; + } + } + return 0; +} + +static int free_cluster_chain(fat32_t *fs, uint32_t first) { + if (fs->readonly) return -EROFS; + uint32_t c = first; + while (c >= 2 && !fat32_is_eoc(c)) { + uint32_t next = fat_read_entry(fs, c); + fat_write_entry(fs, c, FAT32_FREE_CLUSTER); + if (fs->free_count != 0xFFFFFFFF) fs->free_count++; + c = next; + } + return 0; +} + +int fat32_sync(fat32_t *fs) { + if (!fs || fs->readonly) return 0; + if (fs->fsinfo_sector) { + uint8_t sec[FAT32_SECTOR_SIZE]; + if (read_sector(fs, fs->fsinfo_sector, sec) == 0) { + fat32_fsinfo_t *fsi = (fat32_fsinfo_t *)sec; + if (fsi->lead_sig == FAT32_FSINFO_LEAD_SIG + && fsi->struct_sig == FAT32_FSINFO_STRUCT_SIG) { + fsi->free_count = fs->free_count; + fsi->next_free = fs->next_free_hint; + write_sector(fs, fs->fsinfo_sector, sec); + } + } + } + if (fs->dev->ops->flush) fs->dev->ops->flush(fs->dev); + fs->dirty = false; + return 0; +} + +static void fat_name_to_string(const uint8_t raw[11], char *out) { + int o = 0; + for (int i = 0; i < 8; i++) { + if (raw[i] == ' ') break; + out[o++] = raw[i]; + } + if (raw[8] != ' ') { + out[o++] = '.'; + for (int i = 8; i < 11; i++) { + if (raw[i] == ' ') break; + out[o++] = raw[i]; + } + } + out[o] = '\0'; + for (int i = 0; out[i]; i++) + if (out[i] >= 'A' && out[i] <= 'Z') out[i] = (char)(out[i] - 'A' + 'a'); +} + +static void string_to_fat_name(const char *name, uint8_t out[11]) { + memset(out, ' ', 11); + const char *dot = NULL; + for (const char *p = name; *p; p++) if (*p == '.') dot = p; + int base_len = dot ? (int)(dot - name) : (int)strlen(name); + if (base_len > 8) base_len = 8; + for (int i = 0; i < base_len; i++) { + char c = name[i]; + if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A'); + out[i] = (uint8_t)c; + } + if (dot) { + const char *ext = dot + 1; + int el = (int)strlen(ext); + if (el > 3) el = 3; + for (int i = 0; i < el; i++) { + char c = ext[i]; + if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A'); + out[8 + i] = (uint8_t)c; + } + } +} +static uint8_t lfn_checksum(const uint8_t sfn[11]) { + uint8_t s = 0; + for (int i = 0; i < 11; i++) s = (uint8_t)(((s & 1) << 7) + (s >> 1) + sfn[i]); + return s; +} + +static void lfn_append(char *out, int *out_len, int out_cap, const uint8_t *src, int count) { + for (int i = 0; i < count; i++) { + uint16_t wc = (uint16_t)(src[i*2] | (src[i*2+1] << 8)); + if (wc == 0x0000 || wc == 0xFFFF) return; + if (*out_len + 1 >= out_cap) return; + out[(*out_len)++] = (wc < 128) ? (char)wc : '?'; + } + out[*out_len] = '\0'; +} + +static vnode_t *fat32_alloc_vnode(fat32_t *fs, uint32_t first_cluster, + uint32_t size, uint8_t attr, + uint32_t dir_cluster, uint32_t dir_entry_off); +static const vnode_ops_t fat32_file_ops; +static const vnode_ops_t fat32_dir_ops; + +static int fat32_traverse_dir(fat32_t *fs, uint32_t start_cluster, + int (*cb)(fat32_t *fs, uint32_t cluster, + uint32_t entry_off, + fat32_dirent_t *e, + const char *name, void *ud), + void *ud) +{ + uint32_t cluster = start_cluster ? start_cluster : fs->root_cluster; + char lfn_buf[260]; + int lfn_len = 0; + lfn_buf[0] = '\0'; + + uint8_t *cluster_buf = fs->shared_buf; + while (!fat32_is_eoc(cluster) && cluster >= 2) { + if (read_cluster(fs, cluster, cluster_buf) < 0) return -EIO; + + uint32_t entries = fs->bytes_per_cluster / 32; + for (uint32_t i = 0; i < entries; i++) { + fat32_dirent_t *e = (fat32_dirent_t *)(cluster_buf + i * 32); + if (e->name[0] == 0x00) return -ENOENT; + if ((uint8_t)e->name[0] == 0xE5) { lfn_len = 0; lfn_buf[0] = '\0'; continue; } + + if (e->attr == FAT_ATTR_LONG_NAME) { + fat32_lfn_t *lfn = (fat32_lfn_t *)e; + char part[14]; int pl = 0; + lfn_append(part, &pl, sizeof(part), lfn->name1, 5); + lfn_append(part, &pl, sizeof(part), lfn->name2, 6); + lfn_append(part, &pl, sizeof(part), lfn->name3, 2); + char merged[260]; + int ml = 0; + for (int k = 0; k < pl && ml < 259; k++) merged[ml++] = part[k]; + for (int k = 0; k < lfn_len && ml < 259; k++) merged[ml++] = lfn_buf[k]; + merged[ml] = '\0'; + memcpy(lfn_buf, merged, ml + 1); + lfn_len = ml; + continue; + } + + if (e->attr & FAT_ATTR_VOLUME_ID) { lfn_len = 0; lfn_buf[0] = '\0'; continue; } + + char sfn[13]; + fat_name_to_string(e->name, sfn); + const char *name = (lfn_len > 0) ? lfn_buf : sfn; + int r = cb(fs, cluster, i * 32, e, name, ud); + if (r != 0) return r; + lfn_len = 0; lfn_buf[0] = '\0'; + } + cluster = fat_read_entry(fs, cluster); + } + return -ENOENT; +} + +typedef struct { + const char *target; + vnode_t **out; + uint32_t found; +} lookup_ctx_t; + +static int lookup_cb(fat32_t *fs, uint32_t cluster, uint32_t entry_off, + fat32_dirent_t *e, const char *name, void *ud) +{ + lookup_ctx_t *ctx = (lookup_ctx_t *)ud; + bool match = true; + for (int k = 0; ctx->target[k] || name[k]; k++) { + char a = ctx->target[k]; + char b = name[k]; + if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); + if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a'); + if (a != b) { match = false; break; } + } + if (match) { + uint32_t first = ((uint32_t)e->cluster_hi << 16) | e->cluster_lo; + vnode_t *vn = fat32_alloc_vnode(fs, first, e->file_size, e->attr, + cluster, entry_off); + if (!vn) return -ENOMEM; + *ctx->out = vn; + ctx->found = 1; + return 1; + } + return 0; +} + +static int fat32_dir_lookup(vnode_t *dir, const char *name, vnode_t **out) { + fat32_vdata_t *vd = (fat32_vdata_t *)dir->fs_data; + if (!vd || !vd->fs) return -EIO; + lookup_ctx_t ctx = { .target = name, .out = out, .found = 0 }; + int r = fat32_traverse_dir(vd->fs, vd->first_cluster, lookup_cb, &ctx); + if (ctx.found) return 0; + return (r == -ENOENT) ? -ENOENT : r; +} + +typedef struct { + uint64_t wanted; + uint64_t cur; + vfs_dirent_t *out; + int got; +} readdir_ctx_t; + +static int readdir_cb(fat32_t *fs, uint32_t cluster, uint32_t entry_off, + fat32_dirent_t *e, const char *name, void *ud) +{ + (void)fs; (void)cluster; (void)entry_off; + readdir_ctx_t *ctx = (readdir_ctx_t *)ud; + if (ctx->cur == ctx->wanted) { + memset(ctx->out, 0, sizeof(*ctx->out)); + int n = 0; + while (name[n] && n < (int)sizeof(ctx->out->d_name) - 1) { + ctx->out->d_name[n] = name[n]; n++; + } + ctx->out->d_name[n] = '\0'; + ctx->out->d_type = (e->attr & FAT_ATTR_DIRECTORY) ? 1 : 0; + ctx->got = 1; + return 1; + } + ctx->cur++; + return 0; +} + +static int fat32_dir_readdir(vnode_t *dir, uint64_t index, vfs_dirent_t *out) { + fat32_vdata_t *vd = (fat32_vdata_t *)dir->fs_data; + if (!vd || !vd->fs) return -EIO; + readdir_ctx_t ctx = { .wanted = index, .cur = 0, .out = out, .got = 0 }; + int r = fat32_traverse_dir(vd->fs, vd->first_cluster, readdir_cb, &ctx); + if (ctx.got) return 0; + return (r == -ENOENT) ? -ENOENT : r; +} + +static int64_t fat32_file_read(vnode_t *node, void *buf, size_t len, uint64_t offset) { + fat32_vdata_t *vd = (fat32_vdata_t *)node->fs_data; + if (!vd || !vd->fs) return -EIO; + if (offset >= vd->file_size) return 0; + if (offset + len > vd->file_size) len = vd->file_size - offset; + + fat32_t *fs = vd->fs; + uint32_t cluster = vd->first_cluster; + uint32_t cluster_off = (uint32_t)(offset / fs->bytes_per_cluster); + for (uint32_t i = 0; i < cluster_off; i++) { + if (fat32_is_eoc(cluster) || cluster < 2) return 0; + cluster = fat_read_entry(fs, cluster); + } + + size_t done = 0; + uint32_t in_cluster_off = (uint32_t)(offset % fs->bytes_per_cluster); + uint8_t *cluster_buf = fs->shared_buf; + if (!cluster_buf) return -EIO; + + while (done < len && !fat32_is_eoc(cluster) && cluster >= 2) { + if (read_cluster(fs, cluster, cluster_buf) < 0) return -EIO; + uint32_t avail = fs->bytes_per_cluster - in_cluster_off; + uint32_t take = (avail < (len - done)) ? avail : (uint32_t)(len - done); + memcpy((uint8_t *)buf + done, cluster_buf + in_cluster_off, take); + done += take; + in_cluster_off = 0; + if (done < len) cluster = fat_read_entry(fs, cluster); + } + return (int64_t)done; +} + +static int64_t fat32_file_write(vnode_t *node, const void *buf, size_t len, uint64_t offset) { + fat32_vdata_t *vd = (fat32_vdata_t *)node->fs_data; + if (!vd || !vd->fs) return -EIO; + if (vd->fs->readonly) return -EROFS; + fat32_t *fs = vd->fs; + + if (vd->first_cluster == 0) { + uint32_t c = allocate_cluster(fs); + if (c == 0) return -ENOSPC; + vd->first_cluster = c; + vd->cached_cluster = c; + vd->cached_index = 0; + } + + uint32_t target_index = (uint32_t)(offset / fs->bytes_per_cluster); + uint32_t cluster; + uint32_t start_index; + + if (vd->cached_cluster != 0 && vd->cached_index <= target_index) { + cluster = vd->cached_cluster; + start_index = vd->cached_index; + } else { + cluster = vd->first_cluster; + start_index = 0; + } + + for (uint32_t i = start_index; i < target_index; i++) { + uint32_t next = fat_read_entry(fs, cluster); + if (fat32_is_eoc(next) || next < 2) { + uint32_t nc = allocate_cluster(fs); + if (nc == 0) return -ENOSPC; + fat_write_entry(fs, cluster, nc); + cluster = nc; + } else { + cluster = next; + } + } + + size_t done = 0; + uint32_t in_cluster_off = (uint32_t)(offset % fs->bytes_per_cluster); + uint32_t current_index = target_index; + + if (!vd->io_buf) { + vd->io_buf = (uint8_t *)malloc(fs->bytes_per_cluster); + if (!vd->io_buf) return -ENOMEM; + } + + while (done < len) { + uint32_t avail = fs->bytes_per_cluster - in_cluster_off; + uint32_t take = (avail < (len - done)) ? avail : (uint32_t)(len - done); + + if (in_cluster_off == 0 && take == fs->bytes_per_cluster) { + if (fs->dev->ops->write_sectors(fs->dev, + cluster_to_lba(fs, cluster), + fs->sectors_per_cluster, + (const uint8_t *)buf + done) < 0) return -EIO; + } else { + if (read_cluster(fs, cluster, vd->io_buf) < 0) return -EIO; + memcpy(vd->io_buf + in_cluster_off, (const uint8_t *)buf + done, take); + if (write_cluster(fs, cluster, vd->io_buf) < 0) return -EIO; + } + + done += take; + in_cluster_off = 0; + + if (done < len) { + uint32_t next = fat_read_entry(fs, cluster); + if (fat32_is_eoc(next) || next < 2) { + uint32_t nc = allocate_cluster(fs); + if (nc == 0) break; + fat_write_entry(fs, cluster, nc); + next = nc; + } + cluster = next; + current_index++; + } + } + + vd->cached_cluster = cluster; + vd->cached_index = current_index; + + uint64_t new_size = offset + done; + if (new_size > vd->file_size) { + vd->file_size = (uint32_t)new_size; + node->size = new_size; + vd->size_dirty = true; + } + + return (int64_t)done; +} + +static int fat32_file_stat(vnode_t *node, vfs_stat_t *out) { + fat32_vdata_t *vd = (fat32_vdata_t *)node->fs_data; + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = node->type; + out->st_mode = 0644; + out->st_size = vd ? vd->file_size : 0; + return 0; +} + +static void fat32_common_ref(vnode_t *n) { n->refcount++; } +static void fat32_common_unref(vnode_t *n) { + if (--n->refcount <= 0) { + fat32_vdata_t *vd = (fat32_vdata_t *)n->fs_data; + if (vd && vd->fs && vd->size_dirty && n->type == VFS_NODE_FILE) { + uint8_t *tmp = (uint8_t *)malloc(vd->fs->bytes_per_cluster); + if (tmp) { + if (read_cluster(vd->fs, vd->dir_cluster, tmp) == 0) { + fat32_dirent_t ent; + memcpy(&ent, tmp + vd->dir_entry_offset, sizeof(ent)); + ent.file_size = vd->file_size; + ent.cluster_lo = (uint16_t)(vd->first_cluster & 0xFFFF); + ent.cluster_hi = (uint16_t)((vd->first_cluster >> 16) & 0xFFFF); + ent.attr = vd->attr ? vd->attr : FAT_ATTR_ARCHIVE; + memcpy(tmp + vd->dir_entry_offset, &ent, sizeof(ent)); + write_cluster(vd->fs, vd->dir_cluster, tmp); + } + free(tmp); + } + fat32_sync(vd->fs); + } + if (vd) { + if (vd->io_buf) { free(vd->io_buf); vd->io_buf = NULL; } + free(vd); + n->fs_data = NULL; + } + free(n); + } +} + +static bool name_fits_sfn(const char *name) { + int dot = -1; + int len = 0; + for (int i = 0; name[i]; i++) len++; + if (len > 12) return false; + for (int i = 0; i < len; i++) { + if (name[i] == '.') { + if (dot != -1) return false; + dot = i; + } + } + int base_len = (dot == -1) ? len : dot; + int ext_len = (dot == -1) ? 0 : (len - dot - 1); + if (base_len == 0 || base_len > 8) return false; + if (ext_len > 3) return false; + for (int i = 0; i < len; i++) { + char c = name[i]; + if (c == '.') continue; + if (c >= 'a' && c <= 'z') return false; + if (c == ' ') return false; + } + return true; +} + +static void generate_short_name(const char *lfn, uint8_t out[11]) { + memset(out, ' ', 11); + int oi = 0; + int i = 0; + int last_dot = -1; + for (int k = 0; lfn[k]; k++) if (lfn[k] == '.') last_dot = k; + + int base_end = (last_dot >= 0) ? last_dot : -1; + while (lfn[i] && (base_end < 0 || i < base_end) && oi < 6) { + char c = lfn[i++]; + if (c == ' ' || c == '.') continue; + if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A'); + if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || + c == '_' || c == '-')) c = '_'; + out[oi++] = (uint8_t)c; + } + out[oi++] = '~'; + out[oi++] = '1'; + + if (last_dot >= 0) { + int ei = 8; + int k = last_dot + 1; + while (lfn[k] && ei < 11) { + char c = lfn[k++]; + if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A'); + if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || + c == '_' || c == '-')) c = '_'; + out[ei++] = (uint8_t)c; + } + } +} + +static int find_free_slots(fat32_t *fs, uint32_t dir_cluster, uint32_t count, + uint32_t *out_cluster, uint32_t *out_off) +{ + uint32_t cluster = dir_cluster ? dir_cluster : fs->root_cluster; + uint8_t *cluster_buf = fs->shared_buf; + uint32_t run_cluster = 0, run_off = 0, run_len = 0; + + while (true) { + if (read_cluster(fs, cluster, cluster_buf) < 0) return -EIO; + uint32_t entries = fs->bytes_per_cluster / 32; + for (uint32_t i = 0; i < entries; i++) { + uint8_t first = cluster_buf[i * 32]; + if (first == 0x00 || first == 0xE5) { + if (run_len == 0) { run_cluster = cluster; run_off = i * 32; } + run_len++; + if (run_len >= count) { + *out_cluster = run_cluster; + *out_off = run_off; + return 0; + } + } else { + run_len = 0; + } + } + uint32_t next = fat_read_entry(fs, cluster); + if (fat32_is_eoc(next) || next < 2) { + uint32_t nc = allocate_cluster(fs); + if (nc == 0) return -ENOSPC; + fat_write_entry(fs, cluster, nc); + if (run_len == 0) { + *out_cluster = nc; + *out_off = 0; + } else { + *out_cluster = run_cluster; + *out_off = run_off; + } + return 0; + } + cluster = next; + } +} + +static void lfn_put_chars(uint8_t *slot_bytes, const char *src_utf8, int src_len, int offset) { + static const uint8_t positions1[5] = { 1, 3, 5, 7, 9 }; + static const uint8_t positions2[6] = { 14, 16, 18, 20, 22, 24 }; + static const uint8_t positions3[2] = { 28, 30 }; + + for (int i = 0; i < 5; i++) { + int idx = offset + i; + uint16_t ch; + if (idx < src_len) ch = (uint8_t)src_utf8[idx]; + else if (idx == src_len) ch = 0; + else ch = 0xFFFF; + slot_bytes[positions1[i]] = (uint8_t)(ch & 0xFF); + slot_bytes[positions1[i] + 1] = (uint8_t)(ch >> 8); + } + for (int i = 0; i < 6; i++) { + int idx = offset + 5 + i; + uint16_t ch; + if (idx < src_len) ch = (uint8_t)src_utf8[idx]; + else if (idx == src_len) ch = 0; + else ch = 0xFFFF; + slot_bytes[positions2[i]] = (uint8_t)(ch & 0xFF); + slot_bytes[positions2[i] + 1] = (uint8_t)(ch >> 8); + } + for (int i = 0; i < 2; i++) { + int idx = offset + 11 + i; + uint16_t ch; + if (idx < src_len) ch = (uint8_t)src_utf8[idx]; + else if (idx == src_len) ch = 0; + else ch = 0xFFFF; + slot_bytes[positions3[i]] = (uint8_t)(ch & 0xFF); + slot_bytes[positions3[i] + 1] = (uint8_t)(ch >> 8); + } +} + +static int write_dirents_with_lfn(fat32_t *fs, uint32_t host_cluster, uint32_t host_off, + const char *name, const fat32_dirent_t *sfn_entry, + int slot_count) +{ + uint8_t *buf = fs->shared_buf; + if (read_cluster(fs, host_cluster, buf) < 0) return -EIO; + + int name_len = 0; + while (name[name_len]) name_len++; + + uint8_t csum = lfn_checksum(sfn_entry->name); + + int lfn_count = slot_count - 1; + uint32_t off = host_off; + + for (int i = 0; i < lfn_count; i++) { + int seq = lfn_count - i; + int char_off = (seq - 1) * 13; + uint8_t entry[32]; + memset(entry, 0, 32); + uint8_t ord = (uint8_t)seq; + if (i == 0) ord |= 0x40; + entry[0] = ord; + entry[11] = 0x0F; + entry[12] = 0; + entry[13] = csum; + entry[26] = 0; + entry[27] = 0; + lfn_put_chars(entry, name, name_len, char_off); + + if (off >= fs->bytes_per_cluster) { + if (write_cluster(fs, host_cluster, buf) < 0) return -EIO; + uint32_t next = fat_read_entry(fs, host_cluster); + if (fat32_is_eoc(next) || next < 2) { + uint32_t nc = allocate_cluster(fs); + if (nc == 0) return -ENOSPC; + fat_write_entry(fs, host_cluster, nc); + next = nc; + } + host_cluster = next; + if (read_cluster(fs, host_cluster, buf) < 0) return -EIO; + off = 0; + } + memcpy(buf + off, entry, 32); + off += 32; + } + + if (off >= fs->bytes_per_cluster) { + if (write_cluster(fs, host_cluster, buf) < 0) return -EIO; + uint32_t next = fat_read_entry(fs, host_cluster); + if (fat32_is_eoc(next) || next < 2) { + uint32_t nc = allocate_cluster(fs); + if (nc == 0) return -ENOSPC; + fat_write_entry(fs, host_cluster, nc); + next = nc; + } + host_cluster = next; + if (read_cluster(fs, host_cluster, buf) < 0) return -EIO; + off = 0; + } + memcpy(buf + off, sfn_entry, 32); + if (write_cluster(fs, host_cluster, buf) < 0) return -EIO; + + return 0; +} + +static int fat32_dir_create(vnode_t *dir, const char *name, uint32_t mode, + vnode_t **out) +{ + (void)mode; + fat32_vdata_t *vd = (fat32_vdata_t *)dir->fs_data; + if (!vd || !vd->fs) return -EIO; + if (vd->fs->readonly) return -EROFS; + fat32_t *fs = vd->fs; + + vnode_t *tmp = NULL; + if (fat32_dir_lookup(dir, name, &tmp) == 0) { + if (out) *out = tmp; + else fat32_common_unref(tmp); + return -EEXIST; + } + + int name_len = 0; + while (name[name_len]) name_len++; + + bool use_lfn = !name_fits_sfn(name); + int lfn_entries = 0; + if (use_lfn) { + lfn_entries = (name_len + 12) / 13; + } + int slot_count = lfn_entries + 1; + + uint32_t parent_cluster = vd->first_cluster ? vd->first_cluster : fs->root_cluster; + uint32_t host_cluster, host_off; + int r = find_free_slots(fs, parent_cluster, slot_count, &host_cluster, &host_off); + if (r < 0) return r; + + fat32_dirent_t e; + memset(&e, 0, sizeof(e)); + if (use_lfn) generate_short_name(name, e.name); + else string_to_fat_name(name, e.name); + e.attr = FAT_ATTR_ARCHIVE; + e.cluster_lo = 0; + e.cluster_hi = 0; + e.file_size = 0; + + if (use_lfn) { + r = write_dirents_with_lfn(fs, host_cluster, host_off, name, &e, slot_count); + if (r < 0) return r; + } else { + uint8_t *cluster_buf = fs->shared_buf; + if (read_cluster(fs, host_cluster, cluster_buf) < 0) return -EIO; + memcpy(cluster_buf + host_off, &e, sizeof(e)); + if (write_cluster(fs, host_cluster, cluster_buf) < 0) return -EIO; + } + + if (out) { + uint32_t sfn_cluster = host_cluster; + uint32_t sfn_off = host_off + lfn_entries * 32; + if (sfn_off >= fs->bytes_per_cluster) { + uint32_t next = fat_read_entry(fs, host_cluster); + sfn_cluster = next; + sfn_off = sfn_off - fs->bytes_per_cluster; + } + vnode_t *vn = fat32_alloc_vnode(fs, 0, 0, FAT_ATTR_ARCHIVE, + sfn_cluster, sfn_off); + if (!vn) return -ENOMEM; + *out = vn; + } + fat32_sync(fs); + return 0; +} + +static int fat32_dir_mkdir(vnode_t *dir, const char *name, uint32_t mode) { + (void)mode; + fat32_vdata_t *vd = (fat32_vdata_t *)dir->fs_data; + if (!vd || !vd->fs) return -EIO; + if (vd->fs->readonly) return -EROFS; + fat32_t *fs = vd->fs; + + vnode_t *tmp = NULL; + if (fat32_dir_lookup(dir, name, &tmp) == 0) { + fat32_common_unref(tmp); + return -EEXIST; + } + + uint32_t new_cluster = allocate_cluster(fs); + if (new_cluster == 0) return -ENOSPC; + + uint32_t parent_cluster = vd->first_cluster ? vd->first_cluster : fs->root_cluster; + uint8_t *dir_buf = fs->shared_buf; + memset(dir_buf, 0, fs->bytes_per_cluster); + fat32_dirent_t dot, dotdot; + memset(&dot, 0, sizeof(dot)); + memset(&dotdot, 0, sizeof(dotdot)); + memcpy(dot.name, ". ", 11); + dot.attr = FAT_ATTR_DIRECTORY; + dot.cluster_lo = (uint16_t)(new_cluster & 0xFFFF); + dot.cluster_hi = (uint16_t)((new_cluster >> 16) & 0xFFFF); + memcpy(dotdot.name, ".. ", 11); + dotdot.attr = FAT_ATTR_DIRECTORY; + uint32_t parent_ref = (parent_cluster == fs->root_cluster) ? 0 : parent_cluster; + dotdot.cluster_lo = (uint16_t)(parent_ref & 0xFFFF); + dotdot.cluster_hi = (uint16_t)((parent_ref >> 16) & 0xFFFF); + memcpy(dir_buf, &dot, 32); + memcpy(dir_buf + 32, &dotdot, 32); + if (write_cluster(fs, new_cluster, dir_buf) < 0) { + free_cluster_chain(fs, new_cluster); + return -EIO; + } + + int name_len = 0; + while (name[name_len]) name_len++; + bool use_lfn = !name_fits_sfn(name); + int lfn_entries = use_lfn ? (name_len + 12) / 13 : 0; + int slot_count = lfn_entries + 1; + + uint32_t host_cluster, host_off; + int r = find_free_slots(fs, parent_cluster, slot_count, &host_cluster, &host_off); + if (r < 0) { free_cluster_chain(fs, new_cluster); return r; } + + fat32_dirent_t e; + memset(&e, 0, sizeof(e)); + if (use_lfn) generate_short_name(name, e.name); + else string_to_fat_name(name, e.name); + e.attr = FAT_ATTR_DIRECTORY; + e.cluster_lo = (uint16_t)(new_cluster & 0xFFFF); + e.cluster_hi = (uint16_t)((new_cluster >> 16) & 0xFFFF); + e.file_size = 0; + + if (use_lfn) { + r = write_dirents_with_lfn(fs, host_cluster, host_off, name, &e, slot_count); + if (r < 0) { free_cluster_chain(fs, new_cluster); return r; } + } else { + uint8_t *buf2 = fs->shared_buf; + if (read_cluster(fs, host_cluster, buf2) < 0) { + free_cluster_chain(fs, new_cluster); + return -EIO; + } + memcpy(buf2 + host_off, &e, sizeof(e)); + if (write_cluster(fs, host_cluster, buf2) < 0) { + free_cluster_chain(fs, new_cluster); + return -EIO; + } + } + + fat32_sync(fs); + return 0; +} + +typedef struct { + const char *target; + int done; +} unlink_ctx_t; + +static int unlink_cb(fat32_t *fs, uint32_t cluster, uint32_t entry_off, + fat32_dirent_t *e, const char *name, void *ud) +{ + unlink_ctx_t *ctx = (unlink_ctx_t *)ud; + bool match = true; + for (int k = 0; ctx->target[k] || name[k]; k++) { + char a = ctx->target[k]; + char b = name[k]; + if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); + if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a'); + if (a != b) { match = false; break; } + } + if (!match) return 0; + + uint32_t first = ((uint32_t)e->cluster_hi << 16) | e->cluster_lo; + if (first != 0) free_cluster_chain(fs, first); + + uint8_t *buf = fs->shared_buf; + if (read_cluster(fs, cluster, buf) < 0) return -EIO; + buf[entry_off] = 0xE5; + if (write_cluster(fs, cluster, buf) < 0) return -EIO; + ctx->done = 1; + return 1; +} + +static int fat32_dir_unlink(vnode_t *dir, const char *name) { + fat32_vdata_t *vd = (fat32_vdata_t *)dir->fs_data; + if (!vd || !vd->fs) return -EIO; + if (vd->fs->readonly) return -EROFS; + unlink_ctx_t ctx = { .target = name, .done = 0 }; + fat32_traverse_dir(vd->fs, vd->first_cluster, unlink_cb, &ctx); + if (!ctx.done) return -ENOENT; + fat32_sync(vd->fs); + return 0; +} + +static int fat32_dir_stat(vnode_t *node, vfs_stat_t *out) { + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = VFS_NODE_DIR; + out->st_mode = 0755; + out->st_size = 0; + return 0; +} + +static const vnode_ops_t fat32_file_ops = { + .read = fat32_file_read, + .write = fat32_file_write, + .stat = fat32_file_stat, + .ref = fat32_common_ref, + .unref = fat32_common_unref, +}; + +static const vnode_ops_t fat32_dir_ops = { + .lookup = fat32_dir_lookup, + .readdir = fat32_dir_readdir, + .create = fat32_dir_create, + .mkdir = fat32_dir_mkdir, + .unlink = fat32_dir_unlink, + .stat = fat32_dir_stat, + .ref = fat32_common_ref, + .unref = fat32_common_unref, +}; + +static uint64_t g_fat32_ino = 1000; + +static vnode_t *fat32_alloc_vnode(fat32_t *fs, uint32_t first_cluster, + uint32_t size, uint8_t attr, + uint32_t dir_cluster, uint32_t dir_entry_off) +{ + vnode_t *vn = calloc(1, sizeof(vnode_t)); + if (!vn) return NULL; + fat32_vdata_t *vd = calloc(1, sizeof(fat32_vdata_t)); + if (!vd) { free(vn); return NULL; } + + vd->fs = fs; + vd->first_cluster = first_cluster; + vd->file_size = size; + vd->attr = attr; + vd->dir_cluster = dir_cluster; + vd->dir_entry_offset = dir_entry_off; + vd->cached_cluster = first_cluster; + vd->cached_index = 0; + vd->size_dirty = false; + + vn->ino = g_fat32_ino++; + vn->refcount = 1; + vn->fs_data = vd; + + if (attr & FAT_ATTR_DIRECTORY) { + vn->type = VFS_NODE_DIR; + vn->mode = 0755; + vn->ops = &fat32_dir_ops; + } else { + vn->type = VFS_NODE_FILE; + vn->mode = 0644; + vn->size = size; + vn->ops = &fat32_file_ops; + } + return vn; +} + +vnode_t *fat32_mount(blkdev_t *dev) { + if (!dev) return NULL; + + uint8_t sec[FAT32_SECTOR_SIZE]; + if (dev->ops->read_sectors(dev, 0, 1, sec) < 0) { + serial_printf("[FAT32] cannot read BPB on %s\n", dev->name); + return NULL; + } + + fat32_bpb_t *bpb = (fat32_bpb_t *)sec; + if (bpb->bs_signature != 0xAA55) { + serial_printf("[FAT32] %s: no boot signature\n", dev->name); + return NULL; + } + if (bpb->bpb_bytes_per_sector != 512) { + serial_printf("[FAT32] %s: unsupported sector size %u\n", + dev->name, bpb->bpb_bytes_per_sector); + return NULL; + } + if (bpb->bpb_root_entries != 0 || bpb->bpb_fat_size_16 != 0) { + serial_printf("[FAT32] %s: not FAT32 (FAT12/16 detected)\n", dev->name); + return NULL; + } + if (bpb->bpb_fat_size_32 == 0) { + serial_printf("[FAT32] %s: FAT size 0\n", dev->name); + return NULL; + } + + fat32_t *fs = calloc(1, sizeof(fat32_t)); + if (!fs) return NULL; + + fs->dev = dev; + fs->bytes_per_sector = bpb->bpb_bytes_per_sector; + fs->sectors_per_cluster = bpb->bpb_sectors_per_cluster; + fs->bytes_per_cluster = fs->bytes_per_sector * fs->sectors_per_cluster; + fs->reserved_sectors = bpb->bpb_reserved_sectors; + fs->num_fats = bpb->bpb_num_fats; + fs->fat_size_sectors = bpb->bpb_fat_size_32; + fs->total_sectors = bpb->bpb_total_sectors_32 + ? bpb->bpb_total_sectors_32 + : bpb->bpb_total_sectors_16; + fs->root_cluster = bpb->bpb_root_cluster; + fs->first_fat_sector = fs->reserved_sectors; + fs->first_data_sector = fs->reserved_sectors + + (fs->num_fats * fs->fat_size_sectors); + uint32_t data_sectors = fs->total_sectors - fs->first_data_sector; + fs->total_clusters = data_sectors / fs->sectors_per_cluster; + fs->fsinfo_sector = bpb->bpb_fs_info; + fs->next_free_hint = 2; + fs->free_count = 0xFFFFFFFF; + fs->readonly = false; + + fs->shared_buf = (uint8_t *)malloc(fs->bytes_per_cluster); + if (!fs->shared_buf) { free(fs); return NULL; } + if (fs->bytes_per_cluster > MAX_CLUSTER_BYTES) { + serial_printf("[FAT32] %s: cluster too large %u, bailing\n", + dev->name, fs->bytes_per_cluster); + free(fs); + return NULL; + } + + if (fs->fsinfo_sector) { + uint8_t fsibuf[FAT32_SECTOR_SIZE]; + if (read_sector(fs, fs->fsinfo_sector, fsibuf) == 0) { + fat32_fsinfo_t *fsi = (fat32_fsinfo_t *)fsibuf; + if (fsi->lead_sig == FAT32_FSINFO_LEAD_SIG + && fsi->struct_sig == FAT32_FSINFO_STRUCT_SIG) { + fs->free_count = fsi->free_count; + if (fsi->next_free >= 2 && fsi->next_free < fs->total_clusters + 2) + fs->next_free_hint = fsi->next_free; + } + } + } + + serial_printf("[FAT32] mounted %s: clusters=%u cluster_size=%u root_cluster=%u free=%u\n", + dev->name, fs->total_clusters, fs->bytes_per_cluster, + fs->root_cluster, fs->free_count); + + vnode_t *root = fat32_alloc_vnode(fs, fs->root_cluster, 0, + FAT_ATTR_DIRECTORY, 0, 0); + return root; +} + +void fat32_unmount(fat32_t *fs) { + if (!fs) return; + fat32_sync(fs); + if (fs->shared_buf) { free(fs->shared_buf); fs->shared_buf = NULL; } + free(fs); +} + +int fat32_format(blkdev_t *dev, const char *label) { + if (!dev) return -EINVAL; + + uint32_t total_sectors = (uint32_t)dev->sector_count; + if (total_sectors < 65536) { + serial_printf("[FAT32] device too small (%u sectors) for FAT32\n", total_sectors); + return -EINVAL; + } + + uint32_t sectors_per_cluster; + if (total_sectors <= 532480) sectors_per_cluster = 1; + else if (total_sectors <= 16777216) sectors_per_cluster = 8; + else if (total_sectors <= 33554432) sectors_per_cluster = 16; + else if (total_sectors <= 67108864) sectors_per_cluster = 32; + else sectors_per_cluster = 64; + + uint32_t reserved = 32; + uint32_t num_fats = 2; + + uint32_t data_sectors = total_sectors - reserved; + uint32_t fat_size; + uint32_t cluster_count; + for (fat_size = 1; ; fat_size++) { + uint32_t total_fat_sectors = fat_size * num_fats; + if (total_fat_sectors >= data_sectors) return -EINVAL; + uint32_t remaining = data_sectors - total_fat_sectors; + cluster_count = remaining / sectors_per_cluster; + uint32_t need_entries = cluster_count + 2; + uint32_t need_bytes = need_entries * 4; + uint32_t need_sectors = (need_bytes + 511) / 512; + if (need_sectors <= fat_size) break; + if (fat_size > 1024 * 1024) return -EINVAL; + } + + if (cluster_count < 65525) { + serial_printf("[FAT32] ERROR: only %u clusters on %u-sector volume.\n" + " FAT32 requires >= 65525 clusters.\n" + " Minimum volume size is ~33 MB with 512-byte clusters.\n" + " Use a larger partition.\n", + cluster_count, total_sectors); + return -EINVAL; + } + + uint8_t sec[FAT32_SECTOR_SIZE]; + + memset(sec, 0, sizeof(sec)); + fat32_bpb_t *bpb = (fat32_bpb_t *)sec; + bpb->bs_jmp[0] = 0xEB; bpb->bs_jmp[1] = 0x58; bpb->bs_jmp[2] = 0x90; + memcpy(bpb->bs_oem, "CERVUS ", 8); + bpb->bpb_bytes_per_sector = 512; + bpb->bpb_sectors_per_cluster = (uint8_t)sectors_per_cluster; + bpb->bpb_reserved_sectors = (uint16_t)reserved; + bpb->bpb_num_fats = (uint8_t)num_fats; + bpb->bpb_root_entries = 0; + bpb->bpb_total_sectors_16 = 0; + bpb->bpb_media = 0xF8; + bpb->bpb_fat_size_16 = 0; + bpb->bpb_sectors_per_track = 63; + bpb->bpb_num_heads = 255; + bpb->bpb_hidden_sectors = 0; + bpb->bpb_total_sectors_32 = total_sectors; + bpb->bpb_fat_size_32 = fat_size; + bpb->bpb_ext_flags = 0; + bpb->bpb_fs_version = 0; + bpb->bpb_root_cluster = 2; + bpb->bpb_fs_info = 1; + bpb->bpb_backup_boot = 6; + bpb->bs_drive_num = 0x80; + bpb->bs_boot_sig = 0x29; + bpb->bs_vol_id = 0xCE7005; + memset(bpb->bs_vol_label, ' ', 11); + if (label) { + for (int i = 0; i < 11 && label[i]; i++) { + char c = label[i]; + if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A'); + bpb->bs_vol_label[i] = (uint8_t)c; + } + } + memcpy(bpb->bs_fs_type, "FAT32 ", 8); + bpb->bs_signature = 0xAA55; + if (dev->ops->write_sectors(dev, 0, 1, sec) < 0) return -EIO; + if (dev->ops->write_sectors(dev, 6, 1, sec) < 0) return -EIO; + + memset(sec, 0, sizeof(sec)); + fat32_fsinfo_t *fsi = (fat32_fsinfo_t *)sec; + fsi->lead_sig = FAT32_FSINFO_LEAD_SIG; + fsi->struct_sig = FAT32_FSINFO_STRUCT_SIG; + fsi->free_count = cluster_count - 1; + fsi->next_free = 3; + fsi->trail_sig = FAT32_FSINFO_TRAIL_SIG; + if (dev->ops->write_sectors(dev, 1, 1, sec) < 0) return -EIO; + if (dev->ops->write_sectors(dev, 7, 1, sec) < 0) return -EIO; + + { + static uint8_t zeros_batch[128 * FAT32_SECTOR_SIZE]; + memset(zeros_batch, 0, sizeof(zeros_batch)); + for (uint32_t f = 0; f < num_fats; f++) { + uint32_t fat_start = reserved + f * fat_size; + uint32_t remaining = fat_size; + uint32_t sector = fat_start; + serial_printf("[fat32] zeroing FAT#%u: %u sectors starting at LBA %u\n", + f, fat_size, fat_start); + printf(" FAT#%u: ", f); + uint32_t last_pct = 0; + uint32_t spinner = 0; + while (remaining > 0) { + uint32_t batch = (remaining > 128) ? 128 : remaining; + int r = dev->ops->write_sectors(dev, sector, batch, zeros_batch); + if (r < 0) { + serial_printf("[fat32] FAT zero-fill FAILED at LBA %u (batch=%u): %d\n", + sector, batch, r); + printf("\n"); + return -EIO; + } + sector += batch; + remaining -= batch; + uint32_t done = fat_size - remaining; + uint32_t pct = (done * 100) / fat_size; + if (pct != last_pct) { + const char glyphs[4] = { '|', '/', '-', '\\' }; + printf("\r\033[K %c FAT#%u: %u%%", + glyphs[spinner & 3], f, pct); + spinner++; + last_pct = pct; + } + } + printf("\r\033[K FAT#%u: done\n", f); + if (dev->ops->flush) dev->ops->flush(dev); + } + } + uint32_t fat0[3] = { 0x0FFFFF00 | 0xF8, 0x0FFFFFFF, FAT32_EOC }; + for (uint32_t f = 0; f < num_fats; f++) { + uint8_t fat_first[FAT32_SECTOR_SIZE]; + memset(fat_first, 0, FAT32_SECTOR_SIZE); + memcpy(fat_first, fat0, sizeof(fat0)); + if (dev->ops->write_sectors(dev, reserved + f * fat_size, 1, fat_first) < 0) + return -EIO; + } + + uint32_t first_data_sector = reserved + num_fats * fat_size; + uint32_t root_lba = first_data_sector; + uint8_t zero[FAT32_SECTOR_SIZE]; + memset(zero, 0, sizeof(zero)); + for (uint32_t i = 0; i < sectors_per_cluster; i++) { + if (dev->ops->write_sectors(dev, root_lba + i, 1, zero) < 0) return -EIO; + } + + if (dev->ops->flush) dev->ops->flush(dev); + serial_printf("[FAT32] formatted %s: %u clusters, cluster_size=%u, fat_size=%u sectors\n", + dev->name, cluster_count, sectors_per_cluster * 512, fat_size); + return 0; +} + +int fat32_statvfs(vnode_t *root, vfs_statvfs_t *out) { + if (!root || !root->fs_data || !out) return -EINVAL; + fat32_vdata_t *vd = (fat32_vdata_t *)root->fs_data; + fat32_t *fs = vd->fs; + if (!fs) return -EINVAL; + + out->f_bsize = fs->bytes_per_cluster; + out->f_blocks = fs->total_clusters; + + if (fs->free_count == 0xFFFFFFFF) { + uint32_t free = 0; + for (uint32_t cl = 2; cl < fs->total_clusters + 2; cl++) { + uint32_t entry = fat_read_entry(fs, cl) & 0x0FFFFFFF; + if (entry == 0) free++; + } + fs->free_count = free; + } + + out->f_bfree = fs->free_count; + out->f_bavail = fs->free_count; + out->f_files = 0; + out->f_ffree = 0; + out->f_flag = 0; + out->f_namemax = 255; + return 0; +} \ No newline at end of file diff --git a/kernel/src/fs/initramfs.c b/kernel/src/fs/initramfs.c new file mode 100644 index 0000000..358026d --- /dev/null +++ b/kernel/src/fs/initramfs.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include "../../include/fs/initramfs.h" +#include "../../include/fs/vfs.h" +#include "../../include/io/serial.h" + +typedef struct { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char _pad[12]; +} __attribute__((packed)) ustar_header_t; + +#define USTAR_BLOCK 512 + +static uint64_t octal_parse(const char *s, size_t n) { + uint64_t v = 0; + for (size_t i = 0; i < n && s[i] >= '0' && s[i] <= '7'; i++) + v = v * 8 + (uint64_t)(s[i] - '0'); + return v; +} + +static size_t align_block(size_t n) { + return (n + USTAR_BLOCK - 1) & ~(size_t)(USTAR_BLOCK - 1); +} + +static void path_normalize(const char *in, char *out, size_t maxlen) { + if (maxlen == 0) return; + + const char *p = in; + + while (p[0] == '.' && p[1] == '/') p += 2; + + while (*p == '/') p++; + + if (*p == '\0' || (p[0] == '.' && p[1] == '\0')) { + out[0] = '/'; + out[1] = '\0'; + return; + } + + char tmp[VFS_MAX_PATH]; + size_t j = 0; + tmp[j++] = '/'; + + while (*p && j < sizeof(tmp) - 1) { + const char *start = p; + while (*p && *p != '/') p++; + size_t len = (size_t)(p - start); + + if (len == 1 && start[0] == '.') { + while (*p == '/') p++; + continue; + } + + if (j > 1) { + if (j < sizeof(tmp) - 1) + tmp[j++] = '/'; + } + + size_t avail = sizeof(tmp) - 1 - j; + size_t copy = len < avail ? len : avail; + memcpy(tmp + j, start, copy); + j += copy; + + while (*p == '/') p++; + } + tmp[j] = '\0'; + + strncpy(out, tmp, maxlen - 1); + out[maxlen - 1] = '\0'; +} + +static int mkdir_p(const char *abspath) { + if (!abspath || strcmp(abspath, "/") == 0) return 0; + + char buf[VFS_MAX_PATH]; + strncpy(buf, abspath, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + for (char *p = buf + 1; *p; p++) { + if (*p == '/') { + *p = '\0'; + int r = vfs_mkdir(buf, 0755); + if (r < 0 && r != -EEXIST) { + serial_printf("[initramfs] mkdir_p: mkdir '%s' failed: %d\n", buf, r); + *p = '/'; + return r; + } + *p = '/'; + } + } + + int r = vfs_mkdir(buf, 0755); + if (r < 0 && r != -EEXIST) { + serial_printf("[initramfs] mkdir_p: mkdir '%s' failed: %d\n", buf, r); + return r; + } + return 0; +} + +static int write_file(const char *abspath, const void *data, + size_t size, uint32_t mode) +{ + serial_printf("[initramfs] write_file '%s' size=%zu\n", abspath, size); + + char parent[VFS_MAX_PATH]; + strncpy(parent, abspath, sizeof(parent) - 1); + parent[sizeof(parent) - 1] = '\0'; + + char *last_slash = NULL; + for (int i = (int)strlen(parent) - 1; i > 0; i--) { + if (parent[i] == '/') { last_slash = &parent[i]; break; } + } + + if (last_slash) { + *last_slash = '\0'; + if (strlen(parent) > 0) { + serial_printf("[initramfs] ensuring parent dir '%s'\n", parent); + + vnode_t *check = NULL; + int lr = vfs_lookup(parent, &check); + serial_printf("[initramfs] vfs_lookup('%s') before mkdir_p = %d\n", parent, lr); + if (lr == 0) { + serial_printf("[initramfs] parent exists, type=%d\n", check->type); + vnode_unref(check); + } else { + serial_printf("[initramfs] parent not found, calling mkdir_p\n"); + int mr = mkdir_p(parent); + if (mr < 0) { + serial_printf("[initramfs] mkdir_p('%s') failed: %d\n", parent, mr); + *last_slash = '/'; + return mr; + } + lr = vfs_lookup(parent, &check); + serial_printf("[initramfs] vfs_lookup('%s') after mkdir_p = %d\n", parent, lr); + if (lr == 0) { + serial_printf("[initramfs] parent now exists, type=%d\n", check->type); + vnode_unref(check); + } else { + serial_printf("[initramfs] ERROR: parent still not found after mkdir_p!\n"); + *last_slash = '/'; + return -ENOENT; + } + } + } + *last_slash = '/'; + } + + vfs_file_t *file = NULL; + int ret = vfs_open(abspath, O_WRONLY | O_CREAT | O_TRUNC, mode ? mode : 0644, &file); + serial_printf("[initramfs] vfs_open('%s') = %d\n", abspath, ret); + + if (ret < 0) { + serial_printf("[initramfs] open '%s' failed: %d\n", abspath, ret); + return ret; + } + + if (size > 0 && data != NULL) { + int64_t w = vfs_write(file, data, size); + vfs_close(file); + if (w < 0) { + serial_printf("[initramfs] write '%s' failed: %lld\n", + abspath, (long long)w); + return (int)w; + } + serial_printf("[initramfs] wrote %lld bytes\n", (long long)w); + } else { + vfs_close(file); + } + return 0; +} + +int initramfs_mount(const void *data, size_t size) { + if (!data || size < USTAR_BLOCK) return -EINVAL; + + const uint8_t *ptr = data; + const uint8_t *end = ptr + size; + int files_ok = 0, dirs_ok = 0, skipped = 0, errors = 0; + + serial_printf("[initramfs] parsing TAR @ %p, size=%zu\n", data, size); + + { + vnode_t *root_check = NULL; + int r = vfs_lookup("/", &root_check); + serial_printf("[initramfs] pre-parse: vfs_lookup('/') = %d\n", r); + if (r == 0) { + serial_printf("[initramfs] pre-parse: root type=%d refcnt=%d\n", + root_check->type, root_check->refcount); + vnode_unref(root_check); + } + + const char *test_dirs[] = { "/bin", "/etc", "/dev", "/tmp", "/proc", NULL }; + for (int i = 0; test_dirs[i]; i++) { + vnode_t *n = NULL; + int rv = vfs_lookup(test_dirs[i], &n); + serial_printf("[initramfs] pre-parse: vfs_lookup('%s') = %d\n", test_dirs[i], rv); + if (rv == 0) vnode_unref(n); + } + } + + while (ptr + USTAR_BLOCK <= end) { + const ustar_header_t *hdr = (const ustar_header_t *)ptr; + + if (hdr->name[0] == '\0') { + ptr += USTAR_BLOCK; + if (ptr + USTAR_BLOCK <= end && + ((const ustar_header_t *)ptr)->name[0] == '\0') + break; + continue; + } + + if (memcmp(hdr->magic, "ustar", 5) != 0) { + serial_writestring("[initramfs] bad magic, stopping\n"); + break; + } + + uint64_t file_size = octal_parse(hdr->size, sizeof(hdr->size)); + uint32_t mode = (uint32_t)octal_parse(hdr->mode, sizeof(hdr->mode)); + + char raw[VFS_MAX_PATH]; + if (hdr->prefix[0]) { + size_t pl = strnlen(hdr->prefix, sizeof(hdr->prefix)); + size_t nl = strnlen(hdr->name, sizeof(hdr->name)); + if (pl + nl + 2 < sizeof(raw)) { + memcpy(raw, hdr->prefix, pl); + raw[pl] = '/'; + memcpy(raw + pl + 1, hdr->name, nl); + raw[pl + 1 + nl] = '\0'; + } else { + strncpy(raw, hdr->name, sizeof(raw) - 1); + raw[sizeof(raw) - 1] = '\0'; + } + } else { + strncpy(raw, hdr->name, sizeof(raw) - 1); + raw[sizeof(raw) - 1] = '\0'; + } + + char abspath[VFS_MAX_PATH]; + path_normalize(raw, abspath, sizeof(abspath)); + + serial_printf("[initramfs] entry: raw='%s' -> abs='%s' type='%c' size=%llu\n", + raw, abspath, hdr->typeflag ? hdr->typeflag : '0', + (unsigned long long)file_size); + + ptr += USTAR_BLOCK; + + switch (hdr->typeflag) { + + case '5': + if (strcmp(abspath, "/") != 0) { + int r = mkdir_p(abspath); + if (r == 0) { + serial_printf("[initramfs] dir %s\n", abspath); + dirs_ok++; + } else { + serial_printf("[initramfs] dir %s FAILED: %d\n", abspath, r); + errors++; + } + } + break; + + case '0': + case '\0': + { + const void *filedata = (file_size > 0) ? (const void *)ptr : NULL; + int r = write_file(abspath, filedata, (size_t)file_size, + mode ? mode : 0644); + if (r == 0) { + serial_printf("[initramfs] file %s (%llu bytes)\n", + abspath, (unsigned long long)file_size); + files_ok++; + } else { + errors++; + } + break; + } + + case '2': + serial_printf("[initramfs] skip symlink %s\n", abspath); + skipped++; + break; + + default: + serial_printf("[initramfs] skip type='%c' %s\n", + hdr->typeflag ? hdr->typeflag : '0', abspath); + skipped++; + break; + } + + ptr += align_block(file_size); + } + + serial_printf("[initramfs] done: %d files, %d dirs, %d skipped, %d errors\n", + files_ok, dirs_ok, skipped, errors); + return (errors > 0 && files_ok == 0 && dirs_ok == 0) ? -EIO : 0; +} \ No newline at end of file diff --git a/kernel/src/fs/ramfs.c b/kernel/src/fs/ramfs.c new file mode 100644 index 0000000..c0b8360 --- /dev/null +++ b/kernel/src/fs/ramfs.c @@ -0,0 +1,283 @@ +#include +#include +#include +#include "../../include/fs/ramfs.h" +#include "../../include/fs/vfs.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" + +typedef struct { + char *name; + vnode_t *node; +} ramfs_child_t; + +typedef struct { + uint8_t *data; + size_t capacity; + + ramfs_child_t *children; + int child_count; + int child_cap; + + uint64_t ino; +} ramfs_node_t; + +static uint64_t g_next_ino = 1; +static const vnode_ops_t ramfs_file_ops; +static const vnode_ops_t ramfs_dir_ops; + + +static vnode_t *ramfs_alloc_vnode(vnode_type_t type, uint32_t mode) { + vnode_t *v = kzalloc(sizeof(vnode_t)); + if (!v) return NULL; + + ramfs_node_t *rn = kzalloc(sizeof(ramfs_node_t)); + if (!rn) { kfree(v); return NULL; } + + rn->ino = g_next_ino++; + v->type = type; + v->mode = mode; + v->ino = rn->ino; + v->fs_data = rn; + return v; +} + +static void ramfs_ref(vnode_t *node) { + (void)node; +} + +static void ramfs_unref(vnode_t *node) { + ramfs_node_t *rn = node->fs_data; + + if (rn->data) kfree(rn->data); + + if (rn->children) { + for (int i = 0; i < rn->child_count; i++) { + if (rn->children[i].name) kfree(rn->children[i].name); + if (rn->children[i].node) vnode_unref(rn->children[i].node); + } + kfree(rn->children); + } + + kfree(rn); + kfree(node); +} + +static int64_t ramfs_file_read(vnode_t *node, void *buf, + size_t len, uint64_t offset) +{ + ramfs_node_t *rn = node->fs_data; + if (offset >= node->size) return 0; + size_t avail = node->size - (size_t)offset; + if (len > avail) len = avail; + if (len == 0) return 0; + memcpy(buf, rn->data + offset, len); + return (int64_t)len; +} + +static int64_t ramfs_file_write(vnode_t *node, const void *buf, + size_t len, uint64_t offset) +{ + ramfs_node_t *rn = node->fs_data; + size_t end = (size_t)offset + len; + if (end > RAMFS_MAX_FILE_SIZE) return -EFBIG; + + if (end > rn->capacity) { + size_t newcap = (end + 4095) & ~(size_t)4095; + if (newcap > RAMFS_MAX_FILE_SIZE) newcap = RAMFS_MAX_FILE_SIZE; + uint8_t *newdata = kmalloc(newcap); + if (!newdata) return -ENOMEM; + + size_t copy_size = rn->data ? node->size : 0; + if (copy_size > 0) + memcpy(newdata, rn->data, copy_size); + memset(newdata + copy_size, 0, newcap - copy_size); + + if (rn->data) kfree(rn->data); + rn->data = newdata; + rn->capacity = newcap; + } + + memcpy(rn->data + offset, buf, len); + if (end > node->size) node->size = end; + return (int64_t)len; +} + +static int ramfs_file_truncate(vnode_t *node, uint64_t new_size) { + ramfs_node_t *rn = node->fs_data; + if (new_size == 0) { + if (rn->data) { + kfree(rn->data); + rn->data = NULL; + rn->capacity = 0; + } + node->size = 0; + } else if (new_size < node->size) { + node->size = new_size; + } + return 0; +} + +static int ramfs_stat(vnode_t *node, vfs_stat_t *out) { + ramfs_node_t *rn = node->fs_data; + out->st_ino = rn->ino; + out->st_type = node->type; + out->st_mode = node->mode; + out->st_uid = node->uid; + out->st_gid = node->gid; + out->st_size = node->size; + out->st_blocks = (node->size + 511) / 512; + return 0; +} + +static const vnode_ops_t ramfs_file_ops = { + .read = ramfs_file_read, + .write = ramfs_file_write, + .truncate = ramfs_file_truncate, + .stat = ramfs_stat, + .ref = ramfs_ref, + .unref = ramfs_unref, +}; + +static int ramfs_dir_grow(ramfs_node_t *rn) { + int newcap = rn->child_cap == 0 ? 8 : rn->child_cap * 2; + if (newcap > RAMFS_MAX_CHILDREN) return -ENOSPC; + ramfs_child_t *nb = kmalloc((size_t)newcap * sizeof(ramfs_child_t)); + if (!nb) return -ENOMEM; + if (rn->children && rn->child_count > 0) + memcpy(nb, rn->children, (size_t)rn->child_count * sizeof(ramfs_child_t)); + if (rn->children) kfree(rn->children); + rn->children = nb; + rn->child_cap = newcap; + return 0; +} + +static int ramfs_dir_lookup(vnode_t *dir, const char *name, vnode_t **out) { + ramfs_node_t *rn = dir->fs_data; + for (int i = 0; i < rn->child_count; i++) { + if (strcmp(rn->children[i].name, name) == 0) { + vnode_ref(rn->children[i].node); + *out = rn->children[i].node; + return 0; + } + } + return -ENOENT; +} + +static int ramfs_dir_readdir(vnode_t *dir, uint64_t index, vfs_dirent_t *out) { + ramfs_node_t *rn = dir->fs_data; + if ((int64_t)index >= rn->child_count) return -ENOENT; + ramfs_child_t *ch = &rn->children[index]; + out->d_ino = ch->node->ino; + out->d_type = (uint8_t)ch->node->type; + strncpy(out->d_name, ch->name, VFS_MAX_NAME - 1); + out->d_name[VFS_MAX_NAME - 1] = '\0'; + return 0; +} + +static int ramfs_dir_add_child(vnode_t *dir, const char *name, vnode_t *child) { + ramfs_node_t *rn = dir->fs_data; + + if (rn->child_count >= rn->child_cap) { + int r = ramfs_dir_grow(rn); + if (r < 0) return r; + } + + char *dup = kmalloc(strlen(name) + 1); + if (!dup) return -ENOMEM; + strcpy(dup, name); + + rn->children[rn->child_count].name = dup; + rn->children[rn->child_count].node = child; + rn->child_count++; + dir->size = (uint64_t)rn->child_count; + + vnode_ref(child); + return 0; +} + +static int ramfs_dir_mkdir(vnode_t *dir, const char *name, uint32_t mode) { + vnode_t *existing = NULL; + if (ramfs_dir_lookup(dir, name, &existing) == 0) { + vnode_unref(existing); + return -EEXIST; + } + + vnode_t *child = ramfs_alloc_vnode(VFS_NODE_DIR, mode ? mode : 0755); + if (!child) return -ENOMEM; + + child->ops = &ramfs_dir_ops; + child->refcount = 1; + + int ret = ramfs_dir_add_child(dir, name, child); + if (ret < 0) { + kfree(child->fs_data); + kfree(child); + return ret; + } + + vnode_unref(child); + return 0; +} + +static int ramfs_dir_create(vnode_t *dir, const char *name, uint32_t mode, vnode_t **out) { + vnode_t *existing = NULL; + if (ramfs_dir_lookup(dir, name, &existing) == 0) { + *out = existing; + return 0; + } + + vnode_t *child = ramfs_alloc_vnode(VFS_NODE_FILE, mode ? mode : 0644); + if (!child) return -ENOMEM; + + child->ops = &ramfs_file_ops; + child->refcount = 1; + + int ret = ramfs_dir_add_child(dir, name, child); + if (ret < 0) { + kfree(child->fs_data); + kfree(child); + return ret; + } + + *out = child; + return 0; +} + +static int ramfs_dir_unlink(vnode_t *dir, const char *name) { + ramfs_node_t *rn = dir->fs_data; + for (int i = 0; i < rn->child_count; i++) { + if (strcmp(rn->children[i].name, name) == 0) { + kfree(rn->children[i].name); + vnode_unref(rn->children[i].node); + for (int j = i; j < rn->child_count - 1; j++) + rn->children[j] = rn->children[j + 1]; + rn->child_count--; + dir->size = (uint64_t)rn->child_count; + return 0; + } + } + return -ENOENT; +} + +static const vnode_ops_t ramfs_dir_ops = { + .lookup = ramfs_dir_lookup, + .readdir = ramfs_dir_readdir, + .mkdir = ramfs_dir_mkdir, + .create = ramfs_dir_create, + .unlink = ramfs_dir_unlink, + .stat = ramfs_stat, + .ref = ramfs_ref, + .unref = ramfs_unref, +}; + +vnode_t *ramfs_create_root(void) { + vnode_t *root = ramfs_alloc_vnode(VFS_NODE_DIR, 0755); + if (!root) return NULL; + + root->ops = &ramfs_dir_ops; + root->refcount = 1; + + serial_writestring("[ramfs] root created\n"); + return root; +} \ No newline at end of file diff --git a/kernel/src/fs/vfs.c b/kernel/src/fs/vfs.c new file mode 100644 index 0000000..a413cfc --- /dev/null +++ b/kernel/src/fs/vfs.c @@ -0,0 +1,617 @@ +#include "../../include/fs/vfs.h" +#include "../../include/sched/sched.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include + +static vfs_mount_t g_mounts[VFS_MAX_MOUNTS]; +static vfs_file_t g_open_files[VFS_MAX_OPEN_FILES]; +static bool g_vfs_ready = false; + +void vfs_init(void) { + memset(g_mounts, 0, sizeof(g_mounts)); + memset(g_open_files, 0, sizeof(g_open_files)); + g_vfs_ready = true; + serial_writestring("[VFS] initialized\n"); +} + +void vnode_ref(vnode_t *node) { + if (!node) return; + __atomic_fetch_add(&node->refcount, 1, __ATOMIC_RELAXED); +} + +void vnode_unref(vnode_t *node) { + if (!node) return; + int old = __atomic_fetch_sub(&node->refcount, 1, __ATOMIC_ACQ_REL); + if (old <= 1) { + if (node->ops && node->ops->unref) + node->ops->unref(node); + } +} + +int vfs_mount(const char *path, vnode_t *fs_root) { + if (!path || !fs_root) return -EINVAL; + + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (g_mounts[i].used && strcmp(g_mounts[i].path, path) == 0) { + serial_printf("[VFS] mount: '%s' already mounted\n", path); + return -EBUSY; + } + } + + int slot = -1; + if (strcmp(path, "/") == 0) { + slot = 0; + } else { + for (int i = 1; i < VFS_MAX_MOUNTS; i++) { + if (!g_mounts[i].used) { slot = i; break; } + } + } + if (slot < 0) return -ENOMEM; + + strncpy(g_mounts[slot].path, path, VFS_MAX_PATH - 1); + g_mounts[slot].root = fs_root; + g_mounts[slot].used = true; + g_mounts[slot].fs_priv = NULL; + g_mounts[slot].unmount = NULL; + vnode_ref(fs_root); + serial_printf("[VFS] mounted '%s' at slot %d\n", path, slot); + return 0; +} + +int vfs_mount_fs(const char *path, vnode_t *fs_root, + void *fs_priv, void (*unmount_fn)(void *), + void (*sync_fn)(void *)) { + int ret = vfs_mount(path, fs_root); + if (ret < 0) return ret; + + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (g_mounts[i].used && strcmp(g_mounts[i].path, path) == 0) { + g_mounts[i].fs_priv = fs_priv; + g_mounts[i].unmount = unmount_fn; + g_mounts[i].sync = sync_fn; + break; + } + } + return 0; +} + +void vfs_sync_all(void) { + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (g_mounts[i].used && g_mounts[i].fs_priv) { + serial_printf("[VFS] sync_all: flushing mount '%s'\n", g_mounts[i].path); + if (g_mounts[i].sync) + g_mounts[i].sync(g_mounts[i].fs_priv); + } + } +} + +int vfs_umount(const char *path) { + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (g_mounts[i].used && strcmp(g_mounts[i].path, path) == 0) { + if (g_mounts[i].unmount && g_mounts[i].fs_priv) + g_mounts[i].unmount(g_mounts[i].fs_priv); + vnode_unref(g_mounts[i].root); + memset(&g_mounts[i], 0, sizeof(vfs_mount_t)); + return 0; + } + } + return -ENOENT; +} + +static vfs_mount_t *find_mount(const char *path, const char **rel_out) { + vfs_mount_t *best = NULL; + size_t best_len = 0; + + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (!g_mounts[i].used) continue; + size_t mlen = strlen(g_mounts[i].path); + if (strncmp(path, g_mounts[i].path, mlen) == 0) { + if (path[mlen] == '/' || path[mlen] == '\0' || mlen == 1) { + if (mlen > best_len) { + best = &g_mounts[i]; + best_len = mlen; + } + } + } + } + + if (best && rel_out) { + const char *rel = path + best_len; + while (*rel == '/') rel++; + *rel_out = rel; + } + return best; +} + +static const char *path_next_component(const char *src, char *dst, size_t maxlen) { + while (*src == '/') src++; + size_t i = 0; + while (*src && *src != '/' && i < maxlen - 1) + dst[i++] = *src++; + dst[i] = '\0'; + return src; +} + +int vfs_lookup(const char *path, vnode_t **out) { + if (!path || !out) return -EINVAL; + if (!g_vfs_ready) return -EIO; + if (path[0] != '/') return -EINVAL; + + const char *rel; + vfs_mount_t *mnt = find_mount(path, &rel); + if (!mnt) { + serial_printf("[VFS] lookup '%s': no mount found\n", path); + return -ENOENT; + } + + vnode_t *cur = mnt->root; + if (!cur) { + serial_printf("[VFS] lookup '%s': mount root is NULL!\n", path); + return -EIO; + } + vnode_ref(cur); + + if (*rel == '\0') { + *out = cur; + return 0; + } + + char comp[VFS_MAX_NAME]; + const char *p = rel; + while (*p) { + p = path_next_component(p, comp, sizeof(comp)); + if (comp[0] == '\0') continue; + + if (cur->type != VFS_NODE_DIR) { + serial_printf("[VFS] lookup '%s': '%s' is not a dir (type=%d)\n", + path, comp, cur->type); + vnode_unref(cur); + return -ENOTDIR; + } + if (!cur->ops || !cur->ops->lookup) { + serial_printf("[VFS] lookup '%s': no lookup op for comp '%s'\n", path, comp); + vnode_unref(cur); + return -EIO; + } + + vnode_t *next = NULL; + int ret = cur->ops->lookup(cur, comp, &next); + if (ret < 0) { + serial_printf("[VFS] lookup '%s': ops->lookup('%s') = %d\n", path, comp, ret); + vnode_unref(cur); + return ret; + } + + vnode_unref(cur); + cur = next; + + if (cur->mounted) { + vnode_t *mroot = cur->mounted->root; + vnode_ref(mroot); + vnode_unref(cur); + cur = mroot; + } + } + + *out = cur; + return 0; +} + +vfs_file_t *vfs_file_alloc(void) { + for (int i = 0; i < VFS_MAX_OPEN_FILES; i++) { + if (g_open_files[i].refcount == 0) { + memset(&g_open_files[i], 0, sizeof(vfs_file_t)); + g_open_files[i].refcount = 1; + return &g_open_files[i]; + } + } + return NULL; +} + +void vfs_file_free(vfs_file_t *file) { + if (!file) return; + int old = __atomic_fetch_sub(&file->refcount, 1, __ATOMIC_ACQ_REL); + if (old == 1) { + vnode_unref(file->vnode); + file->vnode = NULL; + } +} + +int vfs_open(const char *path, int flags, uint32_t mode, vfs_file_t **out) { + if (!path || !out) return -EINVAL; + + vnode_t *node = NULL; + int ret = vfs_lookup(path, &node); + + if (ret == -ENOENT && (flags & O_CREAT)) { + char dirpath[VFS_MAX_PATH]; + strncpy(dirpath, path, VFS_MAX_PATH - 1); + dirpath[VFS_MAX_PATH - 1] = '\0'; + + char *slash = NULL; + for (int i = (int)strlen(dirpath) - 1; i >= 0; i--) { + if (dirpath[i] == '/') { slash = &dirpath[i]; break; } + } + if (!slash) return -EINVAL; + + char filename[VFS_MAX_NAME]; + strncpy(filename, slash + 1, VFS_MAX_NAME - 1); + filename[VFS_MAX_NAME - 1] = '\0'; + + if (filename[0] == '\0') return -EINVAL; + + if (slash == dirpath) + dirpath[1] = '\0'; + else + *slash = '\0'; + + serial_printf("[VFS] open O_CREAT: parent='%s' name='%s'\n", dirpath, filename); + + vnode_t *dir = NULL; + ret = vfs_lookup(dirpath, &dir); + if (ret < 0) { + serial_printf("[VFS] open O_CREAT: lookup parent '%s' failed: %d\n", dirpath, ret); + return ret; + } + + if (!dir->ops || !dir->ops->create) { + serial_printf("[VFS] open O_CREAT: parent has no create op\n"); + vnode_unref(dir); + return -ENOSYS; + } + ret = dir->ops->create(dir, filename, mode, &node); + vnode_unref(dir); + if (ret < 0) { + serial_printf("[VFS] open O_CREAT: create '%s' failed: %d\n", filename, ret); + return ret; + } + } else if (ret < 0) { + return ret; + } + + int acc = flags & O_ACCMODE; + if ((acc == O_WRONLY || acc == O_RDWR) && node->type == VFS_NODE_DIR) { + vnode_unref(node); + return -EISDIR; + } + + vfs_file_t *file = vfs_file_alloc(); + if (!file) { + vnode_unref(node); + return -ENFILE; + } + + file->vnode = node; + file->flags = flags; + file->offset = (flags & O_APPEND) ? node->size : 0; + + if ((flags & O_TRUNC) && (acc == O_WRONLY || acc == O_RDWR)) { + if (node->ops && node->ops->truncate) { + node->ops->truncate(node, 0); + } else { + node->size = 0; + } + file->offset = 0; + } + + *out = file; + return 0; +} + +void vfs_close(vfs_file_t *file) { + if (file) { + int acc = file->flags & O_ACCMODE; + if (acc == O_WRONLY || acc == O_RDWR) { + vfs_sync_all(); + } + } + vfs_file_free(file); +} + +int64_t vfs_read(vfs_file_t *file, void *buf, size_t len) { + if (!file || !file->vnode) return -EBADF; + if (len == 0) return 0; + if ((file->flags & O_ACCMODE) == O_WRONLY) return -EBADF; + if (!file->vnode->ops || !file->vnode->ops->read) return -EIO; + + int64_t n = file->vnode->ops->read(file->vnode, buf, len, file->offset); + if (n > 0) file->offset += (uint64_t)n; + return n; +} + +int64_t vfs_write(vfs_file_t *file, const void *buf, size_t len) { + if (!file || !file->vnode) return -EBADF; + if (len == 0) return 0; + if ((file->flags & O_ACCMODE) == O_RDONLY) return -EBADF; + if (!file->vnode->ops || !file->vnode->ops->write) return -EIO; + + if (file->flags & O_APPEND) file->offset = file->vnode->size; + + int64_t n = file->vnode->ops->write(file->vnode, buf, len, file->offset); + if (n > 0) file->offset += (uint64_t)n; + return n; +} + +int64_t vfs_seek(vfs_file_t *file, int64_t offset, int whence) { + if (!file || !file->vnode) return -EBADF; + vnode_type_t t = file->vnode->type; + if (t == VFS_NODE_CHARDEV || t == VFS_NODE_PIPE) return -ESPIPE; + + int64_t new_off; + switch (whence) { + case SEEK_SET: new_off = offset; break; + case SEEK_CUR: new_off = (int64_t)file->offset + offset; break; + case SEEK_END: new_off = (int64_t)file->vnode->size + offset; break; + default: return -EINVAL; + } + if (new_off < 0) return -EINVAL; + file->offset = (uint64_t)new_off; + return new_off; +} + +int vfs_stat(const char *path, vfs_stat_t *out) { + if (!path || !out) return -EINVAL; + vnode_t *node = NULL; + int ret = vfs_lookup(path, &node); + if (ret < 0) return ret; + + if (node->ops && node->ops->stat) { + ret = node->ops->stat(node, out); + } else { + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = node->type; + out->st_mode = node->mode; + out->st_uid = node->uid; + out->st_gid = node->gid; + out->st_size = node->size; + ret = 0; + } + vnode_unref(node); + return ret; +} + +int vfs_fstat(vfs_file_t *file, vfs_stat_t *out) { + if (!file || !file->vnode || !out) return -EBADF; + vnode_t *node = file->vnode; + if (node->ops && node->ops->stat) return node->ops->stat(node, out); + memset(out, 0, sizeof(*out)); + out->st_ino = node->ino; + out->st_type = node->type; + out->st_mode = node->mode; + out->st_uid = node->uid; + out->st_gid = node->gid; + out->st_size = node->size; + return 0; +} + +int64_t vfs_ioctl(vfs_file_t *file, uint64_t req, void *arg) { + if (!file || !file->vnode) return -EBADF; + if (!file->vnode->ops || !file->vnode->ops->ioctl) return -ENOTTY; + return file->vnode->ops->ioctl(file->vnode, req, arg); +} + +int vfs_readdir(vfs_file_t *file, vfs_dirent_t *out) { + if (!file || !file->vnode || !out) return -EBADF; + if (file->vnode->type != VFS_NODE_DIR) return -ENOTDIR; + if (!file->vnode->ops || !file->vnode->ops->readdir) return -EIO; + int ret = file->vnode->ops->readdir(file->vnode, file->offset, out); + if (ret == 0) file->offset++; + return ret; +} + +int vfs_mkdir(const char *path, uint32_t mode) { + if (!path) return -EINVAL; + char dirpath[VFS_MAX_PATH]; + strncpy(dirpath, path, VFS_MAX_PATH - 1); + dirpath[VFS_MAX_PATH - 1] = '\0'; + + char *slash = NULL; + for (int i = (int)strlen(dirpath) - 1; i >= 0; i--) { + if (dirpath[i] == '/') { slash = &dirpath[i]; break; } + } + if (!slash) return -EINVAL; + + char dirname[VFS_MAX_NAME]; + strncpy(dirname, slash + 1, VFS_MAX_NAME - 1); + dirname[VFS_MAX_NAME - 1] = '\0'; + + if (dirname[0] == '\0') return -EINVAL; + + if (slash == dirpath) dirpath[1] = '\0'; + else *slash = '\0'; + + vnode_t *dir = NULL; + int ret = vfs_lookup(dirpath, &dir); + if (ret < 0) return ret; + if (!dir->ops || !dir->ops->mkdir) { vnode_unref(dir); return -ENOSYS; } + ret = dir->ops->mkdir(dir, dirname, mode); + vnode_unref(dir); + return ret; +} + +fd_table_t *fd_table_create(void) { + fd_table_t *t = kzalloc(sizeof(fd_table_t)); + return t; +} + +fd_table_t *fd_table_clone(const fd_table_t *src) { + if (!src) return NULL; + fd_table_t *dst = kzalloc(sizeof(fd_table_t)); + if (!dst) return NULL; + for (int i = 0; i < TASK_MAX_FDS; i++) { + if (src->entries[i].file) { + dst->entries[i] = src->entries[i]; + __atomic_fetch_add(&dst->entries[i].file->refcount, 1, __ATOMIC_RELAXED); + } + } + return dst; +} + +void fd_table_cloexec(fd_table_t *table) { + if (!table) return; + for (int i = 0; i < TASK_MAX_FDS; i++) { + if (table->entries[i].file && (table->entries[i].fd_flags & FD_CLOEXEC)) { + vfs_file_free(table->entries[i].file); + table->entries[i].file = NULL; + table->entries[i].fd_flags = 0; + } + } +} + +void fd_table_destroy(fd_table_t *table) { + if (!table) return; + for (int i = 0; i < TASK_MAX_FDS; i++) { + if (table->entries[i].file) { + vfs_file_free(table->entries[i].file); + table->entries[i].file = NULL; + } + } + kfree(table); +} + +int fd_alloc(fd_table_t *table, vfs_file_t *file, int min_fd) { + if (!table || !file) return -EINVAL; + if (min_fd < 0 || min_fd >= TASK_MAX_FDS) return -EINVAL; + for (int i = min_fd; i < TASK_MAX_FDS; i++) { + if (!table->entries[i].file) { + table->entries[i].file = file; + table->entries[i].fd_flags = 0; + return i; + } + } + return -EMFILE; +} + +vfs_file_t *fd_get(const fd_table_t *table, int fd) { + if (!table || fd < 0 || fd >= TASK_MAX_FDS) return NULL; + return table->entries[fd].file; +} + +int fd_close(fd_table_t *table, int fd) { + if (!table || fd < 0 || fd >= TASK_MAX_FDS) return -EBADF; + vfs_file_t *file = table->entries[fd].file; + if (!file) return -EBADF; + vfs_file_free(file); + table->entries[fd].file = NULL; + table->entries[fd].fd_flags = 0; + return 0; +} + +int fd_dup2(fd_table_t *table, int oldfd, int newfd) { + if (!table) return -EBADF; + if (oldfd < 0 || oldfd >= TASK_MAX_FDS) return -EBADF; + if (newfd < 0 || newfd >= TASK_MAX_FDS) return -EBADF; + if (oldfd == newfd) return newfd; + + vfs_file_t *src = table->entries[oldfd].file; + if (!src) return -EBADF; + + if (table->entries[newfd].file) + vfs_file_free(table->entries[newfd].file); + + table->entries[newfd].file = src; + table->entries[newfd].fd_flags = 0; + __atomic_fetch_add(&src->refcount, 1, __ATOMIC_RELAXED); + return newfd; +} + +int fd_set_flags(fd_table_t *table, int fd, int flags) { + if (!table || fd < 0 || fd >= TASK_MAX_FDS) return -EBADF; + if (!table->entries[fd].file) return -EBADF; + table->entries[fd].fd_flags = flags; + return 0; +} + +int fd_get_flags(const fd_table_t *table, int fd) { + if (!table || fd < 0 || fd >= TASK_MAX_FDS) return -EBADF; + if (!table->entries[fd].file) return -EBADF; + return table->entries[fd].fd_flags; +} + +int vfs_init_stdio(void *task_ptr) { + task_t *t = (task_t *)task_ptr; + if (!t || !t->fd_table) return -EINVAL; + + vfs_file_t *in = NULL; + int ret = vfs_open("/dev/tty", O_RDONLY, 0, &in); + if (ret < 0) return ret; + fd_alloc(t->fd_table, in, 0); + + vfs_file_t *out = NULL; + ret = vfs_open("/dev/tty", O_WRONLY, 0, &out); + if (ret < 0) return ret; + fd_alloc(t->fd_table, out, 1); + + vfs_file_t *err = NULL; + ret = vfs_open("/dev/tty", O_WRONLY, 0, &err); + if (ret < 0) return ret; + fd_alloc(t->fd_table, err, 2); + + return 0; +} + +int vfs_set_mount_info(const char *path, const char *device, const char *fstype) { + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (g_mounts[i].used && strcmp(g_mounts[i].path, path) == 0) { + if (device) { + strncpy(g_mounts[i].device, device, sizeof(g_mounts[i].device) - 1); + g_mounts[i].device[sizeof(g_mounts[i].device) - 1] = '\0'; + } + if (fstype) { + strncpy(g_mounts[i].fstype, fstype, sizeof(g_mounts[i].fstype) - 1); + g_mounts[i].fstype[sizeof(g_mounts[i].fstype) - 1] = '\0'; + } + return 0; + } + } + return -ENOENT; +} + +int vfs_list_mounts(vfs_mount_info_t *out, int max) { + int n = 0; + for (int i = 0; i < VFS_MAX_MOUNTS && n < max; i++) { + if (!g_mounts[i].used) continue; + strncpy(out[n].path, g_mounts[i].path, VFS_MAX_PATH - 1); + out[n].path[VFS_MAX_PATH - 1] = '\0'; + strncpy(out[n].device, g_mounts[i].device, sizeof(out[n].device) - 1); + out[n].device[sizeof(out[n].device) - 1] = '\0'; + strncpy(out[n].fstype, g_mounts[i].fstype, sizeof(out[n].fstype) - 1); + out[n].fstype[sizeof(out[n].fstype) - 1] = '\0'; + out[n].flags = 0; + n++; + } + return n; +} + +extern int ext2_statvfs(vnode_t *root, vfs_statvfs_t *out); +extern int fat32_statvfs(vnode_t *root, vfs_statvfs_t *out); + +int vfs_statvfs(const char *path, vfs_statvfs_t *out) { + if (!path || !out) return -EINVAL; + memset(out, 0, sizeof(*out)); + + vfs_mount_t *best = NULL; + size_t best_len = 0; + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (!g_mounts[i].used) continue; + size_t mlen = strlen(g_mounts[i].path); + if (strncmp(path, g_mounts[i].path, mlen) == 0) { + if (path[mlen] == '/' || path[mlen] == '\0' || mlen == 1) { + if (mlen > best_len) { best = &g_mounts[i]; best_len = mlen; } + } + } + } + if (!best) return -ENOENT; + + if (strcmp(best->fstype, "ext2") == 0) { + return ext2_statvfs(best->root, out); + } + if (strcmp(best->fstype, "fat32") == 0) { + return fat32_statvfs(best->root, out); + } + out->f_bsize = 4096; + out->f_namemax = 255; + return 0; +} \ No newline at end of file diff --git a/kernel/src/gdt/gdt.c b/kernel/src/gdt/gdt.c new file mode 100644 index 0000000..62fd783 --- /dev/null +++ b/kernel/src/gdt/gdt.c @@ -0,0 +1,41 @@ +#include "../../include/gdt/gdt.h" +#include "../../include/smp/smp.h" +#include "../../include/io/serial.h" +#include +#include + +gdt_pointer_t gdtr; + +struct { + gdt_entry_t gdt_entries[5 + (MAX_CPUS * 2)]; +} __attribute__((packed)) gdt; + +tss_t *tss[MAX_CPUS] = {0}; +__attribute__((aligned(16))) char kernel_stacks[MAX_CPUS][KERNEL_STACK_SIZE]; +__attribute__((aligned(16))) char def_ist_stacks[MAX_CPUS][KERNEL_STACK_SIZE]; +__attribute__((aligned(16))) char df_stacks[MAX_CPUS][KERNEL_STACK_SIZE]; +__attribute__((aligned(16))) char nmi_stacks[MAX_CPUS][KERNEL_STACK_SIZE]; +__attribute__((aligned(16))) char pf_stacks[MAX_CPUS][KERNEL_STACK_SIZE]; + +extern void _load_gdt(gdt_pointer_t *descriptor); +extern void _reload_segments(uint64_t cs, uint64_t ds); + +void gdt_init(void) { + memset(&gdt, 0, sizeof(gdt)); + gdt.gdt_entries[0] = (gdt_entry_t)GDT_ENTRY(0, 0, 0x00, 0x0); + gdt.gdt_entries[1] = (gdt_entry_t)GDT_ENTRY(0, 0xFFFFF, 0x9A, 0xA); + gdt.gdt_entries[2] = (gdt_entry_t)GDT_ENTRY(0, 0xFFFFF, 0x92, 0xC); + gdt.gdt_entries[3] = (gdt_entry_t)GDT_ENTRY(0, 0xFFFFF, 0xF2, 0x8); + gdt.gdt_entries[4] = (gdt_entry_t)GDT_ENTRY(0, 0xFFFFF, 0xFA, 0xA); + gdtr.size = (5 * sizeof(gdt_entry_t)) - 1; + gdtr.pointer = &gdt.gdt_entries[0]; + + serial_printf("Installing temporary GDT for BSP...\n"); + gdt_load(); + serial_printf("Temporary GDT installed\n"); +} + +void gdt_load(void) { + _load_gdt(&gdtr); + _reload_segments(GDT_CODE_SEGMENT, GDT_DATA_SEGMENT); +} \ No newline at end of file diff --git a/kernel/src/gdt/gdt_asm.asm b/kernel/src/gdt/gdt_asm.asm new file mode 100644 index 0000000..4e0c950 --- /dev/null +++ b/kernel/src/gdt/gdt_asm.asm @@ -0,0 +1,28 @@ +section .text +global _reload_segments +global _load_gdt +global load_tss + +_load_gdt: + lgdt [rdi] + ret + +load_tss: + ltr di + ret + +_reload_segments: + push rdi + + lea rax, [rel .reload_cs] + push rax + + retfq +.reload_cs: + mov ax, si + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + ret \ No newline at end of file diff --git a/kernel/src/graphics/fb/fb.c b/kernel/src/graphics/fb/fb.c new file mode 100644 index 0000000..cb58130 --- /dev/null +++ b/kernel/src/graphics/fb/fb.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include "../../../include/graphics/fb/fb.h" +#include "../../../include/io/serial.h" + +uint32_t *g_backbuf = NULL; +uint32_t g_bb_pitch = 0; +static uint32_t g_bb_w = 0; +static uint32_t g_bb_h = 0; + +void fb_init_backbuffer(struct limine_framebuffer *fb) { + if (!fb) return; + g_bb_w = fb->width; + g_bb_h = fb->height; + g_bb_pitch = fb->pitch / 4; + size_t sz = (size_t)g_bb_pitch * g_bb_h * sizeof(uint32_t); + g_backbuf = (uint32_t *)malloc(sz); + if (g_backbuf) { + memcpy(g_backbuf, fb->address, sz); + serial_printf("[FB] Backbuffer allocated: %ux%u (%zu KB)\n", + g_bb_w, g_bb_h, sz / 1024); + } else { + serial_printf("[FB] WARNING: backbuffer alloc failed, using direct VRAM\n"); + } +} + +static inline uint32_t *fb_get_buf(struct limine_framebuffer *fb) { + return g_backbuf ? g_backbuf : (uint32_t *)fb->address; +} + +static inline uint32_t fb_get_pitch(struct limine_framebuffer *fb) { + return g_backbuf ? g_bb_pitch : (fb->pitch / 4); +} + +void fb_flush(struct limine_framebuffer *fb) { + if (!fb || !g_backbuf) return; + memcpy(fb->address, g_backbuf, (size_t)g_bb_pitch * g_bb_h * sizeof(uint32_t)); +} + +void fb_flush_lines(struct limine_framebuffer *fb, uint32_t y_start, uint32_t y_end) { + if (!fb || !g_backbuf) return; + if (y_start >= g_bb_h) return; + if (y_end > g_bb_h) y_end = g_bb_h; + if (y_start >= y_end) return; + uint32_t *dst = (uint32_t *)fb->address + y_start * g_bb_pitch; + uint32_t *src = g_backbuf + y_start * g_bb_pitch; + size_t bytes = (size_t)(y_end - y_start) * g_bb_pitch * sizeof(uint32_t); + memcpy(dst, src, bytes); +} + +void fb_draw_pixel(struct limine_framebuffer *fb, uint32_t x, uint32_t y, uint32_t color) { + if (x >= fb->width || y >= fb->height) return; + uint32_t *buf = fb_get_buf(fb); + uint32_t pitch = fb_get_pitch(fb); + buf[y * pitch + x] = color; +} + +void fb_fill_rect(struct limine_framebuffer *fb, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color) { + if (!fb) return; + uint32_t *buf = fb_get_buf(fb); + uint32_t pitch = fb_get_pitch(fb); + + if (x >= fb->width || y >= fb->height) return; + if (x + w > fb->width) w = fb->width - x; + if (y + h > fb->height) h = fb->height - y; + + for (uint32_t py = 0; py < h; py++) { + uint32_t *row = buf + (y + py) * pitch + x; + if (color == 0) { + memset(row, 0, w * sizeof(uint32_t)); + } else { + for (uint32_t px = 0; px < w; px++) + row[px] = color; + } + } +} + +void fb_clear(struct limine_framebuffer *fb, uint32_t color) { + if (!fb) return; + uint32_t *buf = fb_get_buf(fb); + size_t total = (size_t)fb_get_pitch(fb) * fb->height; + if (color == 0) { + memset(buf, 0, total * sizeof(uint32_t)); + } else { + for (size_t i = 0; i < total; i++) + buf[i] = color; + } +} + +int psf_validate(void) { + const uint8_t *raw = get_font_data(); + if (raw[0] == 0x72 && raw[1] == 0xb5 && + raw[2] == 0x4a && raw[3] == 0x86) + return 2; + if (raw[0] == 0x36 && raw[1] == 0x04) + return 1; + return 0; +} + +void fb_draw_char(struct limine_framebuffer *fb, char c, uint32_t x, uint32_t y, uint32_t color) { + const uint8_t *raw = get_font_data(); + uint32_t headersize = 32; + uint32_t charsiz = *(uint32_t*)(raw + 20); + uint8_t *glyphs = (uint8_t*)raw + headersize; + + uint32_t glyph_index = ((uint8_t)c < 128) ? (uint8_t)c : '?'; + if (glyph_index >= 512) glyph_index = '?'; + + uint8_t *glyph = &glyphs[glyph_index * charsiz]; + uint32_t *buf = fb_get_buf(fb); + uint32_t pitch = fb_get_pitch(fb); + + if (x + 8 > fb->width || y + 16 > fb->height) return; + + for (uint32_t row = 0; row < 16; row++) { + uint8_t byte = glyph[row]; + uint32_t *line = buf + (y + row) * pitch + x; + for (uint32_t col = 0; col < 8; col++) { + if (byte & (0x80 >> col)) + line[col] = color; + } + } +} + +void fb_draw_string(struct limine_framebuffer *fb, const char *str, uint32_t x, uint32_t y, uint32_t color) { + uint32_t orig_x = x; + if (!psf_validate()) return; + while (*str) { + if (*str == '\n') { x = orig_x; y += 16; } + else { fb_draw_char(fb, *str, x, y, color); x += 8; } + str++; + } +} diff --git a/kernel/src/interrupts/idt.c b/kernel/src/interrupts/idt.c new file mode 100644 index 0000000..efb1b71 --- /dev/null +++ b/kernel/src/interrupts/idt.c @@ -0,0 +1,79 @@ +#include "../../include/interrupts/idt.h" +#include "../../include/interrupts/isr.h" +#include "../../include/interrupts/irq.h" +#include "../../include/io/serial.h" +#include +#include + +extern void *interrupts_stub_table[]; + +__attribute__(( + aligned(0x10))) static idt_entry_t idt_entries[IDT_MAX_DESCRIPTORS]; + +idtr_t idtr; + +void idt_set_gate(uint8_t index, void *base, uint16_t selector, uint8_t flags, uint8_t ist) { + idt_entries[index].base_low = (uint64_t)base & 0xFFFF; + idt_entries[index].kernel_cs = selector; + idt_entries[index].ist = ist; + idt_entries[index].attributes = flags; + idt_entries[index].base_mid = ((uint64_t)base >> 16) & 0xFFFF; + idt_entries[index].base_high = ((uint64_t)base >> 32) & 0xFFFFFFFF; + idt_entries[index].reserved = 0; +} + +void idt_gate_enable(int interrupt) { + FLAG_SET(idt_entries[interrupt].attributes, IDT_FLAG_PRESENT); +} + +void idt_gate_disable(int interrupt) { + FLAG_UNSET(idt_entries[interrupt].attributes, IDT_FLAG_PRESENT); +} + +bool setup_specific_vectors(uint64_t kernel_code_segment, uint64_t vector) { + if(vector == EXCEPTION_DOUBLE_FAULT) { + idt_set_gate(vector, interrupts_stub_table[vector], kernel_code_segment, 0x8E, 1); + return true; + } + + if(vector == EXCEPTION_NMI) { + idt_set_gate(vector, interrupts_stub_table[vector], kernel_code_segment, 0x8E, 2); + return true; + } + + if(vector == EXCEPTION_PAGE_FAULT) { + idt_set_gate(vector, interrupts_stub_table[vector], kernel_code_segment, 0x8E, 3); + return true; + } + + if(vector == 0x40) { + idt_set_gate(vector, interrupts_stub_table[vector], kernel_code_segment, 0x8E, 4); + return true; + } + + return false; +} + +void idt_load(void) { + __asm__ volatile("lidt %0" : : "m"(idtr)); +} + +void setup_interrupt_descriptor_table(uint64_t kernel_code_segment) { + serial_printf("[IDT] Initializing IDT...\n"); + + idtr.base = (idt_entry_t *)&idt_entries[0]; + idtr.limit = (uint16_t)sizeof(idt_entry_t) * IDT_MAX_DESCRIPTORS - 1; + + for (uint16_t vector = 0; vector < IDT_MAX_DESCRIPTORS; vector++) { + + if(setup_specific_vectors(kernel_code_segment, vector)) { + continue; + } + + idt_set_gate(vector, interrupts_stub_table[vector], kernel_code_segment, 0x8E, 0); + } + + idt_load(); + + serial_printf("[IDT] IDT initialized successfully\n"); +} \ No newline at end of file diff --git a/kernel/src/interrupts/interrupt_trap.asm b/kernel/src/interrupts/interrupt_trap.asm new file mode 100644 index 0000000..3e90e3b --- /dev/null +++ b/kernel/src/interrupts/interrupt_trap.asm @@ -0,0 +1,133 @@ +section .text +extern base_trap +extern sched_reschedule +extern get_percpu + +PERCPU_NEED_RESCHED equ 40 + +common_stub: + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + mov rax, ds + push rax + + mov rax, [rsp + 19*8] + and rax, 3 + jz .kernel_entry + swapgs +.kernel_entry: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov rdi, rsp + call base_trap + + pop rax + mov ds, ax + mov es, ax + + mov rax, [rsp + 18*8] + and rax, 3 + jz .kernel_resched + +.check_resched: + call get_percpu + test rax, rax + jz .do_swapgs + cmp byte [rax + PERCPU_NEED_RESCHED], 0 + je .do_swapgs + mov byte [rax + PERCPU_NEED_RESCHED], 0 + call sched_reschedule + jmp .check_resched + +.do_swapgs: + swapgs + jmp .kernel_exit + +.kernel_resched: + call get_percpu + test rax, rax + jz .kernel_check_cs + cmp byte [rax + PERCPU_NEED_RESCHED], 0 + je .kernel_check_cs + mov byte [rax + PERCPU_NEED_RESCHED], 0 + call sched_reschedule + jmp .kernel_resched + +.kernel_check_cs: + mov rax, [rsp + 18*8] + and rax, 3 + jz .kernel_exit + swapgs + jmp .kernel_exit + +.kernel_exit: + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + add rsp, 16 + iretq + +%macro INTERRUPT_ERR_STUB 1 +interrupt_stub_%1: + push qword %1 + jmp common_stub +%endmacro + +%macro INTERRUPT_NO_ERR_STUB 1 +interrupt_stub_%1: + push qword 0 + push qword %1 + jmp common_stub +%endmacro + +%assign i 0 +%rep 32 + %if i = 8 || i = 10 || i = 11 || i = 12 || i = 13 || i = 14 || i = 17 || i = 21 || i = 29 || i = 30 + INTERRUPT_ERR_STUB i + %else + INTERRUPT_NO_ERR_STUB i + %endif + %assign i i+1 +%endrep + +%rep 224 + INTERRUPT_NO_ERR_STUB i + %assign i i+1 +%endrep + +section .data +global interrupts_stub_table +interrupts_stub_table: +%assign i 0 +%rep 256 + dq interrupt_stub_%+i + %assign i i+1 +%endrep diff --git a/kernel/src/interrupts/interrupts.c b/kernel/src/interrupts/interrupts.c new file mode 100644 index 0000000..3ebdc7c --- /dev/null +++ b/kernel/src/interrupts/interrupts.c @@ -0,0 +1,32 @@ +#include "../../include/interrupts/interrupts.h" +#include "../../include/io/serial.h" +#include "../../include/interrupts/isr.h" +#include "../../include/interrupts/irq.h" +#include "../../include/interrupts/idt.h" +#include "../../include/gdt/gdt.h" +#include "../../include/io/ports.h" + +extern const int_desc_t __start_int_handlers[]; +extern const int_desc_t __stop_int_handlers[]; + +void init_interrupt_system(void) { + asm volatile("cli"); + + setup_interrupt_descriptor_table(GDT_CODE_SEGMENT); + + setup_defined_isr_handlers(); + + setup_defined_irq_handlers(); + + asm volatile("sti"); +} + +void base_trap(void *ctx) { + struct int_frame_t *regs = (struct int_frame_t*)ctx; + + if(regs->interrupt < ISR_EXCEPTION_COUNT) { + return isr_common_handler(regs); + } + + return irq_common_handler(regs); +} \ No newline at end of file diff --git a/kernel/src/interrupts/irq/irq.c b/kernel/src/interrupts/irq/irq.c new file mode 100644 index 0000000..4a9a13e --- /dev/null +++ b/kernel/src/interrupts/irq/irq.c @@ -0,0 +1,82 @@ +#include "../../../include/interrupts/interrupts.h" +#include "../../../include/io/serial.h" +#include "../../../include/interrupts/irq.h" +#include "../../../include/interrupts/idt.h" +#include "../../../include/io/ports.h" +#include "../../../include/smp/percpu.h" +#include "../../../include/apic/apic.h" + +extern const int_desc_t __start_irq_handlers[]; +extern const int_desc_t __stop_irq_handlers[]; +static int_handler_f registered_irq_interrupts[IRQ_INTERRUPTS_COUNT]__attribute__((aligned(64))); + +void irq_common_handler(struct int_frame_t* regs) { + uint64_t vec = regs->interrupt; + + if (vec >= IRQ_INTERRUPTS_COUNT || vec < (IDT_MAX_DESCRIPTORS - IRQ_INTERRUPTS_COUNT)) { + serial_printf("IRQ vector out of range: %d\n", vec); + while (1) + { + asm volatile ("hlt"); + } + } + + if(registered_irq_interrupts[vec]) { + return registered_irq_interrupts[vec](regs); + } + + serial_printf("IRQ interrupt handler\n"); + + while (1) + { + asm volatile ("hlt"); + } +} + +void setup_defined_irq_handlers(void) { + const int_desc_t* desc; + for (desc = __start_irq_handlers; desc < __stop_irq_handlers; desc++) { + if(desc->vector >= IRQ_INTERRUPTS_COUNT) { + serial_printf("Invalid IRQ vector number! Must be < %d\n", IRQ_INTERRUPTS_COUNT); + continue; + } + registered_irq_interrupts[desc->vector] = desc->handler; + serial_printf("Registered IRQ vector 0x%d\n", desc->vector); + } +} + +extern percpu_t* percpu_regions[MAX_CPUS]; + +DEFINE_IRQ(IPI_RESCHEDULE_VECTOR, ipi_reschedule_handler) +{ + (void)frame; + + uint32_t id = lapic_get_id(); + if (id < MAX_CPUS && percpu_regions[id] != NULL) { + percpu_regions[id]->need_resched = true; + } + + lapic_eoi(); +} + +DEFINE_IRQ(IPI_TLB_SHOOTDOWN, ipi_tlb_shootdown_handler) +{ + (void)frame; + + uint32_t id = lapic_get_id(); + tlb_shootdown_t* q = &tlb_shootdown_queue[id]; + + if (q->pending) { + for (size_t i = 0; i < q->count; i++) { + uintptr_t addr = q->addresses[i]; + if (addr != 0) { + asm volatile ("invlpg (%0)" :: "r"(addr) : "memory"); + } + } + + q->pending = false; + q->count = 0; + } + + lapic_eoi(); +} \ No newline at end of file diff --git a/kernel/src/interrupts/isr/isr.c b/kernel/src/interrupts/isr/isr.c new file mode 100644 index 0000000..6a69bf3 --- /dev/null +++ b/kernel/src/interrupts/isr/isr.c @@ -0,0 +1,271 @@ +#include "../../../include/interrupts/interrupts.h" +#include "../../../include/io/serial.h" +#include "../../../include/interrupts/isr.h" +#include "../../../include/sched/sched.h" +#include "../../../include/smp/percpu.h" +#include "../../../include/apic/apic.h" +#include "../../../include/memory/vmm.h" +#include "../../../include/memory/pmm.h" +#include "../../../include/panic/panic.h" +#include + +extern const int_desc_t __start_isr_handlers[]; +extern const int_desc_t __stop_isr_handlers[]; +static int_handler_f registered_isr_interrupts[ISR_EXCEPTION_COUNT] __attribute__((aligned(64))); + +extern volatile int g_panic_owner; + +void registers_dump(struct int_frame_t *regs) { + uint64_t cr2 = 0; + asm volatile("mov %%cr2, %0" : "=r"(cr2)); + + serial_printf("\nRegisters Dump:\n"); + serial_printf("\tRAX:0x%llx\n\tRBX:0x%llx\n\tRCX:0x%llx\n\tRDX:0x%llx\n", + regs->rax, regs->rbx, regs->rcx, regs->rdx); + serial_printf("\tRSI:0x%llx\n\tRDI:0x%llx\n\tRBP:0x%llx\n\tRSP:0x%llx\n", + regs->rsi, regs->rdi, regs->rbp, regs->rsp); + serial_printf("\tRIP:0x%llx\n\tRFL:0x%llx\n\tCS:0x%llx\n\tERR:0x%llx\n", + regs->rip, regs->rflags, regs->cs, regs->error); + serial_printf("\tCR2:0x%llx\n", cr2); + serial_printf("\nInt:%d (%s)\n", regs->interrupt, exception_names[regs->interrupt]); +} + +static void dump_rip_bytes_safe(uint64_t rip) { + uintptr_t hhdm = (uintptr_t)pmm_phys_to_virt(0); + uint64_t vpage = rip & ~0xFFFULL; + uintptr_t off = rip & 0xFFF; + + if (off > PAGE_SIZE - 8) { + serial_printf("[ISR-UD] RIP=0x%llx near page boundary, skipping byte dump\n", rip); + return; + } + + uint64_t cr3_val = 0; + asm volatile("mov %%cr3, %0" : "=r"(cr3_val)); + + volatile uint64_t *pml4v = (volatile uint64_t*)(hhdm + (cr3_val & ~0xFFFULL)); + uint64_t e4 = pml4v[(vpage >> 39) & 0x1FF]; + if (!(e4 & 1)) { + serial_printf("[ISR-UD] RIP=0x%llx: PML4 not present\n", rip); + return; + } + + volatile uint64_t *pdpt = (volatile uint64_t*)(hhdm + (e4 & ~0xFFFULL)); + uint64_t e3 = pdpt[(vpage >> 30) & 0x1FF]; + if (!(e3 & 1)) { + serial_printf("[ISR-UD] RIP=0x%llx: PDPT not present\n", rip); + return; + } + + volatile uint64_t *pd = (volatile uint64_t*)(hhdm + (e3 & ~0xFFFULL)); + uint64_t e2 = pd[(vpage >> 21) & 0x1FF]; + if (!(e2 & 1)) { + serial_printf("[ISR-UD] RIP=0x%llx: PD not present\n", rip); + return; + } + + if (e2 & (1ULL << 7)) { + uintptr_t hp_phys = (e2 & ~0x1FFFFFULL) + (vpage & 0x1FFFFFULL); + volatile uint8_t *bytes = (volatile uint8_t*)(hhdm + hp_phys + off); + serial_printf("[ISR-UD] RIP=0x%llx (huge page) bytes: " + "%02x %02x %02x %02x %02x %02x %02x %02x\n", + rip, + (unsigned)bytes[0], (unsigned)bytes[1], + (unsigned)bytes[2], (unsigned)bytes[3], + (unsigned)bytes[4], (unsigned)bytes[5], + (unsigned)bytes[6], (unsigned)bytes[7]); + return; + } + + volatile uint64_t *pt = (volatile uint64_t*)(hhdm + (e2 & ~0xFFFULL)); + uint64_t pte = pt[(vpage >> 12) & 0x1FF]; + if (!(pte & 1)) { + serial_printf("[ISR-UD] RIP=0x%llx: PT not present (pte=0x%llx)\n", rip, pte); + return; + } + + uintptr_t phys_page = pte & ~0xFFFULL; + volatile uint8_t *bytes = (volatile uint8_t*)(hhdm + phys_page + off); + + serial_printf("[ISR-UD] RIP=0x%llx phys=0x%llx flags=0x%03llx bytes: " + "%02x %02x %02x %02x %02x %02x %02x %02x\n", + rip, phys_page, pte & 0xFFFULL, + (unsigned)bytes[0], (unsigned)bytes[1], + (unsigned)bytes[2], (unsigned)bytes[3], + (unsigned)bytes[4], (unsigned)bytes[5], + (unsigned)bytes[6], (unsigned)bytes[7]); + + if (off >= 16) { + volatile uint8_t *prev = (volatile uint8_t*)(hhdm + phys_page + off - 16); + serial_printf("[ISR-UD] RIP-16..RIP-1: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x\n", + (unsigned)prev[0], (unsigned)prev[1], + (unsigned)prev[2], (unsigned)prev[3], + (unsigned)prev[4], (unsigned)prev[5], + (unsigned)prev[6], (unsigned)prev[7], + (unsigned)prev[8], (unsigned)prev[9], + (unsigned)prev[10], (unsigned)prev[11], + (unsigned)prev[12], (unsigned)prev[13], + (unsigned)prev[14], (unsigned)prev[15]); + } +} + +static void dump_user_qword_via_task(uint64_t uaddr, const char *label) { + percpu_t *pc = get_percpu(); + task_t *me = pc ? (task_t *)pc->current_task : NULL; + if (!me) { uint32_t cpu = lapic_get_id(); me = current_task[cpu]; } + if (!me || !me->pagemap) { + serial_printf("[ISR-PF] %s: no task/pagemap\n", label); + return; + } + if ((uaddr & 0x7) != 0) { + serial_printf("[ISR-PF] %s addr 0x%llx not 8-aligned\n", label, uaddr); + return; + } + uint64_t flags = 0; + if (!vmm_get_page_flags(me->pagemap, uaddr, &flags) || !(flags & 1)) { + serial_printf("[ISR-PF] %s addr 0x%llx not mapped (flags=0x%llx)\n", + label, uaddr, flags); + return; + } + uintptr_t phys = 0; + if (!vmm_virt_to_phys(me->pagemap, uaddr, &phys)) { + serial_printf("[ISR-PF] %s virt_to_phys failed for 0x%llx\n", label, uaddr); + return; + } + volatile uint64_t *p = (volatile uint64_t *)pmm_phys_to_virt(phys & ~7ULL); + uint64_t v = *p; + serial_printf("[ISR-PF] %s [0x%llx] = 0x%llx\n", label, uaddr, v); +} + +static __attribute__((noreturn)) void kill_current_task(int exit_code) { + percpu_t *pc = get_percpu(); + task_t *me = pc ? (task_t *)pc->current_task : NULL; + if (!me) { uint32_t cpu = lapic_get_id(); me = current_task[cpu]; } + if (me) me->exit_code = exit_code; + vmm_switch_pagemap(vmm_get_kernel_pagemap()); + task_exit(); +} + +void handle_intercpu_interrupt(struct int_frame_t *regs) +{ + if (regs->interrupt == 2) { + if (__atomic_load_n(&g_panic_owner, __ATOMIC_ACQUIRE) != 0) { + for (;;) asm volatile("cli; hlt"); + } + kernel_panic_regs("Non-Maskable Interrupt (hardware)", regs); + } + + serial_force_unlock(); + registers_dump(regs); + + switch (regs->interrupt) { + case EXCEPTION_DIVIDE_ERROR: + case EXCEPTION_OVERFLOW: + case EXCEPTION_BOUND_RANGE: + case EXCEPTION_INVALID_OPCODE: + case EXCEPTION_DEVICE_NOT_AVAILABLE: + case EXCEPTION_X87_FPU_ERROR: + if ((regs->cs & 3) == 3) { + if (regs->interrupt == EXCEPTION_INVALID_OPCODE) { + dump_rip_bytes_safe(regs->rip); + + serial_printf("[ISR-UD] RBP=0x%llx RSP=0x%llx\n", + regs->rbp, regs->rsp); + } + serial_printf("[ISR] %s in userspace at RIP=0x%llx — killing task\n", + exception_names[regs->interrupt], regs->rip); + kill_current_task(139); + } + kernel_panic_regs(exception_names[regs->interrupt], regs); + + case EXCEPTION_DOUBLE_FAULT: + kernel_panic_regs("Double Fault", regs); + + case EXCEPTION_INVALID_TSS: + case EXCEPTION_SEGMENT_NOT_PRESENT: + case EXCEPTION_STACK_SEGMENT_FAULT: + kernel_panic_regs(exception_names[regs->interrupt], regs); + + case EXCEPTION_GENERAL_PROTECTION_FAULT: + if ((regs->cs & 3) == 3) { + serial_printf("[ISR] GPF in userspace at RIP=0x%llx — killing task\n", + regs->rip); + kill_current_task(139); + } + kernel_panic_regs("General Protection Fault (kernel)", regs); + + case EXCEPTION_PAGE_FAULT: { + uint64_t cr2val = 0; + asm volatile("mov %%cr2, %0" : "=r"(cr2val)); + if ((regs->cs & 3) == 3) { + percpu_t *pc = get_percpu(); + task_t *me = pc ? (task_t *)pc->current_task : NULL; + if (!me) { uint32_t cpu = lapic_get_id(); me = current_task[cpu]; } + serial_printf("[ISR] Page Fault in userspace: RIP=0x%llx CR2=0x%llx ERR=0x%llx task='%s' pid=%u\n", + regs->rip, cr2val, regs->error, + me ? me->name : "?", me ? me->pid : 0); + dump_rip_bytes_safe(regs->rip); + dump_user_qword_via_task(regs->rsp, "[rsp] "); + dump_user_qword_via_task(regs->rsp + 8, "[rsp+8] "); + dump_user_qword_via_task(regs->rsp + 16, "[rsp+16] "); + dump_user_qword_via_task(regs->rsp + 24, "[rsp+24] "); + kill_current_task(139); + } + kernel_panic_regs("Page Fault (kernel)", regs); + } + + case EXCEPTION_MACHINE_CHECK: + kernel_panic_regs("Machine Check Exception", regs); + + default: { + char buf[64]; + serial_printf("UNKNOWN EXCEPTION %llu\n", regs->interrupt); + const char *pfx = "Unknown Exception #"; + int i = 0; + while (pfx[i] && i < 50) { buf[i] = pfx[i]; i++; } + uint64_t v = regs->interrupt; + if (v >= 100) buf[i++] = '0' + (int)(v / 100); + if (v >= 10) buf[i++] = '0' + (int)((v / 10) % 10); + buf[i++] = '0' + (int)(v % 10); + buf[i] = '\0'; + kernel_panic_regs(buf, regs); + } + } +} + +DEFINE_ISR(0x3, isr_breakpoint) { + (void)frame; + serial_printf("Breakpoint hit\n"); +} + +void isr_common_handler(struct int_frame_t *regs) +{ + uint64_t vec = regs->interrupt; + + if (vec >= ISR_EXCEPTION_COUNT) { + serial_printf("Invalid ISR vector %d\n", vec); + while (1) asm volatile("hlt"); + } + + if (registered_isr_interrupts[vec]) { + registered_isr_interrupts[vec](regs); + return; + } + + handle_intercpu_interrupt(regs); +} + +void setup_defined_isr_handlers(void) +{ + const int_desc_t *desc; + for (desc = __start_isr_handlers; desc < __stop_isr_handlers; desc++) { + if (desc->vector >= ISR_EXCEPTION_COUNT) { + serial_printf("ISR: vector %d out of range\n", desc->vector); + continue; + } + registered_isr_interrupts[desc->vector] = desc->handler; + serial_printf("ISR: Registered vector %d\n", desc->vector); + } +} \ No newline at end of file diff --git a/kernel/src/io/serial.c b/kernel/src/io/serial.c new file mode 100644 index 0000000..e129704 --- /dev/null +++ b/kernel/src/io/serial.c @@ -0,0 +1,826 @@ +#include +#include +#include +#include +#include +#include +#include "../../include/io/ports.h" +#include "../../include/io/serial.h" + +static volatile uint8_t serial_lock = 0; +static uint16_t default_serial_port = 0; + +void serial_initialize(uint16_t port, uint32_t baud_rate) { + outb(port + 1, 0x00); + + uint16_t divisor = 115200 / baud_rate; + outb(port + 3, 0x80); + outb(port + 0, divisor & 0xFF); + outb(port + 1, (divisor >> 8) & 0xFF); + + outb(port + 3, 0x03); + outb(port + 2, 0xC7); + outb(port + 4, 0x0B); + outb(port + 4, 0x1E); + outb(port + 0, 0xAE); + + if (inb(port + 0) != 0xAE) { + return; + } + + outb(port + 4, 0x0F); + + default_serial_port = port; +} + +uint16_t serial_get_default_port(void) { + return default_serial_port; +} + +void serial_set_default_port(uint16_t port) { + default_serial_port = port; +} + +void serial_force_unlock(void) { + __sync_lock_release(&serial_lock); + asm volatile("sti"); +} + +int serial_received_port(uint16_t port) { + return inb(port + 5) & 1; +} + +int serial_received(void) { + if (default_serial_port == 0) return 0; + return serial_received_port(default_serial_port); +} + +char serial_read_port(uint16_t port) { + while (serial_received_port(port) == 0); + return inb(port); +} + +char serial_read(void) { + if (default_serial_port == 0) return 0; + return serial_read_port(default_serial_port); +} + +int serial_is_transmit_empty_port(uint16_t port) { + return inb(port + 5) & 0x20; +} + +int serial_is_transmit_empty(void) { + if (default_serial_port == 0) return 1; + return serial_is_transmit_empty_port(default_serial_port); +} + +void serial_write_port(uint16_t port, char c) { + while (serial_is_transmit_empty_port(port) == 0); + outb(port, c); +} + +void serial_write(char c) { + if (default_serial_port == 0) return; + serial_write_port(default_serial_port, c); +} + +void serial_writestring_port(uint16_t port, const char* str) { + while (*str) { + serial_write_port(port, *str++); + } +} + +void serial_writestring(const char* str) { + if (default_serial_port == 0) return; + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + while (__sync_lock_test_and_set(&serial_lock, 1)) + __asm__ volatile("pause" ::: "memory"); + serial_writestring_port(default_serial_port, str); + __sync_lock_release(&serial_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + + +void serial_writebuf(const char* buf, size_t len) { + if (default_serial_port == 0 || !buf || len == 0) return; + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + while (__sync_lock_test_and_set(&serial_lock, 1)) + __asm__ volatile("pause" ::: "memory"); + for (size_t i = 0; i < len; i++) + serial_write_port(default_serial_port, buf[i]); + __sync_lock_release(&serial_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + + + +static void reverse_string(char* str, int length) { + int start = 0; + int end = length - 1; + while (start < end) { + char temp = str[start]; + str[start] = str[end]; + str[end] = temp; + start++; + end--; + } +} + +static void uint_to_str(uint64_t value, char* buffer, int base, bool uppercase) { + char* ptr = buffer; + + if (base < 2 || base > 36) { + *ptr = '\0'; + return; + } + + const char* digits = uppercase ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" : + "0123456789abcdefghijklmnopqrstuvwxyz"; + + if (value == 0) { + *ptr++ = '0'; + *ptr = '\0'; + return; + } + + while (value > 0) { + int digit = value % base; + *ptr++ = digits[digit]; + value /= base; + } + + *ptr = '\0'; + reverse_string(buffer, ptr - buffer); +} + +static void int_to_str(int64_t value, char* buffer, int base, bool uppercase) { + if (value < 0 && (base == 10 || base == 8)) { + buffer[0] = '-'; + uint_to_str(-value, buffer + 1, base, uppercase); + } else { + uint_to_str(value, buffer, base, uppercase); + } +} + +static uint64_t pow10_u64(int n) { + uint64_t r = 1; + while (n-- > 0) r *= 10; + return r; +} + +static int double_to_string(double value, char* buffer, int precision) { + if (isnan(value)) { + strcpy(buffer, "nan"); + return 3; + } + + if (isinf(value)) { + if (value < 0) { + strcpy(buffer, "-inf"); + return 4; + } else { + strcpy(buffer, "inf"); + return 3; + } + } + + if (precision < 0) precision = 6; + if (precision > 16) precision = 16; + + bool negative = (value < 0); + double abs_val = negative ? -value : value; + + uint64_t mult = pow10_u64(precision); + double scaled = abs_val * mult + 0.5; + uint64_t int_scaled = (uint64_t)scaled; + + uint64_t int_part = int_scaled / mult; + uint64_t frac_part = int_scaled % mult; + + char* ptr = buffer; + if (negative) *ptr++ = '-'; + + char int_buf[32]; + char* p = int_buf + sizeof(int_buf) - 1; + *p = '\0'; + + if (int_part == 0) { + *--p = '0'; + } else { + uint64_t n = int_part; + while (n > 0) { + *--p = '0' + (n % 10); + n /= 10; + } + } + + size_t int_len = (int_buf + sizeof(int_buf) - 1) - p; + memcpy(ptr, p, int_len); + ptr += int_len; + + if (precision > 0) { + *ptr++ = '.'; + + char frac_buf[32]; + char* f = frac_buf + sizeof(frac_buf) - 1; + *f = '\0'; + + if (frac_part == 0) { + for (int i = 0; i < precision; i++) { + *--f = '0'; + } + } else { + uint64_t n = frac_part; + int count = 0; + while (n > 0) { + *--f = '0' + (n % 10); + n /= 10; + count++; + } + while (count < precision) { + *--f = '0'; + count++; + } + } + + size_t frac_len = (frac_buf + sizeof(frac_buf) - 1) - f; + memcpy(ptr, f, frac_len); + ptr += frac_len; + } + + *ptr = '\0'; + return ptr - buffer; +} + +static int double_to_scientific(double value, char* buffer, int precision, bool uppercase) { + if (isnan(value)) { + strcpy(buffer, "nan"); + return 3; + } + + if (isinf(value)) { + if (value < 0) { + strcpy(buffer, "-inf"); + return 4; + } else { + strcpy(buffer, "inf"); + return 3; + } + } + + if (value == 0.0) { + if (precision < 0) precision = 6; + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 0; i < precision; i++) { + buffer[2 + i] = '0'; + } + buffer[2 + precision] = uppercase ? 'E' : 'e'; + buffer[3 + precision] = '+'; + buffer[4 + precision] = '0'; + buffer[5 + precision] = '0'; + buffer[6 + precision] = '\0'; + return 6 + precision; + } + + if (precision < 0) precision = 6; + if (precision > 16) precision = 16; + + double abs_val = value < 0 ? -value : value; + int exponent = 0; + + if (abs_val >= 10.0) { + while (abs_val >= 10.0) { + abs_val /= 10.0; + exponent++; + } + } else if (abs_val < 1.0 && abs_val > 0.0) { + while (abs_val < 1.0) { + abs_val *= 10.0; + exponent--; + } + } + + if (value < 0) abs_val = -abs_val; + + int len = double_to_string(abs_val, buffer, precision); + + char e_char = uppercase ? 'E' : 'e'; + buffer[len++] = e_char; + + if (exponent >= 0) { + buffer[len++] = '+'; + } else { + buffer[len++] = '-'; + exponent = -exponent; + } + + if (exponent < 10) { + buffer[len++] = '0'; + buffer[len++] = '0' + exponent; + } else { + buffer[len++] = '0' + (exponent / 10); + buffer[len++] = '0' + (exponent % 10); + } + + buffer[len] = '\0'; + return len; +} + +static int double_to_general(double value, char* buffer, int precision, bool uppercase) { + double abs_val = value < 0 ? -value : value; + + if (abs_val == 0.0) { + strcpy(buffer, "0"); + return 1; + } + + if (precision < 0) precision = 6; + if (precision == 0) precision = 1; + + bool use_scientific = (abs_val >= 1e6 || (abs_val < 1e-4 && abs_val > 0)); + + if (use_scientific) { + return double_to_scientific(value, buffer, precision - 1, uppercase); + } + + int digits_before = 0; + double temp = abs_val; + while (temp >= 1.0) { + temp /= 10.0; + digits_before++; + } + if (digits_before == 0) digits_before = 1; + + int decimal_places = precision - digits_before; + if (decimal_places < 0) decimal_places = 0; + + return double_to_string(value, buffer, decimal_places); +} + +void serial_printf_port(uint16_t port, const char* format, ...) { + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + while (__sync_lock_test_and_set(&serial_lock, 1)) { + __asm__ volatile("pause" ::: "memory"); + } + + va_list args; + va_start(args, format); + + char buffer[256]; + const char* ptr = format; + + while (*ptr) { + if (*ptr != '%') { + serial_write_port(port, *ptr); + ptr++; + continue; + } + + const char* percent_start = ptr++; + + while (*ptr == '0' || *ptr == '-' || *ptr == '+' || *ptr == ' ' || *ptr == '#') { + ptr++; + } + + int width = 0; + while (*ptr >= '0' && *ptr <= '9') { + width = width * 10 + (*ptr - '0'); + ptr++; + } + + int precision = -1; + if (*ptr == '.') { + ptr++; + precision = 0; + while (*ptr >= '0' && *ptr <= '9') { + precision = precision * 10 + (*ptr - '0'); + ptr++; + } + } + + bool has_ll = false; + bool has_l = false; + bool has_size_t = false; + + if (*ptr == 'z') { + ptr++; + has_size_t = true; + } else if (*ptr == 'l') { + ptr++; + if (*ptr == 'l') { + ptr++; + has_ll = true; + } else { + has_l = true; + } + } else if (*ptr == 'h') { + ptr++; + if (*ptr == 'h') ptr++; + } else if (*ptr == 'L' || *ptr == 'j' || *ptr == 't') { + ptr++; + } + + switch (*ptr) { + case 'c': { + char c = (char)va_arg(args, int); + serial_write_port(port, c); + break; + } + + case 's': { + const char* str = va_arg(args, const char*); + if (!str) { + str = "(null)"; + } + serial_writestring_port(port, str); + break; + } + + case 'd': + case 'i': { + int64_t num; + if (has_size_t) { + num = (int64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, int64_t); + } else if (has_l) { + num = (int64_t)va_arg(args, long); + } else { + num = (int64_t)va_arg(args, int); + } + int_to_str(num, buffer, 10, false); + serial_writestring_port(port, buffer); + break; + } + + case 'u': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 10, false); + serial_writestring_port(port, buffer); + break; + } + + case 'x': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 16, false); + serial_writestring_port(port, buffer); + break; + } + + case 'X': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 16, true); + serial_writestring_port(port, buffer); + break; + } + + case 'o': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 8, false); + serial_writestring_port(port, buffer); + break; + } + + case 'p': { + void* ptr_val = va_arg(args, void*); + serial_writestring_port(port, "0x"); + uint_to_str((uintptr_t)ptr_val, buffer, 16, false); + serial_writestring_port(port, buffer); + break; + } + + case 'f': + case 'F': { + double num = va_arg(args, double); + double_to_string(num, buffer, precision); + serial_writestring_port(port, buffer); + break; + } + + case 'e': + case 'E': { + double num = va_arg(args, double); + double_to_scientific(num, buffer, precision, (*ptr == 'E')); + serial_writestring_port(port, buffer); + break; + } + + case 'g': + case 'G': { + double num = va_arg(args, double); + double_to_general(num, buffer, precision, (*ptr == 'G')); + serial_writestring_port(port, buffer); + break; + } + + case 'a': + case 'A': { + double num = va_arg(args, double); + double_to_scientific(num, buffer, precision, (*ptr == 'A')); + buffer[0] = '0'; + buffer[1] = 'x'; + serial_writestring_port(port, buffer); + break; + } + + case 'n': { + int* count_ptr = va_arg(args, int*); + *count_ptr = 0; + break; + } + + case '%': { + serial_write_port(port, '%'); + break; + } + + default: { + for (const char* p = percent_start; p <= ptr; p++) { + serial_write_port(port, *p); + } + break; + } + } + + if (*ptr != '\0') { + ptr++; + } + } + + va_end(args); + __sync_lock_release(&serial_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + +void serial_printf(const char* format, ...) { + if (default_serial_port == 0) return; + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + while (__sync_lock_test_and_set(&serial_lock, 1)) { + __asm__ volatile("pause" ::: "memory"); + } + + va_list args; + va_start(args, format); + + char buffer[256]; + const char* ptr = format; + + while (*ptr) { + if (*ptr != '%') { + serial_write_port(default_serial_port, *ptr); + ptr++; + continue; + } + + const char* percent_start = ptr++; + + while (*ptr == '0' || *ptr == '-' || *ptr == '+' || *ptr == ' ' || *ptr == '#') { + ptr++; + } + + int width = 0; + while (*ptr >= '0' && *ptr <= '9') { + width = width * 10 + (*ptr - '0'); + ptr++; + } + + int precision = -1; + if (*ptr == '.') { + ptr++; + precision = 0; + while (*ptr >= '0' && *ptr <= '9') { + precision = precision * 10 + (*ptr - '0'); + ptr++; + } + } + + bool has_ll = false; + bool has_l = false; + bool has_size_t = false; + + if (*ptr == 'z') { + ptr++; + has_size_t = true; + } else if (*ptr == 'l') { + ptr++; + if (*ptr == 'l') { + ptr++; + has_ll = true; + } else { + has_l = true; + } + } else if (*ptr == 'h') { + ptr++; + if (*ptr == 'h') ptr++; + } else if (*ptr == 'L' || *ptr == 'j' || *ptr == 't') { + ptr++; + } + + switch (*ptr) { + case 'c': { + char c = (char)va_arg(args, int); + serial_write_port(default_serial_port, c); + break; + } + + case 's': { + const char* str = va_arg(args, const char*); + if (!str) { + str = "(null)"; + } + serial_writestring_port(default_serial_port, str); + break; + } + + case 'd': + case 'i': { + int64_t num; + if (has_size_t) { + num = (int64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, int64_t); + } else if (has_l) { + num = (int64_t)va_arg(args, long); + } else { + num = (int64_t)va_arg(args, int); + } + int_to_str(num, buffer, 10, false); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'u': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 10, false); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'x': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 16, false); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'X': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 16, true); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'o': { + uint64_t num; + if (has_size_t) { + num = (uint64_t)va_arg(args, size_t); + } else if (has_ll) { + num = va_arg(args, uint64_t); + } else if (has_l) { + num = (uint64_t)va_arg(args, unsigned long); + } else { + num = (uint64_t)va_arg(args, unsigned int); + } + uint_to_str(num, buffer, 8, false); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'p': { + void* ptr_val = va_arg(args, void*); + serial_writestring_port(default_serial_port, "0x"); + uint_to_str((uintptr_t)ptr_val, buffer, 16, false); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'f': + case 'F': { + double num = va_arg(args, double); + double_to_string(num, buffer, precision); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'e': + case 'E': { + double num = va_arg(args, double); + double_to_scientific(num, buffer, precision, (*ptr == 'E')); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'g': + case 'G': { + double num = va_arg(args, double); + double_to_general(num, buffer, precision, (*ptr == 'G')); + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'a': + case 'A': { + double num = va_arg(args, double); + double_to_scientific(num, buffer, precision, (*ptr == 'A')); + buffer[0] = '0'; + buffer[1] = 'x'; + serial_writestring_port(default_serial_port, buffer); + break; + } + + case 'n': { + int* count_ptr = va_arg(args, int*); + *count_ptr = 0; + break; + } + + case '%': { + serial_write_port(default_serial_port, '%'); + break; + } + + default: { + for (const char* p = percent_start; p <= ptr; p++) { + serial_write_port(default_serial_port, *p); + } + break; + } + } + + if (*ptr != '\0') { + ptr++; + } + } + + va_end(args); + __sync_lock_release(&serial_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} \ No newline at end of file diff --git a/kernel/src/kernel.c b/kernel/src/kernel.c new file mode 100644 index 0000000..b9989e3 --- /dev/null +++ b/kernel/src/kernel.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../include/graphics/fb/fb.h" +#include "../include/io/serial.h" +#include "../include/gdt/gdt.h" +#include "../include/interrupts/interrupts.h" +#include "../include/interrupts/idt.h" +#include "../include/sse/fpu.h" +#include "../include/sse/sse.h" +#include "../include/memory/pmm.h" +#include "../include/memory/vmm.h" +#include "../include/memory/paging.h" +#include "../include/acpi/acpi.h" +#include "../include/apic/apic.h" +#include "../include/io/ports.h" +#include "../include/drivers/timer.h" +#include "../include/smp/smp.h" +#include "../include/smp/percpu.h" +#include "../include/sched/sched.h" +#include "../include/elf/elf.h" +#include "../include/syscall/syscall.h" +#include "../include/drivers/ps2.h" +#include "../include/fs/vfs.h" +#include "../include/fs/ramfs.h" +#include "../include/fs/devfs.h" +#include "../include/fs/initramfs.h" +#include "../include/drivers/ata.h" +#include "../include/drivers/blkdev.h" +#include "../include/drivers/disk.h" +#include "../include/drivers/partition.h" +#include "../include/fs/ext2.h" +#include "../include/fs/fat32.h" + +__attribute__((used, section(".limine_requests"))) +static volatile uint64_t limine_base_revision[] = LIMINE_BASE_REVISION(4); + +__attribute__((used, section(".limine_requests"))) +static volatile struct limine_framebuffer_request framebuffer_request = { + .id = LIMINE_FRAMEBUFFER_REQUEST_ID, + .revision = 0 +}; + +__attribute__((used, section(".limine_requests"))) +static volatile struct limine_memmap_request memmap_request = { + .id = LIMINE_MEMMAP_REQUEST_ID, + .revision = 0 +}; + +__attribute__((used, section(".limine_requests"))) +static volatile struct limine_mp_request mp_request = { + .id = LIMINE_MP_REQUEST_ID, + .revision = 0, + .flags = 0 +}; + +__attribute__((used, section(".limine_requests"))) +static volatile struct limine_hhdm_request hhdm_request = { + .id = LIMINE_HHDM_REQUEST_ID, + .revision = 0 +}; + +__attribute__((used, section(".limine_requests"))) +volatile struct limine_rsdp_request rsdp_request = { + .id = LIMINE_RSDP_REQUEST_ID, + .revision = 0, + .response = NULL +}; + +__attribute__((used, section(".limine_requests"))) +static volatile struct limine_module_request module_request = { + .id = LIMINE_MODULE_REQUEST_ID, + .revision = 0 +}; + +__attribute__((used, section(".limine_requests_start"))) +static volatile uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_MARKER; + +__attribute__((used, section(".limine_requests_end"))) +static volatile uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER; + +struct limine_framebuffer *global_framebuffer = NULL; + +static void hcf(void) { + for (;;) { + asm ("hlt"); + } +} + +static void load_elf_module(void) { + if (!module_request.response) { + serial_writestring("[ELF] No module response from Limine\n"); + return; + } + if (module_request.response->module_count == 0) { + serial_writestring("[ELF] No modules provided\n"); + return; + } + + struct limine_file *mod = module_request.response->modules[0]; + serial_printf("[ELF] Module: path='%s' size=%llu addr=%p\n", mod->path, mod->size, mod->address); + + elf_load_result_t r = elf_load(mod->address, (size_t)mod->size, 0); + if (r.error != ELF_OK) { + serial_printf("[ELF] LOAD FAILED: %s\n", elf_strerror(r.error)); + return; + } + + serial_printf("[ELF] Load OK! entry=0x%llx stack_top=0x%llx\n", r.entry, r.stack_top); + + uint64_t cr3 = (uint64_t)pmm_virt_to_phys(r.pagemap->pml4); + + task_t *t = task_create_user("init", r.entry, r.stack_top, cr3, 16, r.pagemap, 0, 0); + + if (!t) { + serial_writestring("[ELF] task_create_user failed\n"); + elf_unload(&r); + return; + } + + t->brk_start = r.load_end; + t->brk_current = r.load_end; + + int sr = vfs_init_stdio(t); + if (sr < 0) + serial_printf("[VFS] vfs_init_stdio failed: %d\n", sr); + else + serial_writestring("[VFS] stdio assigned to task\n"); + + serial_printf("[ELF] Task 'init' created: cr3=0x%llx\n", t->cr3); +} + +void ps2_task(void* arg) { + (void)arg; + asm volatile ("sti"); + ps2_init(); +} + +static bool detect_installed_system(void) { + blkdev_t *hda2 = blkdev_get_by_name("hda2"); + if (hda2) { + uint16_t magic = 0; + if (blkdev_read(hda2, EXT2_SUPER_OFFSET + 56, &magic, sizeof(magic)) == 0 + && magic == EXT2_SUPER_MAGIC) { + serial_writestring("[boot] ext2 on hda2 -> installed system\n"); + return true; + } + } + + blkdev_t *hda1 = blkdev_get_by_name("hda1"); + if (hda1) { + uint8_t sector[512]; + if (hda1->ops->read_sectors(hda1, 0, 1, sector) == 0) { + if (sector[510] == 0x55 && sector[511] == (uint8_t)0xAA + && memcmp(sector + 82, "FAT32", 5) == 0) { + serial_writestring("[boot] FAT32 on hda1 -> looks like installed system\n"); + return true; + } + } + } + + blkdev_t *hda = blkdev_get_by_name("hda"); + if (hda) { + uint16_t magic = 0; + if (blkdev_read(hda, EXT2_SUPER_OFFSET + 56, &magic, sizeof(magic)) == 0 + && magic == EXT2_SUPER_MAGIC) { + serial_writestring("[boot] legacy ext2-on-whole-disk detected\n"); + return true; + } + } + return false; +} + +void kernel_main(void) { + serial_initialize(COM1, 115200); + serial_writestring("\n=== SERIAL PORT INITIALIZED ===\n"); + + if (LIMINE_BASE_REVISION_SUPPORTED(limine_base_revision) == false) { + serial_writestring("ERROR: Unsupported Limine base revision\n"); + hcf(); + } + + gdt_init(); + init_interrupt_system(); + serial_writestring("GDT&IDT [OK]\n"); + fpu_init(); + sse_init(); + enable_fsgsbase(); + serial_writestring("FPU/SSE/FSGSBASE [OK]\n"); + + if (!framebuffer_request.response || + framebuffer_request.response->framebuffer_count < 1) { + serial_writestring("ERROR: No framebuffer available\n"); + hcf(); + } + if (!memmap_request.response) { + serial_writestring("ERROR: No memory map available\n"); + hcf(); + } + if (!hhdm_request.response) { + serial_writestring("ERROR: No HHDM available\n"); + hcf(); + } + + global_framebuffer = framebuffer_request.response->framebuffers[0]; + + pmm_init(memmap_request.response, hhdm_request.response); + slab_init(); + serial_writestring("PMM [OK]\n"); + paging_init(); + serial_writestring("Paging [OK]\n"); + vmm_init(); + serial_writestring("VMM [OK]\n"); + vfs_init(); + serial_writestring("VFS [OK]\n"); + + vnode_t *rootfs = ramfs_create_root(); + vfs_mount("/", rootfs); + vfs_set_mount_info("/", "ramfs", "ramfs"); + vnode_unref(rootfs); + serial_writestring("rootfs [OK]\n"); + + vfs_mkdir("/dev", 0755); + vfs_mkdir("/bin", 0755); + vfs_mkdir("/etc", 0755); + vfs_mkdir("/tmp", 0755); + vfs_mkdir("/proc", 0755); + vfs_mkdir("/mnt", 0755); + + vnode_t *devroot = devfs_create_root(); + vfs_mount("/dev", devroot); + vfs_set_mount_info("/dev", "devfs", "devfs"); + serial_writestring("devfs [OK]\n"); + acpi_init(); + acpi_print_tables(); + serial_writestring("ACPI [OK]\n"); + apic_init(); + serial_writestring("APIC [OK]\n"); + clear_screen(); + smp_init(mp_request.response); + serial_writestring("SMP [OK]\n"); + + serial_writestring("Waiting until all APs are fully ready...\n"); + while (smp_get_online_count() < (smp_get_cpu_count() - 1)) { + timer_sleep_ms(100); + } + serial_writestring("All APs ready.\n"); + + printf("Kernel initialized successfully!\n\n"); + printf("Framebuffer: %dx%d, %d bpp\n", global_framebuffer->width, global_framebuffer->height, global_framebuffer->bpp); + printf("\nMemory Information:\n"); + printf("HHDM offset: 0x%llx\n", hhdm_request.response->offset); + printf("Memory map entries: %llu\n", memmap_request.response->entry_count); + pmm_print_stats(); + + smp_print_info_fb(); + printf("\nSystem: %u CPU cores detected\n\n", smp_get_cpu_count()); + syscall_init(); + + disk_init(); + serial_writestring("Disk subsystem [OK]\n"); + + bool skip_initramfs = detect_installed_system(); + if (skip_initramfs) { + printf("[boot] installed Cervus detected on disk -- booting from disk\n"); + } else { + serial_writestring("[boot] no installed system detected, using initramfs\n"); + } + + if (!skip_initramfs && module_request.response && + module_request.response->module_count >= 2) { + struct limine_file *tar = module_request.response->modules[1]; + serial_printf("[initramfs] module: '%s' size=%llu\n", tar->path, tar->size); + int r = initramfs_mount(tar->address, (size_t)tar->size); + if (r == 0) + serial_writestring("[initramfs] mounted OK\n"); + else + serial_printf("[initramfs] mount FAILED: %d\n", r); + } else if (!skip_initramfs) { + serial_writestring("[initramfs] no TAR module (modules[1] missing)\n"); + } + + timer_init(); + sched_init(); + sched_notify_ready(); + timer_sleep_ms(10); + clear_screen(); + load_elf_module(); + task_create("PS/2", ps2_task, NULL, 10); + serial_writestring("Manually triggering first reschedule...\n"); + sched_reschedule(); + + while (1) { + hcf(); + } +} \ No newline at end of file diff --git a/kernel/src/memory/paging.c b/kernel/src/memory/paging.c new file mode 100644 index 0000000..0d78fbb --- /dev/null +++ b/kernel/src/memory/paging.c @@ -0,0 +1,320 @@ +#include "../../include/memory/paging.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include +#include + +#define MAX_REGIONS 256 +#define MAX_RESERVED 64 +#define DEFAULT_ALLOC_BASE 0xFFFFFE0000000000ULL + +static paging_region_t regions[MAX_REGIONS]; +static size_t region_count = 0; + +static struct { + uintptr_t start; + uintptr_t end; +} reserved_ranges[MAX_RESERVED]; +static size_t reserved_count = 0; + +static uintptr_t next_alloc_virt = DEFAULT_ALLOC_BASE; + +static inline uintptr_t align_up(uintptr_t addr, size_t alignment) { + return (addr + alignment - 1) & ~(alignment - 1); +} + +static inline bool is_aligned(uintptr_t addr, size_t alignment) { + return (addr & (alignment - 1)) == 0; +} + +static inline bool ranges_overlap(uintptr_t s1, uintptr_t e1, uintptr_t s2, uintptr_t e2) { + return s1 < e2 && s2 < e1; +} + +void paging_init(void) { + memset(regions, 0, sizeof(regions)); + memset(reserved_ranges, 0, sizeof(reserved_ranges)); + region_count = 0; + reserved_count = 0; + next_alloc_virt = DEFAULT_ALLOC_BASE; + + paging_reserve_range(vmm_get_kernel_pagemap(), + 0xFFFFFFFF80000000ULL, + 0xFFFFFFFFC0000000ULL); + + serial_printf("Paging: subsystem initialized\n"); +} + +bool paging_reserve_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t virt_end) { + (void)pagemap; + + if (reserved_count >= MAX_RESERVED) { + serial_printf("PAGING ERROR: too many reserved ranges\n"); + return false; + } + + if (virt_start >= virt_end) { + serial_printf("PAGING ERROR: invalid reserved range\n"); + return false; + } + + for (size_t i = 0; i < reserved_count; i++) { + if (ranges_overlap(virt_start, virt_end, + reserved_ranges[i].start, + reserved_ranges[i].end)) { + serial_printf("PAGING ERROR: reserved range overlaps\n"); + return false; + } + } + + reserved_ranges[reserved_count].start = virt_start; + reserved_ranges[reserved_count].end = virt_end; + reserved_count++; + + serial_printf("Paging: reserved range 0x%llx-0x%llx\n", virt_start, virt_end); + return true; +} + +static bool is_range_reserved(uintptr_t start, uintptr_t end) { + for (size_t i = 0; i < reserved_count; i++) { + if (ranges_overlap(start, end, reserved_ranges[i].start, reserved_ranges[i].end)) + return true; + } + return false; +} + +bool paging_is_range_free(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t virt_end) { + if (is_range_reserved(virt_start, virt_end)) + return false; + + for (uintptr_t virt = virt_start; virt < virt_end; virt += PAGE_SIZE) { + uintptr_t phys; + if (vmm_virt_to_phys(pagemap, virt, &phys)) + return false; + } + return true; +} + +bool paging_map_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, + uintptr_t phys_start, size_t page_count, uint64_t flags) { + if (!pagemap || page_count == 0) + return false; + + if (!is_aligned(virt_start, PAGE_SIZE) || !is_aligned(phys_start, PAGE_SIZE)) { + serial_printf("PAGING ERROR: unaligned addresses\n"); + return false; + } + + uintptr_t virt_end = virt_start + page_count * PAGE_SIZE; + if (is_range_reserved(virt_start, virt_end)) { + serial_printf("PAGING ERROR: range overlaps reserved area\n"); + return false; + } + + for (size_t i = 0; i < page_count; i++) { + uintptr_t virt = virt_start + i * PAGE_SIZE; + uintptr_t phys = phys_start + i * PAGE_SIZE; + + uintptr_t existing_phys; + if (vmm_virt_to_phys(pagemap, virt, &existing_phys)) { + if (existing_phys != phys) { + serial_printf("PAGING ERROR: page at 0x%llx already mapped to 0x%llx\n", + virt, existing_phys); + return false; + } + uint64_t existing_flags; + if (vmm_get_page_flags(pagemap, virt, &existing_flags)) { + if (existing_flags != (flags & 0xFFF)) { + vmm_unmap_page(pagemap, virt); + vmm_map_page(pagemap, virt, phys, flags); + } + } + } else { + if (!vmm_map_page(pagemap, virt, phys, flags)) { + serial_printf("PAGING ERROR: failed to map 0x%llx -> 0x%llx\n", virt, phys); + for (size_t j = 0; j < i; j++) + vmm_unmap_page(pagemap, virt_start + j * PAGE_SIZE); + return false; + } + } + } + return true; +} + +bool paging_unmap_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, size_t page_count) { + if (!pagemap || page_count == 0) + return false; + + for (size_t i = 0; i < page_count; i++) + vmm_unmap_page(pagemap, virt_start + i * PAGE_SIZE); + + return true; +} + +bool paging_change_flags(vmm_pagemap_t* pagemap, uintptr_t virt_start, + size_t page_count, uint64_t new_flags) { + if (!pagemap || page_count == 0) + return false; + + if (!is_aligned(virt_start, PAGE_SIZE)) { + serial_printf("PAGING ERROR: unaligned address 0x%llx\n", virt_start); + return false; + } + + for (size_t i = 0; i < page_count; i++) { + uintptr_t virt = virt_start + i * PAGE_SIZE; + uintptr_t phys; + + if (!vmm_virt_to_phys(pagemap, virt, &phys)) { + serial_printf("PAGING ERROR: page at 0x%llx not mapped\n", virt); + return false; + } + + vmm_unmap_page(pagemap, virt); + if (!vmm_map_page(pagemap, virt, phys, new_flags)) { + serial_printf("PAGING ERROR: failed to remap 0x%llx\n", virt); + return false; + } + } + return true; +} + +paging_region_t* paging_create_region(vmm_pagemap_t* pagemap, + uintptr_t virt_start, size_t size, + uint64_t flags) { + if (region_count >= MAX_REGIONS) { + serial_printf("PAGING ERROR: too many regions\n"); + return NULL; + } + + size_t page_count = align_up(size, PAGE_SIZE) / PAGE_SIZE; + uintptr_t virt_end = virt_start + page_count * PAGE_SIZE; + + if (!paging_is_range_free(pagemap, virt_start, virt_end)) { + serial_printf("PAGING ERROR: region overlaps existing mapping\n"); + return NULL; + } + + void* phys_mem = pmm_alloc_zero(page_count); + if (!phys_mem) { + serial_printf("PAGING ERROR: out of physical memory\n"); + return NULL; + } + + uintptr_t phys_start = pmm_virt_to_phys(phys_mem); + if (!paging_map_range(pagemap, virt_start, phys_start, page_count, flags)) { + pmm_free(phys_mem, page_count); + serial_printf("PAGING ERROR: failed to map region\n"); + return NULL; + } + + paging_region_t* region = ®ions[region_count++]; + region->virtual_start = virt_start; + region->virtual_end = virt_end; + region->physical_start = phys_start; + region->flags = flags; + region->page_count = page_count; + region->allocated = true; + + serial_printf("Paging: created region 0x%llx-0x%llx (%zu pages)\n", + virt_start, virt_end, page_count); + return region; +} + +bool paging_destroy_region(vmm_pagemap_t* pagemap, paging_region_t* region) { + if (!pagemap || !region || !region->allocated) + return false; + + paging_unmap_range(pagemap, region->virtual_start, region->page_count); + void* phys_virt = pmm_phys_to_virt(region->physical_start); + pmm_free(phys_virt, region->page_count); + memset(region, 0, sizeof(paging_region_t)); + return true; +} + +void* paging_alloc_pages(vmm_pagemap_t* pagemap, size_t page_count, + uint64_t flags, uintptr_t preferred_virt) { + if (!pagemap || page_count == 0) + return NULL; + + uintptr_t virt_start; + if (preferred_virt != 0) { + virt_start = align_up(preferred_virt, PAGE_SIZE); + if (!paging_is_range_free(pagemap, virt_start, virt_start + page_count * PAGE_SIZE)) + return NULL; + } else { + virt_start = next_alloc_virt; + while (!paging_is_range_free(pagemap, virt_start, virt_start + page_count * PAGE_SIZE)) + virt_start += PAGE_SIZE; + next_alloc_virt = virt_start + page_count * PAGE_SIZE; + } + + void* phys_mem = pmm_alloc_zero(page_count); + if (!phys_mem) + return NULL; + + uintptr_t phys_start = pmm_virt_to_phys(phys_mem); + if (!paging_map_range(pagemap, virt_start, phys_start, page_count, flags)) { + pmm_free(phys_mem, page_count); + return NULL; + } + + serial_printf("Paging: allocated %zu pages at virt 0x%llx\n", page_count, virt_start); + return (void*)virt_start; +} + +void paging_free_pages(vmm_pagemap_t* pagemap, void* virt_addr, size_t page_count) { + if (!pagemap || !virt_addr || page_count == 0) + return; + + uintptr_t virt_start = (uintptr_t)virt_addr; + paging_unmap_range(pagemap, virt_start, page_count); + serial_printf("Paging: freed %zu pages at virt 0x%llx\n", page_count, virt_start); +} + +void paging_print_stats(vmm_pagemap_t* pagemap) { + (void)pagemap; + + serial_printf("\n=== Paging Statistics ===\n"); + serial_printf("Regions: %zu, Reserved: %zu\n", region_count, reserved_count); + + size_t active = 0, total_pages = 0, total_bytes = 0; + for (size_t i = 0; i < region_count; i++) { + if (regions[i].allocated) { + active++; + total_pages += regions[i].page_count; + total_bytes += regions[i].page_count * PAGE_SIZE; + } + } + + serial_printf("Active regions: %zu\n", active); + serial_printf("Total pages: %zu (%zu KiB)\n", total_pages, total_bytes / 1024); + serial_printf("Next alloc: 0x%llx\n", next_alloc_virt); + + serial_printf("\nReserved ranges:\n"); + for (size_t i = 0; i < reserved_count; i++) { + serial_printf(" 0x%llx - 0x%llx\n", + reserved_ranges[i].start, reserved_ranges[i].end); + } +} + +void paging_dump_range(vmm_pagemap_t* pagemap, uintptr_t virt_start, uintptr_t virt_end) { + serial_printf("\n=== Page Table Dump (0x%llx - 0x%llx) ===\n", virt_start, virt_end); + + size_t mapped = 0, total = 0; + for (uintptr_t virt = virt_start; virt < virt_end; virt += PAGE_SIZE) { + total++; + uintptr_t phys; + uint64_t flags; + if (vmm_virt_to_phys(pagemap, virt, &phys) && vmm_get_page_flags(pagemap, virt, &flags)) { + mapped++; + serial_printf("0x%llx -> 0x%llx [", virt, phys); + if (flags & PAGING_PRESENT) serial_printf("P"); + if (flags & PAGING_WRITE) serial_printf("W"); + if (flags & PAGING_USER) serial_printf("U"); + if (flags & PAGING_NOEXEC) serial_printf("NX"); + serial_printf("]\n"); + } + } + serial_printf("Mapped: %zu/%zu pages (%zu%%)\n", mapped, total, total ? mapped * 100 / total : 0); +} \ No newline at end of file diff --git a/kernel/src/memory/pmm.c b/kernel/src/memory/pmm.c new file mode 100644 index 0000000..ca63675 --- /dev/null +++ b/kernel/src/memory/pmm.c @@ -0,0 +1,614 @@ +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include "../../include/sched/spinlock.h" +#include +#include + +static pmm_buddy_state_t g_buddy; +static spinlock_t g_pmm_lock = SPINLOCK_INIT; + +static inline uintptr_t _align_up(uintptr_t v, uintptr_t a) { + return (v + a - 1) & ~(a - 1); +} + +static inline int _pages_to_order(size_t pages) { + int order = 0; + size_t cap = 1; + while (cap < pages) { cap <<= 1; order++; } + return (order > PMM_MAX_ORDER) ? -1 : order; +} + +static inline uintptr_t _block_phys(pmm_block_t *b) { + return (uintptr_t)b - g_buddy.hhdm_offset; +} + +static inline pmm_block_t *_phys_to_block(uintptr_t phys) { + return (pmm_block_t *)(phys + g_buddy.hhdm_offset); +} + +static inline void _fl_init(pmm_free_list_t *fl) { + fl->head.next = fl->head.prev = &fl->head; + fl->head.order = -1; + fl->count = 0; +} + +static inline void _fl_push(pmm_free_list_t *fl, pmm_block_t *b, int order) { + b->order = order; + b->next = fl->head.next; + b->prev = &fl->head; + fl->head.next->prev = b; + fl->head.next = b; + fl->count++; +} + +static inline void _fl_del(pmm_free_list_t *fl, pmm_block_t *b) { + b->prev->next = b->next; + b->next->prev = b->prev; + b->next = b->prev = NULL; + b->order = -1; + fl->count--; +} + +static inline pmm_block_t *_fl_first(pmm_free_list_t *fl) { + return (fl->head.next == &fl->head) ? NULL : fl->head.next; +} + +static inline int _block_ptr_valid(pmm_free_list_t *fl, pmm_block_t *b) { + uintptr_t p = (uintptr_t)b; + if (p == (uintptr_t)&fl->head) return 1; + if (p < g_buddy.hhdm_offset) return 0; + uintptr_t phys = p - g_buddy.hhdm_offset; + if (phys < g_buddy.mem_start || phys >= g_buddy.mem_end) return 0; + if (phys & 0xFFFULL) return 0; + return 1; +} + +static pmm_block_t *_fl_find(pmm_free_list_t *fl, uintptr_t phys) { + size_t limit = g_buddy.total_pages + 16; + pmm_block_t *b = fl->head.next; + while (b != &fl->head) { + if (!_block_ptr_valid(fl, b)) { + serial_printf("[PMM_FREELIST_CORRUPT] bad ptr=%p in freelist walk\n", (void*)b); + return NULL; + } + if (_block_phys(b) == phys) return b; + pmm_block_t *next = b->next; + if (!_block_ptr_valid(fl, next)) { + serial_printf("[PMM_FREELIST_CORRUPT] bad next=%p at block=%p (phys=0x%llx)\n", + (void*)next, (void*)b, + (unsigned long long)_block_phys(b)); + return NULL; + } + b = next; + if (limit-- == 0) return NULL; + } + return NULL; +} + +static uintptr_t _buddy_alloc_order(int order) { + int found = -1; + for (int o = order; o <= PMM_MAX_ORDER; o++) + if (_fl_first(&g_buddy.orders[o])) { found = o; break; } + if (found < 0) return 0; + + pmm_block_t *b = _fl_first(&g_buddy.orders[found]); + _fl_del(&g_buddy.orders[found], b); + uintptr_t phys = _block_phys(b); + + while (found > order) { + found--; + uintptr_t buddy_phys = phys + ((uintptr_t)PAGE_SIZE << found); + _fl_push(&g_buddy.orders[found], _phys_to_block(buddy_phys), found); + } + + g_buddy.free_pages -= (size_t)1 << order; + return phys; +} + +static void _buddy_free_order(uintptr_t phys, int order) { + if (phys < PMM_FREE_MIN_PHYS) return; + while (order < PMM_MAX_ORDER) { + uintptr_t buddy_phys = phys ^ ((uintptr_t)PAGE_SIZE << order); + + if (buddy_phys < PMM_FREE_MIN_PHYS) break; + if (buddy_phys < g_buddy.mem_start || buddy_phys >= g_buddy.mem_end) + break; + + pmm_block_t *buddy_b = _fl_find(&g_buddy.orders[order], buddy_phys); + if (!buddy_b) break; + + if (buddy_b->order != order) break; + + _fl_del(&g_buddy.orders[order], buddy_b); + if (buddy_phys < phys) phys = buddy_phys; + order++; + } + _fl_push(&g_buddy.orders[order], _phys_to_block(phys), order); + g_buddy.free_pages += (size_t)1 << order; +} + +static void _buddy_free_nocoalesce(uintptr_t phys) { + _fl_push(&g_buddy.orders[0], _phys_to_block(phys), 0); + g_buddy.free_pages += 1; +} + +void pmm_init(struct limine_memmap_response *memmap, + struct limine_hhdm_response *hhdm) { + g_buddy.hhdm_offset = hhdm->offset; + g_buddy.free_pages = 0; + + uintptr_t max_phys = 0; + size_t usable_pages = 0; + for (uint64_t i = 0; i < memmap->entry_count; i++) { + struct limine_memmap_entry *e = memmap->entries[i]; + if (e->type == LIMINE_MEMMAP_USABLE) { + uintptr_t end = e->base + e->length; + if (end > max_phys) max_phys = end; + usable_pages += e->length / PAGE_SIZE; + } + } + max_phys = _align_up(max_phys, PAGE_SIZE); + + g_buddy.mem_start = 0; + g_buddy.mem_end = max_phys; + g_buddy.total_pages = max_phys >> PAGE_SHIFT; + g_buddy.usable_pages = usable_pages; + + for (int o = 0; o < PMM_MAX_ORDER_NR; o++) _fl_init(&g_buddy.orders[o]); + + for (uint64_t i = 0; i < memmap->entry_count; i++) { + struct limine_memmap_entry *e = memmap->entries[i]; + if (e->type != LIMINE_MEMMAP_USABLE) continue; + + uintptr_t base = _align_up(e->base, PAGE_SIZE); + uintptr_t end = (e->base + e->length) & ~(PAGE_SIZE - 1); + + if (base < PMM_FREE_MIN_PHYS) { + base = PMM_FREE_MIN_PHYS; + if (base >= end) continue; + } + + while (base < end) { + size_t rem = (end - base) >> PAGE_SHIFT; + int order = PMM_MAX_ORDER; + while (order > 0) { + size_t bpages = (size_t)1 << order; + if ((base & (bpages * PAGE_SIZE - 1)) == 0 && rem >= bpages) + break; + order--; + } + _fl_push(&g_buddy.orders[order], _phys_to_block(base), order); + g_buddy.free_pages += (size_t)1 << order; + base += (uintptr_t)(PAGE_SIZE << order); + } + } + + serial_printf("[PMM] buddy init: total=%zu free=%zu\n", + g_buddy.total_pages, g_buddy.free_pages); +} + +static volatile uint64_t g_pmm_alloc_count = 0; +static volatile uint64_t g_pmm_free_count = 0; + +void *pmm_alloc(size_t pages) { + if (!pages) return NULL; + int order = _pages_to_order(pages); + if (order < 0) return NULL; + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_pmm_lock); + uintptr_t phys = _buddy_alloc_order(order); + size_t free_after = g_buddy.free_pages; + spinlock_release(&g_pmm_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + if (phys) { + if (phys < PMM_FREE_MIN_PHYS) { + serial_printf("[PMM_BUG] alloc returned low phys=0x%llx — discarding (should not happen after pmm_init fix)\n", + (unsigned long long)phys); + phys = 0; + } else { + __atomic_fetch_add(&g_pmm_alloc_count, (size_t)1 << order, __ATOMIC_RELAXED); + } + } + if (!phys) { + serial_printf("[PMM_OOM] alloc FAILED pages=%zu order=%d free=%zu allocs=%llu frees=%llu\n", + pages, order, free_after, + (unsigned long long)g_pmm_alloc_count, + (unsigned long long)g_pmm_free_count); + } + return phys ? (void *)(phys + g_buddy.hhdm_offset) : NULL; +} + +void *pmm_alloc_zero(size_t pages) { + if (!pages) return NULL; + int order = _pages_to_order(pages); + if (order < 0) return NULL; + void *p = pmm_alloc(pages); + if (p) memset(p, 0, pages * PAGE_SIZE); + return p; +} + +void *pmm_alloc_aligned(size_t pages, size_t alignment) { + if (!pages) return NULL; + if (alignment < PAGE_SIZE) alignment = PAGE_SIZE; + + if (alignment & (alignment - 1)) { + size_t a = 1; + while (a < alignment) a <<= 1; + alignment = a; + } + + size_t align_pages = alignment / PAGE_SIZE; + size_t req = pages > align_pages ? pages : align_pages; + int order = _pages_to_order(req); + if (order < 0) return NULL; + + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_pmm_lock); + uintptr_t phys = _buddy_alloc_order(order); + if (!phys) { spinlock_release(&g_pmm_lock); asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); return NULL; } + + if (phys & (alignment - 1)) { + _buddy_free_order(phys, order); + spinlock_release(&g_pmm_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + return NULL; + } + spinlock_release(&g_pmm_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + + return (void *)(phys + g_buddy.hhdm_offset); +} + +void pmm_free(void *addr, size_t pages) { + if (!addr || !pages) return; + uintptr_t phys = (uintptr_t)addr - g_buddy.hhdm_offset; + if (phys < g_buddy.mem_start || phys >= g_buddy.mem_end) { + serial_printf("[PMM_FREE_BUG] out-of-range addr=%p phys=0x%llx pages=%zu\n", + addr, (unsigned long long)phys, pages); + return; + } + if (phys < 0x100000ULL) { + serial_printf("[PMM_FREE_BUG] low-phys addr=%p phys=0x%llx pages=%zu\n", + addr, (unsigned long long)phys, pages); + return; + } + int order = _pages_to_order(pages); + if (order < 0) { serial_printf("[PMM_FREE] bad order pages=%zu\n", pages); return; } + + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_pmm_lock); + + pmm_block_t *existing = _fl_find(&g_buddy.orders[order], phys); + if (existing) { + serial_printf("[PMM_DOUBLE_FREE] addr=%p phys=0x%llx pages=%zu order=%d ALREADY FREE!\n", + addr, (unsigned long long)phys, pages, order); + spinlock_release(&g_pmm_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + return; + } + + size_t free_before = g_buddy.free_pages; + memset(addr, 0, 64); + _buddy_free_order(phys, order); + size_t free_after = g_buddy.free_pages; + __atomic_fetch_add(&g_pmm_free_count, (size_t)1 << order, __ATOMIC_RELAXED); + + if (free_after <= free_before) { + serial_printf("[PMM_FREE_BUG] free_pages didn't grow: before=%zu after=%zu phys=0x%llx\n", + free_before, free_after, (unsigned long long)phys); + } + + spinlock_release(&g_pmm_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + +void pmm_free_single(void *addr) { + if (!addr) return; + uintptr_t phys = (uintptr_t)addr - g_buddy.hhdm_offset; + if (phys < g_buddy.mem_start || phys >= g_buddy.mem_end) return; + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_pmm_lock); + _buddy_free_nocoalesce(phys); + spinlock_release(&g_pmm_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + +void *pmm_phys_to_virt(uintptr_t phys) { return (void *)(phys + g_buddy.hhdm_offset); } +uintptr_t pmm_virt_to_phys(void *vaddr) { return (uintptr_t)vaddr - g_buddy.hhdm_offset; } +uint64_t pmm_get_hhdm_offset(void) { return g_buddy.hhdm_offset; } +size_t pmm_get_total_pages(void) { return g_buddy.total_pages; } +size_t pmm_get_usable_pages(void) { return g_buddy.usable_pages; } +size_t pmm_get_free_pages(void) { return g_buddy.free_pages; } +size_t pmm_get_used_pages(void) { return g_buddy.usable_pages > g_buddy.free_pages + ? g_buddy.usable_pages - g_buddy.free_pages : 0; } + +static void _print_size(size_t bytes, const char *label) { + uint64_t v = (uint64_t)bytes; + const char *unit; + uint64_t whole, frac; + + if (v >= 1024ULL * 1024 * 1024) { + whole = v / (1024ULL * 1024 * 1024); + frac = (v % (1024ULL * 1024 * 1024)) * 100 / (1024ULL * 1024 * 1024); + unit = "GiB"; + } else if (v >= 1024ULL * 1024) { + whole = v / (1024ULL * 1024); + frac = (v % (1024ULL * 1024)) * 100 / (1024ULL * 1024); + unit = "MiB"; + } else if (v >= 1024ULL) { + whole = v / 1024; + frac = (v % 1024) * 100 / 1024; + unit = "KiB"; + } else { + whole = v; frac = 0; unit = "B"; + } + printf(" %-16s %llu.%02llu %s\n", label, + (unsigned long long)whole, (unsigned long long)frac, unit); + serial_printf(" %-16s %llu.%02llu %s\n", label, + (unsigned long long)whole, (unsigned long long)frac, unit); +} + +void pmm_print_stats(void) { + size_t usable_bytes = g_buddy.usable_pages * PAGE_SIZE; + size_t free_bytes = g_buddy.free_pages * PAGE_SIZE; + size_t total_bytes = g_buddy.total_pages * PAGE_SIZE; + size_t used_bytes = usable_bytes > free_bytes ? usable_bytes - free_bytes : 0; + size_t reserved = total_bytes > usable_bytes ? total_bytes - usable_bytes : 0; + + serial_printf("\n=== Physical Memory ===\n"); + printf("\n=== Physical Memory ===\n"); + _print_size(usable_bytes, "Usable RAM:"); + _print_size(free_bytes, "Free RAM:"); + _print_size(used_bytes, "Used (kernel):"); + _print_size(reserved, "Reserved/MMIO:"); + serial_printf(" %-16s %u bytes\n", "Page size:", (unsigned)PAGE_SIZE); + printf(" %-16s %u bytes\n", "Page size:", (unsigned)PAGE_SIZE); + serial_printf("=======================\n"); + printf("=======================\n"); + + serial_printf("[PMM] buddy free-list:\n"); + for (int o = 0; o < PMM_MAX_ORDER_NR; o++) { + if (g_buddy.orders[o].count) + serial_printf(" order %2d (%4zu KiB): %zu blocks\n", + o, (PAGE_SIZE << o) / 1024, + g_buddy.orders[o].count); + } +} + +#define SLAB_PAGE_ALLOC(n) pmm_alloc_zero(n) +#define SLAB_PAGE_FREE(p, n) pmm_free(p, n) + +static const size_t g_size_classes[SLAB_NUM_CACHES] = { + 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 +}; + +slab_cache_t g_caches[SLAB_NUM_CACHES]; + +static inline uintptr_t _slab_obj_start(slab_t *s) { + return _align_up((uintptr_t)s + sizeof(slab_t), 8); +} + +static inline size_t _slab_pages(size_t obj_size) { + uintptr_t hdr_end = _align_up(sizeof(slab_t), 8); + if (obj_size <= PAGE_SIZE - hdr_end) + return 1; + if (obj_size <= PAGE_SIZE) + return 2; + return 0; +} + +static inline uint16_t _slab_capacity(size_t obj_size) { + size_t pages = _slab_pages(obj_size); + if (pages == 0) return 0; + uintptr_t obj_start = _align_up(sizeof(slab_t), 8); + size_t avail = pages * PAGE_SIZE - obj_start; + uint16_t cap = (uint16_t)(avail / obj_size); + return cap > 0 ? cap : 0; +} + +static void _slab_list_push(slab_t **head, slab_t *s) { + s->next = *head; s->prev = NULL; + if (*head) (*head)->prev = s; + *head = s; +} + +static void _slab_list_remove(slab_t **head, slab_t *s) { + if (s->prev) s->prev->next = s->next; + else *head = s->next; + if (s->next) s->next->prev = s->prev; + s->next = s->prev = NULL; +} + +static slab_t *_slab_new(slab_cache_t *cache) { + uint16_t cap = _slab_capacity(cache->obj_size); + if (!cap) return NULL; + + size_t pages = _slab_pages(cache->obj_size); + + slab_t *s = (slab_t *)SLAB_PAGE_ALLOC(pages); + if (!s) return NULL; + + s->obj_size = (uint16_t)cache->obj_size; + s->total = cap; + s->used = 0; + s->next = s->prev = NULL; + + uintptr_t start = _slab_obj_start(s); + s->freelist = (void *)start; + for (uint16_t i = 0; i < s->total; i++) { + void **slot = (void **)(start + (uintptr_t)i * cache->obj_size); + *slot = (i + 1 < s->total) + ? (void *)(start + (uintptr_t)(i + 1) * cache->obj_size) + : NULL; + } + return s; +} + +#define LARGE_ALLOC_MAGIC 0xDEADBEEFCAFEBABEULL + +typedef struct { + uint64_t magic; + uint64_t pages; +} large_hdr_t; + +static inline bool _is_large_alloc(void *ptr) { + large_hdr_t *hdr = (large_hdr_t *)ptr - 1; + return hdr->magic == LARGE_ALLOC_MAGIC; +} + +static slab_cache_t *_cache_for(size_t size) { + for (int i = 0; i < SLAB_NUM_CACHES; i++) + if (g_caches[i].obj_size >= size) return &g_caches[i]; + return NULL; +} + +void slab_init(void) { + for (int i = 0; i < SLAB_NUM_CACHES; i++) { + g_caches[i].obj_size = g_size_classes[i]; + g_caches[i].partial = NULL; + g_caches[i].full = NULL; + g_caches[i].total_allocs = 0; + g_caches[i].total_frees = 0; + } + serial_printf("[PMM] slab init: %d caches, sizes 8..4096 bytes\n", + SLAB_NUM_CACHES); +} + +static spinlock_t g_slab_lock = SPINLOCK_INIT; + +void *kmalloc(size_t size) { + if (!size) return NULL; + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_slab_lock); + + void *result; + if (size > SLAB_MAX_SIZE) { + size_t pages = (size + sizeof(large_hdr_t) + PAGE_SIZE - 1) / PAGE_SIZE; + spinlock_release(&g_slab_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + large_hdr_t *hdr = (large_hdr_t *)SLAB_PAGE_ALLOC(pages); + if (!hdr) return NULL; + hdr->magic = LARGE_ALLOC_MAGIC; + hdr->pages = (uint64_t)pages; + return (void *)(hdr + 1); + } + + slab_cache_t *cache = _cache_for(size); + if (!cache) { spinlock_release(&g_slab_lock); asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); return NULL; } + + if (!cache->partial) { + spinlock_release(&g_slab_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + slab_t *s = _slab_new(cache); + if (!s) return NULL; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_slab_lock); + _slab_list_push(&cache->partial, s); + } + slab_t *s = cache->partial; + + void *obj = s->freelist; + s->freelist = *(void **)obj; + s->used++; + cache->total_allocs++; + + if (s->used == s->total) { + _slab_list_remove(&cache->partial, s); + _slab_list_push(&cache->full, s); + } + result = obj; + spinlock_release(&g_slab_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + return result; +} + +void *kzalloc(size_t size) { + void *p = kmalloc(size); + if (p) memset(p, 0, size); + return p; +} + +void kfree(void *ptr) { + if (!ptr) return; + + if (_is_large_alloc(ptr)) { + large_hdr_t *hdr = (large_hdr_t *)ptr - 1; + size_t pages = (size_t)hdr->pages; + hdr->magic = 0; + SLAB_PAGE_FREE(hdr, pages); + return; + } + + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + spinlock_acquire(&g_slab_lock); + + slab_t *s = (slab_t *)((uintptr_t)ptr & ~((uintptr_t)PAGE_SIZE - 1)); + + slab_cache_t *cache = NULL; + for (int i = 0; i < SLAB_NUM_CACHES; i++) { + if (g_caches[i].obj_size == s->obj_size) { + cache = &g_caches[i]; + break; + } + } + if (!cache) { spinlock_release(&g_slab_lock); asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); return; } + + bool was_full = (s->used == s->total); + *(void **)ptr = s->freelist; + s->freelist = ptr; + s->used--; + cache->total_frees++; + + if (was_full) { + _slab_list_remove(&cache->full, s); + _slab_list_push(&cache->partial, s); + } + if (s->used == 0) { + _slab_list_remove(&cache->partial, s); + size_t pages = _slab_pages(s->obj_size); + spinlock_release(&g_slab_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); + SLAB_PAGE_FREE(s, pages > 0 ? pages : 1); + return; + } + spinlock_release(&g_slab_lock); + asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc"); +} + +void *krealloc(void *ptr, size_t new_size) { + if (!ptr) return kmalloc(new_size); + if (!new_size) { kfree(ptr); return NULL; } + + size_t old_size; + if (_is_large_alloc(ptr)) { + large_hdr_t *hdr = (large_hdr_t *)ptr - 1; + old_size = (size_t)hdr->pages * PAGE_SIZE - sizeof(large_hdr_t); + } else { + slab_t *s = (slab_t *)((uintptr_t)ptr & ~((uintptr_t)PAGE_SIZE - 1)); + old_size = s->obj_size; + } + + void *np = kmalloc(new_size); + if (!np) return NULL; + memcpy(np, ptr, old_size < new_size ? old_size : new_size); + kfree(ptr); + return np; +} + +void slab_print_stats(void) { + serial_printf("[PMM] slab stats:\n"); + for (int i = 0; i < SLAB_NUM_CACHES; i++) { + slab_cache_t *c = &g_caches[i]; + size_t np = 0, nf = 0; + for (slab_t *s = c->partial; s && np < 10000; s = s->next) np++; + for (slab_t *s = c->full; s && nf < 10000; s = s->next) nf++; + serial_printf(" [%4zu B] partial=%zu full=%zu allocs=%zu frees=%zu\n", + c->obj_size, np, nf, c->total_allocs, c->total_frees); + } +} \ No newline at end of file diff --git a/kernel/src/memory/vmm.c b/kernel/src/memory/vmm.c new file mode 100644 index 0000000..988429e --- /dev/null +++ b/kernel/src/memory/vmm.c @@ -0,0 +1,402 @@ +#include "../../include/memory/vmm.h" +#include "../../include/memory/pmm.h" +#include "../../include/smp/smp.h" +#include "../../include/apic/apic.h" +#include "../../include/io/serial.h" +#include +#include + +#define KERNEL_TEST_BASE 0xFFFF800000100000ULL +#define PTE_PHYS_MASK 0x000FFFFFFFFFF000ULL +#define MASK 0x1FF + +static vmm_pagemap_t kernel_pagemap; + +static inline void invlpg(void* addr) { + asm volatile ("invlpg (%0)" :: "r"(addr) : "memory"); +} + +static vmm_pte_t* alloc_table(void) { + void* page = pmm_alloc_zero(1); + if (!page) { + serial_printf("[VMM] alloc_table: OUT OF MEMORY (pmm_alloc_zero(1) returned NULL)!\n"); + serial_printf("[VMM] free_pages=%zu\n", (size_t)pmm_get_free_pages()); + for (;;) asm volatile ("hlt"); + } + return (vmm_pte_t*)page; +} + +static vmm_pte_t* get_table(vmm_pte_t* parent, size_t index, uint64_t flags) { + if (!(parent[index] & VMM_PRESENT)) { + vmm_pte_t* table = alloc_table(); + uintptr_t table_phys = pmm_virt_to_phys(table); + parent[index] = table_phys + | VMM_PRESENT + | VMM_WRITE + | (flags & VMM_USER); + } else if (flags & VMM_USER) { + parent[index] |= VMM_USER; + } + return (vmm_pte_t*)pmm_phys_to_virt(parent[index] & PTE_PHYS_MASK); +} + +bool vmm_map_page(vmm_pagemap_t* map, uintptr_t virt, uintptr_t phys, uint64_t flags) { + if (!map || !map->pml4) { + serial_printf("[VMM_BUG] vmm_map_page: map=%p pml4=%p virt=0x%llx\n", + (void*)map, map ? (void*)map->pml4 : NULL, + (unsigned long long)virt); + return false; + } + size_t pml4_i = (virt >> 39) & MASK; + size_t pdpt_i = (virt >> 30) & MASK; + size_t pd_i = (virt >> 21) & MASK; + size_t pt_i = (virt >> 12) & MASK; + vmm_pte_t* pdpt = get_table(map->pml4, pml4_i, flags); + vmm_pte_t* pd = get_table(pdpt, pdpt_i, flags); + vmm_pte_t* pt = get_table(pd, pd_i, flags); + pt[pt_i] = (phys & PTE_PHYS_MASK) | (flags | VMM_PRESENT); + + asm volatile ("lock addl $0, (%%rsp)" ::: "memory", "cc"); + invlpg((void*)virt); + asm volatile ("lock addl $0, (%%rsp)" ::: "memory", "cc"); + + return true; +} + +void vmm_unmap_page_noflush(vmm_pagemap_t* map, uintptr_t virt) { + size_t pml4_i = (virt >> 39) & MASK; + size_t pdpt_i = (virt >> 30) & MASK; + size_t pd_i = (virt >> 21) & MASK; + size_t pt_i = (virt >> 12) & MASK; + + if (!(map->pml4[pml4_i] & VMM_PRESENT)) return; + vmm_pte_t* pdpt = (vmm_pte_t*)pmm_phys_to_virt(map->pml4[pml4_i] & PTE_PHYS_MASK); + if (!(pdpt[pdpt_i] & VMM_PRESENT)) return; + vmm_pte_t* pd = (vmm_pte_t*)pmm_phys_to_virt(pdpt[pdpt_i] & PTE_PHYS_MASK); + if (!(pd[pd_i] & VMM_PRESENT)) return; + vmm_pte_t* pt = (vmm_pte_t*)pmm_phys_to_virt(pd[pd_i] & PTE_PHYS_MASK); + pt[pt_i] = 0; +} + +void vmm_unmap_page(vmm_pagemap_t* map, uintptr_t virt) { + size_t pml4_i = (virt >> 39) & MASK; + size_t pdpt_i = (virt >> 30) & MASK; + size_t pd_i = (virt >> 21) & MASK; + size_t pt_i = (virt >> 12) & MASK; + + if (!(map->pml4[pml4_i] & VMM_PRESENT)) return; + vmm_pte_t* pdpt = (vmm_pte_t*)pmm_phys_to_virt(map->pml4[pml4_i] & PTE_PHYS_MASK); + if (!(pdpt[pdpt_i] & VMM_PRESENT)) return; + vmm_pte_t* pd = (vmm_pte_t*)pmm_phys_to_virt(pdpt[pdpt_i] & PTE_PHYS_MASK); + if (!(pd[pd_i] & VMM_PRESENT)) return; + vmm_pte_t* pt = (vmm_pte_t*)pmm_phys_to_virt(pd[pd_i] & PTE_PHYS_MASK); + + if (!(pt[pt_i] & VMM_PRESENT)) return; + + uintptr_t phys = pt[pt_i] & PTE_PHYS_MASK; + + pt[pt_i] = 0; + asm volatile ("lock addl $0, (%%rsp)" ::: "memory", "cc"); + invlpg((void*)virt); + if (smp_get_cpu_count() > 1 && (virt >= 0xffff800000000000ULL)) { + ipi_tlb_shootdown_broadcast(&virt, 1); + } + + if (phys >= PMM_FREE_MIN_PHYS) { + pmm_free(pmm_phys_to_virt(phys), 1); + } +} + +vmm_pagemap_t* vmm_create_pagemap(void) { + vmm_pagemap_t* map = pmm_alloc_zero(1); + if (!map) { + serial_printf("[VMM] vmm_create_pagemap: pmm_alloc_zero for map failed\n"); + return NULL; + } + + map->pml4 = alloc_table(); + if (!map->pml4) { + serial_printf("[VMM] vmm_create_pagemap: alloc_table for pml4 failed\n"); + pmm_free(map, 1); + return NULL; + } + + for (size_t i = 256; i < 512; i++) { + map->pml4[i] = kernel_pagemap.pml4[i]; + } + + return map; +} + +void vmm_switch_pagemap(vmm_pagemap_t* map) { + uintptr_t phys = pmm_virt_to_phys(map->pml4); + asm volatile ("mov %0, %%cr3" :: "r"(phys) : "memory"); +} + +bool vmm_virt_to_phys(vmm_pagemap_t* map, uintptr_t virt, uintptr_t* phys_out) { + if (!map || !phys_out) { + serial_printf("VMM_VIRT_TO_PHYS ERROR: null parameters\n"); + return false; + } + + size_t pml4_i = (virt >> 39) & MASK; + size_t pdpt_i = (virt >> 30) & MASK; + size_t pd_i = (virt >> 21) & MASK; + size_t pt_i = (virt >> 12) & MASK; + + if (!(map->pml4[pml4_i] & VMM_PRESENT)) return false; + vmm_pte_t* pdpt = (vmm_pte_t*)pmm_phys_to_virt(map->pml4[pml4_i] & PTE_PHYS_MASK); + if (!(pdpt[pdpt_i] & VMM_PRESENT)) return false; + vmm_pte_t* pd = (vmm_pte_t*)pmm_phys_to_virt(pdpt[pdpt_i] & PTE_PHYS_MASK); + if (!(pd[pd_i] & VMM_PRESENT)) return false; + vmm_pte_t* pt = (vmm_pte_t*)pmm_phys_to_virt(pd[pd_i] & PTE_PHYS_MASK); + if (!(pt[pt_i] & VMM_PRESENT)) return false; + + *phys_out = (pt[pt_i] & PTE_PHYS_MASK) | (virt & 0xFFF); + return true; +} + +bool vmm_get_page_flags(vmm_pagemap_t* map, uintptr_t virt, uint64_t* flags_out) { + if (!map || !flags_out) return false; + + size_t pml4_i = (virt >> 39) & MASK; + size_t pdpt_i = (virt >> 30) & MASK; + size_t pd_i = (virt >> 21) & MASK; + size_t pt_i = (virt >> 12) & MASK; + + if (!(map->pml4[pml4_i] & VMM_PRESENT)) return false; + vmm_pte_t* pdpt = (vmm_pte_t*)pmm_phys_to_virt(map->pml4[pml4_i] & PTE_PHYS_MASK); + if (!(pdpt[pdpt_i] & VMM_PRESENT)) return false; + vmm_pte_t* pd = (vmm_pte_t*)pmm_phys_to_virt(pdpt[pdpt_i] & PTE_PHYS_MASK); + if (!(pd[pd_i] & VMM_PRESENT)) return false; + vmm_pte_t* pt = (vmm_pte_t*)pmm_phys_to_virt(pd[pd_i] & PTE_PHYS_MASK); + if (!(pt[pt_i] & VMM_PRESENT)) return false; + + *flags_out = pt[pt_i] & (0xFFF | (1ULL << 63)); + return true; +} + +vmm_pagemap_t* vmm_clone_pagemap(vmm_pagemap_t* src) { + if (!src) return NULL; + + vmm_pagemap_t* dst = pmm_alloc_zero(1); + if (!dst) return NULL; + dst->pml4 = alloc_table(); + + for (size_t i = 256; i < 512; i++) + dst->pml4[i] = kernel_pagemap.pml4[i]; + + for (size_t pml4_i = 0; pml4_i < 256; pml4_i++) { + if (!(src->pml4[pml4_i] & VMM_PRESENT)) continue; + + vmm_pte_t* src_pdpt = (vmm_pte_t*)pmm_phys_to_virt(src->pml4[pml4_i] & PTE_PHYS_MASK); + vmm_pte_t* dst_pdpt = alloc_table(); + dst->pml4[pml4_i] = pmm_virt_to_phys(dst_pdpt) | (src->pml4[pml4_i] & 0xFFF); + + for (size_t pdpt_i = 0; pdpt_i < 512; pdpt_i++) { + if (!(src_pdpt[pdpt_i] & VMM_PRESENT)) continue; + + vmm_pte_t* src_pd = (vmm_pte_t*)pmm_phys_to_virt(src_pdpt[pdpt_i] & PTE_PHYS_MASK); + vmm_pte_t* dst_pd = alloc_table(); + dst_pdpt[pdpt_i] = pmm_virt_to_phys(dst_pd) | (src_pdpt[pdpt_i] & 0xFFF); + + for (size_t pd_i = 0; pd_i < 512; pd_i++) { + if (!(src_pd[pd_i] & VMM_PRESENT)) continue; + if (src_pd[pd_i] & VMM_PSE) { + void* new_hp = pmm_alloc(512); + if (!new_hp) continue; + void* old_hp = pmm_phys_to_virt(src_pd[pd_i] & PTE_PHYS_MASK & ~0x1FFFFFULL); + memcpy(new_hp, old_hp, 512 * 0x1000); + uint64_t hp_flags = src_pd[pd_i] & ~(PTE_PHYS_MASK & ~0x1FFFFFULL); + dst_pd[pd_i] = (pmm_virt_to_phys(new_hp) & (PTE_PHYS_MASK & ~0x1FFFFFULL)) + | hp_flags; + continue; + } + + vmm_pte_t* src_pt = (vmm_pte_t*)pmm_phys_to_virt(src_pd[pd_i] & PTE_PHYS_MASK); + vmm_pte_t* dst_pt = alloc_table(); + dst_pd[pd_i] = pmm_virt_to_phys(dst_pt) | (src_pd[pd_i] & 0xFFF); + + for (size_t pt_i = 0; pt_i < 512; pt_i++) { + if (!(src_pt[pt_i] & VMM_PRESENT)) continue; + + uintptr_t src_phys = src_pt[pt_i] & PTE_PHYS_MASK; + if (src_phys < PMM_FREE_MIN_PHYS) continue; + + void* new_page = pmm_alloc_zero(1); + if (!new_page) continue; + void* old_page = pmm_phys_to_virt(src_phys); + memcpy(new_page, old_page, 0x1000); + dst_pt[pt_i] = pmm_virt_to_phys(new_page) + | (src_pt[pt_i] & (0xFFF | (1ULL << 63))); + } + } + } + } + + return dst; +} + +void vmm_free_pagemap(vmm_pagemap_t* map) +{ + if (!map || !map->pml4) return; + + for (size_t pml4_i = 0; pml4_i < 256; pml4_i++) { + if (!(map->pml4[pml4_i] & VMM_PRESENT)) continue; + + vmm_pte_t* pdpt = (vmm_pte_t*)pmm_phys_to_virt(map->pml4[pml4_i] & PTE_PHYS_MASK); + + for (size_t pdpt_i = 0; pdpt_i < 512; pdpt_i++) { + if (!(pdpt[pdpt_i] & VMM_PRESENT)) continue; + vmm_pte_t* pd = (vmm_pte_t*)pmm_phys_to_virt(pdpt[pdpt_i] & PTE_PHYS_MASK); + + for (size_t pd_i = 0; pd_i < 512; pd_i++) { + if (!(pd[pd_i] & VMM_PRESENT)) continue; + + if (pd[pd_i] & VMM_PSE) { + uintptr_t hp_phys = pd[pd_i] & PTE_PHYS_MASK & ~0x1FFFFFULL; + if (hp_phys >= PMM_FREE_MIN_PHYS) + pmm_free(pmm_phys_to_virt(hp_phys), 512); + continue; + } + + vmm_pte_t* pt = (vmm_pte_t*)pmm_phys_to_virt(pd[pd_i] & PTE_PHYS_MASK); + + for (size_t pt_i = 0; pt_i < 512; pt_i++) { + if (!(pt[pt_i] & VMM_PRESENT)) continue; + uintptr_t phys = pt[pt_i] & PTE_PHYS_MASK; + if (phys >= PMM_FREE_MIN_PHYS) + pmm_free(pmm_phys_to_virt(phys), 1); + } + + uintptr_t pt_phys = pd[pd_i] & PTE_PHYS_MASK; + if (pt_phys >= PMM_FREE_MIN_PHYS) + pmm_free(pt, 1); + } + + uintptr_t pd_phys = pdpt[pdpt_i] & PTE_PHYS_MASK; + if (pd_phys >= PMM_FREE_MIN_PHYS) + pmm_free(pd, 1); + } + + uintptr_t pdpt_phys = map->pml4[pml4_i] & PTE_PHYS_MASK; + if (pdpt_phys >= PMM_FREE_MIN_PHYS) + pmm_free(pdpt, 1); + } + + pmm_free(map->pml4, 1); + pmm_free(map, 1); +} +uintptr_t kernel_pml4_phys; + +void vmm_init(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r"(cr3)); + kernel_pagemap.pml4 = (vmm_pte_t*)pmm_phys_to_virt(cr3); + kernel_pml4_phys = cr3; + serial_printf("VMM: kernel pagemap initialized\n"); + serial_printf("VMM: kernel PML4 phys = 0x%llx\n", kernel_pml4_phys); +} + +vmm_pagemap_t* vmm_get_kernel_pagemap(void) { + return &kernel_pagemap; +} + +void vmm_sync_kernel_mappings(vmm_pagemap_t* map) { + if (!map) return; + for (size_t i = 256; i < 512; i++) { + map->pml4[i] = kernel_pagemap.pml4[i]; + } +} + +void vmm_test(void) { + serial_printf("\n--- VMM EXTENDED 64-BIT TEST ---\n"); + + void* phys1 = pmm_alloc_zero(1); + void* phys2 = pmm_alloc_zero(1); + + uintptr_t paddr1 = pmm_virt_to_phys(phys1); + uintptr_t paddr2 = pmm_virt_to_phys(phys2); + + uintptr_t vaddr1 = KERNEL_TEST_BASE; + uintptr_t vaddr2 = KERNEL_TEST_BASE + 0x1000; + + vmm_map_page(&kernel_pagemap, vaddr1, paddr1, VMM_WRITE); + vmm_map_page(&kernel_pagemap, vaddr2, paddr2, VMM_WRITE); + + uint64_t* ptr1 = (uint64_t*)vaddr1; + uint64_t* ptr2 = (uint64_t*)vaddr2; + + *ptr1 = 0xDEADBEEFCAFEBABE; + *ptr2 = 0xFEEDFACE12345678; + + vmm_pagemap_t* new_map = vmm_create_pagemap(); + + void* phys3 = pmm_alloc_zero(1); + uintptr_t paddr3 = pmm_virt_to_phys(phys3); + uintptr_t vaddr3 = KERNEL_TEST_BASE + 0x2000; + + vmm_map_page(new_map, vaddr3, paddr3, VMM_WRITE); + + uint64_t* ptr3 = (uint64_t*)vaddr3; + *ptr3 = 0xBAADF00DBAADF00D; + + vmm_switch_pagemap(new_map); + serial_printf("Value (new): 0x%llx\n", *ptr3); + + vmm_switch_pagemap(&kernel_pagemap); + serial_printf("Value (kernel): 0x%llx\n", *ptr1); + + serial_printf("--- VMM TEST DONE ---\n"); + + serial_printf("\n--- VMM TRANSLATION TEST ---\n"); + + void* phys_page = pmm_alloc_zero(1); + uintptr_t paddr = pmm_virt_to_phys(phys_page); + uintptr_t vaddr = KERNEL_TEST_BASE + 0x3000; + + vmm_map_page(&kernel_pagemap, vaddr, paddr, VMM_WRITE); + + uintptr_t translated_phys; + if (vmm_virt_to_phys(&kernel_pagemap, vaddr, &translated_phys)) { + serial_printf("Virt 0x%llx -> Phys 0x%llx\n", vaddr, translated_phys); + serial_printf("Original phys: 0x%llx\n", paddr); + serial_printf("Match: %s\n", translated_phys == paddr ? "YES" : "NO"); + } else { + serial_printf("Translation failed!\n"); + } + + uint64_t flags; + if (vmm_get_page_flags(&kernel_pagemap, vaddr, &flags)) { + serial_printf("Page flags: 0x%llx\n", flags); + serial_printf("Present: %s\n", (flags & VMM_PRESENT) ? "YES" : "NO"); + serial_printf("Writable: %s\n", (flags & VMM_WRITE) ? "YES" : "NO"); + } + + uint64_t hhdm = pmm_get_hhdm_offset(); + serial_printf("HHDM offset: 0x%llx\n", hhdm); + + serial_printf("\n--- Memory statistics after tests ---\n"); + size_t free_before = pmm_get_free_pages(); + + serial_printf("\n--- Memory free test ---\n"); + void* test_alloc = pmm_alloc_aligned(2, 4096); + if (test_alloc) { + size_t free_after_alloc = pmm_get_free_pages(); + serial_printf("Allocated 2 pages. Free pages: %zu -> %zu\n", + free_before, free_after_alloc); + + pmm_free(test_alloc, 2); + size_t free_after_free = pmm_get_free_pages(); + serial_printf("Freed 2 pages. Free pages: %zu -> %zu\n", + free_after_alloc, free_after_free); + + if (free_after_free == free_before) { + serial_printf("Memory free test PASSED\n"); + } else { + serial_printf("Memory free test FAILED (possible leak)\n"); + } + } + + serial_printf("--- VMM TRANSLATION TEST DONE ---\n"); +} \ No newline at end of file diff --git a/kernel/src/panic/panic.c b/kernel/src/panic/panic.c new file mode 100644 index 0000000..dc0d6ed --- /dev/null +++ b/kernel/src/panic/panic.c @@ -0,0 +1,211 @@ +#include "../../include/panic/panic.h" +#include "../../include/io/serial.h" +#include "../../include/graphics/fb/fb.h" +#include "../../include/apic/apic.h" +#include "../../include/smp/smp.h" +#include "../../include/smp/percpu.h" +#include "../../include/sched/sched.h" +#include +#include + +extern struct limine_framebuffer *global_framebuffer; + +#define COL_BG 0x000080 +#define COL_WHITE 0xFFFFFF +#define COL_YELLOW 0xFFFF00 +#define COL_RED 0xFF4444 +#define COL_CYAN 0x00FFFF +#define COL_GRAY 0xAAAAAA + +static uint32_t fb_x = 0, fb_y = 0; +static struct limine_framebuffer *g_fb = NULL; + +static void fb_nl(void) { + fb_x = 0; + fb_y += 18; +} + +static void fb_puts_col(const char *s, uint32_t col) { + if (!g_fb) return; + while (*s) { + if (*s == '\n') { fb_nl(); s++; continue; } + fb_draw_char(g_fb, *s, fb_x, fb_y, col); + fb_x += 9; + if (fb_x + 9 > (uint32_t)g_fb->width) fb_nl(); + s++; + } +} + +static void fb_puts(const char *s) { fb_puts_col(s, COL_WHITE); } + +static void fb_puthex(uint64_t v) { + static const char h[] = "0123456789ABCDEF"; + char full[19] = "0x"; + for (int i = 0; i < 16; i++) + full[2 + i] = h[(v >> (60 - i * 4)) & 0xF]; + full[18] = '\0'; + fb_puts_col(full, COL_CYAN); +} + +static void fb_putdec(uint64_t v) { + char buf[22]; int i = 20; buf[21] = '\0'; + if (v == 0) { fb_puts_col("0", COL_CYAN); return; } + while (v && i >= 0) { buf[i--] = '0' + (v % 10); v /= 10; } + fb_puts_col(buf + i + 1, COL_CYAN); +} + +volatile int g_panic_owner = 0; + +static int panic_try_own(void) { + int expected = 0; + return __atomic_compare_exchange_n(&g_panic_owner, &expected, 1, + 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED); +} + +static void halt_other_cpus(void) { + asm volatile("cli"); + lapic_send_nmi_to_all_but_self(); + for (volatile int i = 0; i < 200000; i++) + asm volatile("pause"); +} + +static void draw_panic_screen(const char *msg, struct int_frame_t *regs) { + g_fb = global_framebuffer; + if (!g_fb) return; + + uint32_t W = (uint32_t)g_fb->width; + uint32_t H = (uint32_t)g_fb->height; + + fb_fill_rect(g_fb, 0, 0, W, H, COL_BG); + fb_fill_rect(g_fb, 0, 0, W, 4, COL_WHITE); + fb_fill_rect(g_fb, 0, H - 4, W, 4, COL_WHITE); + + fb_x = 16; fb_y = 16; + + fb_puts_col("*** KERNEL PANIC ***\n", COL_YELLOW); + fb_nl(); + fb_puts_col("Cervus OS has encountered a fatal error and cannot continue.\n", COL_WHITE); + fb_nl(); + + fb_puts_col("Reason: ", COL_GRAY); + fb_puts_col(msg, COL_YELLOW); + fb_nl(); fb_nl(); + + if (regs) { + percpu_t *pc = get_percpu(); + task_t *t = pc ? (task_t *)pc->current_task : NULL; + uint32_t cpu = lapic_get_id(); + if (!t) t = current_task[cpu]; + + fb_puts_col("CPU: ", COL_GRAY); fb_putdec(cpu); fb_puts("\n"); + + if (t) { + fb_puts_col("Task: ", COL_GRAY); + fb_puts_col(t->name, COL_CYAN); + fb_puts(" PID: "); fb_putdec(t->pid); + fb_puts("\n"); + } + fb_nl(); + + fb_puts_col(" Register Dump \n", COL_YELLOW); + + fb_puts_col("RIP: ", COL_GRAY); fb_puthex(regs->rip); + fb_puts(" CS: "); fb_puthex(regs->cs); fb_puts("\n"); + + fb_puts_col("RSP: ", COL_GRAY); fb_puthex(regs->rsp); + fb_puts(" RFL: "); fb_puthex(regs->rflags); fb_puts("\n"); + + fb_puts_col("RAX: ", COL_GRAY); fb_puthex(regs->rax); + fb_puts(" RBX: "); fb_puthex(regs->rbx); fb_puts("\n"); + + fb_puts_col("RCX: ", COL_GRAY); fb_puthex(regs->rcx); + fb_puts(" RDX: "); fb_puthex(regs->rdx); fb_puts("\n"); + + fb_puts_col("RSI: ", COL_GRAY); fb_puthex(regs->rsi); + fb_puts(" RDI: "); fb_puthex(regs->rdi); fb_puts("\n"); + + fb_puts_col("RBP: ", COL_GRAY); fb_puthex(regs->rbp); fb_puts("\n"); + + fb_puts_col("R8: ", COL_GRAY); fb_puthex(regs->r8); + fb_puts(" R9: "); fb_puthex(regs->r9); fb_puts("\n"); + + fb_puts_col("R10: ", COL_GRAY); fb_puthex(regs->r10); + fb_puts(" R11: "); fb_puthex(regs->r11); fb_puts("\n"); + + fb_puts_col("R12: ", COL_GRAY); fb_puthex(regs->r12); + fb_puts(" R13: "); fb_puthex(regs->r13); fb_puts("\n"); + + fb_puts_col("R14: ", COL_GRAY); fb_puthex(regs->r14); + fb_puts(" R15: "); fb_puthex(regs->r15); fb_puts("\n"); + fb_nl(); + + uint64_t cr2 = 0; + asm volatile("mov %%cr2, %0" : "=r"(cr2)); + fb_puts_col("CR2: ", COL_GRAY); fb_puthex(cr2); fb_puts("\n"); + + fb_puts_col("ERR: ", COL_GRAY); fb_puthex(regs->error); + fb_puts(" INT: "); fb_putdec(regs->interrupt); fb_puts("\n"); + } + + fb_nl(); + fb_puts_col("System halted. Check serial output for details.\n", COL_GRAY); +} + +static void serial_panic_dump(const char *msg, struct int_frame_t *regs) { + serial_printf("\n"); + serial_printf(" KERNEL PANIC \n"); + serial_printf("Reason: %s\n", msg); + + uint32_t cpu = lapic_get_id(); + serial_printf("CPU: %u\n", cpu); + + percpu_t *pc = get_percpu(); + task_t *t = pc ? (task_t *)pc->current_task : NULL; + if (!t) t = current_task[cpu]; + if (t) { + serial_printf("Task: %s PID=%u PPID=%u\n", + t->name, t->pid, t->ppid); + } + + if (regs) { + uint64_t cr2 = 0; + asm volatile("mov %%cr2, %0" : "=r"(cr2)); + serial_printf("\nRegisters:\n"); + serial_printf(" RIP=0x%016llx CS=0x%llx\n", regs->rip, regs->cs); + serial_printf(" RSP=0x%016llx SS=0x%llx\n", regs->rsp, regs->ss); + serial_printf(" RFLAGS=0x%016llx\n", regs->rflags); + serial_printf(" RAX=0x%016llx RBX=0x%016llx\n", regs->rax, regs->rbx); + serial_printf(" RCX=0x%016llx RDX=0x%016llx\n", regs->rcx, regs->rdx); + serial_printf(" RSI=0x%016llx RDI=0x%016llx\n", regs->rsi, regs->rdi); + serial_printf(" RBP=0x%016llx\n", regs->rbp); + serial_printf(" R8 =0x%016llx R9 =0x%016llx\n", regs->r8, regs->r9); + serial_printf(" R10=0x%016llx R11=0x%016llx\n", regs->r10, regs->r11); + serial_printf(" R12=0x%016llx R13=0x%016llx\n", regs->r12, regs->r13); + serial_printf(" R14=0x%016llx R15=0x%016llx\n", regs->r14, regs->r15); + serial_printf(" CR2=0x%016llx\n", cr2); + serial_printf(" ERR=0x%016llx INT=%llu\n", regs->error, regs->interrupt); + } + serial_printf("========================================\n"); + serial_printf("System halted.\n\n"); +} + +__attribute__((noreturn)) +void kernel_panic(const char *msg) { + kernel_panic_regs(msg, NULL); +} + +__attribute__((noreturn)) +void kernel_panic_regs(const char *msg, struct int_frame_t *regs) { + asm volatile("cli"); + + if (!panic_try_own()) { + for (;;) asm volatile("cli; hlt"); + } + + serial_force_unlock(); + halt_other_cpus(); + serial_panic_dump(msg, regs); + draw_panic_screen(msg, regs); + + for (;;) asm volatile("cli; hlt"); +} \ No newline at end of file diff --git a/kernel/src/sched/sched.c b/kernel/src/sched/sched.c new file mode 100644 index 0000000..8a3f3f4 --- /dev/null +++ b/kernel/src/sched/sched.c @@ -0,0 +1,669 @@ +#include "../include/sched/capabilities.h" +#include "../include/sched/sched.h" +#include "../include/sched/spinlock.h" +#include "../include/memory/pmm.h" +#include "../include/memory/vmm.h" +#include "../include/io/serial.h" +#include "../include/smp/smp.h" +#include "../include/smp/percpu.h" +#include "../include/apic/apic.h" +#include "../include/gdt/gdt.h" +#include "../include/fs/vfs.h" +#include "../include/panic/panic.h" +#include +#include + +#define KERNEL_STACK_PAGES (KERNEL_STACK_SIZE / 0x1000) +#define MAX_PIDS 4096 + +task_t* ready_queues[MAX_PRIORITY + 1] = {0}; +task_t* current_task[MAX_CPUS] = {0}; + +static task_t idle_tasks[MAX_CPUS]; +static task_t bootstrap_tasks[MAX_CPUS]; +static volatile uint64_t reschedule_calls = 0; +static spinlock_t ready_queue_lock = SPINLOCK_INIT; +static task_t* pid_table[MAX_PIDS] = {0}; +static uint32_t next_pid = 1; +static spinlock_t pid_lock = SPINLOCK_INIT; + +spinlock_t children_lock = SPINLOCK_INIT; + +extern tss_t* tss[MAX_CPUS]; + +static inline void fix_gs_base(percpu_t* pc) { + uint64_t val = (uint64_t)pc; + asm volatile("wrmsr" + :: "c"(0xC0000101U), + "a"((uint32_t)val), + "d"((uint32_t)(val >> 32))); +} + +uint32_t task_alloc_pid(void) { + uint64_t _irqf; + _irqf = spinlock_acquire_irqsave(&pid_lock); + uint32_t found = 0; + for (uint32_t attempt = 0; attempt < MAX_PIDS - 1; attempt++) { + uint32_t i = next_pid; + if (i == 0 || i >= MAX_PIDS) { + next_pid = 1; + i = 1; + } + next_pid = (next_pid + 1 >= MAX_PIDS) ? 1 : next_pid + 1; + if (!pid_table[i]) { found = i; break; } + } + if (!found) { + serial_printf("[PID] FATAL: pid table exhausted (MAX_PIDS=%u)!\n", MAX_PIDS); + } + spinlock_release_irqrestore(&pid_lock, _irqf); + return found; +} + +task_t* task_find_by_pid(uint32_t pid) { + if (pid == 0 || pid >= MAX_PIDS) return NULL; + return pid_table[pid]; +} + +static void pid_register(task_t* t) { + if (t->pid && t->pid < MAX_PIDS) pid_table[t->pid] = t; +} + +static void pid_unregister(task_t* t) { + if (t->pid && t->pid < MAX_PIDS) pid_table[t->pid] = NULL; +} + +static void idle_loop(void* arg); + +#define STACK_CANARY_VALUE 0xDEADC0DEDEADC0DEULL + +static uint64_t alloc_and_init_stack(task_t* t) { + uintptr_t stack_virt = (uintptr_t)pmm_alloc_zero(KERNEL_STACK_PAGES); + if (!stack_virt) return 0; + t->stack_base = stack_virt; + + uint64_t* canary_page = (uint64_t*)stack_virt; + for (size_t i = 0; i < PAGE_SIZE / sizeof(uint64_t); i++) + canary_page[i] = STACK_CANARY_VALUE; + + uintptr_t stack_top = (stack_virt + KERNEL_STACK_SIZE) & ~0xFULL; + uint64_t* sp = (uint64_t*)stack_top; + + if (t->flags & TASK_FLAG_FORK) { + extern void task_trampoline_fork(void); + *--sp = (uint64_t)task_trampoline_fork; + } else if (t->is_userspace) { + extern void task_trampoline_user(void); + *--sp = (uint64_t)task_trampoline_user; + } else { + extern void task_trampoline(void); + *--sp = (uint64_t)task_trampoline; + } + + *--sp = (uint64_t)t; + *--sp = 0; + *--sp = 0; + *--sp = 0; + *--sp = 0; + *--sp = 0; + + return (uint64_t)sp; +} + +static void enqueue_global(task_t* t) { + uint64_t f = spinlock_acquire_irqsave(&ready_queue_lock); + t->next = ready_queues[t->priority]; + ready_queues[t->priority] = t; + spinlock_release_irqrestore(&ready_queue_lock, f); +} + +void __attribute__((used)) ctx_rsp_corruption_detected(task_t* old, uint64_t saved_rsp) { + serial_printf("[CTX-CORRUPT] pid=%u rsp=0x%llx saved but INVALID (stack=0x%llx..0x%llx)!\n", + old ? old->pid : 0, + saved_rsp, + old ? old->stack_base : 0, + old ? (old->stack_base + KERNEL_STACK_SIZE) : 0); + kernel_panic("context_switch: saved invalid RSP into task->rsp"); +} + +static void process_deferred_free(void) { + percpu_t* pc = get_percpu(); + if (!pc) return; + task_t* dead = (task_t*)pc->deferred_free_task; + if (!dead) return; + pc->deferred_free_task = NULL; + task_destroy(dead); +} + +void sched_init(void) { + memset(pid_table, 0, sizeof(pid_table)); + memset(bootstrap_tasks, 0, sizeof(bootstrap_tasks)); + next_pid = 1; + for (uint32_t i = 0; i < smp_get_cpu_count(); i++) { + task_t* idle = &idle_tasks[i]; + memset(idle, 0, sizeof(task_t)); + idle->priority = 0; + idle->runnable = true; + idle->state = TASK_READY; + idle->cpu_id = i; + idle->last_cpu = i; + idle->time_slice = 1; + idle->time_slice_init = 1; + idle->entry = idle_loop; + idle->arg = NULL; + idle->is_userspace = TASK_TYPE_KERNEL; + idle->pid = 0; + idle->uid = UID_ROOT; + idle->gid = GID_ROOT; + idle->capabilities = CAP_ALL; + atomic_init_bool(&idle->on_cpu, false); + idle->name[0]='i'; idle->name[1]='d'; + idle->name[2]='l'; idle->name[3]='e'; + idle->rsp = alloc_and_init_stack(idle); + if (!idle->rsp) { + kernel_panic("SCHED: failed to allocate idle stack"); + } + current_task[i] = NULL; + } + serial_writestring("Scheduler initialized (PREEMPTIVE SMP MODE)\n"); +} + +task_t* task_create(const char* name, void (*entry)(void*), void* arg, int priority) { + task_t* t = calloc(1, sizeof(task_t)); + if (!t) return NULL; + t->pid = task_alloc_pid(); + if (!t->pid) { free(t); return NULL; } + t->ppid = 0; + t->uid = UID_ROOT; + t->gid = GID_ROOT; + t->capabilities = CAP_ALL; + t->entry = entry; + t->arg = arg; + t->priority = priority > MAX_PRIORITY ? MAX_PRIORITY : priority; + t->runnable = true; + t->state = TASK_READY; + t->cpu_id = (uint32_t)-1; + t->time_slice = TASK_DEFAULT_TIMESLICE; + t->time_slice_init = TASK_DEFAULT_TIMESLICE; + t->rip = (uint64_t)entry; + t->is_userspace = TASK_TYPE_KERNEL; + atomic_init_bool(&t->on_cpu, false); + strncpy(t->name, name, sizeof(t->name) - 1); + t->rsp = alloc_and_init_stack(t); + if (!t->rsp) { free(t); return NULL; } + t->fpu_state = (fpu_state_t*)pmm_alloc_zero(1); + pid_register(t); + enqueue_global(t); + serial_printf("[SCHED] task_create: '%s' pid=%u prio=%d\n", t->name, t->pid, t->priority); + return t; +} + +task_t* task_create_user(const char* name, uintptr_t entry, uintptr_t user_rsp, uint64_t cr3, int priority, vmm_pagemap_t* pagemap, uint32_t uid, uint32_t gid) { + task_t* t = calloc(1, sizeof(task_t)); + if (!t) return NULL; + t->pid = task_alloc_pid(); + if (!t->pid) { free(t); return NULL; } + t->ppid = 0; + t->uid = uid; + t->gid = gid; + t->capabilities = cap_initial(uid); + t->entry = (void (*)(void*))entry; + t->arg = NULL; + t->priority = priority > MAX_PRIORITY ? MAX_PRIORITY : priority; + t->runnable = true; + t->state = TASK_READY; + t->cpu_id = (uint32_t)-1; + t->time_slice = TASK_DEFAULT_TIMESLICE; + t->time_slice_init = TASK_DEFAULT_TIMESLICE; + t->rip = entry; + t->is_userspace = TASK_TYPE_USER; + t->user_rsp = user_rsp; + t->cr3 = cr3; + t->pagemap = pagemap; + t->brk_start = 0; + t->brk_current = 0; + t->brk_max = 0x0000700000000000ULL; + atomic_init_bool(&t->on_cpu, false); + strncpy(t->name, name, sizeof(t->name) - 1); + t->rsp = alloc_and_init_stack(t); + if (!t->rsp) { free(t); return NULL; } + t->fpu_state = (fpu_state_t*)pmm_alloc_zero(1); + + t->fd_table = fd_table_create(); + if (t->fd_table) { + int stdio_ret = vfs_init_stdio(t); + if (stdio_ret < 0) + serial_printf("[SCHED] task_create_user: vfs_init_stdio failed: %d\n", + stdio_ret); + } + + t->flags |= TASK_FLAG_OWN_PAGEMAP; + + pid_register(t); + enqueue_global(t); + serial_printf("[SCHED] task_create_user: '%s' pid=%u uid=%u entry=0x%llx user_rsp=0x%llx caps=0x%llx\n", + t->name, t->pid, t->uid, entry, user_rsp, t->capabilities); + return t; +} + +task_t* task_fork(task_t* parent) { + if (!parent) return NULL; + task_t* child = calloc(1, sizeof(task_t)); + if (!child) return NULL; + child->pid = task_alloc_pid(); + if (!child->pid) { free(child); return NULL; } + child->ppid = parent->pid; + child->priority = parent->priority; + child->is_userspace = parent->is_userspace; + strncpy(child->name, parent->name, sizeof(child->name)-1); + child->uid = parent->uid; + child->gid = parent->gid; + child->capabilities = parent->capabilities; + child->time_slice = parent->time_slice_init; + child->time_slice_init = parent->time_slice_init; + child->pagemap = vmm_clone_pagemap(parent->pagemap); + if (!child->pagemap) { free(child); return NULL; } + child->cr3 = (uint64_t)pmm_virt_to_phys(child->pagemap->pml4); + child->brk_start = parent->brk_start; + child->brk_current = parent->brk_current; + child->brk_max = parent->brk_max; + child->user_rsp = parent->user_rsp; + serial_printf("[FORK-DBG2] parent pid=%u user_rsp=0x%llx user_saved_rip=0x%llx\n", + parent->pid, parent->user_rsp, parent->user_saved_rip); + + child->user_saved_rip = parent->user_saved_rip; + child->user_saved_rbp = parent->user_saved_rbp; + child->user_saved_rbx = parent->user_saved_rbx; + child->user_saved_r12 = parent->user_saved_r12; + child->user_saved_r13 = parent->user_saved_r13; + child->user_saved_r14 = parent->user_saved_r14; + child->user_saved_r15 = parent->user_saved_r15; + child->user_saved_r11 = parent->user_saved_r11 | (1ULL << 9); + + child->flags |= TASK_FLAG_FORK; + atomic_init_bool(&child->on_cpu, false); + child->rsp = alloc_and_init_stack(child); + if (!child->rsp) { + vmm_free_pagemap(child->pagemap); + free(child); + return NULL; + } + vmm_sync_kernel_mappings(child->pagemap); + serial_printf("[FORK-DBG] child pid=%u stack_base=0x%llx rsp=0x%llx\n", + child->pid, child->stack_base, child->rsp); + child->fpu_state = (fpu_state_t*)pmm_alloc_zero(1); + if (child->fpu_state && parent->fpu_state) { + memcpy(child->fpu_state, parent->fpu_state, sizeof(fpu_state_t)); + child->fpu_used = parent->fpu_used; + } + + if (parent->fd_table) + child->fd_table = fd_table_clone(parent->fd_table); + + child->state = TASK_READY; + child->runnable = true; + child->parent = parent; + + { + uint64_t _cf = spinlock_acquire_irqsave(&children_lock); + child->sibling = parent->children; + parent->children = child; + spinlock_release_irqrestore(&children_lock, _cf); + } + + pid_register(child); + + serial_printf("[FORK-CHK] child=%p rsp=0x%llx stk=0x%llx rip=0x%llx\n", + (void*)child, child->rsp, child->stack_base, child->user_saved_rip); + + serial_printf("[SCHED] fork: parent='%s' pid=%u -> child pid=%u rip=0x%llx\n", + parent->name, parent->pid, child->pid, child->user_saved_rip); + + enqueue_global(child); + return child; +} + +void task_destroy(task_t* task) { + if (!task) return; + + uint32_t old_flags = __atomic_fetch_or(&task->flags, TASK_FLAG_DESTROYED, __ATOMIC_ACQ_REL); + if (old_flags & TASK_FLAG_DESTROYED) return; + + serial_printf("[DESTROY] pid=%u flags=0x%x on_cpu=%d\n", + task->pid, task->flags, (int)atomic_load_bool_acq(&task->on_cpu)); + + pid_unregister(task); + + if (task->fpu_state) { + pmm_free(task->fpu_state, 1); + task->fpu_state = NULL; + } + + if (task->stack_base) { + pmm_free((void*)task->stack_base, KERNEL_STACK_PAGES); + task->stack_base = 0; + } + + if (task->pagemap && (task->flags & (TASK_FLAG_FORK | TASK_FLAG_OWN_PAGEMAP))) { + vmm_free_pagemap(task->pagemap); + task->pagemap = NULL; + } + + if (task->fd_table) { + fd_table_destroy(task->fd_table); + task->fd_table = NULL; + } + + free(task); +} + +void task_reparent(task_t* child, task_t* new_parent) { + if (!child || !new_parent) return; + child->parent = new_parent; + child->ppid = new_parent->pid; + child->sibling = new_parent->children; + new_parent->children = child; +} + +void task_wakeup_waiters(uint32_t pid) { + task_t* to_wake[64]; + int wake_count = 0; + + uint64_t _irqf = spinlock_acquire_irqsave(&pid_lock); + for (uint32_t i = 1; i < MAX_PIDS && wake_count < 64; i++) { + task_t* t = pid_table[i]; + if (!t) continue; + if (t->state != TASK_BLOCKED) continue; + if (t->wait_for_pid != pid && t->wait_for_pid != (uint32_t)-1) continue; + + serial_printf("[SCHED] wakeup_waiters: waking pid=%u (waited for pid=%u)\n", t->pid, pid); + + t->wait_for_pid = 0; + t->runnable = true; + t->state = TASK_READY; + + to_wake[wake_count++] = t; + } + spinlock_release_irqrestore(&pid_lock, _irqf); + + for (int i = 0; i < wake_count; i++) + enqueue_global(to_wake[i]); +} + +__attribute__((noreturn)) void task_exit(void) +{ + asm volatile("cli"); + uint32_t cpu = lapic_get_id(); + percpu_t* pc = get_percpu(); + task_t* me = pc ? (task_t*)pc->current_task : current_task[cpu]; + + if (!me) kernel_panic("task_exit: no current task"); + + serial_printf("[EXIT] task_exit called cpu=%u me=%p pid=%u\n", cpu, (void*)me, me->pid); + + task_t* init = task_find_by_pid(1); + if (init && init != me) { + uint64_t _cf = spinlock_acquire_irqsave(&children_lock); + task_t* child = me->children; + me->children = NULL; + while (child) { + task_t* sib = child->sibling; + task_reparent(child, init); + child = sib; + } + spinlock_release_irqrestore(&children_lock, _cf); + } + + vmm_switch_pagemap(vmm_get_kernel_pagemap()); + + me->runnable = false; + me->state = TASK_ZOMBIE; + me->cr3 = 0; + + task_wakeup_waiters(me->pid); + + sched_reschedule(); + + kernel_panic("task_exit: returned from sched_reschedule (should never happen)"); +} + +void task_kill(task_t* target) { + if (!target) return; + + if (target->pid == 0) return; + if (target->is_userspace == TASK_TYPE_KERNEL) return; + + if (target->state == TASK_ZOMBIE || target->state == TASK_DEAD) return; + if (target->pending_kill) return; + + serial_printf("[KILL] task_kill pid=%u state=%d cpu=%u\n", + target->pid, (int)target->state, lapic_get_id()); + target->exit_code = 130; + target->pending_kill = true; + + if (target->state == TASK_BLOCKED) { + target->wakeup_time_ns = 0; + task_unblock(target); + } + + if (!(target->flags & TASK_FLAG_STARTED)) { + return; + } + + for (uint32_t cpu = 0; cpu < smp_get_cpu_count(); cpu++) { + if (current_task[cpu] == target) { + extern uint32_t smp_get_lapic_id_for_cpu(uint32_t); + ipi_reschedule_cpu(smp_get_lapic_id_for_cpu(cpu)); + } + } +} + +volatile uint32_t g_foreground_pid = 0; + +void task_set_foreground(uint32_t pid) { + g_foreground_pid = pid; +} + +task_t* task_find_foreground(void) { + uint32_t fpid = g_foreground_pid; + if (fpid == 0) return NULL; + task_t *t = task_find_by_pid(fpid); + if (!t || t->state == TASK_ZOMBIE || t->state == TASK_DEAD) { + g_foreground_pid = 0; + return NULL; + } + return t; +} + +static task_t* sched_pick_next(uint32_t cpu) { + uint64_t _irqf; + + _irqf = spinlock_acquire_irqsave(&ready_queue_lock); + task_t* found = NULL; + for (int p = MAX_PRIORITY; p >= 0 && !found; p--) { + task_t** head = &ready_queues[p]; + task_t* t = *head; + while (t) { + if (t->runnable && t->state != TASK_ZOMBIE && t->state != TASK_DEAD) { + bool expected = false; + if (atomic_cas_bool(&t->on_cpu, &expected, true)) { + *head = t->next; + t->next = NULL; + found = t; + break; + } + } else { + if (t->state == TASK_ZOMBIE || t->state == TASK_DEAD || !t->runnable) { + *head = t->next; + t->next = NULL; + t = *head; + continue; + } + } + head = &t->next; + t = *head; + } + } + spinlock_release_irqrestore(&ready_queue_lock, _irqf); + return found ? found : &idle_tasks[cpu]; +} + +void sched_reschedule(void) { + asm volatile("cli"); + + process_deferred_free(); + + reschedule_calls++; + uint32_t cpu = lapic_get_id(); + + task_t* old = current_task[cpu]; + task_t* next = sched_pick_next(cpu); + + if (!next) { asm volatile("sti"); return; } + + if (old == next) { + if (old == &idle_tasks[cpu]) { + next = sched_pick_next(cpu); + if (next == &idle_tasks[cpu]) { + asm volatile("sti"); + return; + } + } else { + old->time_slice = old->time_slice_init; + asm volatile("sti"); + return; + } + } + + if (old && old->fpu_state && old->state != TASK_ZOMBIE && old->state != TASK_DEAD) { + fpu_save(old->fpu_state); + old->fpu_used = true; + } + + if (old && old != &idle_tasks[cpu]) { + if (old->state == TASK_ZOMBIE) { + percpu_t* pc = get_percpu(); + if (pc) pc->deferred_free_task = old; + } else if (old->runnable && old->state != TASK_DEAD) { + old->time_slice = old->time_slice_init; + old->last_cpu = cpu; + old->state = TASK_READY; + enqueue_global(old); + } + } + + uint64_t switch_cr3 = 0; + if (next->cr3 && (!old || old->cr3 != next->cr3)) { + if (next->pagemap) + vmm_sync_kernel_mappings(next->pagemap); + asm volatile("lock addl $0, (%%rsp)" ::: "memory", "cc"); + switch_cr3 = next->cr3; + } else if (!next->cr3) { + vmm_pagemap_t* kpm = vmm_get_kernel_pagemap(); + if (kpm && kpm->pml4) { + uint64_t kphys = (uint64_t)pmm_virt_to_phys(kpm->pml4); + if (!old || old->cr3 != kphys) + switch_cr3 = kphys; + } + } + + if (tss[cpu]) { + if (next->is_userspace && next->stack_base == 0) { + kernel_panic("SCHED: userspace task has stack_base=0"); + } + tss[cpu]->rsp0 = next->stack_base + KERNEL_STACK_SIZE; + percpu_t* pc = get_percpu(); + if (pc) { + pc->syscall_kernel_rsp = tss[cpu]->rsp0; + if (next->is_userspace) { + pc->syscall_user_rsp = next->user_rsp; + } + } + } + + next->cpu_id = cpu; + next->state = TASK_RUNNING; + if (next->fpu_state) fpu_restore(next->fpu_state); + + if (!(next->flags & TASK_FLAG_STARTED)) { + next->flags |= TASK_FLAG_STARTED; + current_task[cpu] = next; + if (next->cr3) { + serial_printf("[SCHED] CR3 switch cpu=%u old=0x%llx -> new=0x%llx (pid %u->%u)\n", + cpu, old ? old->cr3 : 0ULL, switch_cr3, + old ? old->pid : 0, next->pid); + asm volatile("mov %0, %%cr3" :: "r"(next->cr3) : "memory"); + switch_cr3 = 0; + } else { + asm volatile("mov %%cr3, %%rax; mov %%rax, %%cr3" ::: "rax", "memory"); + } + if (next->is_userspace) { + serial_printf("[SCHED] CPU %u: first start '%s' pid=%u entry=0x%llx user_rsp=0x%llx\n", + cpu, next->name, next->pid, + (uint64_t)next->entry, next->user_rsp); + } + } + + if (old) context_switch(old, next, ¤t_task[cpu], switch_cr3); + else context_switch(&bootstrap_tasks[cpu], next, ¤t_task[cpu], switch_cr3); + + process_deferred_free(); + + asm volatile("sti"); +} + +void task_yield(void) { + sched_reschedule(); +} + +void sched_print_stats(void) { + uint64_t _irqf; + serial_printf("[SCHED] reschedule_calls=%llu\n", reschedule_calls); + _irqf = spinlock_acquire_irqsave(&ready_queue_lock); + for (int p = MAX_PRIORITY; p >= 0; p--) { + int n = 0; + for (task_t* t = ready_queues[p]; t; t = t->next) n++; + if (n) serial_printf(" prio %d: %d tasks\n", p, n); + } + spinlock_release_irqrestore(&ready_queue_lock, _irqf); +} + +void task_unblock(task_t* t) { + if (!t) return; + t->runnable = true; + t->state = TASK_READY; + enqueue_global(t); +} + +void sched_wakeup_sleepers(uint64_t now_ns) { + task_t* to_wake[64]; + int wake_count = 0; + + uint64_t _irqf = spinlock_acquire_irqsave(&pid_lock); + for (uint32_t i = 1; i < MAX_PIDS && wake_count < 64; i++) { + task_t *t = pid_table[i]; + if (!t) continue; + if (t->state != TASK_BLOCKED) continue; + if (t->wakeup_time_ns == 0) continue; + if (now_ns >= t->wakeup_time_ns) { + t->wakeup_time_ns = 0; + t->runnable = true; + t->state = TASK_READY; + to_wake[wake_count++] = t; + } + } + spinlock_release_irqrestore(&pid_lock, _irqf); + + for (int i = 0; i < wake_count; i++) { + enqueue_global(to_wake[i]); + } +} + +static void idle_loop(void* arg) { + (void)arg; + uint32_t cpu = lapic_get_id(); + serial_printf("[IDLE] CPU %u entering idle loop\n", cpu); + while (1) { + process_deferred_free(); + asm volatile("sti; hlt"); + } +} \ No newline at end of file diff --git a/kernel/src/sched/sched_asm.asm b/kernel/src/sched/sched_asm.asm new file mode 100644 index 0000000..58c5ce7 --- /dev/null +++ b/kernel/src/sched/sched_asm.asm @@ -0,0 +1,157 @@ +section .text +extern task_exit +global context_switch +global first_task_start +global fpu_save +global fpu_restore +global task_trampoline +global task_trampoline_user +global task_trampoline_fork + +TASK_ENTRY_OFFSET equ 120 +TASK_ARG_OFFSET equ 128 +TASK_USER_RSP_OFFSET equ 144 +TASK_CR3_OFFSET equ 24 +TASK_USER_SAVED_RIP_OFFSET equ 272 +TASK_USER_SAVED_RBP_OFFSET equ 280 +TASK_USER_SAVED_RBX_OFFSET equ 288 +TASK_USER_SAVED_R12_OFFSET equ 296 +TASK_USER_SAVED_R13_OFFSET equ 304 +TASK_USER_SAVED_R14_OFFSET equ 312 +TASK_USER_SAVED_R15_OFFSET equ 320 +TASK_USER_SAVED_R11_OFFSET equ 328 + +PERCPU_CURRENT_TASK equ 24 +TASK_ON_CPU_OFFSET equ 0x158 + +context_switch: + push rbp + push rbx + push r12 + push r13 + push r14 + push r15 + + mov [rdi], rsp + + mov [rdx], rsi + mov [gs:PERCPU_CURRENT_TASK], rsi + + mov rsp, [rsi] + + test rcx, rcx + jz .skip_cr3 + mov cr3, rcx +.skip_cr3: + + lock add dword [rsp], 0 + mov byte [rdi + TASK_ON_CPU_OFFSET], 0 + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rbp + + ret + +first_task_start: + mov [gs:PERCPU_CURRENT_TASK], rdi + + mov rsi, [rdi + TASK_CR3_OFFSET] + mov rsp, [rdi] + + test rsi, rsi + jz .skip_cr3_fts + mov cr3, rsi +.skip_cr3_fts: + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rbp + + ret + +fpu_save: + fxsave [rdi] + ret + +fpu_restore: + fxrstor [rdi] + ret + +task_trampoline: + mov rdi, [rbp + TASK_ARG_OFFSET] + mov rax, [rbp + TASK_ENTRY_OFFSET] + xor rbp, rbp + sti + call rax + call task_exit +.hang: + cli + hlt + jmp .hang + +task_trampoline_user: + mov rax, [rbp + TASK_ENTRY_OFFSET] + mov rcx, [rbp + TASK_USER_RSP_OFFSET] + + xor rbp, rbp + + push qword 0x1B + push rcx + pushfq + pop rdx + or rdx, (1 << 9) + and rdx, ~(3 << 12) + push rdx + push qword 0x23 + push rax + + xor rax, rax + xor rbx, rbx + xor rcx, rcx + xor rdx, rdx + xor rdi, rdi + xor rsi, rsi + xor r8, r8 + xor r9, r9 + xor r10, r10 + xor r11, r11 + xor r12, r12 + xor r13, r13 + xor r14, r14 + xor r15, r15 + + swapgs + iretq + +task_trampoline_fork: + cli + + mov rax, [rbp + TASK_USER_SAVED_RIP_OFFSET] + mov rcx, [rbp + TASK_USER_RSP_OFFSET] + mov r11, [rbp + TASK_USER_SAVED_R11_OFFSET] + mov rbx, [rbp + TASK_USER_SAVED_RBX_OFFSET] + mov r12, [rbp + TASK_USER_SAVED_R12_OFFSET] + mov r13, [rbp + TASK_USER_SAVED_R13_OFFSET] + mov r14, [rbp + TASK_USER_SAVED_R14_OFFSET] + mov r15, [rbp + TASK_USER_SAVED_R15_OFFSET] + mov rbp, [rbp + TASK_USER_SAVED_RBP_OFFSET] + + or r11, (1 << 9) + + push qword 0x1B + push rcx + push r11 + push qword 0x23 + push rax + + xor rax, rax + + swapgs + iretq \ No newline at end of file diff --git a/kernel/src/smp/percpu.c b/kernel/src/smp/percpu.c new file mode 100644 index 0000000..aacc7bb --- /dev/null +++ b/kernel/src/smp/percpu.c @@ -0,0 +1,88 @@ +#include "../../include/smp/percpu.h" +#include "../../include/smp/smp.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include +#include + +extern uintptr_t __percpu_start; +extern uintptr_t __percpu_end; + +PERCPU_SECTION percpu_t percpu = {0}; +PERCPU_SECTION int dummy_percpu = 0xDEADBEEF; + +percpu_t* percpu_regions[MAX_CPUS] = {0}; + +bool g_has_fsgsbase = false; + +#define MSR_GS_BASE 0xC0000101 +#define MSR_KERNEL_GS_BASE 0xC0000102 + +static inline uint64_t rdmsr_local(uint32_t msr) { + uint32_t lo, hi; + asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr)); + return ((uint64_t)hi << 32) | lo; +} + +static inline void wrmsr_local(uint32_t msr, uint64_t val) { + asm volatile("wrmsr" :: "c"(msr), "a"((uint32_t)val), "d"((uint32_t)(val >> 32))); +} + +static bool detect_fsgsbase(void) { + uint32_t eax = 7, ebx, ecx = 0, edx; + asm volatile("cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "0"(eax), "2"(ecx)); + return (ebx & (1u << 0)) != 0; +} + +void init_percpu_regions(void) { + g_has_fsgsbase = detect_fsgsbase(); + serial_printf("[PerCPU] FSGSBASE: %s\n", g_has_fsgsbase ? "YES" : "NO (using MSR fallback)"); + + size_t percpu_size = &__percpu_end - &__percpu_start; + serial_printf("PerCPU size: %zu bytes\n", percpu_size); + + smp_info_t* info = smp_get_info(); + for (uint32_t i = 0; i < info->cpu_count; i++) { + void* region = malloc(percpu_size); + if (!region) { + serial_printf("PerCPU: Alloc failed for CPU %u\n", i); + continue; + } + memset(region, 0, percpu_size); + memcpy(region, &__percpu_start, percpu_size); + + percpu_regions[i] = (percpu_t*)region; + percpu_regions[i]->cpu_id = info->cpus[i].lapic_id; + percpu_regions[i]->syscall_kernel_rsp = 0; + percpu_regions[i]->syscall_user_rsp = 0; + + serial_printf("PerCPU region for CPU %u at 0x%llx\n", i, (uint64_t)region); + } +} + +percpu_t* get_percpu(void) { + uint64_t gs_base; + if (g_has_fsgsbase) { + asm volatile("rdgsbase %0" : "=r"(gs_base)); + } else { + gs_base = rdmsr_local(MSR_GS_BASE); + } + if (gs_base == 0) return NULL; + return (percpu_t*)gs_base; +} + +percpu_t* get_percpu_mut(void) { + return get_percpu(); +} + +void set_percpu_base(percpu_t* base) { + uint64_t val = (uint64_t)base; + if (g_has_fsgsbase) { + asm volatile("wrgsbase %0" :: "r"(val) : "memory"); + } else { + wrmsr_local(MSR_GS_BASE, val); + } + wrmsr_local(MSR_KERNEL_GS_BASE, val); +} diff --git a/kernel/src/smp/smp.c b/kernel/src/smp/smp.c new file mode 100644 index 0000000..731d310 --- /dev/null +++ b/kernel/src/smp/smp.c @@ -0,0 +1,327 @@ +#include "../../include/smp/smp.h" +#include "../../include/smp/percpu.h" +#include "../../include/acpi/acpi.h" +#include "../../include/apic/apic.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include "../../include/memory/vmm.h" +#include "../../include/gdt/gdt.h" +#include "../../include/interrupts/idt.h" +#include "../../include/sse/fpu.h" +#include "../../include/sse/sse.h" +#include "../../include/sched/sched.h" +#include "../include/syscall/syscall.h" +#include +#include +#include +#include + +extern tss_t *tss[MAX_CPUS]; +extern struct { + gdt_entry_t gdt_entries[5 + (MAX_CPUS * 2)]; +} __attribute__((packed)) gdt; + +static smp_info_t smp_info = {0}; +static volatile uint32_t ap_online_count = 0; +tlb_shootdown_t tlb_shootdown_queue[MAX_CPUS] = {0}; + +volatile uint32_t sched_ready_flag = 0; + +void sched_notify_ready(void) { + __sync_synchronize(); + sched_ready_flag = 1; + __sync_synchronize(); + serial_writestring("[SCHED] Scheduler ready, notifying all APs\n"); +} + +__attribute__((used)) +void ap_entry_init(struct limine_mp_info* cpu_info) { + (void)cpu_info; + asm volatile ("cli"); + + lapic_write(0xF0, 0); + + gdt_load(); + idt_load(); + + uint32_t lapic_id = lapic_get_id(); + smp_info_t* info = smp_get_info(); + uint32_t my_index = 0; + for (uint32_t i = 0; i < info->cpu_count; i++) { + if (info->cpus[i].lapic_id == lapic_id && !info->cpus[i].is_bsp) { + my_index = i; + info->cpus[i].state = CPU_ONLINE; + break; + } + } + load_tss(info->cpus[my_index].tss_selector); + serial_printf("TSS Loaded (selector 0x%x)\n", info->cpus[my_index].tss_selector); + + fpu_init(); + sse_init(); + enable_fsgsbase(); + lapic_enable(); + apic_timer_calibrate(); + serial_printf("[SMP] AP %u LAPIC timer started\n", lapic_id); + + cpu_info_t* cpu = smp_get_current_cpu(); + percpu_t* region = percpu_regions[cpu->cpu_index]; + set_percpu_base(region); + serial_printf("PerCPU base set for AP %u: 0x%llx\n", + lapic_id, (uint64_t)region); + syscall_init(); + __sync_fetch_and_add(&ap_online_count, 1); + lapic_eoi(); + + serial_printf("[SMP] AP (LAPIC ID %u) initialized and online!\n", lapic_id); + + while (!sched_ready_flag) + asm volatile ("pause"); + + serial_printf("[SMP] AP %u entering scheduler loop\n", lapic_id); + + asm volatile ("sti"); + + sched_reschedule(); + + while (1) + asm volatile ("hlt"); +} + +void ap_entry_point(struct limine_mp_info* cpu_info) { + uint64_t stack_top; + asm volatile ( + "mov 24(%%rdi), %0" + : "=r"(stack_top) + : "D"(cpu_info) + ); + asm volatile ( + "mov %0, %%rsp\n" + "cli\n" + "jmp ap_entry_init\n" + : + : "r"(stack_top), "D"(cpu_info) + : "memory" + ); +} + +static uint64_t smp_allocate_stack(uint32_t cpu_index, size_t stack_size) { + size_t pages = (stack_size + PAGE_SIZE - 1) / PAGE_SIZE; + void* stack_pages = pmm_alloc(pages); + if (!stack_pages) { + serial_printf("[SMP WARNING] Failed to allocate stack for CPU %u\n", cpu_index); + return 0; + } + uint64_t stack_virt = (uint64_t)stack_pages + stack_size; + serial_printf("[SMP] Allocated stack for CPU %u at 0x%llx (size %zu)\n", + cpu_index, stack_virt, stack_size); + return stack_virt; +} + +void smp_boot_aps(struct limine_mp_response* mp_response) { + if (!mp_response) { + serial_writestring("[SMP ERROR] MP response is NULL\n"); + return; + } + + serial_writestring("\n[SMP] Booting Application Processors \n"); + + smp_info_t* info = smp_get_info(); + uint32_t bsp_lapic_id = info->bsp_lapic_id; + uint32_t ap_count = 0; + + for (uint64_t i = 0; i < mp_response->cpu_count; i++) { + struct limine_mp_info* cpu = mp_response->cpus[i]; + if (cpu->lapic_id == bsp_lapic_id) continue; + + uint64_t stack_top = smp_allocate_stack(i, AP_STACK_SIZE); + if (stack_top == 0) { + serial_printf("[SMP] Skipping CPU %u (stack alloc failed)\n", i); + info->cpus[i].state = CPU_FAULTED; + continue; + } + + info->cpus[i].stack_top = stack_top; + info->cpus[i].state = CPU_BOOTED; + cpu->extra_argument = stack_top; + cpu->goto_address = (void*)ap_entry_point; + + serial_printf("[SMP] Configured AP %lu (LAPIC ID %u) to boot at 0x%llx\n", + i, cpu->lapic_id, (uint64_t)ap_entry_point); + ap_count++; + } + + __sync_synchronize(); + + if (ap_count > 0) { + serial_printf("[SMP] Waiting for %u AP(s) to initialize...\n", ap_count); + uint64_t timeout = 10000000; + while (ap_online_count < ap_count && timeout--) + asm volatile ("pause"); + + uint32_t online = ap_online_count; + if (online == ap_count) + serial_printf("[SMP SUCCESS] All %u AP(s) online!\n", ap_count); + else + serial_printf("[SMP WARNING] Only %u/%u AP(s) online (timeout)\n", + online, ap_count); + info->online_count = 1 + online; + } else { + serial_writestring("[SMP] No APs to boot\n"); + } + + serial_writestring("[SMP] AP Boot Sequence Complete \n\n"); +} + +static void smp_init_limine(struct limine_mp_response* response) { + if (!response) { serial_writestring("[SMP] Limine MP response is NULL\n"); return; } + + serial_printf("[SMP] Initializing via Limine MP (CPU count: %u)\n", + response->cpu_count); + smp_info.cpu_count = response->cpu_count; + smp_info.online_count = 1; + + acpi_madt_t* madt = (acpi_madt_t*)acpi_find_table("APIC", 0); + if (madt) { + smp_info.lapic_base = madt->local_apic_address; + serial_printf("[SMP] LAPIC base from ACPI: 0x%x\n", smp_info.lapic_base); + } else { + smp_info.lapic_base = 0xFEE00000; + } + + for (uint64_t i = 0; i < response->cpu_count; i++) { + struct limine_mp_info* cpu = response->cpus[i]; + smp_info.cpus[i].lapic_id = cpu->lapic_id; + smp_info.cpus[i].processor_id = cpu->lapic_id; + smp_info.cpus[i].acpi_id = 0; + smp_info.cpus[i].state = CPU_UNINITIALIZED; + smp_info.cpus[i].is_bsp = (cpu->lapic_id == response->bsp_lapic_id); + smp_info.cpus[i].cpu_index = i; + + if (smp_info.cpus[i].is_bsp) { + smp_info.bsp_lapic_id = cpu->lapic_id; + smp_info.cpus[i].state = CPU_ONLINE; + serial_printf("[SMP] BSP detected - APIC ID: %u\n", cpu->lapic_id); + } + serial_printf("[SMP] CPU[%lu] - APIC ID: %u, Processor ID: %u, BSP: %s\n", + i, cpu->lapic_id, cpu->lapic_id, + smp_info.cpus[i].is_bsp ? "YES" : "NO"); + } +} + +void smp_init(struct limine_mp_response* mp_response) { + serial_writestring("\n[SMP] Initialization\n"); + smp_init_limine(mp_response); + + uint32_t bsp_index = 0; + for (uint32_t i = 0; i < smp_info.cpu_count; i++) + if (smp_info.cpus[i].is_bsp) { bsp_index = i; break; } + + for (uint32_t i = 0; i < smp_info.cpu_count; i++) { + tss[i] = (tss_t *)calloc(1, sizeof(tss_t)); + if (!tss[i]) { serial_printf("[SMP ERROR] FAILED to allocate TSS for CPU %u\n", i); continue; } + + tss[i]->rsp0 = smp_allocate_stack(i, KERNEL_STACK_SIZE); + tss[i]->ist[0] = smp_allocate_stack(i, KERNEL_STACK_SIZE); + tss[i]->ist[1] = smp_allocate_stack(i, KERNEL_STACK_SIZE); + tss[i]->ist[2] = smp_allocate_stack(i, KERNEL_STACK_SIZE); + tss[i]->ist[3] = smp_allocate_stack(i, KERNEL_STACK_SIZE); + tss[i]->iobase = sizeof(tss_t); + + serial_printf("TSS[%u] base: 0x%llx\n", i, (uint64_t)tss[i]); + + tss_entry_t *entry = (tss_entry_t *)&gdt.gdt_entries[5 + (i * 2)]; + entry->limit_low = sizeof(tss_t) - 1; + uint64_t addr = (uint64_t)tss[i]; + entry->base_low = addr & 0xffff; + entry->base_middle = (addr >> 16) & 0xff; + entry->access = 0x89; + entry->limit_high_and_flags = 0; + entry->base_high = (addr >> 24) & 0xff; + entry->base_higher = addr >> 32; + entry->zero = 0; + + smp_info.cpus[i].tss_selector = TSS_SELECTOR_BASE + (i * 0x10); + } + + gdtr.size = (5 + (smp_info.cpu_count * 2)) * sizeof(gdt_entry_t) - 1; + serial_printf("Reloading extended GDT on BSP...\n"); + gdt_load(); + + load_tss(smp_info.cpus[bsp_index].tss_selector); + serial_printf("BSP TSS loaded (selector 0x%x)\n", + smp_info.cpus[bsp_index].tss_selector); + + uint32_t current_lapic_id = lapic_get_id(); + serial_printf("[SMP] Current LAPIC ID: %u\n", current_lapic_id); + for (uint32_t i = 0; i < smp_info.cpu_count; i++) { + if (smp_info.cpus[i].lapic_id == current_lapic_id) { + smp_info.cpus[i].is_bsp = true; + smp_info.cpus[i].state = CPU_ONLINE; + smp_info.bsp_lapic_id = current_lapic_id; + break; + } + } + + smp_print_info(); + init_percpu_regions(); + smp_boot_aps(mp_response); + set_percpu_base(percpu_regions[bsp_index]); + serial_printf("PerCPU base set for BSP %u: 0x%llx\n", + smp_info.bsp_lapic_id, (uint64_t)percpu_regions[bsp_index]); + serial_writestring("[SMP] Initialization Complete \n\n"); +} + +smp_info_t* smp_get_info(void) { return &smp_info; } +uint32_t smp_get_cpu_count(void) { return smp_info.cpu_count; } +uint32_t smp_get_online_count(void){ return smp_info.online_count; } +bool smp_is_bsp(void) { return lapic_get_id() == smp_info.bsp_lapic_id; } + +cpu_info_t* smp_get_current_cpu(void) { + uint32_t id = lapic_get_id(); + for (uint32_t i = 0; i < smp_info.cpu_count; i++) + if (smp_info.cpus[i].lapic_id == id) return &smp_info.cpus[i]; + for (uint32_t i = 0; i < smp_info.cpu_count; i++) + if (smp_info.cpus[i].is_bsp) return &smp_info.cpus[i]; + return NULL; +} + +void smp_wait_for_ready(void) { + serial_writestring("Waiting until all APs are fully ready...\n"); + while (smp_get_online_count() < smp_get_cpu_count()) + asm volatile ("pause"); + serial_writestring("All APs ready.\n"); +} + +uint32_t smp_get_lapic_id_for_cpu(uint32_t cpu_index) { + smp_info_t* info = smp_get_info(); + if (cpu_index >= info->cpu_count) return 0xFFFFFFFF; + return info->cpus[cpu_index].lapic_id; +} + +void smp_print_info(void) { + serial_printf("\n[SMP] CPU Information \n"); + serial_printf("Total CPUs: %u\n", smp_info.cpu_count); + serial_printf("Online CPUs: %u\n", smp_info.online_count); + serial_printf("BSP APIC ID: %u\n", smp_info.bsp_lapic_id); + serial_printf("LAPIC Base: 0x%llx\n",smp_info.lapic_base); + const char* states[] = {"UNINITIALIZED","BOOTED","ONLINE","OFFLINE","FAULTED"}; + for (uint32_t i = 0; i < smp_info.cpu_count; i++) + serial_printf("CPU[%u]: APIC ID: %u, Processor ID: %u, ACPI ID: %u, State: %s, BSP: %s\n", + i, smp_info.cpus[i].lapic_id, smp_info.cpus[i].processor_id, + smp_info.cpus[i].acpi_id, states[smp_info.cpus[i].state], + smp_info.cpus[i].is_bsp ? "YES" : "NO"); + serial_printf("[SMP] End CPU Information \n"); +} + +void smp_print_info_fb(void) { + printf("[SMP] CPU Information \n"); + printf("Total CPUs: %u\n", smp_info.cpu_count); + printf("Online CPUs: %u\n", smp_info.online_count); + printf("BSP APIC ID: %u\n", smp_info.bsp_lapic_id); + const char* states[] = {"UNINITIALIZED","BOOTED","ONLINE","OFFLINE","FAULTED"}; + for (uint32_t i = 0; i < smp_info.cpu_count; i++) + printf("CPU[%u]: APIC ID: %u, State: %s, BSP: %s\n", + i, smp_info.cpus[i].lapic_id, states[smp_info.cpus[i].state], + smp_info.cpus[i].is_bsp ? "YES" : "NO"); +} \ No newline at end of file diff --git a/kernel/src/sse/fpu.c b/kernel/src/sse/fpu.c new file mode 100644 index 0000000..a70acfe --- /dev/null +++ b/kernel/src/sse/fpu.c @@ -0,0 +1,96 @@ +#include "../../include/sse/fpu.h" +#include "../../include/io/serial.h" + +#define COM1 0x3F8 + +void fpu_init(void) { + serial_writestring("[FPU] Initializing x87 FPU...\n"); + + if (!fpu_detect()) { + serial_writestring("[FPU] No FPU detected!\n"); + return; + } + + uint64_t cr0; + asm volatile("mov %%cr0, %0" : "=r"(cr0)); + + cr0 &= ~(1 << 2); + cr0 |= (1 << 1); + cr0 &= ~(1 << 3); + cr0 &= ~(1 << 5); + + asm volatile("mov %0, %%cr0" : : "r"(cr0)); + + asm volatile("finit"); + + fpu_set_control_word(0x037F); + + uint64_t cr4; + asm volatile("mov %%cr4, %0" : "=r"(cr4)); + cr4 |= (1ULL << 9); + cr4 |= (1ULL << 10); + asm volatile("mov %0, %%cr4" : : "r"(cr4)); + + uint32_t mxcsr = 0x1F80; + asm volatile("ldmxcsr %0" : : "m"(mxcsr)); + + serial_writestring("[FPU] SSE/SSE2 enabled (CR4.OSFXSR|OSXMMEXCPT)\n"); + serial_writestring("[FPU] FPU initialized successfully\n"); +} + +bool fpu_detect(void) { + uint32_t edx; + asm volatile( + "mov $1, %%eax\n" + "cpuid\n" + "mov %%edx, %0\n" + : "=r"(edx) + : + : "eax", "ebx", "ecx", "edx" + ); + return (edx & (1 << 0)) != 0; +} + +void fpu_reset(void) { + asm volatile("finit"); +} + +void fpu_set_control_word(uint16_t cw) { + asm volatile("fldcw %0" : : "m"(cw)); +} + +uint16_t fpu_get_control_word(void) { + uint16_t cw; + asm volatile("fnstcw %0" : "=m"(cw)); + return cw; +} + +void fpu_set_status_word(uint16_t sw) { + (void)sw; +} + +uint16_t fpu_get_status_word(void) { + uint16_t sw; + asm volatile("fnstsw %0" : "=m"(sw)); + return sw; +} + +void fpu_set_tag_word(uint16_t tw) { + (void)tw; +} + +uint16_t fpu_get_tag_word(void) { + struct { + uint16_t control_word; + uint16_t status_word; + uint16_t tag_word; + uint16_t fpu_ip; + uint16_t fpu_cs; + uint16_t fpu_opcode; + uint16_t fpu_dp; + uint16_t fpu_ds; + } __attribute__((packed)) fpu_env; + + asm volatile("fstenv %0" : "=m"(fpu_env)); + return fpu_env.tag_word; +} \ No newline at end of file diff --git a/kernel/src/sse/sse.c b/kernel/src/sse/sse.c new file mode 100644 index 0000000..3651272 --- /dev/null +++ b/kernel/src/sse/sse.c @@ -0,0 +1,238 @@ +#include "../../include/sse/sse.h" +#include "../../include/io/serial.h" +#include "../../include/apic/apic.h" +#include +#include + +static uint32_t cpuid_edx = 0; +static uint32_t cpuid_ecx = 0; +static uint32_t cpuid_ebx_ext = 0; +static bool features_cached = false; + +static void cpuid_cache_features(void) { + if (features_cached) return; + + uint32_t eax, ebx, ecx, edx; + asm volatile( + "mov $1, %%eax\n" + "cpuid\n" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : + : "memory" + ); + cpuid_edx = edx; + cpuid_ecx = ecx; + + uint32_t max_leaf; + asm volatile("cpuid" : "=a"(max_leaf) : "0"(0) : "ebx", "ecx", "edx"); + if (max_leaf >= 7) { + asm volatile( + "mov $7, %%eax\n" + "xor %%ecx, %%ecx\n" + "cpuid\n" + "mov %%ebx, %0\n" + : "=r"(cpuid_ebx_ext) + : + : "eax", "ecx", "edx" + ); + } + + features_cached = true; +} + +bool sse_supported(void) { cpuid_cache_features(); return (cpuid_edx & (1 << 25)) != 0; } +bool sse2_supported(void) { cpuid_cache_features(); return (cpuid_edx & (1 << 26)) != 0; } +bool sse3_supported(void) { cpuid_cache_features(); return (cpuid_ecx & (1 << 0)) != 0; } +bool ssse3_supported(void) { cpuid_cache_features(); return (cpuid_ecx & (1 << 9)) != 0; } +bool sse4_1_supported(void){ cpuid_cache_features(); return (cpuid_ecx & (1 << 19)) != 0; } +bool sse4_2_supported(void){ cpuid_cache_features(); return (cpuid_ecx & (1 << 20)) != 0; } +bool avx_supported(void) { cpuid_cache_features(); return (cpuid_ecx & (1 << 28)) != 0; } +bool avx2_supported(void) { cpuid_cache_features(); return (cpuid_ebx_ext & (1 << 5)) != 0; } +bool mmx_supported(void) { cpuid_cache_features(); return (cpuid_edx & (1 << 23)) != 0; } + +void sse_init(void) { + serial_writestring("[SSE] Initializing SSE/AVX...\n"); + + cpuid_cache_features(); + + if (!sse_supported()) { + serial_writestring("[SSE] SSE not supported — skipping SSE/AVX init\n"); + return; + } + + uint64_t cr4; + asm volatile("mov %%cr4, %0" : "=r"(cr4)); + cr4 |= (1 << 9); + cr4 |= (1 << 10); + + if (avx_supported()) { + uint32_t ecx; + asm volatile( + "mov $1, %%eax\n" + "cpuid\n" + "mov %%ecx, %0\n" + : "=r"(ecx) + : + : "eax", "ebx", "edx" + ); + if (ecx & (1 << 27)) { + serial_writestring("[SSE] AVX supported, enabling...\n"); + cr4 |= (1 << 18); + uint64_t xcr0; + asm volatile("xgetbv" : "=a"(xcr0) : "c"(0) : "edx"); + xcr0 |= (1 << 1) | (1 << 2); + asm volatile("xsetbv" : : "c"(0), "a"(xcr0), "d"(xcr0 >> 32)); + } + } + + asm volatile("mov %0, %%cr4" : : "r"(cr4)); + + sse_set_mxcsr(MXCSR_DEFAULT); + + serial_writestring("[SSE] SSE initialized successfully\n"); + if (sse_supported()) serial_writestring("[SSE] SSE: YES\n"); + if (sse2_supported()) serial_writestring("[SSE] SSE2: YES\n"); + if (sse3_supported()) serial_writestring("[SSE] SSE3: YES\n"); + if (sse4_1_supported())serial_writestring("[SSE] SSE4.1: YES\n"); + if (sse4_2_supported())serial_writestring("[SSE] SSE4.2: YES\n"); + if (avx_supported()) serial_writestring("[SSE] AVX: YES\n"); + if (mmx_supported()) serial_writestring("[SSE] MMX: YES\n"); +} + +void sse_set_mxcsr(uint32_t mxcsr) { + if (!sse_supported()) return; + asm volatile("ldmxcsr %0" : : "m"(mxcsr)); +} + +uint32_t sse_get_mxcsr(void) { + if (!sse_supported()) return 0; + uint32_t mxcsr; + asm volatile("stmxcsr %0" : "=m"(mxcsr)); + return mxcsr; +} + +void mmx_enter(void) { + if (!mmx_supported()) return; +} + +void mmx_exit(void) { + if (!mmx_supported()) return; + asm volatile("emms"); +} + +void sse_memcpy_fast(void *dest, const void *src, size_t n) { + if (!sse_supported() || n < 64) { + memcpy(dest, src, n); + return; + } + + uint8_t *d = (uint8_t *)dest; + const uint8_t *s = (const uint8_t *)src; + + size_t align_offset = (16 - ((uintptr_t)d & 0xF)) & 0xF; + if (align_offset > n) align_offset = n; + for (size_t i = 0; i < align_offset; i++) d[i] = s[i]; + d += align_offset; s += align_offset; n -= align_offset; + + size_t i; + for (i = 0; i + 64 <= n; i += 64) { + asm volatile( + "movdqu (%0), %%xmm0\n" + "movdqu 16(%0), %%xmm1\n" + "movdqu 32(%0), %%xmm2\n" + "movdqu 48(%0), %%xmm3\n" + "movdqu %%xmm0, (%1)\n" + "movdqu %%xmm1, 16(%1)\n" + "movdqu %%xmm2, 32(%1)\n" + "movdqu %%xmm3, 48(%1)\n" + : + : "r"(s + i), "r"(d + i) + : "xmm0", "xmm1", "xmm2", "xmm3", "memory" + ); + } + for (; i < n; i++) d[i] = s[i]; +} + +void sse_memset_fast(void *dest, int value, size_t n) { + if (!sse_supported() || n < 64) { + memset(dest, value, n); + return; + } + + uint8_t *d = (uint8_t *)dest; + uint8_t v = (uint8_t)value; + uint64_t pattern64 = + (uint64_t)v << 56 | (uint64_t)v << 48 | (uint64_t)v << 40 | (uint64_t)v << 32 | + (uint64_t)v << 24 | (uint64_t)v << 16 | (uint64_t)v << 8 | (uint64_t)v; + + size_t align_offset = (16 - ((uintptr_t)d & 0xF)) & 0xF; + if (align_offset > n) align_offset = n; + for (size_t i = 0; i < align_offset; i++) d[i] = v; + d += align_offset; n -= align_offset; + + asm volatile( + "movq %0, %%xmm0\n" + "punpcklqdq %%xmm0, %%xmm0\n" + : : "r"(pattern64) : "xmm0" + ); + + size_t i; + for (i = 0; i + 64 <= n; i += 64) { + asm volatile( + "movdqu %%xmm0, (%0)\n" + "movdqu %%xmm0, 16(%0)\n" + "movdqu %%xmm0, 32(%0)\n" + "movdqu %%xmm0, 48(%0)\n" + : + : "r"(d + i) + : "memory" + ); + } + for (; i < n; i++) d[i] = v; +} + +void print_simd_cpuid(void) { + printf("\n=== CPUID/SIMD INFORMATION ===\n"); + + uint32_t eax, ebx, ecx, edx; + char vendor[13] = {0}; + asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0)); + *(uint32_t *)vendor = ebx; + *(uint32_t *)(vendor + 4) = edx; + *(uint32_t *)(vendor + 8) = ecx; + + printf("CPU Vendor: %s\n", vendor); + + asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1)); + printf("Processor Signature: 0x%08x\n", eax); + printf("Stepping: %d Model: %d Family: %d\n", + eax & 0xF, (eax >> 4) & 0xF, (eax >> 8) & 0xF); + + printf("\nSIMD Extensions:\n"); + printf("MMX: %s\n", (edx & (1 << 23)) ? "YES" : "NO"); + printf("SSE: %s\n", (edx & (1 << 25)) ? "YES" : "NO"); + printf("SSE2: %s\n", (edx & (1 << 26)) ? "YES" : "NO"); + printf("SSE3: %s\n", (ecx & (1 << 0)) ? "YES" : "NO"); + printf("SSSE3: %s\n", (ecx & (1 << 9)) ? "YES" : "NO"); + printf("SSE4.1: %s\n", (ecx & (1 << 19)) ? "YES" : "NO"); + printf("SSE4.2: %s\n", (ecx & (1 << 20)) ? "YES" : "NO"); + printf("AVX: %s\n", (ecx & (1 << 28)) ? "YES" : "NO"); + + printf("=== END OF CPUID/SIMD INFO ===\n\n"); +} + +void enable_fsgsbase(void) { + uint32_t eax = 7, ebx, ecx = 0, edx; + asm volatile("cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "0"(eax), "2"(ecx)); + if (ebx & (1 << 0)) { + uint64_t cr4; + asm volatile("mov %%cr4, %0" : "=r"(cr4)); + cr4 |= (1ULL << 16); + asm volatile("mov %0, %%cr4" :: "r"(cr4)); + serial_printf("[FSGSBASE] Enabled on CPU %u\n", lapic_get_id()); + } else { + serial_writestring("[FSGSBASE] Not supported — using MSR fallback\n"); + } +} \ No newline at end of file diff --git a/kernel/src/syscall/disk_syscalls.c b/kernel/src/syscall/disk_syscalls.c new file mode 100644 index 0000000..b037bb0 --- /dev/null +++ b/kernel/src/syscall/disk_syscalls.c @@ -0,0 +1,431 @@ +#include "../../include/syscall/syscall.h" +#include "../../include/syscall/syscall_nums.h" +#include "../../include/syscall/errno.h" +#include "../../include/drivers/disk.h" +#include "../../include/drivers/blkdev.h" +#include "../../include/drivers/ata.h" +#include "../../include/drivers/partition.h" +#include "../../include/sched/sched.h" +#include "../../include/sched/capabilities.h" +#include "../../include/smp/percpu.h" +#include "../../include/io/serial.h" +#include "../../include/memory/pmm.h" +#include "../../include/fs/vfs.h" +#include "../../include/fs/ext2.h" +#include "../../include/fs/fat32.h" +#include +#include + +static inline task_t* disk_cur_task(void) { + percpu_t* pc = get_percpu(); + return pc ? (task_t*)pc->current_task : NULL; +} + +static int disk_strncpy_from_user(char *dst, const char *src, size_t max) { + uintptr_t addr = (uintptr_t)src; + if (addr < 0x1000ULL || addr >= 0x0000800000000000ULL) return -EFAULT; + for (size_t i = 0; i < max - 1; i++) { + dst[i] = src[i]; + if (!dst[i]) return (int)i; + } + dst[max-1] = '\0'; + return (int)(max-1); +} + +static int user_ptr_ok(uint64_t p) { + return p >= 0x1000ULL && p < 0x0000800000000000ULL; +} + +int64_t sys_disk_mount(uint64_t devname_ptr, uint64_t path_ptr, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a3; (void)a4; (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + + char devname[64], path[256]; + if (disk_strncpy_from_user(devname, (const char *)devname_ptr, sizeof(devname)) < 0) return -EFAULT; + if (disk_strncpy_from_user(path, (const char *)path_ptr, sizeof(path)) < 0) return -EFAULT; + serial_printf("[SYSCALL] disk_mount('%s', '%s') by pid=%u\n", devname, path, t->pid); + return disk_mount(devname, path); +} + +int64_t sys_disk_umount(uint64_t path_ptr, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + char path[256]; + if (disk_strncpy_from_user(path, (const char *)path_ptr, sizeof(path)) < 0) return -EFAULT; + return disk_umount(path); +} + +int64_t sys_disk_format(uint64_t devname_ptr, uint64_t label_ptr, + uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) +{ + (void)a3; (void)a4; (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + + char devname[64], label[64]; + if (disk_strncpy_from_user(devname, (const char *)devname_ptr, sizeof(devname)) < 0) return -EFAULT; + if (label_ptr) { + if (disk_strncpy_from_user(label, (const char *)label_ptr, sizeof(label)) < 0) return -EFAULT; + } else { + strncpy(label, devname, sizeof(label) - 1); + } + return disk_format(devname, label); +} + +int64_t sys_disk_info(uint64_t index, uint64_t buf_ptr, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a3; (void)a4; (void)a5; (void)a6; + if (!buf_ptr) return -EINVAL; + blkdev_t *dev = blkdev_get((int)index); + if (!dev || !dev->present) return -ENODEV; + struct { + char name[32]; + uint64_t sectors; + uint64_t size_bytes; + char model[41]; + uint8_t present; + uint8_t _pad[6]; + } info; + memset(&info, 0, sizeof(info)); + strncpy(info.name, dev->name, 31); + info.sectors = dev->sector_count; + info.size_bytes = dev->size_bytes; + info.present = 1; + ata_drive_t *ata = (ata_drive_t *)dev->priv; + if (ata) strncpy(info.model, ata->model, 40); + if (!user_ptr_ok(buf_ptr)) return -EFAULT; + memcpy((void *)buf_ptr, &info, sizeof(info)); + return 0; +} + +int64_t sys_disk_read_raw(uint64_t devname_ptr, uint64_t lba, uint64_t count, + uint64_t buf_ptr, uint64_t a5, uint64_t a6) +{ + (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + + char devname[64]; + if (disk_strncpy_from_user(devname, (const char *)devname_ptr, sizeof(devname)) < 0) return -EFAULT; + if (!user_ptr_ok(buf_ptr)) return -EFAULT; + if (count == 0 || count > 256) return -EINVAL; + + const char *name = devname; + if (strncmp(name, "/dev/", 5) == 0) name += 5; + blkdev_t *dev = blkdev_get_by_name(name); + if (!dev) return -ENODEV; + if (lba + count > dev->sector_count) return -EINVAL; + + int r = dev->ops->read_sectors(dev, lba, (uint32_t)count, (void *)buf_ptr); + if (r < 0) return r; + return (int64_t)(count * dev->sector_size); +} + +int64_t sys_disk_write_raw(uint64_t devname_ptr, uint64_t lba, uint64_t count, + uint64_t buf_ptr, uint64_t a5, uint64_t a6) +{ + (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + + char devname[64]; + if (disk_strncpy_from_user(devname, (const char *)devname_ptr, sizeof(devname)) < 0) return -EFAULT; + if (!user_ptr_ok(buf_ptr)) return -EFAULT; + if (count == 0 || count > 256) return -EINVAL; + + const char *name = devname; + if (strncmp(name, "/dev/", 5) == 0) name += 5; + blkdev_t *dev = blkdev_get_by_name(name); + if (!dev) return -ENODEV; + if (lba + count > dev->sector_count) return -EINVAL; + + int r = dev->ops->write_sectors(dev, lba, (uint32_t)count, (const void *)buf_ptr); + if (r < 0) return r; + if (dev->ops->flush) dev->ops->flush(dev); + return (int64_t)(count * dev->sector_size); +} + +int64_t sys_disk_partition(uint64_t devname_ptr, uint64_t specs_ptr, uint64_t nparts, + uint64_t a4, uint64_t a5, uint64_t a6) +{ + (void)a4; (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + + char devname[64]; + if (disk_strncpy_from_user(devname, (const char *)devname_ptr, sizeof(devname)) < 0) return -EFAULT; + if (!user_ptr_ok(specs_ptr)) return -EFAULT; + if (nparts == 0 || nparts > 4) return -EINVAL; + + const char *name = devname; + if (strncmp(name, "/dev/", 5) == 0) name += 5; + blkdev_t *dev = blkdev_get_by_name(name); + if (!dev) return -ENODEV; + + cervus_mbr_part_t specs[4]; + memset(specs, 0, sizeof(specs)); + memcpy(specs, (const void *)specs_ptr, sizeof(cervus_mbr_part_t) * nparts); + + mbr_partition_t parts[4]; + memset(parts, 0, sizeof(parts)); + for (uint64_t i = 0; i < nparts; i++) { + parts[i].boot_flag = specs[i].boot_flag ? 0x80 : 0x00; + parts[i].type = specs[i].type; + parts[i].lba_start = specs[i].lba_start; + parts[i].sector_count = specs[i].sector_count; + parts[i].chs_start[0] = 0xFE; + parts[i].chs_start[1] = 0xFF; + parts[i].chs_start[2] = 0xFF; + parts[i].chs_end[0] = 0xFE; + parts[i].chs_end[1] = 0xFF; + parts[i].chs_end[2] = 0xFF; + } + + uint32_t sig = 0xCE705CE7; + int r = partition_write_mbr(dev, parts, sig); + if (r < 0) return r; + if (dev->ops->flush) dev->ops->flush(dev); + + partition_scan(dev); + return 0; +} + +int64_t sys_disk_mkfs_fat32(uint64_t devname_ptr, uint64_t label_ptr, + uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) +{ + (void)a3; (void)a4; (void)a5; (void)a6; + task_t *t = disk_cur_task(); + if (!t) return -ESRCH; + if (t->uid != 0 && !(t->capabilities & (1ULL << 1))) return -EPERM; + + char devname[64], label[16]; + if (disk_strncpy_from_user(devname, (const char *)devname_ptr, sizeof(devname)) < 0) return -EFAULT; + if (label_ptr) { + if (disk_strncpy_from_user(label, (const char *)label_ptr, sizeof(label)) < 0) return -EFAULT; + } else { + strncpy(label, "CERVUS", sizeof(label) - 1); + label[sizeof(label) - 1] = '\0'; + } + + const char *name = devname; + if (strncmp(name, "/dev/", 5) == 0) name += 5; + blkdev_t *dev = blkdev_get_by_name(name); + if (!dev) return -ENODEV; + + return fat32_format(dev, label); +} + +int64_t sys_disk_bios_install(uint64_t a1, uint64_t a2, uint64_t a3, + uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a4; (void)a5; (void)a6; + const char *disk_name = (const char *)a1; + const void *sys_data = (const void *)a2; + uint32_t sys_size = (uint32_t)a3; + + if (!disk_name || !sys_data || sys_size < 512) return -EINVAL; + + blkdev_t *dev = blkdev_get_by_name(disk_name); + if (!dev) return -ENOENT; + if (dev->sector_size != 512) return -EINVAL; + + uint8_t sector0[512]; + int r = dev->ops->read_sectors(dev, 0, 1, sector0); + if (r < 0) return r; + + uint8_t saved_timestamp[6]; + uint8_t saved_parttable[70]; + memcpy(saved_timestamp, sector0 + 218, 6); + memcpy(saved_parttable, sector0 + 440, 70); + + const uint8_t *src = (const uint8_t *)sys_data; + + memcpy(sector0, src, 512); + + memcpy(sector0 + 218, saved_timestamp, 6); + memcpy(sector0 + 440, saved_parttable, 70); + + uint64_t stage2_loc = 512; + memcpy(sector0 + 0x1A4, &stage2_loc, 8); + + uint32_t stage2_bytes = sys_size - 512; + uint32_t stage2_sectors = (stage2_bytes + 511) / 512; + + if (1 + stage2_sectors >= 2048) return -ENOSPC; + + uint8_t sector_buf[512]; + for (uint32_t i = 0; i < stage2_sectors; i++) { + uint32_t off = i * 512; + uint32_t take = (stage2_bytes - off >= 512) ? 512 : (stage2_bytes - off); + memset(sector_buf, 0, 512); + memcpy(sector_buf, src + 512 + off, take); + r = dev->ops->write_sectors(dev, 1 + i, 1, sector_buf); + if (r < 0) return r; + } + + r = dev->ops->write_sectors(dev, 0, 1, sector0); + if (r < 0) return r; + + if (dev->ops->flush) dev->ops->flush(dev); + + serial_printf("[bios-install] deployed: stage1=512B at LBA 0, stage2=%uB at LBA 1..%u\n", + stage2_bytes, stage2_sectors); + return 0; +} + +int64_t sys_disk_list_parts(uint64_t out_ptr, uint64_t max, + uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) +{ + (void)a3; (void)a4; (void)a5; (void)a6; + if (!user_ptr_ok(out_ptr)) return -EFAULT; + if (max == 0) return -EINVAL; + + cervus_part_info_t *out = (cervus_part_info_t *)out_ptr; + int total = blkdev_count(); + uint64_t written = 0; + + for (int i = 0; i < total && written < max; i++) { + blkdev_t *d = blkdev_get(i); + if (!d || !d->present) continue; + + size_t nlen = strlen(d->name); + bool is_part = false; + if (nlen >= 2) { + for (size_t k = 0; k < nlen; k++) { + if (d->name[k] >= '0' && d->name[k] <= '9') { is_part = true; break; } + } + } + cervus_part_info_t info; + memset(&info, 0, sizeof(info)); + strncpy(info.part_name, d->name, sizeof(info.part_name) - 1); + if (is_part) { + char base[32]; int bi = 0; + for (size_t k = 0; k < nlen && bi < 31; k++) { + if (d->name[k] >= '0' && d->name[k] <= '9') break; + base[bi++] = d->name[k]; + } + base[bi] = '\0'; + strncpy(info.disk_name, base, sizeof(info.disk_name) - 1); + info.part_num = (uint32_t)atoi(d->name + bi); + } else { + strncpy(info.disk_name, d->name, sizeof(info.disk_name) - 1); + info.part_num = 0; + } + info.size_bytes = d->size_bytes; + info.sector_count = d->sector_count; + info.lba_start = 0; + info.type = 0; + info.bootable = 0; + memcpy(&out[written], &info, sizeof(info)); + written++; + } + return (int64_t)written; +} + +int64_t sys_unlink(uint64_t path_ptr, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a2;(void)a3;(void)a4;(void)a5;(void)a6; + char path[256]; + if (disk_strncpy_from_user(path, (const char *)path_ptr, sizeof(path)) < 0) return -EFAULT; + char dirpath[256]; strncpy(dirpath, path, 255); + char *slash = NULL; + for (int i = (int)strlen(dirpath)-1; i >= 0; i--) { if (dirpath[i]=='/') { slash=&dirpath[i]; break; } } + if (!slash) return -EINVAL; + char name[256]; strncpy(name, slash+1, 255); + if (slash==dirpath) dirpath[1]='\0'; else *slash='\0'; + vnode_t *dir = NULL; + int r = vfs_lookup(dirpath, &dir); + if (r<0) return r; + if (!dir->ops || !dir->ops->unlink) { vnode_unref(dir); return -ENOSYS; } + r = dir->ops->unlink(dir, name); + vnode_unref(dir); + if (r == 0) vfs_sync_all(); + return r; +} + +int64_t sys_rmdir(uint64_t p, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + return sys_unlink(p,a2,a3,a4,a5,a6); +} + +int64_t sys_mkdir(uint64_t path_ptr, uint64_t mode, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a3;(void)a4;(void)a5;(void)a6; + char path[256]; + if (disk_strncpy_from_user(path, (const char *)path_ptr, sizeof(path)) < 0) return -EFAULT; + int r = vfs_mkdir(path, (uint32_t)mode); + if (r == 0) vfs_sync_all(); + return r; +} + +int64_t sys_rename(uint64_t old_ptr, uint64_t new_ptr, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a3;(void)a4;(void)a5;(void)a6; + char oldp[256], newp[256]; + if (disk_strncpy_from_user(oldp, (const char *)old_ptr, 256) < 0) return -EFAULT; + if (disk_strncpy_from_user(newp, (const char *)new_ptr, 256) < 0) return -EFAULT; + + vnode_t *src_node = NULL; + int r = vfs_lookup(oldp, &src_node); + if (r < 0) return r; + + vnode_t *dst_node = NULL; + if (vfs_lookup(newp, &dst_node) == 0) { + if (dst_node->type == VFS_NODE_DIR) { + const char *base = oldp; + for (const char *p = oldp; *p; p++) if (*p == '/') base = p + 1; + size_t dlen = strlen(newp); + if (dlen + 1 + strlen(base) < 255) { + if (newp[dlen-1] != '/') { newp[dlen] = '/'; newp[dlen+1] = '\0'; dlen++; } + strncat(newp, base, 254 - dlen); + } + } + vnode_unref(dst_node); + } + + vfs_file_t *src_f = NULL, *dst_f = NULL; + r = vfs_open(oldp, O_RDONLY, 0, &src_f); + if (r < 0) { vnode_unref(src_node); return r; } + r = vfs_open(newp, O_WRONLY | O_CREAT | O_TRUNC, src_node->mode, &dst_f); + if (r < 0) { vfs_close(src_f); vnode_unref(src_node); return r; } + char buf[512]; int64_t n; + while ((n = vfs_read(src_f, buf, sizeof(buf))) > 0) vfs_write(dst_f, buf, (size_t)n); + vfs_close(src_f); + vfs_close(dst_f); + vnode_unref(src_node); + + char dirp[256]; strncpy(dirp, oldp, 255); dirp[255] = '\0'; + char *sl = NULL; + for (int i = (int)strlen(dirp)-1; i >= 0; i--) if (dirp[i] == '/') { sl = &dirp[i]; break; } + if (sl) { + char nm[256]; strncpy(nm, sl+1, 255); + if (sl == dirp) dirp[1] = '\0'; else *sl = '\0'; + vnode_t *dir = NULL; + if (vfs_lookup(dirp, &dir) == 0) { + if (dir->ops && dir->ops->unlink) dir->ops->unlink(dir, nm); + vnode_unref(dir); + } + } + vfs_sync_all(); + return 0; +} + +int64_t sys_list_mounts(uint64_t a1, uint64_t a2, uint64_t a3, + uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a3; (void)a4; (void)a5; (void)a6; + vfs_mount_info_t *out = (vfs_mount_info_t *)a1; + int max = (int)a2; + if (!out || max <= 0) return -EINVAL; + return vfs_list_mounts(out, max); +} + +int64_t sys_statvfs(uint64_t a1, uint64_t a2, uint64_t a3, + uint64_t a4, uint64_t a5, uint64_t a6) { + (void)a3; (void)a4; (void)a5; (void)a6; + const char *path = (const char *)a1; + vfs_statvfs_t *out = (vfs_statvfs_t *)a2; + if (!path || !out) return -EINVAL; + return vfs_statvfs(path, out); +} \ No newline at end of file diff --git a/kernel/src/syscall/syscall.c b/kernel/src/syscall/syscall.c new file mode 100644 index 0000000..bae9b21 --- /dev/null +++ b/kernel/src/syscall/syscall.c @@ -0,0 +1,1313 @@ +#include "../../include/syscall/syscall.h" +#include "../../include/syscall/syscall_nums.h" +#include "../../include/syscall/errno.h" +#include "../../include/acpi/acpi.h" +#include "../../include/sched/sched.h" +#include "../../include/sched/capabilities.h" +#include "../../include/smp/smp.h" +#include "../../include/smp/percpu.h" +#include "../../include/apic/apic.h" +#include "../../include/gdt/gdt.h" +#include "../../include/memory/vmm.h" +#include "../../include/memory/pmm.h" +#include "../../include/io/serial.h" +#include "../../include/fs/vfs.h" +#include "../../include/elf/elf.h" +#include "../../include/drivers/disk.h" +#include "../../include/drivers/blkdev.h" +#include "../../include/drivers/ata.h" +#include "../../include/panic/panic.h" +#include "../include/io/ports.h" +#include +#include +#include +#include + +#define MSR_EFER 0xC0000080 +#define MSR_STAR 0xC0000081 +#define MSR_LSTAR 0xC0000082 +#define MSR_SFMASK 0xC0000084 +#define EFER_SCE (1ULL << 0) +#define EFER_NXE (1ULL << 11) + +static inline uint64_t rdmsr(uint32_t msr) { + uint32_t lo, hi; + asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr)); + return ((uint64_t)hi << 32) | lo; +} +static inline void wrmsr(uint32_t msr, uint64_t val) { + asm volatile("wrmsr" :: "c"(msr), "a"((uint32_t)val), + "d"((uint32_t)(val >> 32))); +} + +static inline task_t* cur_task(void) { + percpu_t* pc = get_percpu(); + return pc ? (task_t*)pc->current_task : NULL; +} + +static void save_user_regs(task_t* t) { + if (!t) return; + percpu_t* pc = get_percpu(); + if (!pc) return; + t->user_rsp = pc->syscall_user_rsp; + t->user_saved_rip = pc->user_saved_rip; + t->user_saved_rbp = pc->user_saved_rbp; + t->user_saved_rbx = pc->user_saved_rbx; + t->user_saved_r12 = pc->user_saved_r12; + t->user_saved_r13 = pc->user_saved_r13; + t->user_saved_r14 = pc->user_saved_r14; + t->user_saved_r15 = pc->user_saved_r15; + t->user_saved_r11 = pc->user_saved_r11; +} + +static bool uptr_validate(const void* ptr, size_t len) { + uintptr_t addr = (uintptr_t)ptr; + if (addr < 0x1000ULL) return false; + if (addr >= 0x0000800000000000ULL) return false; + if (len > 0x0000800000000000ULL) return false; + if (len && addr + len - 1 < addr) return false; + return true; +} + +static int copy_from_user(void* dst, const void* src, size_t n) { + if (!uptr_validate(src, n)) return -EFAULT; + memcpy(dst, src, n); return 0; +} +static int copy_to_user(void* dst, const void* src, size_t n) { + if (!uptr_validate(dst, n)) return -EFAULT; + memcpy(dst, src, n); return 0; +} +static int strncpy_from_user(char* dst, const char* src, size_t max) { + if (!uptr_validate(src, 1)) return -EFAULT; + for (size_t i = 0; i < max - 1; i++) { + if ((i == 0) || (!((uintptr_t)(src+i) & 0xFFF))) + if (!uptr_validate(src+i, 1)) return -EFAULT; + dst[i] = src[i]; + if (!dst[i]) return (int)i; + } + dst[max-1] = '\0'; + return (int)(max-1); +} + +static int64_t sys_exit(uint64_t code) { + task_t* t = cur_task(); + if (t) t->exit_code = (int)(uint8_t)code; + serial_printf("[SYSCALL] exit(%llu) task='%s' pid=%u\n", + code, t?t->name:"?", t?t->pid:0); + vmm_switch_pagemap(vmm_get_kernel_pagemap()); + task_exit(); +} +static int64_t sys_exit_group(uint64_t code) { return sys_exit(code); } + +static int64_t sys_getpid(void) { task_t*t=cur_task(); return t?(int64_t)t->pid:-ESRCH; } +static int64_t sys_getppid(void) { task_t*t=cur_task(); return t?(int64_t)t->ppid:-ESRCH; } +static int64_t sys_getuid(void) { task_t*t=cur_task(); return t?(int64_t)t->uid:-ESRCH; } +static int64_t sys_getgid(void) { task_t*t=cur_task(); return t?(int64_t)t->gid:-ESRCH; } + +static int64_t sys_setuid(uint64_t u) { + task_t*t=cur_task(); if(!t) return -ESRCH; + if(t->uid!=UID_ROOT && !cap_has(t->capabilities,CAP_SETUID)) return -EPERM; + if(u>65535) return -EINVAL; + t->uid=(uint32_t)u; return 0; +} +static int64_t sys_setgid(uint64_t g) { + task_t*t=cur_task(); if(!t) return -ESRCH; + if(t->uid!=UID_ROOT && !cap_has(t->capabilities,CAP_SETUID)) return -EPERM; + if(g>65535) return -EINVAL; + t->gid=(uint32_t)g; return 0; +} + +static int64_t sys_cap_get(void) { + task_t*t=cur_task(); return t?(int64_t)t->capabilities:-ESRCH; +} +static int64_t sys_cap_drop(uint64_t mask) { + task_t*t=cur_task(); if(!t) return -ESRCH; + t->capabilities=cap_drop(t->capabilities,mask); + serial_printf("[SYSCALL] cap_drop: pid=%u caps=0x%llx\n",t->pid,t->capabilities); + return 0; +} + +static int64_t sys_task_info(uint64_t pid_arg, uint64_t buf_ptr) { + if (!buf_ptr) return -EINVAL; + task_t* target = (pid_arg==0)?cur_task():task_find_by_pid((uint32_t)pid_arg); + if (!target) return -ESRCH; + task_t* me = cur_task(); + if (me && me!=target && !cap_has(me->capabilities,CAP_TASK_INFO)) return -EPERM; + cervus_task_info_t info; memset(&info,0,sizeof(info)); + info.pid=target->pid; info.ppid=target->ppid; + info.uid=target->uid; info.gid=target->gid; + info.capabilities=target->capabilities; + info.state=(uint32_t)target->state; info.priority=(uint32_t)target->priority; + info.total_runtime_ns=target->total_runtime; + strncpy(info.name,target->name,sizeof(info.name)-1); + return copy_to_user((void*)buf_ptr,&info,sizeof(info)); +} + +static int64_t sys_task_kill(uint64_t pid_arg) { + task_t*me=cur_task(); + task_t*target=task_find_by_pid((uint32_t)pid_arg); + if (!target) return -ESRCH; + bool own=(target->ppid==(me?me->pid:0)); + if (!own && !cap_has(me?me->capabilities:0,CAP_KILL_ANY)) return -EPERM; + task_kill(target); return 0; +} + +static int64_t sys_fork(void) { + task_t*parent=cur_task(); if(!parent) return -ESRCH; + save_user_regs(parent); + task_t*child=task_fork(parent); if(!child) return -ENOMEM; + serial_printf("[SYSCALL] fork: parent pid=%u → child pid=%u\n", + parent->pid,child->pid); + return (int64_t)child->pid; +} + +static int64_t sys_yield(void) { task_yield(); return 0; } + +static int64_t sys_wait(uint64_t pid_arg, uint64_t status_ptr, uint64_t flags) { + task_t*parent=cur_task(); if(!parent) return -ESRCH; +retry:; + task_t*zombie=NULL; + { + uint64_t _cf = spinlock_acquire_irqsave(&children_lock); + task_t*child=parent->children; + bool has_children = (child != NULL); + while (child) { + bool match=(pid_arg==(uint64_t)-1)||(child->pid==(uint32_t)pid_arg); + if (match && child->state==TASK_ZOMBIE) { zombie=child; break; } + child=child->sibling; + } + spinlock_release_irqrestore(&children_lock, _cf); + + (void)has_children; + } + + if (!zombie) { + if (flags & WNOHANG) return 0; + save_user_regs(parent); + parent->wait_for_pid=(pid_arg==(uint64_t)-1)?(uint32_t)-1:(uint32_t)pid_arg; + parent->runnable=false; parent->state=TASK_BLOCKED; + if (pid_arg != (uint64_t)-1) + task_set_foreground((uint32_t)pid_arg); + + serial_printf("[WAIT] pid=%u blocking: user_rsp=0x%llx task_rsp=0x%llx\n", + parent->pid,parent->user_rsp,parent->rsp); + sched_reschedule(); goto retry; + } + + if (status_ptr) { + int status=(zombie->exit_code&0xFF)<<8; + if (copy_to_user((void*)status_ptr,&status,sizeof(int))<0) return -EFAULT; + } + uint32_t zpid=zombie->pid; + + { + uint64_t _cf = spinlock_acquire_irqsave(&children_lock); + if (parent->children == zombie) { + parent->children = zombie->sibling; + } else { + task_t *prev = parent->children; + while (prev && prev->sibling != zombie) prev = prev->sibling; + if (prev) prev->sibling = zombie->sibling; + } + zombie->sibling = NULL; + zombie->parent = NULL; + spinlock_release_irqrestore(&children_lock, _cf); + } + + serial_printf("[SYSCALL] wait: parent pid=%u reaped child pid=%u\n", + parent->pid,zpid); + + if (g_foreground_pid == zpid) + task_set_foreground(0); + task_destroy(zombie); + serial_printf("[SYSCALL] wait: task_destroy done, returning %u\n", zpid); + serial_printf("[WAIT-RET] parent pid=%u user_saved_rip=0x%llx user_rsp=0x%llx\n", + parent->pid, parent->user_saved_rip, parent->user_rsp); + return (int64_t)zpid; +} + +#define EXECVE_MAX_PATH 512 +#define EXECVE_MAX_ARGS 128 +#define EXECVE_MAX_ARGLEN 4096 +#define AT_NULL 0 +#define AT_PHDR 3 +#define AT_PHENT 4 +#define AT_PHNUM 5 +#define AT_PAGESZ 6 +#define AT_ENTRY 9 + +static uintptr_t execve_build_stack(vmm_pagemap_t *map, uintptr_t stack_top, const char *argv[], int argc, const elf_load_result_t *elf) { + size_t str_total = 0; + for (int i = 0; i < argc; i++) str_total += strlen(argv[i]) + 1; + + size_t n_auxv = 6; + size_t ptr_count = 1 + (size_t)argc + 1 + 1 + (n_auxv * 2); + size_t frame_bytes = ptr_count * 8; + + uintptr_t aligned_top = stack_top & ~(uintptr_t)0xF; + uintptr_t strings_base = (aligned_top - str_total) & ~(uintptr_t)0xF; + + uintptr_t candidate = strings_base - frame_bytes; + uintptr_t new_rsp = ((candidate - 8) & ~(uintptr_t)0xF) + 8; + + if (new_rsp + frame_bytes > strings_base) { + new_rsp -= 16; + } + + uintptr_t page_base = new_rsp & ~(uintptr_t)0xFFF; + uintptr_t page_end = (stack_top + 0xFFFULL) & ~(uintptr_t)0xFFF; + size_t total_pages = (page_end - page_base) >> 12; + size_t kbuf_size = total_pages * 0x1000; + + if (new_rsp + frame_bytes > strings_base || + strings_base + str_total > stack_top) { + return 0; + } + + uint8_t *kbuf = (uint8_t*)malloc(kbuf_size); + if (!kbuf) return 0; + memset(kbuf, 0, kbuf_size); + + uint64_t argv_user[EXECVE_MAX_ARGS + 1]; + size_t str_off = 0; + for (int i = 0; i < argc; i++) { + size_t slen = strlen(argv[i]) + 1; + memcpy(kbuf + (strings_base + str_off - page_base), argv[i], slen); + argv_user[i] = strings_base + str_off; + str_off += slen; + } + + uint64_t frame[256]; + size_t fi = 0; + frame[fi++] = (uint64_t)argc; + for (int i = 0; i < argc; i++) frame[fi++] = argv_user[i]; + frame[fi++] = 0; + frame[fi++] = 0; + frame[fi++] = AT_PHDR; frame[fi++] = elf->load_base + 0x40; + frame[fi++] = AT_PHENT; frame[fi++] = 56; + frame[fi++] = AT_PHNUM; frame[fi++] = 0; + frame[fi++] = AT_ENTRY; frame[fi++] = elf->entry; + frame[fi++] = AT_PAGESZ; frame[fi++] = 4096; + frame[fi++] = AT_NULL; frame[fi++] = 0; + + memcpy(kbuf + (new_rsp - page_base), frame, fi * 8); + + for (size_t pi = 0; pi < total_pages; pi++) { + uintptr_t virt = page_base + pi * 0x1000; + uintptr_t phys = 0; + uint64_t pf = 0; + + if (!vmm_get_page_flags(map, virt, &pf) || !(pf & VMM_PRESENT)) { + void *pg = pmm_alloc_zero(1); + if (!pg) { free(kbuf); return 0; } + phys = pmm_virt_to_phys(pg); + if (!vmm_map_page(map, virt, phys, + VMM_PRESENT | VMM_WRITE | VMM_USER | VMM_NOEXEC)) { + pmm_free(pg, 1); + free(kbuf); + return 0; + } + } else { + if (!vmm_virt_to_phys(map, virt, &phys)) + { free(kbuf); return 0; } + phys &= ~(uintptr_t)0xFFF; + } + memcpy(pmm_phys_to_virt(phys), kbuf + pi * 0x1000, 0x1000); + } + + serial_printf("[EXECVE] stack built: page_base=0x%llx rsp=0x%llx argc=%d envc=0\n", + (unsigned long long)page_base, (unsigned long long)new_rsp, argc); + free(kbuf); + return new_rsp; +} + +static int64_t sys_execve(uint64_t path_ptr, uint64_t argv_ptr, uint64_t envp_ptr) { + (void)envp_ptr; + task_t *t = cur_task(); + if (!t || !t->is_userspace) return -EPERM; + + char kpath[EXECVE_MAX_PATH]; + if (strncpy_from_user(kpath, (const char*)path_ptr, sizeof(kpath)) < 0) return -EFAULT; + if (!kpath[0]) return -ENOENT; + serial_printf("[EXECVE] pid=%u execve(\"%s\")\n", t->pid, kpath); + + const char *kargv_ptrs[EXECVE_MAX_ARGS + 1]; + char (*kargv_store)[EXECVE_MAX_ARGLEN] = malloc(EXECVE_MAX_ARGS * EXECVE_MAX_ARGLEN); + if (!kargv_store) return -ENOMEM; + int argc = 0; + + if (argv_ptr) { + for (;;) { + if (argc >= EXECVE_MAX_ARGS) { free(kargv_store); return -E2BIG; } + uint64_t uslot = argv_ptr + (uint64_t)argc * 8; + uint64_t aptr = 0; + if (copy_from_user(&aptr, (const void*)uslot, 8) < 0) + { free(kargv_store); return -EFAULT; } + if (!aptr) break; + if (strncpy_from_user(kargv_store[argc], (const char*)aptr, EXECVE_MAX_ARGLEN) < 0) + { free(kargv_store); return -EFAULT; } + kargv_ptrs[argc] = kargv_store[argc]; argc++; + } + } + kargv_ptrs[argc] = NULL; + if (argc == 0) { + strncpy(kargv_store[0], kpath, EXECVE_MAX_ARGLEN-1); + kargv_store[0][EXECVE_MAX_ARGLEN-1] = '\0'; + kargv_ptrs[0] = kargv_store[0]; kargv_ptrs[1] = NULL; argc = 1; + } + + vfs_file_t *vfile = NULL; + int vret = vfs_open(kpath, O_RDONLY, 0, &vfile); + if (vret < 0) { serial_printf("[EXECVE] open failed: %d\n",vret); free(kargv_store); return (int64_t)vret; } + vfs_stat_t st; + if (vfs_fstat(vfile,&st)<0 || st.st_size==0) { serial_printf("[EXECVE] fstat/size failed: path='%s' size=%llu\n", kpath, (unsigned long long)st.st_size); vfs_close(vfile); free(kargv_store); return -EIO; } + size_t fsize = (size_t)st.st_size; + uint8_t *elf_data = malloc(fsize); + if (!elf_data) { serial_printf("[EXECVE] malloc(%zu) failed for path='%s'\n", fsize, kpath); vfs_close(vfile); free(kargv_store); return -ENOMEM; } + int64_t nr = vfs_read(vfile, elf_data, fsize); vfs_close(vfile); + if (nr<0 || (size_t)nr!=fsize) { serial_printf("[EXECVE] read failed: path='%s' expected=%zu got=%lld\n", kpath, fsize, (long long)nr); free(elf_data); free(kargv_store); return -EIO; } + if (fsize < 4 || elf_data[0] != 0x7F || elf_data[1] != 'E' || elf_data[2] != 'L' || elf_data[3] != 'F') { + serial_printf("[EXECVE] not an ELF: path='%s' magic=%02x%02x%02x%02x\n", + kpath, + fsize > 0 ? elf_data[0] : 0, + fsize > 1 ? elf_data[1] : 0, + fsize > 2 ? elf_data[2] : 0, + fsize > 3 ? elf_data[3] : 0); + free(elf_data); free(kargv_store); return -ENOEXEC; + } + + elf_load_result_t elf = elf_load(elf_data, fsize, 0); free(elf_data); + if (elf.error != ELF_OK) { + serial_printf("[EXECVE] elf_load: %s\n",elf_strerror(elf.error)); + if (elf.pagemap) vmm_free_pagemap(elf.pagemap); + free(kargv_store); return -ENOEXEC; + } + + uintptr_t new_rsp = execve_build_stack(elf.pagemap, elf.stack_top, kargv_ptrs, argc, &elf); + free(kargv_store); + if (!new_rsp) { vmm_free_pagemap(elf.pagemap); return -ENOMEM; } + + if (t->fd_table) fd_table_cloexec(t->fd_table); + + vmm_switch_pagemap(vmm_get_kernel_pagemap()); + + vmm_pagemap_t* old_pagemap = t->pagemap; + uint32_t old_flags = t->flags; + + t->pagemap = elf.pagemap; + t->cr3 = (uint64_t)pmm_virt_to_phys(elf.pagemap->pml4); + t->flags |= TASK_FLAG_OWN_PAGEMAP; + t->flags &= ~TASK_FLAG_FORK; + t->brk_start = t->brk_current = elf.load_end; + t->brk_max = 0x0000700000000000ULL; + + t->user_rsp = new_rsp; + t->user_saved_rip = elf.entry; + t->user_saved_rbp = t->user_saved_rbx = 0; + t->user_saved_r12 = t->user_saved_r13 = t->user_saved_r14 = 0; + t->user_saved_r15 = t->user_saved_r11 = 0; + + const char *bn = kpath; + for (const char *p=kpath;*p;p++) if (*p=='/') bn=p+1; + strncpy(t->name, bn, sizeof(t->name)-1); t->name[sizeof(t->name)-1]='\0'; + + percpu_t *pc = get_percpu(); + if (pc) { + pc->syscall_user_rsp = new_rsp; + pc->user_saved_rip = elf.entry; + pc->user_saved_rbp = pc->user_saved_rbx = 0; + pc->user_saved_r12 = pc->user_saved_r13 = pc->user_saved_r14 = 0; + pc->user_saved_r15 = 0; + pc->user_saved_r11 = 0x200; + } + + if (old_pagemap && (old_flags & (TASK_FLAG_OWN_PAGEMAP|TASK_FLAG_FORK))) + vmm_free_pagemap(old_pagemap); + + asm volatile("lock addl $0, (%%rsp)" ::: "memory", "cc"); + vmm_switch_pagemap(t->pagemap); + serial_printf("[EXECVE] exec ok: entry=0x%llx rsp=0x%llx name='%s'\n", + elf.entry, new_rsp, t->name); + return 0; +} + +static int64_t sys_write(uint64_t fd, uint64_t buf_ptr, uint64_t count) { + if (count == 0) return 0; + if (count > 4096) count = 4096; + + char kbuf[4097]; + if (copy_from_user(kbuf, (const void*)buf_ptr, count) < 0) return -EFAULT; + kbuf[count] = '\0'; + + task_t *t = cur_task(); + if (t && t->fd_table) { + vfs_file_t *file = fd_get(t->fd_table, (int)fd); + if (file) + return vfs_write(file, kbuf, count); + } + + if (fd != 1 && fd != 2) return -EBADF; + + { + static bool at_line_start = true; + uint64_t i = 0; + while (i < count) { + uint64_t j = i; + while (j < count && kbuf[j] != '\n') j++; + bool has_newline = (j < count && kbuf[j] == '\n'); + + char chunk[4096 + 8]; + size_t clen = 0; + if (at_line_start && j > i) { + chunk[clen++] = '['; chunk[clen++] = 'U'; + chunk[clen++] = 'S'; chunk[clen++] = 'E'; + chunk[clen++] = 'R'; chunk[clen++] = ']'; + chunk[clen++] = ' '; + } + size_t seg = j - i; + if (seg > 0) { + __builtin_memcpy(chunk + clen, kbuf + i, seg); + clen += seg; + } + if (has_newline) { + chunk[clen++] = '\n'; + at_line_start = true; + i = j + 1; + } else { + if (seg > 0) at_line_start = false; + i = j; + } + if (clen > 0) { + serial_writebuf(chunk, clen); + printf("%.*s", (int)clen, chunk); + } + if (!has_newline) break; + } + } + return (int64_t)count; +} + +static int64_t sys_read(uint64_t fd, uint64_t buf_ptr, uint64_t count) { + if (count == 0) return 0; + if (count > 65536) count = 65536; + + task_t *t = cur_task(); + if (!t) return -ESRCH; + if (!uptr_validate((void*)buf_ptr, count)) return -EFAULT; + + vfs_file_t *file = NULL; + if (t->fd_table) file = fd_get(t->fd_table, (int)fd); + if (!file) return -EBADF; + + char kbuf[4096]; + size_t chunk = count > 4096 ? 4096 : count; + int64_t r = vfs_read(file, kbuf, chunk); + if (r <= 0) return r; + memcpy((void*)buf_ptr, kbuf, (size_t)r); + return r; +} + +static int64_t sys_open(uint64_t path_ptr, uint64_t flags, uint64_t mode) { + task_t *t = cur_task(); + if (!t) return -ESRCH; + if (!t->fd_table) return -ENOMEM; + + char kpath[VFS_MAX_PATH]; + if (strncpy_from_user(kpath, (const char*)path_ptr, sizeof(kpath)) < 0) return -EFAULT; + if (!kpath[0]) return -ENOENT; + + vfs_file_t *file = NULL; + int ret = vfs_open(kpath, (int)flags, (uint32_t)mode, &file); + if (ret < 0) return (int64_t)ret; + + int newfd = fd_alloc(t->fd_table, file, 0); + if (newfd < 0) { vfs_close(file); return -EMFILE; } + return (int64_t)newfd; +} + +static int64_t sys_close(uint64_t fd) { + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + return (int64_t)fd_close(t->fd_table, (int)fd); +} + +static int64_t sys_seek(uint64_t fd, uint64_t offset, uint64_t whence) { + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + vfs_file_t *f = fd_get(t->fd_table, (int)fd); + if (!f) return -EBADF; + return vfs_seek(f, (int64_t)offset, (int)whence); +} + +static int64_t sys_stat(uint64_t path_ptr, uint64_t stat_ptr) { + if (!stat_ptr) return -EINVAL; + char kpath[VFS_MAX_PATH]; + if (strncpy_from_user(kpath, (const char*)path_ptr, sizeof(kpath)) < 0) return -EFAULT; + vfs_stat_t st; + int r = vfs_stat(kpath, &st); + if (r < 0) return (int64_t)r; + return copy_to_user((void*)stat_ptr, &st, sizeof(st)); +} + +static int64_t sys_fstat(uint64_t fd, uint64_t stat_ptr) { + if (!stat_ptr) return -EINVAL; + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + vfs_file_t *f = fd_get(t->fd_table, (int)fd); + if (!f) return -EBADF; + vfs_stat_t st; + int r = vfs_fstat(f, &st); + if (r < 0) return (int64_t)r; + return copy_to_user((void*)stat_ptr, &st, sizeof(st)); +} + +#define IOCTL_KBUF_MAX 128 + +#define TIOCGWINSZ 0x5413 +#define TIOCGCURSOR 0x5480 +#define TCGETS 0x5401 +#define TCSETS 0x5402 +#define TCSETSW 0x5403 +#define TCSETSF 0x5404 + +#define IOCTL_TERMIOS_SIZE 48 + +static size_t ioctl_out_size(uint64_t request) { + switch (request) { + case TIOCGWINSZ: return 8; + case TIOCGCURSOR: return 8; + case TCGETS: return IOCTL_TERMIOS_SIZE; + default: return 0; + } +} + +static size_t ioctl_in_size(uint64_t request) { + switch (request) { + case TCSETS: + case TCSETSW: + case TCSETSF: return IOCTL_TERMIOS_SIZE; + default: return 0; + } +} + +static int64_t sys_ioctl(uint64_t fd, uint64_t request, uint64_t arg_ptr) { + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + vfs_file_t *f = fd_get(t->fd_table, (int)fd); + if (!f) return -EBADF; + + size_t out_sz = ioctl_out_size(request); + size_t in_sz = ioctl_in_size(request); + + if (arg_ptr) { + size_t validate_sz = out_sz > in_sz ? out_sz : in_sz; + if (validate_sz == 0) validate_sz = IOCTL_KBUF_MAX; + if (!uptr_validate((void *)arg_ptr, validate_sz)) + return -EFAULT; + } + + char kbuf[IOCTL_KBUF_MAX]; + memset(kbuf, 0, sizeof(kbuf)); + + if (arg_ptr && in_sz > 0) { + if (copy_from_user(kbuf, (const void *)arg_ptr, in_sz) < 0) + return -EFAULT; + } + + int64_t r = vfs_ioctl(f, request, arg_ptr ? (void *)kbuf : (void *)0); + if (r < 0) return r; + + if (arg_ptr && out_sz > 0) { + if (copy_to_user((void *)arg_ptr, kbuf, out_sz) < 0) + return -EFAULT; + } + return r; +} + +static int64_t sys_readdir(uint64_t fd, uint64_t dirent_ptr) { + if (!dirent_ptr) return -EINVAL; + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + vfs_file_t *f = fd_get(t->fd_table, (int)fd); + if (!f) return -EBADF; + vfs_dirent_t kd; + int r = vfs_readdir(f, &kd); + if (r < 0) return (int64_t)r; + return copy_to_user((void*)dirent_ptr, &kd, sizeof(kd)); +} + +static int64_t sys_dup(uint64_t fd) { + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + vfs_file_t *f = fd_get(t->fd_table, (int)fd); + if (!f) return -EBADF; + if (f->vnode && f->vnode->ops && f->vnode->ops->ref) + f->vnode->ops->ref(f->vnode); + int nfd = fd_alloc(t->fd_table, f, 0); + if (nfd < 0) { + if (f->vnode && f->vnode->ops && f->vnode->ops->unref) + f->vnode->ops->unref(f->vnode); + return -EMFILE; + } + return (int64_t)nfd; +} + +static int64_t sys_dup2(uint64_t oldfd, uint64_t newfd) { + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + int r = fd_dup2(t->fd_table, (int)oldfd, (int)newfd); + return r < 0 ? (int64_t)r : (int64_t)newfd; +} + +#define PIPE_BUFSZ 4096 + +typedef struct { + char buf[PIPE_BUFSZ]; + uint32_t head, tail; + int readers, writers; + uint32_t reader_waiting_pid; + spinlock_t lock; +} pipe_shared_t; + +typedef struct { + pipe_shared_t *shared; + int end; +} pipe_vdata_t; + +static int64_t pipe_read_op(vnode_t *n, void *buf, size_t len, uint64_t off) { + (void)off; + pipe_vdata_t *vd = (pipe_vdata_t*)n->fs_data; + pipe_shared_t *ps = vd->shared; + size_t got = 0; + char *dst = (char*)buf; + while (got < len) { + if (ps->head == ps->tail) { + if (ps->writers == 0) break; + if (got > 0) break; + + task_t *me = cur_task(); + if (me) { + save_user_regs(me); + ps->reader_waiting_pid = me->pid; + me->runnable = false; + me->state = TASK_BLOCKED; + } + sched_reschedule(); + if (me) ps->reader_waiting_pid = 0; + if (me && me->pending_kill) return got > 0 ? (int64_t)got : -EINTR; + continue; + } + dst[got++] = ps->buf[ps->head]; + ps->head = (ps->head + 1) % PIPE_BUFSZ; + } + return (int64_t)got; +} + +static int64_t pipe_write_op(vnode_t *n, const void *buf, size_t len, uint64_t off) { + (void)off; + pipe_vdata_t *vd = (pipe_vdata_t*)n->fs_data; + pipe_shared_t *ps = vd->shared; + if (ps->readers == 0) return -EPIPE; + const char *src = (const char*)buf; + for (size_t i = 0; i < len; i++) { + uint32_t next = (ps->tail + 1) % PIPE_BUFSZ; + int spins = 0; + while (next == ps->head) { + if (ps->readers == 0) return (i > 0) ? (int64_t)i : -EPIPE; + task_t *me = cur_task(); + if (me) save_user_regs(me); + task_yield(); + if (me && me->pending_kill) return (i > 0) ? (int64_t)i : -EINTR; + next = (ps->tail + 1) % PIPE_BUFSZ; + spins++; + if (spins > 10000) break; + } + if (next == ps->head) { + return (i > 0) ? (int64_t)i : -EAGAIN; + } + ps->buf[ps->tail] = src[i]; + ps->tail = next; + if (ps->reader_waiting_pid) { + task_t *reader = task_find_by_pid(ps->reader_waiting_pid); + if (reader && !reader->runnable) { + task_unblock(reader); + } + } + } + return (int64_t)len; +} + +static int pipe_stat_op(vnode_t *n, vfs_stat_t *out) { + memset(out,0,sizeof(*out)); + out->st_ino = n->ino; + out->st_type = VFS_NODE_PIPE; + return 0; +} + +static void pipe_ref_op(vnode_t *n) { (void)n; } + +static void pipe_unref_op(vnode_t *n) { + pipe_vdata_t *vd = (pipe_vdata_t*)n->fs_data; + pipe_shared_t *ps = vd->shared; + if (vd->end == 0) ps->readers--; + else { + ps->writers--; + if (ps->reader_waiting_pid) { + task_t *reader = task_find_by_pid(ps->reader_waiting_pid); + if (reader && !reader->runnable) { + task_unblock(reader); + } + ps->reader_waiting_pid = 0; + } + } + + int r = ps->readers; + int w = ps->writers; + + free(vd); free(n); + + if (r <= 0 && w <= 0) + free(ps); +} + +static const vnode_ops_t pipe_read_ops = { + .read = pipe_read_op, + .stat = pipe_stat_op, + .ref = pipe_ref_op, + .unref = pipe_unref_op, +}; +static const vnode_ops_t pipe_write_ops = { + .write = pipe_write_op, + .stat = pipe_stat_op, + .ref = pipe_ref_op, + .unref = pipe_unref_op, +}; + +static int64_t sys_pipe(uint64_t fds_ptr) { + if (!uptr_validate((void*)fds_ptr, 2*sizeof(int))) return -EFAULT; + + task_t *t = cur_task(); + if (!t || !t->fd_table) return -ENOMEM; + + pipe_shared_t *ps = (pipe_shared_t*)malloc(sizeof(pipe_shared_t)); + if (!ps) return -ENOMEM; + memset(ps, 0, sizeof(*ps)); + ps->readers = 1; ps->writers = 1; + + vnode_t *rv = (vnode_t*) malloc(sizeof(vnode_t)); + vnode_t *wv = (vnode_t*) malloc(sizeof(vnode_t)); + pipe_vdata_t*rd = (pipe_vdata_t*)malloc(sizeof(pipe_vdata_t)); + pipe_vdata_t*wd = (pipe_vdata_t*)malloc(sizeof(pipe_vdata_t)); + if (!rv||!wv||!rd||!wd) { + free(ps);free(rv);free(wv);free(rd);free(wd); return -ENOMEM; + } + memset(rv,0,sizeof(*rv)); memset(wv,0,sizeof(*wv)); + rd->shared=ps; rd->end=0; + wd->shared=ps; wd->end=1; + + static uint64_t pipe_ino = 0x10000; + rv->type=VFS_NODE_PIPE; rv->mode=0600; rv->ino=pipe_ino++; + rv->ops=&pipe_read_ops; rv->fs_data=rd; rv->refcount=1; + wv->type=VFS_NODE_PIPE; wv->mode=0600; wv->ino=pipe_ino++; + wv->ops=&pipe_write_ops; wv->fs_data=wd; wv->refcount=1; + + vfs_file_t *rf = vfs_file_alloc(); + vfs_file_t *wf = vfs_file_alloc(); + if (!rf||!wf) { + free(ps);free(rv);free(wv);free(rd);free(wd); + if(rf) vfs_file_free(rf); + if(wf) vfs_file_free(wf); + return -ENOMEM; + } + rf->vnode=rv; rf->flags=O_RDONLY; rf->offset=0; rf->refcount=1; + wf->vnode=wv; wf->flags=O_WRONLY; wf->offset=0; wf->refcount=1; + + int rfd = fd_alloc(t->fd_table, rf, 0); + int wfd = fd_alloc(t->fd_table, wf, 0); + if (rfd<0||wfd<0) { + fd_close(t->fd_table,rfd); fd_close(t->fd_table,wfd); return -EMFILE; + } + + int fds[2] = {rfd, wfd}; + memcpy((void*)fds_ptr, fds, sizeof(fds)); + return 0; +} + +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 + +static int64_t sys_fcntl(uint64_t fd, uint64_t cmd, uint64_t arg) { + task_t *t = cur_task(); + if (!t || !t->fd_table) return -EBADF; + vfs_file_t *f = fd_get(t->fd_table, (int)fd); + if (!f) return -EBADF; + switch (cmd) { + case F_GETFD: return (int64_t)fd_get_flags(t->fd_table,(int)fd); + case F_SETFD: return (int64_t)fd_set_flags(t->fd_table,(int)fd,(int)arg); + case F_GETFL: return (int64_t)f->flags; + case F_SETFL: f->flags=(f->flags & O_ACCMODE)|((int)arg & ~O_ACCMODE); return 0; + default: return -EINVAL; + } +} + +static int64_t sys_brk(uint64_t new_brk) { + task_t *t = cur_task(); + if (!t || !t->is_userspace) return -EINVAL; + if (!new_brk) return (int64_t)t->brk_current; + if (new_brk < t->brk_start || new_brk > t->brk_max) return (int64_t)t->brk_current; + + uintptr_t old_brk = t->brk_current; + uintptr_t old_page = (old_brk + 0xFFFULL) & ~0xFFFULL; + uintptr_t new_page = (new_brk + 0xFFFULL) & ~0xFFFULL; + + if (new_brk > old_brk) { + for (uintptr_t p = old_page; p < new_page; p += 0x1000) { + void *ph = pmm_alloc_zero(1); + if (!ph) return (int64_t)t->brk_current; + if (!vmm_map_page(t->pagemap, p, pmm_virt_to_phys(ph), + VMM_PRESENT|VMM_WRITE|VMM_USER|VMM_NOEXEC)) + { pmm_free(ph,1); return (int64_t)t->brk_current; } + } + } else { + for (uintptr_t p = new_page; p < old_page; p += 0x1000) + vmm_unmap_page(t->pagemap, p); + } + t->brk_current = new_brk; + return (int64_t)new_brk; +} + +static int64_t sys_mmap(uint64_t hint, uint64_t length, uint64_t prot, uint64_t flags, uint64_t fd, uint64_t offset) { + (void)offset; + task_t *t = cur_task(); + if (!t || !t->is_userspace) return (int64_t)MAP_FAILED; + if (!(flags & MAP_ANONYMOUS)) return (int64_t)MAP_FAILED; + if (fd!=(uint64_t)-1 && fd!=0) return (int64_t)MAP_FAILED; + if (!length) return (int64_t)MAP_FAILED; + + size_t pages = (length + 0xFFFULL) >> 12; + uintptr_t addr; + if (flags & MAP_FIXED) addr = hint & ~0xFFFULL; + else if (hint) addr = hint & ~0xFFFULL; + else { addr = (t->brk_max - (uint64_t)pages*0x1000) & ~0xFFFULL; t->brk_max = addr; } + + uint64_t vf = VMM_PRESENT|VMM_USER; + if (prot & PROT_WRITE) vf |= VMM_WRITE; + if (!(prot & PROT_EXEC)) vf |= VMM_NOEXEC; + + for (size_t i = 0; i < pages; i++) { + void *ph = pmm_alloc_zero(1); + if (!ph) { for (size_t j=0;jpagemap,addr+j*0x1000); return (int64_t)MAP_FAILED; } + if (!vmm_map_page(t->pagemap, addr+i*0x1000, pmm_virt_to_phys(ph), vf)) { + pmm_free(ph,1); for(size_t j=0;jpagemap,addr+j*0x1000); return (int64_t)MAP_FAILED; + } + } + serial_printf("[SYSCALL] mmap: addr=0x%llx pages=%zu prot=0x%llx\n", addr, pages, prot); + return (int64_t)addr; +} + +static int64_t sys_munmap(uint64_t addr, uint64_t length) { + task_t *t = cur_task(); + if (!t||!t->is_userspace||addr&0xFFF||!length) return -EINVAL; + size_t pages = (length+0xFFFULL)>>12; + for (size_t i=0;ipagemap, addr+i*0x1000); + return 0; +} + +static int64_t sys_uptime(void) { return (int64_t)hpet_elapsed_ns(); } + +static int64_t sys_meminfo(uint64_t buf_ptr) { + if (!buf_ptr) return -EINVAL; + cervus_meminfo_t info; + + uint64_t usable = (uint64_t)pmm_get_usable_pages() * PAGE_SIZE; + uint64_t free = (uint64_t)pmm_get_free_pages() * PAGE_SIZE; + uint64_t used = (uint64_t)pmm_get_used_pages() * PAGE_SIZE; + + if (free > usable) free = usable; + if (used > usable) used = usable; + + info.total_bytes = usable; + info.free_bytes = free; + info.used_bytes = used; + info.usable_bytes = usable; + info.page_size = PAGE_SIZE; + return copy_to_user((void*)buf_ptr, &info, sizeof(info)); +} + +static int64_t sys_sleep_ns(uint64_t ns) { + if (ns == 0) return 0; + if (!hpet_is_available()) { + task_yield(); + return 0; + } + task_t *me = cur_task(); + if (!me) return -ESRCH; + + save_user_regs(me); + + serial_printf("[SLEEP] pid=%u sleeping %llu ns\n", me->pid, ns); + + uint64_t now = hpet_elapsed_ns(); + me->wakeup_time_ns = now + ns; + me->runnable = false; + me->state = TASK_BLOCKED; + + sched_reschedule(); + + serial_printf("[SLEEP] pid=%u woke up\n", me ? me->pid : 0); + return 0; +} + +static int _rtc_bcd2bin(int v) { return (v & 0x0F) + ((v >> 4) * 10); } + +static uint8_t _cmos_read(uint8_t reg) { + outb(0x70, reg & 0x7F); + io_wait(); + return inb(0x71); +} + +static bool _rtc_updating(void) { + outb(0x70, 0x0A); + io_wait(); + return (inb(0x71) & 0x80) != 0; +} + +static int64_t _rtc_read_unix(void) { + for (int i = 0; i < 1000 && _rtc_updating(); i++) + io_wait(); + + uint8_t sec, min, hour, mday, mon, year; + uint8_t sec2, min2; + do { + sec = _cmos_read(0x00); + min = _cmos_read(0x02); + hour = _cmos_read(0x04); + mday = _cmos_read(0x07); + mon = _cmos_read(0x08); + year = _cmos_read(0x09); + sec2 = _cmos_read(0x00); + min2 = _cmos_read(0x02); + } while (sec != sec2 || min != min2); + + uint8_t regb = _cmos_read(0x0B); + int binary_mode = (regb & 0x04); + int hour24 = (regb & 0x02); + + if (!binary_mode) { + sec = (uint8_t)_rtc_bcd2bin(sec); + min = (uint8_t)_rtc_bcd2bin(min); + mday = (uint8_t)_rtc_bcd2bin(mday); + mon = (uint8_t)_rtc_bcd2bin(mon); + year = (uint8_t)_rtc_bcd2bin(year); + if (!hour24 && (hour & 0x80)) + hour = (uint8_t)(_rtc_bcd2bin(hour & 0x7F) + 12); + else + hour = (uint8_t)_rtc_bcd2bin(hour); + } + + int iyear = year + ((year < 70) ? 2000 : 1900); + + if (sec > 59 || min > 59 || hour > 23) return 0; + if (mday < 1 || mday > 31) return 0; + if (mon < 1 || mon > 12) return 0; + if (iyear < 2000) return 0; + + static const int mdays[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; + #define IS_LEAP(y) (((y)%4==0 && (y)%100!=0) || (y)%400==0) + + int64_t days = 0; + for (int y = 1970; y < iyear; y++) + days += IS_LEAP(y) ? 366 : 365; + for (int m = 1; m < (int)mon; m++) + days += mdays[m-1] + (m == 2 && IS_LEAP(iyear) ? 1 : 0); + days += mday - 1; + + return days * 86400LL + (int64_t)hour*3600LL + (int64_t)min*60LL + (int64_t)sec; +} + +static volatile int64_t g_rtc_base_sec = 0; +static volatile uint64_t g_rtc_base_ns = 0; +static volatile bool g_rtc_initialized = false; + +static void _ensure_rtc_base(void) { + if (g_rtc_initialized) return; + int64_t t = _rtc_read_unix(); + if (t > 0) { + g_rtc_base_sec = t; + g_rtc_base_ns = hpet_elapsed_ns(); + g_rtc_initialized = true; + } +} + +static int64_t sys_clock_get(uint64_t id, uint64_t ts_ptr) { + (void)id; + if (!ts_ptr) return -EINVAL; + + cervus_timespec_t ts; + + if (id == 1) { + uint64_t ns = hpet_elapsed_ns(); + ts.tv_sec = (int64_t)(ns / 1000000000ULL); + ts.tv_nsec = (int64_t)(ns % 1000000000ULL); + } else { + _ensure_rtc_base(); + + if (g_rtc_initialized) { + uint64_t now_ns = hpet_elapsed_ns(); + uint64_t delta = now_ns - g_rtc_base_ns; + int64_t real_s = g_rtc_base_sec + (int64_t)(delta / 1000000000ULL); + uint64_t real_ns = delta % 1000000000ULL; + ts.tv_sec = real_s; + ts.tv_nsec = (int64_t)real_ns; + } else { + uint64_t ns = hpet_elapsed_ns(); + ts.tv_sec = (int64_t)(ns / 1000000000ULL); + ts.tv_nsec = (int64_t)(ns % 1000000000ULL); + } + } + + return copy_to_user((void*)ts_ptr, &ts, sizeof(ts)); +} + +static int64_t sys_dbg_print(uint64_t str, uint64_t len) { + task_t *t = cur_task(); if (!t) return -ESRCH; + if (t->uid!=UID_ROOT && !cap_has(t->capabilities,CAP_DBG_SERIAL)) return -EPERM; + if (!len) return 0; + if (len>512) len=512; + char kbuf[513]; + if (copy_from_user(kbuf,(const void*)str,len)<0) return -EFAULT; + kbuf[len]='\0'; serial_printf("[DBG pid=%u] %s",t->pid,kbuf); + return (int64_t)len; +} + +static int64_t sys_ioport_read(uint64_t port, uint64_t width) { + task_t *t=cur_task(); if(!t||!cap_has(t->capabilities,CAP_IOPORT)) return -EPERM; + if (port>0xFFFF) return -EINVAL; + uint64_t v=0; + switch(width){ + case 1:{uint8_t x;asm volatile("inb %w1,%b0":"=a"(x):"Nd"((uint16_t)port));v=x;break;} + case 2:{uint16_t x;asm volatile("inw %w1,%w0":"=a"(x):"Nd"((uint16_t)port));v=x;break;} + case 4:{uint32_t x;asm volatile("inl %w1,%k0":"=a"(x):"Nd"((uint16_t)port));v=x;break;} + default:return -EINVAL; + } + return (int64_t)v; +} +static int64_t sys_ioport_write(uint64_t port, uint64_t width, uint64_t val) { + task_t *t=cur_task(); if(!t||!cap_has(t->capabilities,CAP_IOPORT)) return -EPERM; + if (port>0xFFFF) return -EINVAL; + switch(width){ + case 1:asm volatile("outb %b0,%w1"::"a"((uint8_t)val),"Nd"((uint16_t)port));break; + case 2:asm volatile("outw %w0,%w1"::"a"((uint16_t)val),"Nd"((uint16_t)port));break; + case 4:asm volatile("outl %k0,%w1"::"a"((uint32_t)val),"Nd"((uint16_t)port));break; + default:return -EINVAL; + } + return 0; +} + +static int64_t sys_shutdown(void) { + task_t *t = cur_task(); + if (t && t->uid != 0) return -EPERM; + serial_writestring("[SYSCALL] shutdown requested\n"); + vfs_sync_all(); + acpi_shutdown(); + return 0; +} + +static int64_t sys_reboot(void) { + task_t *t = cur_task(); + if (t && t->uid != 0) return -EPERM; + serial_writestring("[SYSCALL] reboot requested\n"); + vfs_sync_all(); + acpi_reboot(); + return 0; +} + +typedef int64_t (*syscall_fn_t)(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); + +#define W0(fn) static int64_t _##fn(uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){(void)a;(void)b;(void)c;(void)d;(void)e;(void)f;return fn();} +#define W1(fn) static int64_t _##fn(uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){(void)b;(void)c;(void)d;(void)e;(void)f;return fn(a);} +#define W2(fn) static int64_t _##fn(uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){(void)c;(void)d;(void)e;(void)f;return fn(a,b);} +#define W3(fn) static int64_t _##fn(uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){(void)d;(void)e;(void)f;return fn(a,b,c);} +#define W6(fn) static int64_t _##fn(uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){return fn(a,b,c,d,e,f);} + +W1(sys_exit) W1(sys_exit_group) +W0(sys_getpid) W0(sys_getppid) +W0(sys_getuid) W0(sys_getgid) +W1(sys_setuid) W1(sys_setgid) +W0(sys_fork) W0(sys_yield) +W0(sys_cap_get) W1(sys_cap_drop) +W2(sys_task_info) W1(sys_task_kill) +W3(sys_read) W3(sys_write) +W3(sys_open) W1(sys_close) +W3(sys_seek) W2(sys_stat) +W2(sys_fstat) W1(sys_dup) +W2(sys_dup2) W1(sys_pipe) +W3(sys_fcntl) +W3(sys_ioctl) +W2(sys_readdir) +W1(sys_brk) W6(sys_mmap) +W2(sys_munmap) +W2(sys_clock_get) W1(sys_sleep_ns) W0(sys_uptime) W1(sys_meminfo) +W2(sys_dbg_print) +W2(sys_ioport_read) W3(sys_ioport_write) +W0(sys_shutdown) W0(sys_reboot) + +extern int64_t sys_disk_mount(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_umount(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_format(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_info(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_read_raw(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_write_raw(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_partition(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_mkfs_fat32(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_list_parts(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_disk_bios_install(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_unlink(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_rmdir(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_mkdir(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_rename(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_list_mounts(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +extern int64_t sys_statvfs(uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); + +static int64_t _sys_execve(uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){(void)d;(void)e;(void)f;return sys_execve(a,b,c);} +static int64_t _sys_wait (uint64_t a,uint64_t b,uint64_t c,uint64_t d,uint64_t e,uint64_t f){(void)d;(void)e;(void)f;return sys_wait(a,b,c);} + +static const syscall_fn_t syscall_table[SYSCALL_TABLE_SIZE] = { + [SYS_EXIT] = _sys_exit, + [SYS_EXIT_GROUP] = _sys_exit_group, + [SYS_GETPID] = _sys_getpid, + [SYS_GETPPID] = _sys_getppid, + [SYS_GETUID] = _sys_getuid, + [SYS_GETGID] = _sys_getgid, + [SYS_SETUID] = _sys_setuid, + [SYS_SETGID] = _sys_setgid, + [SYS_FORK] = _sys_fork, + [SYS_EXECVE] = _sys_execve, + [SYS_WAIT] = _sys_wait, + [SYS_YIELD] = _sys_yield, + [SYS_CAP_GET] = _sys_cap_get, + [SYS_CAP_DROP] = _sys_cap_drop, + [SYS_TASK_INFO] = _sys_task_info, + [SYS_TASK_KILL] = _sys_task_kill, + [SYS_READ] = _sys_read, + [SYS_WRITE] = _sys_write, + [SYS_OPEN] = _sys_open, + [SYS_CLOSE] = _sys_close, + [SYS_SEEK] = _sys_seek, + [SYS_STAT] = _sys_stat, + [SYS_FSTAT] = _sys_fstat, + [SYS_IOCTL] = _sys_ioctl, + [SYS_DUP] = _sys_dup, + [SYS_DUP2] = _sys_dup2, + [SYS_PIPE] = _sys_pipe, + [SYS_FCNTL] = _sys_fcntl, + [SYS_READDIR] = _sys_readdir, + [SYS_BRK] = _sys_brk, + [SYS_MMAP] = _sys_mmap, + [SYS_MUNMAP] = _sys_munmap, + [SYS_CLOCK_GET] = _sys_clock_get, + [SYS_SLEEP_NS] = _sys_sleep_ns, + [SYS_UPTIME] = _sys_uptime, + [SYS_MEMINFO] = _sys_meminfo, + [SYS_DBG_PRINT] = _sys_dbg_print, + [SYS_IOPORT_READ] = _sys_ioport_read, + [SYS_IOPORT_WRITE] = _sys_ioport_write, + [SYS_SHUTDOWN] = _sys_shutdown, + [SYS_REBOOT] = _sys_reboot, + [SYS_DISK_MOUNT] = sys_disk_mount, + [SYS_DISK_UMOUNT] = sys_disk_umount, + [SYS_DISK_FORMAT] = sys_disk_format, + [SYS_DISK_INFO] = sys_disk_info, + [SYS_UNLINK] = sys_unlink, + [SYS_RMDIR] = sys_rmdir, + [SYS_MKDIR] = sys_mkdir, + [SYS_RENAME] = sys_rename, + [SYS_DISK_READ_RAW] = sys_disk_read_raw, + [SYS_DISK_WRITE_RAW] = sys_disk_write_raw, + [SYS_DISK_PARTITION] = sys_disk_partition, + [SYS_DISK_MKFS_FAT32] = sys_disk_mkfs_fat32, + [SYS_DISK_LIST_PARTS] = sys_disk_list_parts, + [SYS_DISK_BIOS_INSTALL] = sys_disk_bios_install, + [SYS_LIST_MOUNTS] = sys_list_mounts, + [SYS_STATVFS] = sys_statvfs, +}; + +__attribute__((noreturn)) void sysret_bad_rip_panic(uint64_t bad_rip, uint64_t retval) { + task_t *t = cur_task(); + serial_printf("[SYSRET-PANIC] Non-canonical user RIP=0x%llx before SYSRET!\n" + " syscall retval=0x%llx task=%s pid=%u\n" + " user_saved_rip=0x%llx user_rsp=0x%llx cr3=0x%llx\n", + bad_rip, retval, + t ? t->name : "?", t ? t->pid : 0, + t ? t->user_saved_rip : 0, + t ? t->user_rsp : 0, + t ? t->cr3 : 0); + kernel_panic("SYSRET: non-canonical user RIP — would have caused triple fault"); +} + +__attribute__((noreturn)) void sysret_bad_rsp_panic(uint64_t bad_rsp, uint64_t user_rip) { + task_t *t = cur_task(); + serial_printf("[SYSRET-PANIC] Non-canonical user RSP=0x%llx before SYSRET!\n" + " user_rip=0x%llx task=%s pid=%u\n" + " user_saved_rip=0x%llx user_rsp=0x%llx cr3=0x%llx\n", + bad_rsp, user_rip, + t ? t->name : "?", t ? t->pid : 0, + t ? t->user_saved_rip : 0, + t ? t->user_rsp : 0, + t ? t->cr3 : 0); + kernel_panic("SYSRET: non-canonical user RSP — would have caused triple fault"); +} + +int64_t syscall_handler_c(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t user_rip) { + (void)user_rip; + task_t *t = cur_task(); + if (t) { + save_user_regs(t); + } + if (nr >= SYSCALL_TABLE_SIZE || !syscall_table[nr]) { + serial_printf("[SYSCALL] unknown nr=%llu\n", nr); + return -ENOSYS; + } + + int64_t ret = syscall_table[nr](a1, a2, a3, a4, a5, 0); + + task_t *me = cur_task(); + if (me && me->pending_kill) { + me->pending_kill = false; + me->exit_code = 130; + vmm_switch_pagemap(vmm_get_kernel_pagemap()); + task_exit(); + } + return ret; +} + +void syscall_init(void) { + extern void syscall_entry(void); + + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SCE | EFER_NXE); + + uint64_t star = ((uint64_t)GDT_STAR_SYSRET_BASE << 48) + | ((uint64_t)GDT_STAR_SYSCALL_CS << 32); + wrmsr(MSR_STAR, star); + wrmsr(MSR_LSTAR, (uint64_t)syscall_entry); + wrmsr(MSR_SFMASK, (1U << 9) | (1U << 10) | (1U << 8) | (1U << 18)); + + percpu_t *pc = get_percpu(); + if (!pc) { + serial_printf("[SYSCALL] WARNING: no percpu, skipping kernel_rsp\n"); + return; + } + + extern tss_t *tss[MAX_CPUS]; + cpu_info_t *cpu_info = smp_get_current_cpu(); + if (!cpu_info) return; + + uint32_t idx = cpu_info->cpu_index; + if (idx < MAX_CPUS && tss[idx]) { + pc->syscall_kernel_rsp = tss[idx]->rsp0; + serial_printf("[SYSCALL] CPU %u (index %u): kernel_rsp=0x%llx\n", + pc->cpu_id, idx, pc->syscall_kernel_rsp); + } +} \ No newline at end of file diff --git a/kernel/src/syscall/syscall_asm.asm b/kernel/src/syscall/syscall_asm.asm new file mode 100644 index 0000000..883602f --- /dev/null +++ b/kernel/src/syscall/syscall_asm.asm @@ -0,0 +1,145 @@ +section .text +global syscall_entry +extern syscall_handler_c +extern sched_reschedule + +PERCPU_KERNEL_RSP equ 0 +PERCPU_USER_RSP equ 8 +PERCPU_SAVED_RBP equ 48 +PERCPU_SAVED_RBX equ 56 +PERCPU_SAVED_R12 equ 64 +PERCPU_SAVED_R13 equ 72 +PERCPU_SAVED_R14 equ 80 +PERCPU_SAVED_R15 equ 88 +PERCPU_SAVED_R11 equ 96 +PERCPU_SAVED_RIP equ 104 +PERCPU_NEED_RESCHED equ 40 +PERCPU_CURRENT_TASK equ 24 + +TASK_USER_RSP equ 144 +TASK_USER_SAVED_RIP equ 272 +TASK_USER_SAVED_RBP equ 280 +TASK_USER_SAVED_RBX equ 288 +TASK_USER_SAVED_R12 equ 296 +TASK_USER_SAVED_R13 equ 304 +TASK_USER_SAVED_R14 equ 312 +TASK_USER_SAVED_R15 equ 320 +TASK_USER_SAVED_R11 equ 328 + +syscall_entry: + swapgs + mov [gs:PERCPU_USER_RSP], rsp + mov [gs:PERCPU_SAVED_RBP], rbp + mov [gs:PERCPU_SAVED_RBX], rbx + mov [gs:PERCPU_SAVED_R12], r12 + mov [gs:PERCPU_SAVED_R13], r13 + mov [gs:PERCPU_SAVED_R14], r14 + mov [gs:PERCPU_SAVED_R15], r15 + mov [gs:PERCPU_SAVED_R11], r11 + mov [gs:PERCPU_SAVED_RIP], rcx + mov rsp, [gs:PERCPU_KERNEL_RSP] + + push r11 + push rcx + push rbx + push r12 + push r13 + push r14 + push r15 + push rbp + + push rcx + mov rcx, rdx + mov r9, r8 + mov r8, r10 + mov rdx, rsi + mov rsi, rdi + mov rdi, rax + call syscall_handler_c + + add rsp, 8 + pop rbp + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rcx + pop r11 + +.check_resched: + cmp byte [gs:PERCPU_NEED_RESCHED], 0 + je .no_resched + mov byte [gs:PERCPU_NEED_RESCHED], 0 + push rax + call sched_reschedule + pop rax + jmp .check_resched + +.no_resched: + cli + + mov r10, rax + mov rax, [gs:PERCPU_CURRENT_TASK] + + mov rcx, [rax + TASK_USER_SAVED_RIP] + test rcx, rcx + jnz .rip_ok + push rax + push rcx + extern serial_printf + mov rdi, rax + mov rsi, [rax + 168] + mov esi, esi + mov rdx, [rax + 272] + mov rcx, [rax + 144] + lea rdi, [rel .fmt_zero_rip] + call serial_printf + lea rdi, [rel .msg_zero_rip] + extern kernel_panic + call kernel_panic +.fmt_zero_rip: db "[NO_RESCHED-BUG] task=0x%llx pid=%u user_saved_rip=0x%llx user_rsp=0x%llx", 10, 0 +.msg_zero_rip: db "sysret: user_saved_rip=0 — would fault at NULL", 0 +.rip_ok: + mov rsp, [rax + TASK_USER_RSP] + mov rcx, [rax + TASK_USER_SAVED_RIP] + mov r11, [rax + TASK_USER_SAVED_R11] + mov rbp, [rax + TASK_USER_SAVED_RBP] + mov rbx, [rax + TASK_USER_SAVED_RBX] + mov r12, [rax + TASK_USER_SAVED_R12] + mov r13, [rax + TASK_USER_SAVED_R13] + mov r14, [rax + TASK_USER_SAVED_R14] + mov r15, [rax + TASK_USER_SAVED_R15] + mov rax, r10 + + and r11, 0x00000000003C0FFF + or r11, 0x0000000000000202 + + mov r9, rcx + shr r9, 47 + jnz .sysret_bad_rip + + mov r9, rsp + shr r9, 47 + jnz .sysret_bad_rsp + + swapgs + o64 sysret + +.sysret_bad_rip: + extern sysret_bad_rip_panic + sti + mov rdi, rcx + mov rsi, rax + call sysret_bad_rip_panic + cli + hlt + +.sysret_bad_rsp: + extern sysret_bad_rsp_panic + sti + mov rdi, rsp + mov rsi, rcx + call sysret_bad_rsp_panic + cli + hlt \ No newline at end of file diff --git a/libc/include/ctype.h b/libc/include/ctype.h new file mode 100644 index 0000000..7a8304a --- /dev/null +++ b/libc/include/ctype.h @@ -0,0 +1,19 @@ +#ifndef _CTYPE_H +#define _CTYPE_H + +int isalnum(int c); +int isalpha(int c); +int isblank(int c); +int iscntrl(int c); +int isdigit(int c); +int isgraph(int c); +int islower(int c); +int isupper(int c); +int isprint(int c); +int ispunct(int c); +int isspace(int c); +int isxdigit(int c); +int tolower(int c); +int toupper(int c); + +#endif \ No newline at end of file diff --git a/libc/include/math.h b/libc/include/math.h new file mode 100644 index 0000000..60b7e3e --- /dev/null +++ b/libc/include/math.h @@ -0,0 +1,62 @@ +#ifndef _MATH_H +#define _MATH_H + +#include + +int abs(int x); +double fabs(double x); +double pow(double base, double exp); +double pow10(int n); +int isinf(double x); +int isnan(double x); + +static inline double floor(double x) { + int64_t i = (int64_t)x; + return (double)(i - (x < (double)i)); +} + +static inline double ceil(double x) { + int64_t i = (int64_t)x; + return (double)(i + (x > (double)i)); +} + +static inline double round(double x) { + return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5); +} + +static inline double sqrt(double x) { + double result; + asm volatile ("sqrtsd %1, %0" : "=x"(result) : "x"(x)); + return result; +} + +static inline float sqrtf(float x) { + float result; + asm volatile ("sqrtss %1, %0" : "=x"(result) : "x"(x)); + return result; +} + +static inline double log2(double x) { + double result; + asm volatile ( + "fld1\n\t" + "fld %1\n\t" + "fyl2x\n\t" + "fstp %0\n\t" + : "=m"(result) : "m"(x) + ); + return result; +} + +#define MIN(a, b) ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a < _b ? _a : _b; }) +#define MAX(a, b) ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a > _b ? _a : _b; }) + +#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1)) +#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1)) + +#define IS_POWER_OF_TWO(x) ((x) != 0 && (((x) & ((x) - 1)) == 0)) + +#define INFINITY (1.0/0.0) +#define NAN (0.0/0.0) + +#endif \ No newline at end of file diff --git a/libc/include/stdio.h b/libc/include/stdio.h new file mode 100644 index 0000000..e2f5310 --- /dev/null +++ b/libc/include/stdio.h @@ -0,0 +1,44 @@ +#ifndef _STDIO_H +#define _STDIO_H + +#include +#include +#include +#include + +#define EOF (-1) +#define MAX_SCROLL_LINES 1000 + +int putchar(int c); +int puts(const char *str); +int printf(const char *format, ...); + +int sprintf (char* restrict buf, const char* restrict fmt, ...); +int snprintf(char* restrict buf, size_t size, const char* restrict fmt, ...); +int vsprintf (char* restrict buf, const char* restrict fmt, va_list ap); +int vsnprintf(char* restrict buf, size_t size, const char* restrict fmt, va_list ap); +int vprintf(const char* restrict fmt, va_list ap); + +int getchar(void); +int scanf (const char * restrict fmt, ...); +int vscanf(const char * restrict fmt, va_list ap); +int sscanf (const char * restrict str, const char * restrict fmt, ...); +int vsscanf(const char *str, const char *fmt, va_list ap); + +extern uint32_t cursor_x; +extern uint32_t cursor_y; + +extern uint32_t text_color; +extern uint32_t bg_color; + +void scroll_screen(int lines); +uint32_t get_screen_width(void); +uint32_t get_screen_height(void); + +void set_cursor_position(uint32_t x, uint32_t y); +void get_cursor_position(uint32_t *x, uint32_t *y); +void set_text_color(uint32_t color); +void clear_screen(void); +void scroll_up(int lines); + +#endif \ No newline at end of file diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h new file mode 100644 index 0000000..906be8f --- /dev/null +++ b/libc/include/stdlib.h @@ -0,0 +1,30 @@ +#ifndef _STDLIB_H +#define _STDLIB_H + +#include +#include + +char* itoa(int val, char* restrict str, int base); + +long strtol (const char * restrict s, char ** restrict end, int base); +unsigned long strtoul (const char * restrict s, char ** restrict end, int base); + +long long strtoll (const char * restrict s, char ** restrict end, int base); +unsigned long long strtoull(const char * restrict s, char ** restrict end, int base); + +static inline int atoi(const char* s) { return (int)strtol (s, (char**)0, 10); } +static inline long atol(const char* s) { return strtol (s, (char**)0, 10); } + +void* malloc (size_t size); +void* calloc (size_t nmemb, size_t size); +void* realloc(void* ptr, size_t size); +void free (void* ptr); +void* aligned_alloc(size_t alignment, size_t size); +void aligned_free (void* ptr); + +static inline void abort(void) { + __asm__ volatile ("cli; hlt"); + __builtin_unreachable(); +} + +#endif \ No newline at end of file diff --git a/libc/include/string.h b/libc/include/string.h new file mode 100644 index 0000000..36da3a7 --- /dev/null +++ b/libc/include/string.h @@ -0,0 +1,45 @@ +#ifndef _STRING_H +#define _STRING_H + +#include +#include + +void* memcpy (void* restrict dst, const void* restrict src, size_t n); +void* memmove(void* dst, const void* src, size_t n); +void* memset (void* dst, int c, size_t n); +int memcmp (const void* a, const void* b, size_t n); +void *memchr(void *p, int val, size_t n); +void *rawmemchr(void *p, int c); + +void* memset_explicit(void* dst, int c, size_t n); + +size_t strlen (const char* s); +size_t strnlen(const char* s, size_t maxlen); + +char *strcpy(char *dest, const char *src); +char *strncpy(char *dest, const char *src, size_t n); + +char *strcat(char *dest, const char *src); +char* strncat(char* restrict dst, const char* restrict src, size_t n); + +int strcmp (const char* a, const char* b); +int strncmp(const char* a, const char* b, size_t n); + +char* strchr (const char* s, int c); +char* strrchr(const char* s, int c); +char* strstr (const char* haystack, const char* needle); +char* strpbrk(const char* s, const char* accept); +size_t strspn (const char* s, const char* accept); +size_t strcspn(const char* s, const char* reject); +char *strtok(char *str, const char *delim); + +long strtol (const char* restrict s, char** restrict end, int base); +unsigned long strtoul(const char* restrict s, char** restrict end, int base); + +char* strdup(const char* s); + +static inline void bzero(void* s, size_t n) { + memset(s, 0, n); +} + +#endif \ No newline at end of file diff --git a/libc/src/ctype/isalnum.c b/libc/src/ctype/isalnum.c new file mode 100644 index 0000000..b922331 --- /dev/null +++ b/libc/src/ctype/isalnum.c @@ -0,0 +1,7 @@ +#include + +int isalnum(int c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); +} diff --git a/libc/src/ctype/isalpha.c b/libc/src/ctype/isalpha.c new file mode 100644 index 0000000..e749e2f --- /dev/null +++ b/libc/src/ctype/isalpha.c @@ -0,0 +1,6 @@ +#include + +int isalpha(int c) { + return (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); +} diff --git a/libc/src/ctype/isblank.c b/libc/src/ctype/isblank.c new file mode 100644 index 0000000..66d99e0 --- /dev/null +++ b/libc/src/ctype/isblank.c @@ -0,0 +1,5 @@ +#include + +int isblank(int c) { + return c == ' ' || c == '\t'; +} diff --git a/libc/src/ctype/iscntrl.c b/libc/src/ctype/iscntrl.c new file mode 100644 index 0000000..8542795 --- /dev/null +++ b/libc/src/ctype/iscntrl.c @@ -0,0 +1,8 @@ +#include + +int iscntrl(int c) { + return c == '\n' || c == '\t' || + c == '\r' || c == '\b' || + c == '\f' || c == '\a' || + c == '\0'; +} diff --git a/libc/src/ctype/isdigit.c b/libc/src/ctype/isdigit.c new file mode 100644 index 0000000..c7c45ee --- /dev/null +++ b/libc/src/ctype/isdigit.c @@ -0,0 +1,5 @@ +#include + +int isdigit(int c) { + return c >= '0' && c <= '9'; +} \ No newline at end of file diff --git a/libc/src/ctype/isgraph.c b/libc/src/ctype/isgraph.c new file mode 100644 index 0000000..fc4963e --- /dev/null +++ b/libc/src/ctype/isgraph.c @@ -0,0 +1,5 @@ +#include + +int isgraph(int c) { + return c >= 33 && c <= 126; +} diff --git a/libc/src/ctype/islower.c b/libc/src/ctype/islower.c new file mode 100644 index 0000000..85d6b3a --- /dev/null +++ b/libc/src/ctype/islower.c @@ -0,0 +1,5 @@ +#include + +int islower(int c) { + return c >= 'a' && c <= 'z'; +} \ No newline at end of file diff --git a/libc/src/ctype/isprint.c b/libc/src/ctype/isprint.c new file mode 100644 index 0000000..fd68c3e --- /dev/null +++ b/libc/src/ctype/isprint.c @@ -0,0 +1,5 @@ +#include + +int isprint(int c) { + return c >= 32 && c <= 126; +} \ No newline at end of file diff --git a/libc/src/ctype/ispunct.c b/libc/src/ctype/ispunct.c new file mode 100644 index 0000000..f1b3c2e --- /dev/null +++ b/libc/src/ctype/ispunct.c @@ -0,0 +1,8 @@ +#include + +int ispunct(int c) { + return (c >= 33 && c <= 47) || + (c >= 58 && c <= 64) || + (c >= 91 && c <= 96) || + (c >= 123 && c <= 126); +} diff --git a/libc/src/ctype/isspace.c b/libc/src/ctype/isspace.c new file mode 100644 index 0000000..9edad9b --- /dev/null +++ b/libc/src/ctype/isspace.c @@ -0,0 +1,6 @@ +#include + +int isspace(int c) { + return c == ' ' || c == '\t' || c == '\n' || + c == '\v' || c == '\f' || c == '\r'; +} diff --git a/libc/src/ctype/isupper.c b/libc/src/ctype/isupper.c new file mode 100644 index 0000000..26839de --- /dev/null +++ b/libc/src/ctype/isupper.c @@ -0,0 +1,5 @@ +#include + +int isupper(int c) { + return c >= 'A' && c <= 'Z'; +} diff --git a/libc/src/ctype/isxdigit.c b/libc/src/ctype/isxdigit.c new file mode 100644 index 0000000..6d9c960 --- /dev/null +++ b/libc/src/ctype/isxdigit.c @@ -0,0 +1,7 @@ +#include + +int isxdigit(int c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} diff --git a/libc/src/ctype/tolower.c b/libc/src/ctype/tolower.c new file mode 100644 index 0000000..0621ac0 --- /dev/null +++ b/libc/src/ctype/tolower.c @@ -0,0 +1,8 @@ +#include + +int tolower(int c) { + if (!isalpha(c)) + return c; + + return c + ('a' - 'A'); +} diff --git a/libc/src/ctype/toupper.c b/libc/src/ctype/toupper.c new file mode 100644 index 0000000..7b122ca --- /dev/null +++ b/libc/src/ctype/toupper.c @@ -0,0 +1,8 @@ +#include + +int toupper(int c) { + if (!isalpha(c)) + return c; + + return c - ('a' - 'A'); +} diff --git a/libc/src/math/abs.c b/libc/src/math/abs.c new file mode 100644 index 0000000..a499d2c --- /dev/null +++ b/libc/src/math/abs.c @@ -0,0 +1,5 @@ +#include + +int abs(int n) { + return n >= 0 ? n : -n; +} diff --git a/libc/src/math/fabs.c b/libc/src/math/fabs.c new file mode 100644 index 0000000..16e12de --- /dev/null +++ b/libc/src/math/fabs.c @@ -0,0 +1,12 @@ +#include +#include + +double fabs(double x) { + union { + double d; + uint64_t i; + } u = { .d = x }; + + u.i &= 0x7FFFFFFFFFFFFFFFULL; + return u.d; +} \ No newline at end of file diff --git a/libc/src/math/isinf.c b/libc/src/math/isinf.c new file mode 100644 index 0000000..12a6a00 --- /dev/null +++ b/libc/src/math/isinf.c @@ -0,0 +1,14 @@ +#include +#include + +int isinf(double x) { + union { + double f; + uint64_t i; + } u = { .f = x }; + + uint64_t exp = (u.i >> 52) & 0x7FF; + uint64_t mantissa = u.i & 0xFFFFFFFFFFFFFULL; + + return (exp == 0x7FF) && (mantissa == 0); +} \ No newline at end of file diff --git a/libc/src/math/isnan.c b/libc/src/math/isnan.c new file mode 100644 index 0000000..66afe95 --- /dev/null +++ b/libc/src/math/isnan.c @@ -0,0 +1,14 @@ +#include +#include + +int isnan(double x) { + union { + double f; + uint64_t i; + } u = { .f = x }; + + uint64_t exp = (u.i >> 52) & 0x7FF; + uint64_t mantissa = u.i & 0xFFFFFFFFFFFFFULL; + + return (exp == 0x7FF) && (mantissa != 0); +} \ No newline at end of file diff --git a/libc/src/math/pow.c b/libc/src/math/pow.c new file mode 100644 index 0000000..2f0acc7 --- /dev/null +++ b/libc/src/math/pow.c @@ -0,0 +1,37 @@ +#include + +double pow(double x, double y) { + if (y == 0.0) return 1.0; + + if (y == (int)y) { + int n = (int)y; + double result = 1.0; + + if (n > 0) { + for (int i = 0; i < n; i++) { + result *= x; + } + } else { + for (int i = 0; i < -n; i++) { + result /= x; + } + } + + return result; + } + + if (x > 0.0) { + int int_part = (int)y; + double frac_part = y - int_part; + + double int_pow = pow(x, int_part); + + if (frac_part > 0.0) { + return int_pow * (1.0 + frac_part * (x - 1.0)); + } else { + return int_pow / (1.0 - frac_part * (x - 1.0)); + } + } + + return NAN; +} \ No newline at end of file diff --git a/libc/src/math/pow10.c b/libc/src/math/pow10.c new file mode 100644 index 0000000..389fd43 --- /dev/null +++ b/libc/src/math/pow10.c @@ -0,0 +1,17 @@ +#include + +double pow10(int n) { + double result = 1.0; + + if (n >= 0) { + for (int i = 0; i < n; i++) { + result *= 10.0; + } + } else { + for (int i = 0; i < -n; i++) { + result /= 10.0; + } + } + + return result; +} \ No newline at end of file diff --git a/libc/src/stdio/getchar.c b/libc/src/stdio/getchar.c new file mode 100644 index 0000000..b230a78 --- /dev/null +++ b/libc/src/stdio/getchar.c @@ -0,0 +1,6 @@ +#include +#include "../../../kernel/include/drivers/ps2.h" + +int getchar(void) { + return (unsigned char)kb_buf_getc(); +} \ No newline at end of file diff --git a/libc/src/stdio/printf.c b/libc/src/stdio/printf.c new file mode 100644 index 0000000..a283f14 --- /dev/null +++ b/libc/src/stdio/printf.c @@ -0,0 +1,9 @@ +#include +#include + +int printf(const char *format, ...) { + va_list args; + va_start(args, format); + int n = vprintf(format, args); + return n; +} \ No newline at end of file diff --git a/libc/src/stdio/putchar.c b/libc/src/stdio/putchar.c new file mode 100644 index 0000000..2b6b89a --- /dev/null +++ b/libc/src/stdio/putchar.c @@ -0,0 +1,360 @@ +#include +#include +#include "../../../kernel/include/graphics/fb/fb.h" + +uint32_t cursor_x = 0; +uint32_t cursor_y = 0; +uint32_t text_color = COLOR_WHITE; +uint32_t bg_color = COLOR_BLACK; + +extern struct limine_framebuffer *global_framebuffer; + +static int cursor_visible = 1; +static int scroll_buffer_index = 0; +static int total_scroll_lines = 0; +static int flush_inhibit = 0; +static int autowrap = 1; + +uint32_t get_screen_width(void) { + if (!global_framebuffer) return 1024; + return global_framebuffer->width; +} +uint32_t get_screen_height(void) { + if (!global_framebuffer) return 768; + return global_framebuffer->height; +} + +uint32_t get_cursor_row(void) { return cursor_y / 16; } +uint32_t get_cursor_col(void) { return cursor_x / 8; } + +static void flush_all(void) { + if (!flush_inhibit && global_framebuffer) + fb_flush(global_framebuffer); +} + +static void flush_region(uint32_t y_start, uint32_t h) { + if (!flush_inhibit && global_framebuffer) + fb_flush_lines(global_framebuffer, y_start, y_start + h); +} + +void scroll_screen(int lines) { + if (!global_framebuffer || lines <= 0) return; + uint32_t sh = get_screen_height(); + uint32_t sp = (uint32_t)(lines * 16); + if (sp >= sh) { fb_clear(global_framebuffer, bg_color); flush_all(); return; } + + uint32_t *buf = (uint32_t *)global_framebuffer->address; + extern uint32_t *g_backbuf; + extern uint32_t g_bb_pitch; + uint32_t pitch; + uint32_t *target; + if (g_backbuf) { + target = g_backbuf; + pitch = g_bb_pitch; + } else { + target = buf; + pitch = global_framebuffer->pitch / 4; + } + + uint32_t rows_to_move = sh - sp; + memmove(target, target + sp * pitch, rows_to_move * pitch * sizeof(uint32_t)); + memset(target + rows_to_move * pitch, 0, sp * pitch * sizeof(uint32_t)); + + if (bg_color != 0) { + uint32_t sw = get_screen_width(); + uint32_t *clear_start = target + rows_to_move * pitch; + for (uint32_t y = 0; y < sp; y++) { + uint32_t *row = clear_start + y * pitch; + for (uint32_t x = 0; x < sw; x++) + row[x] = bg_color; + } + } + + flush_all(); +} + +static void draw_cursor_at(uint32_t x, uint32_t y) { + if (!global_framebuffer || !cursor_visible) return; + if (x + 8 > global_framebuffer->width || y + 16 > global_framebuffer->height) return; + for (uint32_t col = 0; col < 8; col++) { + fb_draw_pixel(global_framebuffer, x + col, y + 14, text_color); + fb_draw_pixel(global_framebuffer, x + col, y + 15, text_color); + } + flush_region(y + 14, 2); +} + +static void erase_cursor_at(uint32_t x, uint32_t y) { + if (!global_framebuffer) return; + if (x + 8 > global_framebuffer->width || y + 16 > global_framebuffer->height) return; + for (uint32_t col = 0; col < 8; col++) { + fb_draw_pixel(global_framebuffer, x + col, y + 14, bg_color); + fb_draw_pixel(global_framebuffer, x + col, y + 15, bg_color); + } + flush_region(y + 14, 2); +} + +void draw_cursor(void) { draw_cursor_at(cursor_x, cursor_y); } +void erase_cursor(void) { erase_cursor_at(cursor_x, cursor_y); } + +static uint32_t ansi_color(int code, int bright) { + static const uint32_t base[8] = { + 0x000000, 0xAA0000, 0x00AA00, 0xAA5500, + 0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, + }; + static const uint32_t bright8[8] = { + 0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, + 0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF, + }; + return bright ? bright8[code & 7] : base[code & 7]; +} + +#define ESC_MAX_PARAMS 8 + +typedef enum { + PS_NORMAL, + PS_ESC, + PS_CSI, + PS_CSI_PRIV, + PS_ESC_SP, +} parse_state_t; + +static parse_state_t ps_state = PS_NORMAL; +static int ps_params[ESC_MAX_PARAMS]; +static int ps_nparams = 0; +static int ps_cur = 0; +static int ps_bold = 0; + +static void ps_reset_params(void) { + for (int i = 0; i < ESC_MAX_PARAMS; i++) ps_params[i] = -1; + ps_nparams = 0; ps_cur = 0; +} +static void ps_push_param(void) { + if (ps_nparams < ESC_MAX_PARAMS) ps_params[ps_nparams++] = ps_cur; + ps_cur = 0; +} +static int ps_get(int i, int def) { + if (i >= ps_nparams || ps_params[i] < 0) return def; + return ps_params[i]; +} + +static int ps_reverse = 0; + +static void handle_sgr(void) { + if (ps_nparams == 0) { + text_color = COLOR_WHITE; bg_color = COLOR_BLACK; + ps_bold = 0; ps_reverse = 0; + return; + } + for (int i = 0; i < ps_nparams; i++) { + int p = ps_params[i]; if (p < 0) p = 0; + if (p == 0) { text_color = COLOR_WHITE; bg_color = COLOR_BLACK; ps_bold = 0; ps_reverse = 0; } + else if (p == 1) { ps_bold = 1; } + else if (p == 22) { ps_bold = 0; } + else if (p == 7) { + if (!ps_reverse) { + uint32_t tmp = text_color; text_color = bg_color; bg_color = tmp; + ps_reverse = 1; + } + } + else if (p == 27) { + if (ps_reverse) { + uint32_t tmp = text_color; text_color = bg_color; bg_color = tmp; + ps_reverse = 0; + } + } + else if (p >= 30 && p <= 37) { + uint32_t c = ansi_color(p-30, ps_bold); + if (ps_reverse) bg_color = c; else text_color = c; + } + else if (p >= 90 && p <= 97) { + uint32_t c = ansi_color(p-90, 1); + if (ps_reverse) bg_color = c; else text_color = c; + } + else if (p >= 40 && p <= 47) { + uint32_t c = ansi_color(p-40, 0); + if (ps_reverse) text_color = c; else bg_color = c; + } + } +} + +static void erase_to_eol(void) { + if (!global_framebuffer) return; + uint32_t sw = get_screen_width(); + if (cursor_x >= sw) return; + fb_fill_rect(global_framebuffer, cursor_x, cursor_y, sw - cursor_x, 16, bg_color); + flush_region(cursor_y, 16); +} + +static void cursor_move_right(int n) { + uint32_t sw = get_screen_width(); + cursor_x += (uint32_t)(n * 8); + if (cursor_x + 8 > sw) cursor_x = sw - 8; +} +static void cursor_move_left(int n) { + uint32_t delta = (uint32_t)(n * 8); + if (cursor_x >= delta) cursor_x -= delta; + else cursor_x = 0; +} + +static uint32_t saved_cx = 0, saved_cy = 0; + +static void clear_cell(uint32_t x, uint32_t y) { + if (!global_framebuffer) return; + fb_fill_rect(global_framebuffer, x, y, 8, 16, bg_color); +} + +static void draw_and_advance(char c) { + if (!global_framebuffer) return; + uint32_t sh = get_screen_height(); + uint32_t sw = get_screen_width(); + if (!autowrap && cursor_x + 8 >= sw) { + clear_cell(cursor_x, cursor_y); + fb_draw_char(global_framebuffer, c, cursor_x, cursor_y, text_color); + flush_region(cursor_y, 16); + return; + } + clear_cell(cursor_x, cursor_y); + fb_draw_char(global_framebuffer, c, cursor_x, cursor_y, text_color); + flush_region(cursor_y, 16); + cursor_x += 8; + if (cursor_x + 8 > sw) { cursor_x = 0; cursor_y += 16; } + if (cursor_y + 16 > sh) { scroll_screen(1); cursor_y = sh - 16; } +} + +int putchar(int c) { + if (!global_framebuffer) return EOF; + uint8_t ch = (uint8_t)c; + + switch (ps_state) { + + case PS_NORMAL: + if (ch == 0x1B) { ps_state = PS_ESC; return c; } + switch (ch) { + case '\n': + cursor_x = 0; cursor_y += 16; + if (cursor_y + 16 > get_screen_height()) { + scroll_screen(1); cursor_y = get_screen_height() - 16; + } + break; + case '\r': cursor_x = 0; break; + case '\t': cursor_x = (cursor_x + 32) & ~31u; break; + case '\b': + if (cursor_x >= 8) { + cursor_x -= 8; + clear_cell(cursor_x, cursor_y); + flush_region(cursor_y, 16); + } + break; + default: + if (ch >= 32 && ch <= 126) draw_and_advance((char)ch); + break; + } + break; + + case PS_ESC: + if (ch == '[') { ps_state = PS_CSI; ps_reset_params(); } + else if (ch == ' ') { ps_state = PS_ESC_SP; } + else { ps_state = PS_NORMAL; } + break; + + case PS_ESC_SP: + ps_state = PS_NORMAL; + break; + + case PS_CSI: + if (ch == '?') { ps_state = PS_CSI_PRIV; break; } + __attribute__((fallthrough)); + case PS_CSI_PRIV: + if (ch >= '0' && ch <= '9') { + ps_cur = ps_cur * 10 + (ch - '0'); + } else if (ch == ';') { + ps_push_param(); + } else { + ps_push_param(); + + if (ps_state == PS_CSI_PRIV) { + int p = ps_get(0, 0); + if (p == 25) { + if (ch == 'l') { + cursor_visible = 0; + flush_inhibit = 1; + } else if (ch == 'h') { + cursor_visible = 1; + flush_inhibit = 0; + if (global_framebuffer) + fb_flush(global_framebuffer); + } + } else if (p == 7) { + if (ch == 'l') autowrap = 0; + else if (ch == 'h') autowrap = 1; + } + ps_state = PS_NORMAL; + break; + } + + switch (ch) { + case 'm': handle_sgr(); break; + case 'J': { + int mode = ps_get(0, 0); + if (mode == 2 || mode == 3) { + fb_clear(global_framebuffer, bg_color); + cursor_x = 0; cursor_y = 0; + flush_all(); + } + break; + } + case 'K': { + int mode = ps_get(0, 0); + if (mode == 0) erase_to_eol(); + break; + } + case 'H': + case 'f': { + int row = ps_get(0, 1); if (row < 1) row = 1; + int col = ps_get(1, 1); if (col < 1) col = 1; + uint32_t cx = (uint32_t)((col - 1) * 8); + uint32_t cy = (uint32_t)((row - 1) * 16); + uint32_t sw = get_screen_width(); + uint32_t sh = get_screen_height(); + if (cx + 8 > sw) cx = (sw >= 8) ? sw - 8 : 0; + if (cy + 16 > sh) cy = (sh >= 16) ? sh - 16 : 0; + cursor_x = cx; + cursor_y = cy; + break; + } + case 'A': { + int n = ps_get(0, 1); if (n < 1) n = 1; + uint32_t d = (uint32_t)(n * 16); + cursor_y = (cursor_y >= d) ? cursor_y - d : 0; + break; + } + case 'B': { + int n = ps_get(0, 1); if (n < 1) n = 1; + cursor_y += (uint32_t)(n * 16); + break; + } + case 'C': cursor_move_right(ps_get(0, 1)); break; + case 'D': cursor_move_left (ps_get(0, 1)); break; + case 's': saved_cx = cursor_x; saved_cy = cursor_y; break; + case 'u': cursor_x = saved_cx; cursor_y = saved_cy; break; + default: break; + } + ps_state = PS_NORMAL; + } + break; + } + return c; +} + +void clear_screen_with_scroll(void) { + if (global_framebuffer) { + fb_clear(global_framebuffer, bg_color); + cursor_x = 0; cursor_y = 0; + scroll_buffer_index = 0; total_scroll_lines = 0; + flush_all(); + } +} +void get_cursor_position(uint32_t *x, uint32_t *y) { + if (x) *x = cursor_x; + if (y) *y = cursor_y; +} \ No newline at end of file diff --git a/libc/src/stdio/puts.c b/libc/src/stdio/puts.c new file mode 100644 index 0000000..781e8b7 --- /dev/null +++ b/libc/src/stdio/puts.c @@ -0,0 +1,27 @@ +#include +#include + +int puts(const char *str) { + int count = 0; + + if (!str) { + const char *null_str = "(null)"; + while (*null_str) { + putchar(*null_str++); + count++; + } + putchar('\n'); + count++; + return count; + } + + while (*str) { + putchar(*str++); + count++; + } + + putchar('\n'); + count++; + + return count; +} \ No newline at end of file diff --git a/libc/src/stdio/scanf.c b/libc/src/stdio/scanf.c new file mode 100644 index 0000000..2280398 --- /dev/null +++ b/libc/src/stdio/scanf.c @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../../kernel/include/drivers/ps2.h" +#include "../../kernel/include/graphics/fb/fb.h" + +extern struct limine_framebuffer *global_framebuffer; +extern uint32_t bg_color; + +static int readline_buf(char *buf, size_t size) { + if (!buf || size == 0) return 0; + + uint32_t prompt_x, prompt_y; + get_cursor_position(&prompt_x, &prompt_y); + + size_t len = 0; + + while (len < size - 1) { + int c = getchar(); + if (c == EOF) break; + + if (c == '\n' || c == '\r') { + putchar('\n'); + break; + } + + if (c == '\b') { + if (len == 0) continue; + + len--; + + if (cursor_x >= 8) { + cursor_x -= 8; + } else if (cursor_y > prompt_y || + (cursor_y == prompt_y && cursor_x > prompt_x)) { + cursor_y -= 16; + cursor_x = (get_screen_width() / 8) * 8 - 8; + } else { + continue; + } + + if (global_framebuffer) + fb_fill_rect(global_framebuffer, + cursor_x, cursor_y, 8, 16, bg_color); + continue; + } + + if ((unsigned char)c < 32 && c != '\t') continue; + + buf[len++] = (char)c; + putchar(c); + } + + buf[len] = '\0'; + return (int)len; +} + +int vsscanf(const char *str, const char *fmt, va_list ap) { + if (!str || !fmt) return EOF; + + const char *s = str; + const char *f = fmt; + int n = 0; + + while (*f) { + if (*f == ' ' || *f == '\t' || *f == '\n' || *f == '\r') { + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') + s++; + f++; + continue; + } + + if (*f != '%') { + if (*s != *f) break; + s++; f++; + continue; + } + + f++; + + bool suppress = false; + if (*f == '*') { suppress = true; f++; } + + int width = 0; + while (*f >= '0' && *f <= '9') { + width = width * 10 + (*f - '0'); + f++; + } + + enum { LEN_INT, LEN_LONG, LEN_LLONG } len = LEN_INT; + if (*f == 'l') { + f++; + if (*f == 'l') { f++; len = LEN_LLONG; } + else { len = LEN_LONG; } + } else if (*f == 'h') { + f++; + if (*f == 'h') f++; + } + + char spec = *f++; + + if (spec == '%') { + while (*s == ' ' || *s == '\t') s++; + if (*s != '%') break; + s++; + continue; + } + + if (spec == 'n') { + if (!suppress) { + *va_arg(ap, int *) = (int)(s - str); + } + continue; + } + + if (spec == 'c') { + if (!*s) break; + if (!suppress) { + char *out = va_arg(ap, char *); + *out = *s; + n++; + } + s++; + continue; + } + + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') + s++; + + if (!*s) break; + + if (spec == 's') { + char *out = suppress ? NULL : va_arg(ap, char *); + int cnt = 0; + while (*s && *s != ' ' && *s != '\t' && + *s != '\n' && *s != '\r') { + if (width && cnt >= width) break; + if (out) out[cnt] = *s; + cnt++; + s++; + } + if (cnt == 0) break; + if (out) { out[cnt] = '\0'; n++; } + continue; + } + + int base = 10; + bool is_signed = false; + + switch (spec) { + case 'd': base = 10; is_signed = true; break; + case 'i': base = 0; is_signed = true; break; + case 'u': base = 10; is_signed = false; break; + case 'o': base = 8; is_signed = false; break; + case 'x': + case 'X': base = 16; is_signed = false; break; + default: + goto done; + } + + char tmp[32]; + int tlen = 0; + int maxw = width ? width : (int)sizeof(tmp) - 1; + + if (is_signed && (*s == '-' || *s == '+') && tlen < maxw) + tmp[tlen++] = *s++; + + if ((base == 16 || base == 0) && + *s == '0' && (s[1] == 'x' || s[1] == 'X') && tlen + 2 <= maxw) { + tmp[tlen++] = *s++; tmp[tlen++] = *s++; + if (base == 0) base = 16; + } else if (base == 0 && *s == '0') { + tmp[tlen++] = *s++; + base = 8; + } else if (base == 0) { + base = 10; + } + + while (*s && tlen < maxw) { + char c = *s; + bool ok = false; + if (base == 10 && c >= '0' && c <= '9') ok = true; + if (base == 8 && c >= '0' && c <= '7') ok = true; + if (base == 16 && ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) ok = true; + if (!ok) break; + tmp[tlen++] = c; + s++; + } + + if (tlen == 0) break; + tmp[tlen] = '\0'; + + if (!suppress) { + char *end = NULL; + if (is_signed) { + long long val = strtoll(tmp, &end, base); + if (end == tmp) break; + switch (len) { + case LEN_INT: *va_arg(ap, int *) = (int)val; break; + case LEN_LONG: *va_arg(ap, long *) = (long)val; break; + case LEN_LLONG: *va_arg(ap, long long *) = val; break; + } + } else { + unsigned long long val = strtoull(tmp, &end, base); + if (end == tmp) break; + switch (len) { + case LEN_INT: *va_arg(ap, unsigned int *) = (unsigned int)val; break; + case LEN_LONG: *va_arg(ap, unsigned long *) = (unsigned long)val; break; + case LEN_LLONG: *va_arg(ap, unsigned long long *) = val; break; + } + } + n++; + } + } + +done: + return n; +} + +int sscanf(const char * restrict str, const char * restrict fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vsscanf(str, fmt, ap); + va_end(ap); + return r; +} + +int vscanf(const char * restrict fmt, va_list ap) { + char buf[256]; + readline_buf(buf, sizeof(buf)); + return vsscanf(buf, fmt, ap); +} + +int scanf(const char * restrict fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vscanf(fmt, ap); + va_end(ap); + return r; +} \ No newline at end of file diff --git a/libc/src/stdio/screen.c b/libc/src/stdio/screen.c new file mode 100644 index 0000000..d8ee662 --- /dev/null +++ b/libc/src/stdio/screen.c @@ -0,0 +1,38 @@ +#include +#include "../../../kernel/include/graphics/fb/fb.h" + +extern struct limine_framebuffer *global_framebuffer; + +void set_cursor_position(uint32_t x, uint32_t y) { + cursor_x = x; + cursor_y = y; +} + +void set_text_color(uint32_t color) { + text_color = color; +} + +void set_background_color(uint32_t color) { + bg_color = color; +} + +void clear_screen(void) { + if (global_framebuffer) { + fb_clear(global_framebuffer, bg_color); + cursor_x = 0; + cursor_y = 0; + } +} + +void scroll_up(int lines) { + if (lines <= 0) return; + + scroll_screen(lines); + + uint32_t scroll_pixels = (uint32_t)(lines * 16); + if (cursor_y >= scroll_pixels) { + cursor_y -= scroll_pixels; + } else { + cursor_y = 0; + } +} \ No newline at end of file diff --git a/libc/src/stdio/snprintf.c b/libc/src/stdio/snprintf.c new file mode 100644 index 0000000..f3ec1bc --- /dev/null +++ b/libc/src/stdio/snprintf.c @@ -0,0 +1,381 @@ +#include +#include +#include +#include + +typedef struct { + char* buf; + size_t pos; + size_t size; +} snprintf_ctx_t; + +static void ctx_putc(snprintf_ctx_t* ctx, char c) { + if (ctx->pos + 1 < ctx->size) + ctx->buf[ctx->pos] = c; + ctx->pos++; +} + +static void ctx_puts(snprintf_ctx_t* ctx, const char* s, int len) { + for (int i = 0; i < len; i++) ctx_putc(ctx, s[i]); +} + +static void write_uint_full(snprintf_ctx_t* ctx, + unsigned long long val, int base, int upper, + int width, int zero_pad, int left, + int alt, int show_sign, int space_sign, + char forced_sign) { + const char* digs = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + char tmp[72]; int len = 0; + if (val == 0) { tmp[len++] = '0'; } + else { while (val) { tmp[len++] = digs[val % (unsigned)base]; val /= (unsigned)base; } } + + char prefix[4]; int pfx = 0; + if (forced_sign) prefix[pfx++] = forced_sign; + else if (show_sign) prefix[pfx++] = '+'; + else if (space_sign) prefix[pfx++] = ' '; + if (alt && base == 16) { prefix[pfx++] = '0'; prefix[pfx++] = upper ? 'X':'x'; } + else if (alt && base == 8 && !(len==1 && tmp[0]=='0')) { prefix[pfx++] = '0'; } + else if (alt && base == 2) { prefix[pfx++] = '0'; prefix[pfx++] = upper ? 'B':'b'; } + + int total = len + pfx; + int pad = (width > total) ? width - total : 0; + + if (!left && !zero_pad) { for (int i=0;i= 0; i--) ctx_putc(ctx, tmp[i]); + if ( left) { for (int i=0;i= 1.0) { + if (val >= 1e16) { val /= 1e16; e += 16; } + if (val >= 1e8) { val /= 1e8; e += 8; } + if (val >= 1e4) { val /= 1e4; e += 4; } + if (val >= 1e2) { val /= 1e2; e += 2; } + if (val >= 1e1) { val /= 1e1; e += 1; } + } else { + if (val < 1e-15) { val *= 1e16; e -= 16; } + if (val < 1e-7) { val *= 1e8; e -= 8; } + if (val < 1e-3) { val *= 1e4; e -= 4; } + if (val < 1e-1) { val *= 1e2; e -= 2; } + if (val < 1e0) { val *= 1e1; e -= 1; } + } + (void)val; + return e; +} + +static int double_to_str(char* buf, double val, char fmt_char, int precision, + int show_sign, int space_sign, int alt, int is_neg) { + if (precision < 0) precision = 6; + if (precision > 17) precision = 17; + + int upper = (fmt_char == 'F' || fmt_char == 'E' || fmt_char == 'G'); + int i = 0; + + if (is_neg) buf[i++] = '-'; + else if (show_sign) buf[i++] = '+'; + else if (space_sign) buf[i++] = ' '; + + int exp10 = (val != 0.0) ? ilog10(val) : 0; + + int use_exp; + if (fmt_char == 'e' || fmt_char == 'E') { + use_exp = 1; + } else if (fmt_char == 'g' || fmt_char == 'G') { + int eff = (precision == 0) ? 1 : precision; + use_exp = (exp10 < -4 || exp10 >= eff); + precision = use_exp ? (eff - 1) : (eff - 1 - exp10); + if (precision < 0) precision = 0; + if (precision > 17) precision = 17; + } else { + use_exp = (exp10 > 15 || (val != 0.0 && exp10 < -17)); + if (use_exp) { fmt_char = upper ? 'E' : 'e'; upper = (fmt_char == 'E'); } + } + + double src = val; + if (use_exp && val != 0.0) { + int e = exp10; + if (e > 0 && e <= 17) src /= (double)pow10_tbl[e]; + else if (e > 17) { src /= (double)pow10_tbl[17]; src /= (double)pow10_tbl[e - 17 < 17 ? e-17 : 17]; } + else if (e < 0 && -e <= 17) src *= (double)pow10_tbl[-e]; + else if (e < -17) { src *= (double)pow10_tbl[17]; src *= (double)pow10_tbl[-e-17 < 17 ? -e-17 : 17]; } + if (src >= 10.0) { src /= 10.0; exp10++; } + if (src < 1.0 && src != 0.0) { src *= 10.0; exp10--; } + } + + unsigned long long scale = pow10_tbl[precision]; + double scaled_d = src * (double)scale + 0.5; + if (scaled_d >= 1.8e19) scaled_d = 1.8e19 - 1.0; + unsigned long long iscaled = (unsigned long long)scaled_d; + unsigned long long int_part = iscaled / scale; + unsigned long long frac_part = iscaled % scale; + + if (use_exp && int_part >= 10) { int_part = 1; frac_part = 0; exp10++; } + + { + char itmp[24]; int ilen = 0; + unsigned long long ip = int_part; + if (ip == 0) itmp[ilen++] = '0'; + else while (ip) { itmp[ilen++] = '0' + (int)(ip % 10); ip /= 10; } + for (int j = ilen-1; j >= 0; j--) buf[i++] = itmp[j]; + } + + if (precision > 0 || alt) { + char ftmp[20]; + unsigned long long fp2 = frac_part; + for (int k = precision - 1; k >= 0; k--) { + ftmp[k] = '0' + (int)(fp2 % 10); + fp2 /= 10; + } + int fend = precision; + if ((fmt_char == 'g' || fmt_char == 'G') && !alt) { + while (fend > 0 && ftmp[fend-1] == '0') fend--; + } + if (fend > 0 || alt) { + buf[i++] = '.'; + for (int k = 0; k < fend; k++) buf[i++] = ftmp[k]; + } + } + + if (use_exp) { + buf[i++] = upper ? 'E' : 'e'; + int ae = exp10 < 0 ? -exp10 : exp10; + buf[i++] = exp10 < 0 ? '-' : '+'; + if (ae >= 100) buf[i++] = '0' + ae / 100; + buf[i++] = '0' + (ae / 10) % 10; + buf[i++] = '0' + ae % 10; + } + + return i; +} + +static void write_float(snprintf_ctx_t* ctx, double val, char fmt_char, + int precision, int width, int zero_pad, int left, + int show_sign, int space_sign, int alt) { + char tmp[64]; int len; + int upper = (fmt_char == 'F' || fmt_char == 'E' || fmt_char == 'G'); + + if (val != val) { + const char* s = upper ? "NAN" : "nan"; + len = 3; tmp[0]=s[0]; tmp[1]=s[1]; tmp[2]=s[2]; + } else { + int is_neg = 0; + if (val < 0.0) { is_neg = 1; val = -val; } + if (val > 1.7976931348623157e+308) { + len = 0; + if (is_neg) tmp[len++] = '-'; + else if (show_sign) tmp[len++] = '+'; + else if (space_sign) tmp[len++] = ' '; + const char* s = upper ? "INF" : "inf"; + tmp[len++]=s[0]; tmp[len++]=s[1]; tmp[len++]=s[2]; + } else { + len = double_to_str(tmp, val, fmt_char, precision, + show_sign, space_sign, alt, is_neg); + } + } + + int has_sign = (tmp[0]=='-' || tmp[0]=='+' || tmp[0]==' '); + int pad = (width > len) ? width - len : 0; + if (!left && !zero_pad) { for (int j=0;j= '0' && *fmt <= '9') width = width*10 + (*fmt++ - '0'); + } + + int precision=-1, has_prec=0; + if (*fmt == '.') { + has_prec=1; precision=0; fmt++; + if (*fmt == '*') { + precision = va_arg(ap, int); + if (precision < 0) { has_prec=0; precision=-1; } + fmt++; + } else { + while (*fmt >= '0' && *fmt <= '9') + precision = precision*10 + (*fmt++ - '0'); + } + } + + int is_hh=0,is_h=0,is_l=0,is_ll=0,is_z=0; + if (*fmt=='h') { + fmt++; + if (*fmt=='h') { is_hh=1; fmt++; } else is_h=1; + } else if (*fmt=='l') { + is_l=1; fmt++; + if (*fmt=='l') { is_ll=1; fmt++; } + } else if (*fmt=='z') { is_z=1; fmt++; } + else if (*fmt=='t') { is_l=1; fmt++; } + else if (*fmt=='L') { fmt++; } + + if (!*fmt) break; + + switch (*fmt) { + case 'd': case 'i': { + long long v; + if (is_ll) v = va_arg(ap, long long); + else if (is_l) v = va_arg(ap, long); + else if (is_hh) v = (signed char) va_arg(ap, int); + else if (is_h) v = (short) va_arg(ap, int); + else if (is_z) v = (long long) va_arg(ap, size_t); + else v = va_arg(ap, int); + write_sint(&ctx, v, width, zero_pad, left, show_sign, space_sign); + break; + } + case 'u': { + unsigned long long v; + if (is_ll) v = va_arg(ap, unsigned long long); + else if (is_l) v = va_arg(ap, unsigned long); + else if (is_hh) v = (unsigned char) va_arg(ap, unsigned int); + else if (is_h) v = (unsigned short)va_arg(ap, unsigned int); + else if (is_z) v = va_arg(ap, size_t); + else v = va_arg(ap, unsigned int); + write_uint_full(&ctx,v,10,0,width,zero_pad,left,0,show_sign,space_sign,0); + break; + } + case 'o': { + unsigned long long v; + if (is_ll) v = va_arg(ap, unsigned long long); + else if (is_l) v = va_arg(ap, unsigned long); + else v = va_arg(ap, unsigned int); + write_uint_full(&ctx,v,8,0,width,zero_pad,left,alt,0,0,0); + break; + } + case 'x': case 'X': { + unsigned long long v; + if (is_ll) v = va_arg(ap, unsigned long long); + else if (is_l) v = va_arg(ap, unsigned long); + else v = va_arg(ap, unsigned int); + write_uint_full(&ctx,v,16,(*fmt=='X'),width,zero_pad,left,alt,0,0,0); + break; + } + case 'b': case 'B': { + unsigned long long v; + if (is_ll) v = va_arg(ap, unsigned long long); + else if (is_l) v = va_arg(ap, unsigned long); + else v = va_arg(ap, unsigned int); + write_uint_full(&ctx,v,2,(*fmt=='B'),width,zero_pad,left,alt,0,0,0); + break; + } + case 'p': { + uintptr_t v = (uintptr_t)va_arg(ap, void*); + ctx_putc(&ctx,'0'); ctx_putc(&ctx,'x'); + write_uint_full(&ctx,(unsigned long long)v,16,0,width,1,left,0,0,0,0); + break; + } + case 'f': case 'F': + case 'e': case 'E': + case 'g': case 'G': { + double v = va_arg(ap, double); + write_float(&ctx, v, *fmt, + has_prec ? precision : 6, + width, zero_pad, left, show_sign, space_sign, alt); + break; + } + case 's': { + const char* s = va_arg(ap, const char*); + if (!s) s = "(null)"; + int slen = (int)strlen(s); + if (has_prec && precision < slen) slen = precision; + int pad = (width > slen) ? width - slen : 0; + if (!left) { for (int j=0;j 1) ? width - 1 : 0; + if (!left) { for (int j=0;j 0) buf[ctx.pos < size ? ctx.pos : size-1] = '\0'; + return (int)ctx.pos; +} + +int vprintf(const char* restrict fmt, va_list ap) { + char buf[1024]; + int n = vsnprintf(buf, sizeof(buf), fmt, ap); + for (int i = 0; i < n && buf[i]; i++) putchar(buf[i]); + return n; +} + +int vsprintf(char* restrict buf, const char* restrict fmt, va_list ap) { + return vsnprintf(buf, (size_t)-1, fmt, ap); +} + +int snprintf(char* restrict buf, size_t size, const char* restrict fmt, ...) { + va_list ap; va_start(ap, fmt); + int n = vsnprintf(buf, size, fmt, ap); + va_end(ap); return n; +} + +int sprintf(char* restrict buf, const char* restrict fmt, ...) { + va_list ap; va_start(ap, fmt); + int n = vsnprintf(buf, (size_t)-1, fmt, ap); + va_end(ap); return n; +} \ No newline at end of file diff --git a/libc/src/stdlib/aligned_alloc.c b/libc/src/stdlib/aligned_alloc.c new file mode 100644 index 0000000..d691e99 --- /dev/null +++ b/libc/src/stdlib/aligned_alloc.c @@ -0,0 +1,14 @@ +#include +#include "../../../kernel/include/memory/pmm.h" + +void *aligned_alloc(size_t alignment, size_t size) { + if (size == 0) return NULL; + if (alignment == 0 || (alignment & (alignment - 1))) return NULL; + if (alignment < 16) alignment = 16; + void *raw = kmalloc(size + alignment + sizeof(void *)); + if (!raw) return NULL; + uintptr_t addr = (uintptr_t)raw + sizeof(void *); + uintptr_t aligned = (addr + alignment - 1) & ~(alignment - 1); + ((void **)aligned)[-1] = raw; + return (void *)aligned; +} diff --git a/libc/src/stdlib/aligned_free.c b/libc/src/stdlib/aligned_free.c new file mode 100644 index 0000000..3496579 --- /dev/null +++ b/libc/src/stdlib/aligned_free.c @@ -0,0 +1,8 @@ +#include +#include "../../../kernel/include/memory/pmm.h" + +void aligned_free(void* ptr) { + if (!ptr) return; + void* raw = ((void**)ptr)[-1]; + kfree(raw); +} diff --git a/libc/src/stdlib/calloc.c b/libc/src/stdlib/calloc.c new file mode 100644 index 0000000..ac4f2d1 --- /dev/null +++ b/libc/src/stdlib/calloc.c @@ -0,0 +1,11 @@ +#include +#include +#include "../../../kernel/include/memory/pmm.h" + +void *calloc(size_t nmemb, size_t size) { + if (nmemb == 0 || size == 0) return NULL; + if (nmemb > (size_t)-1 / size) return NULL; + void *ptr = kmalloc(nmemb * size); + if (ptr) memset(ptr, 0, nmemb * size); + return ptr; +} diff --git a/libc/src/stdlib/free.c b/libc/src/stdlib/free.c new file mode 100644 index 0000000..a469d8d --- /dev/null +++ b/libc/src/stdlib/free.c @@ -0,0 +1,6 @@ +#include +#include "../../../kernel/include/memory/pmm.h" + +void free(void *ptr) { + kfree(ptr); +} \ No newline at end of file diff --git a/libc/src/stdlib/itoa.c b/libc/src/stdlib/itoa.c new file mode 100644 index 0000000..2ca12b9 --- /dev/null +++ b/libc/src/stdlib/itoa.c @@ -0,0 +1,41 @@ +#include +#include +#include + +char *itoa(int val, char *restrict str, int base) { + int i = 0; + bool negative = false; + + if (val == 0) { + str[i++] = '0'; + str[i] = '\0'; + return str; + } + + if (val < 0 && base == 10) { + negative = true; + val = -val; + } + + while (val != 0) { + int rem = val % base; + str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0'; + val = val / base; + } + + if (negative) + str[i++] = '-'; + + str[i] = '\0'; + + int start = 0, end = strlen(str) - 1; + while (start < end) { + char temp = str[start]; + str[start] = str[end]; + str[end] = temp; + end--; + start++; + } + + return str; +} diff --git a/libc/src/stdlib/malloc.c b/libc/src/stdlib/malloc.c new file mode 100644 index 0000000..7463a04 --- /dev/null +++ b/libc/src/stdlib/malloc.c @@ -0,0 +1,7 @@ +#include +#include "../../../kernel/include/memory/pmm.h" + +void *malloc(size_t size) { + if (size == 0) return NULL; + return kmalloc(size); +} diff --git a/libc/src/stdlib/realloc.c b/libc/src/stdlib/realloc.c new file mode 100644 index 0000000..913b263 --- /dev/null +++ b/libc/src/stdlib/realloc.c @@ -0,0 +1,7 @@ +#include +#include +#include "../../../kernel/include/memory/pmm.h" + +void *realloc(void *ptr, size_t size) { + return krealloc(ptr, size); +} \ No newline at end of file diff --git a/libc/src/stdlib/strtoll.c b/libc/src/stdlib/strtoll.c new file mode 100644 index 0000000..95861cf --- /dev/null +++ b/libc/src/stdlib/strtoll.c @@ -0,0 +1,40 @@ +#include +#include +#include + +long long strtoll(const char * restrict s, char ** restrict end, int base) { + while (*s == ' ' || *s == '\t') s++; + + bool neg = false; + if (*s == '-') { neg = true; s++; } + else if (*s == '+') { s++; } + + if (base == 0) { + if (*s == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (*s == '0') { base = 8; s++; } + else { base = 10; } + } else if (base == 16 && *s == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + + unsigned long long acc = 0; + const char *start = s; + + while (*s) { + int digit; + char c = *s; + if (c >= '0' && c <= '9') digit = c - '0'; + else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10; + else break; + + if (digit >= base) break; + acc = acc * (unsigned)base + (unsigned)digit; + s++; + } + + if (end) *end = (s == start) ? (char *)start : (char *)s; + + if (neg) return -(long long)acc; + return (long long)acc; +} \ No newline at end of file diff --git a/libc/src/stdlib/strtoull.c b/libc/src/stdlib/strtoull.c new file mode 100644 index 0000000..b8d67de --- /dev/null +++ b/libc/src/stdlib/strtoull.c @@ -0,0 +1,36 @@ +#include +#include +#include + +unsigned long long strtoull(const char * restrict s, char ** restrict end, int base) { + while (*s == ' ' || *s == '\t') s++; + + if (*s == '+') s++; + + if (base == 0) { + if (*s == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (*s == '0') { base = 8; s++; } + else { base = 10; } + } else if (base == 16 && *s == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + + unsigned long long acc = 0; + const char *start = s; + + while (*s) { + int digit; + char c = *s; + if (c >= '0' && c <= '9') digit = c - '0'; + else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10; + else break; + + if (digit >= base) break; + acc = acc * (unsigned)base + (unsigned)digit; + s++; + } + + if (end) *end = (s == start) ? (char *)start : (char *)s; + return acc; +} \ No newline at end of file diff --git a/libc/src/string/memchr.c b/libc/src/string/memchr.c new file mode 100644 index 0000000..74362e3 --- /dev/null +++ b/libc/src/string/memchr.c @@ -0,0 +1,12 @@ +#include + +void *memchr(void *ptr, int val, size_t n) { + const unsigned char *p = (const unsigned char*)ptr; + unsigned char c = (unsigned char)val; + for (size_t i = 0; i < n; i++) { + if (p[i] == c) + return (void*)(p + i); + } + + return NULL; +} diff --git a/libc/src/string/memcmp.c b/libc/src/string/memcmp.c new file mode 100644 index 0000000..2b1d2f1 --- /dev/null +++ b/libc/src/string/memcmp.c @@ -0,0 +1,14 @@ +#include + +int memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *p1 = (const uint8_t *)s1; + const uint8_t *p2 = (const uint8_t *)s2; + + for (size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] < p2[i] ? -1 : 1; + } + } + + return 0; +} \ No newline at end of file diff --git a/libc/src/string/memcpy.c b/libc/src/string/memcpy.c new file mode 100644 index 0000000..fc86ad2 --- /dev/null +++ b/libc/src/string/memcpy.c @@ -0,0 +1,12 @@ +#include + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) { + uint8_t *restrict pdest = (uint8_t *restrict)dest; + const uint8_t *restrict psrc = (const uint8_t *restrict)src; + + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + + return dest; +} \ No newline at end of file diff --git a/libc/src/string/memmove.c b/libc/src/string/memmove.c new file mode 100644 index 0000000..5b9d943 --- /dev/null +++ b/libc/src/string/memmove.c @@ -0,0 +1,18 @@ +#include + +void *memmove(void *dest, const void *src, size_t n) { + uint8_t *pdest = (uint8_t *)dest; + const uint8_t *psrc = (const uint8_t *)src; + + if (src > dest) { + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + } else if (src < dest) { + for (size_t i = n; i > 0; i--) { + pdest[i-1] = psrc[i-1]; + } + } + + return dest; +} \ No newline at end of file diff --git a/libc/src/string/memset.c b/libc/src/string/memset.c new file mode 100644 index 0000000..45d2f56 --- /dev/null +++ b/libc/src/string/memset.c @@ -0,0 +1,11 @@ +#include + +void *memset(void *s, int c, size_t n) { + uint8_t *p = (uint8_t *)s; + + for (size_t i = 0; i < n; i++) { + p[i] = (uint8_t)c; + } + + return s; +} \ No newline at end of file diff --git a/libc/src/string/memset_explicit.c b/libc/src/string/memset_explicit.c new file mode 100644 index 0000000..306c102 --- /dev/null +++ b/libc/src/string/memset_explicit.c @@ -0,0 +1,7 @@ +#include + +void* memset_explicit(void* dst, int c, size_t n) { + volatile unsigned char* p = (volatile unsigned char*)dst; + while (n--) *p++ = (unsigned char)c; + return dst; +} \ No newline at end of file diff --git a/libc/src/string/rawmemchr.c b/libc/src/string/rawmemchr.c new file mode 100644 index 0000000..a54ac24 --- /dev/null +++ b/libc/src/string/rawmemchr.c @@ -0,0 +1,11 @@ +#include + +void *rawmemchr(void *ptr, int val) { + const unsigned char *p = (const unsigned char*)ptr; + unsigned char c = (unsigned char)val; + for (;;) { + if (*p == c) + return (void*)p; + p++; + } +} diff --git a/libc/src/string/strcat.c b/libc/src/string/strcat.c new file mode 100644 index 0000000..c09e93d --- /dev/null +++ b/libc/src/string/strcat.c @@ -0,0 +1,17 @@ +#include + +char *strcat(char *dest, const char *src) { + char *ptr = dest; + + while (*ptr != '\0') { + ptr++; + } + + while (*src != '\0') { + *ptr++ = *src++; + } + + *ptr = '\0'; + + return dest; +} \ No newline at end of file diff --git a/libc/src/string/strchr.c b/libc/src/string/strchr.c new file mode 100644 index 0000000..5323015 --- /dev/null +++ b/libc/src/string/strchr.c @@ -0,0 +1,16 @@ +#include + +char *strchr(const char *str, int c) { + char ch = (char)c; + while (*str) { + if (*str == ch) + return (char*)str; + + str++; + } + + if (ch == '\0') + return (char*)str; + + return NULL; +} diff --git a/libc/src/string/strcmp.c b/libc/src/string/strcmp.c new file mode 100644 index 0000000..56500bc --- /dev/null +++ b/libc/src/string/strcmp.c @@ -0,0 +1,13 @@ +#include + +int strcmp(const char *str1, const char *str2) { + size_t i = 0; + while (str1[i] != '\0' && str2[i] != '\0') { + if (str1[i] != str2[i]) + goto ret; + + i++; + } +ret: + return (unsigned char)str1[i] - (unsigned char)str2[i]; +} diff --git a/libc/src/string/strcpy.c b/libc/src/string/strcpy.c new file mode 100644 index 0000000..4313542 --- /dev/null +++ b/libc/src/string/strcpy.c @@ -0,0 +1,7 @@ +#include + +char *strcpy(char *dest, const char *src) { + char *p = dest; + while ((*p++ = *src++) != '\0') { } + return dest; +} diff --git a/libc/src/string/strcspn.c b/libc/src/string/strcspn.c new file mode 100644 index 0000000..5418ec9 --- /dev/null +++ b/libc/src/string/strcspn.c @@ -0,0 +1,11 @@ +#include + +size_t strcspn(const char* s, const char* reject) { + size_t n = 0; + while (s[n]) { + for (const char* r = reject; *r; r++) + if (s[n] == *r) return n; + n++; + } + return n; +} \ No newline at end of file diff --git a/libc/src/string/strdup.c b/libc/src/string/strdup.c new file mode 100644 index 0000000..e65f65b --- /dev/null +++ b/libc/src/string/strdup.c @@ -0,0 +1,9 @@ +#include +#include + +char* strdup(const char* s) { + size_t len = strlen(s) + 1; + char* copy = malloc(len); + if (copy) memcpy(copy, s, len); + return copy; +} \ No newline at end of file diff --git a/libc/src/string/strlen.c b/libc/src/string/strlen.c new file mode 100644 index 0000000..58dc618 --- /dev/null +++ b/libc/src/string/strlen.c @@ -0,0 +1,11 @@ +#include + +size_t strlen(const char *str) { + size_t len = 0; + while (*str != '\0') { + len++; + str++; + } + + return len; +} diff --git a/libc/src/string/strncat.c b/libc/src/string/strncat.c new file mode 100644 index 0000000..a1a5a88 --- /dev/null +++ b/libc/src/string/strncat.c @@ -0,0 +1,9 @@ +#include + +char* strncat(char* restrict dst, const char* restrict src, size_t n) { + char* d = dst + strlen(dst); + while (n-- && *src) + *d++ = *src++; + *d = '\0'; + return dst; +} \ No newline at end of file diff --git a/libc/src/string/strncmp.c b/libc/src/string/strncmp.c new file mode 100644 index 0000000..96d56c0 --- /dev/null +++ b/libc/src/string/strncmp.c @@ -0,0 +1,18 @@ +#include + +int strncmp(const char *str1, const char *str2, size_t n) { + size_t i = 0; + while (i < n) { + unsigned char c1 = (unsigned char)str1[i], + c2 = (unsigned char)str2[i]; + if (c1 != c2) + return c1 - c2; + + if (c1 == '\0') + return 0; + + i++; + } + + return 0; +} diff --git a/libc/src/string/strncpy.c b/libc/src/string/strncpy.c new file mode 100644 index 0000000..08a86eb --- /dev/null +++ b/libc/src/string/strncpy.c @@ -0,0 +1,11 @@ +#include + +char *strncpy(char *dest, const char *src, size_t n) { + size_t i = 0; + for (; i < n && src[i] != '\0'; i++) + dest[i] = src[i]; + for (; i < n; i++) + dest[i] = '\0'; + + return dest; +} diff --git a/libc/src/string/strnlen.c b/libc/src/string/strnlen.c new file mode 100644 index 0000000..3b542d6 --- /dev/null +++ b/libc/src/string/strnlen.c @@ -0,0 +1,7 @@ +#include + +size_t strnlen(const char* s, size_t maxlen) { + size_t n = 0; + while (n < maxlen && s[n]) n++; + return n; +} \ No newline at end of file diff --git a/libc/src/string/strpbrk.c b/libc/src/string/strpbrk.c new file mode 100644 index 0000000..c55a880 --- /dev/null +++ b/libc/src/string/strpbrk.c @@ -0,0 +1,11 @@ +#include + +char *strpbrk(const char *str1, const char *str2) { + for (; *str1 != '\0'; str1++) { + for (const char *p = str2; *p != '\0'; p++) { + if (*str1 == *p) + return (char*)str1; + } + } + return NULL; +} diff --git a/libc/src/string/strrchr.c b/libc/src/string/strrchr.c new file mode 100644 index 0000000..55962f7 --- /dev/null +++ b/libc/src/string/strrchr.c @@ -0,0 +1,9 @@ +#include + +char* strrchr(const char* s, int c) { + const char* last = NULL; + do { + if (*s == (char)c) last = s; + } while (*s++); + return (char*)last; +} \ No newline at end of file diff --git a/libc/src/string/strspn.c b/libc/src/string/strspn.c new file mode 100644 index 0000000..49c7474 --- /dev/null +++ b/libc/src/string/strspn.c @@ -0,0 +1,24 @@ +#include + +size_t strspn(const char *str1, const char *str2) { + size_t count = 0; + while (*str1) { + const char *p = str2; + int found = 0; + while (*p) { + if (*str1 == *p) { + found = 1; + break; + } + p++; + } + + if (!found) + break; + + count++; + str1++; + } + + return count; +} diff --git a/libc/src/string/strstr.c b/libc/src/string/strstr.c new file mode 100644 index 0000000..a3d3f6d --- /dev/null +++ b/libc/src/string/strstr.c @@ -0,0 +1,17 @@ +#include + +char *strstr(const char *haystack, const char *needle) { + if (*needle == '\0') + return (char*)haystack; + + for (size_t i = 0; haystack[i] != '\0'; i++) { + size_t j = 0; + while (needle[j] != '\0' && haystack[j + i] == needle[j]) + j++; + + if (needle[j] == '\0') + return (char*)&haystack[i]; + } + + return NULL; +} diff --git a/libc/src/string/strtok.c b/libc/src/string/strtok.c new file mode 100644 index 0000000..0c92bf0 --- /dev/null +++ b/libc/src/string/strtok.c @@ -0,0 +1,22 @@ +#include + +static char *old_string = NULL; + +char *strtok(char *str, const char *delim) { + if (str == NULL) str = old_string; + str += strspn(str, delim); + if (*str == '\0') { + old_string = str; + return NULL; + } + + char *token = str; + str = strpbrk(token, delim); + if (str == NULL) + old_string = rawmemchr(token, '\0'); + else { + *str = '\0'; + old_string = str + 1; + } + return token; +} diff --git a/libc/src/string/strtol.c b/libc/src/string/strtol.c new file mode 100644 index 0000000..5100921 --- /dev/null +++ b/libc/src/string/strtol.c @@ -0,0 +1,39 @@ +#include +#include + +long strtol(const char* restrict s, char** restrict end, int base) { + while (isspace((unsigned char)*s)) s++; + + int neg = 0; + if (*s == '-') { neg = 1; s++; } + else if (*s == '+') { s++; } + + if ((base == 0 || base == 16) && s[0] == '0' && + (s[1] == 'x' || s[1] == 'X')) { + base = 16; + s += 2; + } else if (base == 0 && s[0] == '0') { + base = 8; + s++; + } else if (base == 0) { + base = 10; + } + + long result = 0; + int any = 0; + for (; *s; s++) { + int digit; + unsigned char c = (unsigned char)*s; + if (isdigit(c)) digit = c - '0'; + else if (isupper(c)) digit = c - 'A' + 10; + else if (islower(c)) digit = c - 'a' + 10; + else break; + + if (digit >= base) break; + result = result * base + digit; + any = 1; + } + + if (end) *end = (char*)(any ? s : (const char*)s); + return neg ? -result : result; +} \ No newline at end of file diff --git a/libc/src/string/strtoul.c b/libc/src/string/strtoul.c new file mode 100644 index 0000000..4a293e2 --- /dev/null +++ b/libc/src/string/strtoul.c @@ -0,0 +1,37 @@ +#include +#include + +unsigned long strtoul(const char* restrict s, char** restrict end, int base) { + while (isspace((unsigned char)*s)) s++; + + if (*s == '+') s++; + + if ((base == 0 || base == 16) && s[0] == '0' && + (s[1] == 'x' || s[1] == 'X')) { + base = 16; + s += 2; + } else if (base == 0 && s[0] == '0') { + base = 8; + s++; + } else if (base == 0) { + base = 10; + } + + unsigned long result = 0; + int any = 0; + for (; *s; s++) { + int digit; + unsigned char c = (unsigned char)*s; + if (isdigit(c)) digit = c - '0'; + else if (isupper(c)) digit = c - 'A' + 10; + else if (islower(c)) digit = c - 'a' + 10; + else break; + + if (digit >= base) break; + result = result * (unsigned long)base + (unsigned long)digit; + any = 1; + } + + if (end) *end = (char*)(any ? s : (const char*)s); + return result; +} \ No newline at end of file diff --git a/usr/apps/cal.c b/usr/apps/cal.c new file mode 100644 index 0000000..0389cca --- /dev/null +++ b/usr/apps/cal.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include + +#define A_INVERT "\x1b[7m" +#define A_BOLD "\x1b[1m" +#define A_RESET "\x1b[0m" + +static const char *MNAME[12] = { + "January","February","March","April","May","June", + "July","August","September","October","November","December" +}; +static int MDAYS[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; + +static int is_leap(int y) +{ + return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); +} + +static int days_in_month(int y, int m) +{ + if (m == 2 && is_leap(y)) return 29; + return MDAYS[m - 1]; +} + +static int first_dow(int y, int m) +{ + int64_t days = 0; + for (int yr = 1970; yr < y; yr++) days += is_leap(yr) ? 366 : 365; + int md[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; + md[1] = is_leap(y) ? 29 : 28; + for (int mo = 0; mo < m - 1; mo++) days += md[mo]; + return (int)((days + 4) % 7); +} + +static void get_today(int *out_y, int *out_m, int *out_d) +{ + *out_y = 0; *out_m = 0; *out_d = 0; + + cervus_timespec_t ts; + if (cervus_clock_gettime(CLOCK_REALTIME, &ts) != 0 || ts.tv_sec <= 0) + return; + + int64_t t = ts.tv_sec; + int64_t days = t / 86400; + + int y = 1970; + while (1) { + int dy = is_leap(y) ? 366 : 365; + if (days < dy) break; + days -= dy; + y++; + } + + int md[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; + md[1] = is_leap(y) ? 29 : 28; + + int mo = 0; + while (mo < 12) { if (days < md[mo]) break; days -= md[mo]; mo++; } + + *out_y = y; + *out_m = mo + 1; + *out_d = (int)(days + 1); +} + +static void print_header(int y, int m, int today_y, int today_m) +{ + char title[64]; + snprintf(title, sizeof(title), "%s %d", MNAME[m - 1], y); + int tlen = (int)strlen(title); + int pad = (20 - tlen) / 2; + + for (int i = 0; i < pad; i++) putchar(' '); + + if (y == today_y && m == today_m) { + fputs(A_INVERT A_BOLD, stdout); + fputs(title, stdout); + fputs(A_RESET, stdout); + } else { + fputs(A_BOLD, stdout); + fputs(title, stdout); + fputs(A_RESET, stdout); + } + putchar('\n'); + fputs(C_GRAY " Su Mo Tu We Th Fr Sa" A_RESET "\n", stdout); +} + +static void print_month(int y, int m, int today_y, int today_m, int today_d) +{ + print_header(y, m, today_y, today_m); + + int mdays = days_in_month(y, m); + int dow = first_dow(y, m); + + for (int i = 0; i < dow; i++) fputs(" ", stdout); + + for (int d = 1; d <= mdays; d++) { + int is_today = (y == today_y && m == today_m && d == today_d); + + if (is_today) { + fprintf(stdout, " " A_INVERT A_BOLD "%2d" A_RESET, d); + } else { + fprintf(stdout, " %2d", d); + } + + if (++dow == 7) { + putchar('\n'); + dow = 0; + } + } + if (dow != 0) putchar('\n'); +} + +static void print_help(void) +{ + fputs( + "Usage: cal [OPTION] [[MONTH] YEAR]\n" + "Display a calendar.\n" + "\n" + " (no args) current month\n" + " YEAR all 12 months of YEAR\n" + " MONTH YEAR specific month (MONTH = 1-12)\n" + " --help display this help and exit\n" + "\n" + "Today's day is highlighted with " A_INVERT "inverted colours" A_RESET ".\n" + "The current month header is also " A_INVERT "inverted" A_RESET ".\n", + stdout + ); +} + +int main(int argc, char **argv) +{ + int today_y, today_m, today_d; + get_today(&today_y, &today_m, &today_d); + + int year = today_y ? today_y : 2025; + int month = today_m ? today_m : 1; + + const char *args[2] = {NULL, NULL}; + int real_argc = 0; + int flag_help = 0; + + for (int i = 1; i < argc; i++) { + if (is_shell_flag(argv[i])) continue; + if (strcmp(argv[i], "--help") == 0) { flag_help = 1; continue; } + if (real_argc < 2) args[real_argc] = argv[i]; + real_argc++; + } + + if (flag_help) { print_help(); return 0; } + + if (real_argc == 2) { + month = atoi(args[0]); + year = atoi(args[1]); + } else if (real_argc == 1) { + year = atoi(args[0]); + putchar('\n'); + fprintf(stdout, " " A_BOLD "%d" A_RESET "\n\n", year); + for (int m = 1; m <= 12; m++) { + print_month(year, m, today_y, today_m, today_d); + putchar('\n'); + } + return 0; + } + + if (month < 1 || month > 12) { + fputs("cal: invalid month (must be 1-12)\n", stderr); + return 1; + } + + putchar('\n'); + print_month(year, month, today_y, today_m, today_d); + putchar('\n'); + return 0; +} \ No newline at end of file diff --git a/usr/apps/calc.c b/usr/apps/calc.c new file mode 100644 index 0000000..67ef4e9 --- /dev/null +++ b/usr/apps/calc.c @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include + +#define SCALE 1000000LL + +typedef long long fx; + +static const char *p; + +static void skip_ws(void) { while (*p && isspace((unsigned char)*p)) p++; } + +static fx parse_number(int *err) +{ + skip_ws(); + long long intpart = 0, frac = 0, fscale = 1; + int had_digit = 0; + while (isdigit((unsigned char)*p)) { + intpart = intpart * 10 + (*p - '0'); + had_digit = 1; p++; + } + if (*p == '.') { + p++; + while (isdigit((unsigned char)*p)) { + if (fscale < SCALE) { frac = frac * 10 + (*p - '0'); fscale *= 10; } + had_digit = 1; p++; + } + } + if (!had_digit) { *err = 1; return 0; } + return intpart * SCALE + (frac * SCALE) / fscale; +} + +static fx parse_expr(int *err); + +static fx parse_primary(int *err) +{ + skip_ws(); + if (*p == '(') { + p++; + fx v = parse_expr(err); + skip_ws(); + if (*p == ')') p++; + else *err = 1; + return v; + } + if (*p == '-') { p++; return -parse_primary(err); } + if (*p == '+') { p++; return parse_primary(err); } + return parse_number(err); +} + +static fx parse_term(int *err) +{ + fx v = parse_primary(err); + while (!*err) { + skip_ws(); + if (*p == '*') { + p++; + fx r = parse_primary(err); + v = (v * r) / SCALE; + } else if (*p == '/') { + p++; + fx r = parse_primary(err); + if (r == 0) { *err = 2; return 0; } + v = (v * SCALE) / r; + } else break; + } + return v; +} + +static fx parse_expr(int *err) +{ + fx v = parse_term(err); + while (!*err) { + skip_ws(); + if (*p == '+') { p++; v += parse_term(err); } + else if (*p == '-') { p++; v -= parse_term(err); } + else break; + } + return v; +} + +static void print_fx(fx v) +{ + if (v < 0) { putchar('-'); v = -v; } + long long ip = v / SCALE; + long long fp = v % SCALE; + + char ibuf[32]; + int ilen = 0; + if (ip == 0) { + ibuf[ilen++] = '0'; + } else { + long long tmp = ip; + while (tmp > 0) { ibuf[ilen++] = '0' + (int)(tmp % 10); tmp /= 10; } + for (int a = 0, b = ilen - 1; a < b; a++, b--) { + char t = ibuf[a]; ibuf[a] = ibuf[b]; ibuf[b] = t; + } + } + ibuf[ilen] = '\0'; + fputs(ibuf, stdout); + + if (fp != 0) { + char fbuf[8]; + int flen = 0; + long long tmp = fp; + long long scale = SCALE; + fbuf[flen++] = '.'; + while (scale > 1) { + scale /= 10; + fbuf[flen++] = '0' + (int)(tmp / scale); + tmp %= scale; + } + fbuf[flen] = '\0'; + while (flen > 1 && fbuf[flen - 1] == '0') { fbuf[--flen] = '\0'; } + fputs(fbuf, stdout); + } + putchar('\n'); +} + +static void calc_help(void) +{ + putchar('\n'); + fputs(C_CYAN "Cervus calc" C_RESET " - fixed-point calculator (6 decimal digits)\n", stdout); + fputs(C_GRAY "-------------------------------------------" C_RESET "\n", stdout); + fputs(C_BOLD "Operators:" C_RESET " + - * / ( )\n", stdout); + fputs(C_GRAY "-------------------------------------------" C_RESET "\n", stdout); + fputs(C_BOLD "Examples:" C_RESET "\n", stdout); + fputs(" calc> " C_YELLOW "3.14 + 5" C_RESET " = 8.14\n", stdout); + fputs(" calc> " C_YELLOW "10 / 3" C_RESET " = 3.333333\n", stdout); + fputs(" calc> " C_YELLOW "2 * (3 + 4)" C_RESET " = 14\n", stdout); + fputs(" calc> " C_YELLOW "(1.5 + 2.5) * 4" C_RESET " = 16\n", stdout); + fputs(" calc> " C_YELLOW "100 / 7" C_RESET " = 14.285714\n", stdout); + fputs(" calc> " C_YELLOW "-5 + 3" C_RESET " = -2\n", stdout); + fputs(C_GRAY "-------------------------------------------" C_RESET "\n", stdout); + fputs(" Type " C_BOLD "q" C_RESET " or " C_BOLD "exit" C_RESET " to quit.\n", stdout); + putchar('\n'); +} + +static int readline_calc(char *buf, int maxlen) +{ + int i = 0; + for (;;) { + char c; + ssize_t r = read(0, &c, 1); + if (r <= 0) { + buf[i] = '\0'; + return (i > 0) ? i : -1; + } + if (c == '\r') continue; + if (c == '\n') { + write(1, "\n", 1); + buf[i] = '\0'; + return i; + } + if (c == '\b' || c == 0x7F) { + if (i > 0) { + i--; + write(1, "\b \b", 3); + } + continue; + } + if (c == 0x03) { + write(1, "^C\n", 3); + buf[0] = '\0'; + return 0; + } + if (c >= 0x20 && c < 0x7F && i < maxlen - 1) { + buf[i++] = c; + write(1, &c, 1); + } + } +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + fputs(C_CYAN "Cervus calc" C_RESET " - fixed-point (6 digits). " + "Type " C_BOLD "help" C_RESET " for examples, " C_BOLD "q" C_RESET " to exit.\n", stdout); + + char line[256]; + for (;;) { + fputs("calc> ", stdout); + int n = readline_calc(line, sizeof(line)); + if (n < 0) { putchar('\n'); break; } + while (n > 0 && isspace((unsigned char)line[n - 1])) line[--n] = '\0'; + if (n == 0) continue; + if (strcmp(line, "q") == 0 || strcmp(line, "quit") == 0 || + strcmp(line, "exit") == 0) break; + if (strcmp(line, "help") == 0) { calc_help(); continue; } + + p = line; + int err = 0; + fx v = parse_expr(&err); + skip_ws(); + if (*p != '\0') err = 1; + + if (err == 1) fputs(C_RED " parse error\n" C_RESET, stdout); + else if (err == 2) fputs(C_RED " division by zero\n" C_RESET, stdout); + else { fputs(" = ", stdout); print_fx(v); } + } + putchar('\n'); + return 0; +} \ No newline at end of file diff --git a/usr/apps/date.c b/usr/apps/date.c new file mode 100644 index 0000000..ed02fd9 --- /dev/null +++ b/usr/apps/date.c @@ -0,0 +1,98 @@ +#include +#include +#include + +static const int MDAYS[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; +static const char *MNAME[12] = { + "Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec" +}; +static const char *WDAY[7] = {"Thu","Fri","Sat","Sun","Mon","Tue","Wed"}; + +static int is_leap(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); } +static int bcd2bin(int v) { return (v & 0x0F) + ((v >> 4) * 10); } + +static int cmos_read(int reg) +{ + if (cervus_ioport_write(0x70, 1, (uint32_t)(reg & 0x7F)) < 0) return -1; + return (int)(cervus_ioport_read(0x71, 1) & 0xFF); +} +static void cmos_wait_ready(void) +{ + for (int i = 0; i < 2000; i++) { + cervus_ioport_write(0x70, 1, 0x0A); + uint32_t sta = cervus_ioport_read(0x71, 1); + if (!(sta & 0x80)) return; + } +} + +static int64_t rtc_read_unix(void) +{ + cmos_wait_ready(); + int sec = cmos_read(0x00); + int min = cmos_read(0x02); + int hour = cmos_read(0x04); + int mday = cmos_read(0x07); + int mon = cmos_read(0x08); + int year = cmos_read(0x09); + if (sec < 0 || min < 0 || hour < 0 || mday < 0 || mon < 0 || year < 0) return 0; + + cervus_ioport_write(0x70, 1, 0x0B); + int regb = (int)cervus_ioport_read(0x71, 1); + int binary_mode = (regb >= 0) && (regb & 0x04); + int hour24 = (regb >= 0) && (regb & 0x02); + + if (!binary_mode) { + sec = bcd2bin(sec); + min = bcd2bin(min); + mday = bcd2bin(mday); + mon = bcd2bin(mon); + year = bcd2bin(year); + if (!hour24 && (hour & 0x80)) hour = bcd2bin(hour & 0x7F) + 12; + else hour = bcd2bin(hour); + } + year += (year < 70) ? 2000 : 1900; + if (sec < 0 || sec > 59 || min < 0 || min > 59) return 0; + if (hour < 0 || hour > 23) return 0; + if (mday < 1 || mday > 31) return 0; + if (mon < 1 || mon > 12) return 0; + if (year < 2000) return 0; + + int64_t days = 0; + for (int y = 1970; y < year; y++) days += is_leap(y) ? 366 : 365; + for (int m = 1; m < mon; m++) days += MDAYS[m-1] + (m == 2 && is_leap(year) ? 1 : 0); + days += mday - 1; + return days * 86400LL + (int64_t)hour * 3600LL + (int64_t)min * 60LL + (int64_t)sec; +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + int64_t t = rtc_read_unix(); + if (t <= 0) { + uint64_t up = cervus_uptime_ns() / 1000000000ULL; + fputs(" RTC not available.\n", stdout); + printf(" Uptime: %lus\n", (unsigned long)up); + return 0; + } + int wday = (int)((t / 86400 + 4) % 7); + int64_t days = t / 86400; + int64_t rem = t % 86400; + if (rem < 0) { rem += 86400; days--; } + int hour = (int)(rem / 3600); + int min = (int)((rem % 3600) / 60); + int sec = (int)(rem % 60); + + int year = 1970; + while (1) { int dy = is_leap(year) ? 366 : 365; if (days < dy) break; days -= dy; year++; } + int mon = 0; + while (mon < 12) { + int dm = MDAYS[mon] + (mon == 1 && is_leap(year) ? 1 : 0); + if (days < dm) break; + days -= dm; mon++; + } + int mday = (int)days + 1; + printf(" %s %s %2d %02d:%02d:%02d UTC %04d\n", + WDAY[wday], MNAME[mon], mday, hour, min, sec, year); + return 0; +} diff --git a/usr/apps/execve_target.c b/usr/apps/execve_target.c new file mode 100644 index 0000000..13b9bc5 --- /dev/null +++ b/usr/apps/execve_target.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + puts("=== execve_target: STARTED ==="); + printf(" PID: %d\n", (int)getpid()); + printf(" PPID: %d\n", (int)getppid()); + printf(" argc: %d\n", argc); + for (int i = 0; i < argc; i++) + printf(" argv[%d] = %s\n", i, argv[i]); + puts("=== execve_target: DONE, exit(99) ==="); + return 99; +} diff --git a/usr/apps/fetch.c b/usr/apps/fetch.c new file mode 100644 index 0000000..865e1f7 --- /dev/null +++ b/usr/apps/fetch.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +static const char *logo[] = { + " L ", + " 'k.i , ", + " \";\"+U., ", + " \\_' -. ", + " .f ,_.;.", + " I ,f ", + " ' ", + NULL +}; + +static void cpuid_leaf(uint32_t leaf, uint32_t *a, uint32_t *b, + uint32_t *c, uint32_t *d) +{ + asm volatile ("cpuid" + : "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d) + : "0"(leaf), "2"(0)); +} + +static void print_uptime(void) +{ + uint64_t ns = cervus_uptime_ns(); + uint64_t total_s = ns / 1000000000ULL; + uint64_t ms = (ns / 1000000ULL) % 1000ULL; + uint64_t secs = total_s % 60; + uint64_t mins = (total_s / 60) % 60; + uint64_t hours = (total_s / 3600) % 24; + uint64_t days = total_s / 86400; + fputs("uptime: ", stdout); + if (days > 0) printf("%lud, ", (unsigned long)days); + printf("%02lu:%02lu:%02lu (%lus %lums)", + (unsigned long)hours, (unsigned long)mins, (unsigned long)secs, + (unsigned long)total_s, (unsigned long)ms); +} + +static void print_cpu(void) +{ + uint32_t a, b, c, d; + cpuid_leaf(0x80000000, &a, &b, &c, &d); + if (a >= 0x80000004) { + char brand[49]; + uint32_t *p = (uint32_t *)brand; + cpuid_leaf(0x80000002, &p[0], &p[1], &p[2], &p[3]); + cpuid_leaf(0x80000003, &p[4], &p[5], &p[6], &p[7]); + cpuid_leaf(0x80000004, &p[8], &p[9], &p[10], &p[11]); + brand[48] = '\0'; + const char *br = brand; + while (*br == ' ') br++; + printf("cpu: %s", br); + } +} + +static void print_mem(void) +{ + cervus_meminfo_t mi; + if (cervus_meminfo(&mi) != 0) return; + uint64_t used = mi.used_bytes; + uint64_t total = mi.total_bytes; + const uint64_t MiB = 1024ULL * 1024; + const uint64_t GiB = 1024ULL * 1024 * 1024; + fputs("mem: ", stdout); + if (total >= GiB) + printf("%lu.%02lu / %lu.%02lu GiB", + (unsigned long)(used / GiB), (unsigned long)((used % GiB) * 100 / GiB), + (unsigned long)(total / GiB), (unsigned long)((total % GiB) * 100 / GiB)); + else + printf("%lu / %lu MiB", + (unsigned long)(used / MiB), (unsigned long)(total / MiB)); +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + putchar('\n'); + for (int i = 0; logo[i]; i++) { + printf(" %s ", logo[i]); + switch (i) { + case 1: fputs("os: Cervus OS", stdout); break; + case 2: print_uptime(); break; + case 3: print_cpu(); break; + case 4: fputs(C_RESET "shell: CSH", stdout); break; + case 5: print_mem(); break; + } + putchar('\n'); + } + putchar('\n'); + return 0; +} diff --git a/usr/apps/hello.c b/usr/apps/hello.c new file mode 100644 index 0000000..31756a5 --- /dev/null +++ b/usr/apps/hello.c @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char **argv) +{ + (void)argv; + printf("Hello, Cervus world!\n"); + printf("argc = %d\n", argc); + return 0; +} diff --git a/usr/apps/neo.c b/usr/apps/neo.c new file mode 100644 index 0000000..17ed48f --- /dev/null +++ b/usr/apps/neo.c @@ -0,0 +1,908 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NEO_VERSION "0.1" +#define NEO_TABSTOP 4 +#define NEO_QUIT_CONFIRM 1 + +#define KEY_NONE 0 +#define KEY_ESC 0x1B +#define KEY_BACKSPACE 127 +#define KEY_CTRL(k) ((k) & 0x1f) + +#define KEY_ARROW_UP 1000 +#define KEY_ARROW_DOWN 1001 +#define KEY_ARROW_LEFT 1002 +#define KEY_ARROW_RIGHT 1003 +#define KEY_HOME 1004 +#define KEY_END 1005 +#define KEY_DEL 1006 +#define KEY_PAGE_UP 1007 +#define KEY_PAGE_DOWN 1008 + +#define TIOCGWINSZ 0x5413 + +typedef struct { uint16_t ws_row, ws_col, ws_xpixel, ws_ypixel; } neo_winsize_t; + +typedef struct { + int size; + int cap; + char *chars; + int rsize; + char *render; +} neo_row_t; + +typedef struct { + int cx, cy; + int rx; + int rowoff; + int coloff; + int screenrows; + int screencols; + int numrows; + int rowscap; + neo_row_t *row; + int dirty; + char *filename; + char statusmsg[256]; + int statusmsg_visible; + int quit_pending; + long disk_size; + struct termios orig_termios; +} neo_t; + +static neo_t E; +static const char *g_cwd = "/"; + +static void die(const char *msg) +{ + write(1, "\x1b[2J", 4); + write(1, "\x1b[H", 3); + if (msg) { + write(2, "neo: ", 5); + write(2, msg, strlen(msg)); + write(2, "\n", 1); + } + exit(1); +} + +static void disable_raw_mode(void) +{ + tcsetattr(0, TCSAFLUSH, &E.orig_termios); + write(1, "\x1b[?7h\x1b[?25h", 11); +} + +static void enable_raw_mode(void) +{ + if (tcgetattr(0, &E.orig_termios) < 0) die("tcgetattr"); + atexit(disable_raw_mode); + + struct termios raw = E.orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + if (tcsetattr(0, TCSAFLUSH, &raw) < 0) die("tcsetattr"); + write(1, "\x1b[?7l", 5); +} + +static int read_key(void) +{ + char c; + ssize_t n; + while ((n = read(0, &c, 1)) == 0) { } + if (n < 0) return KEY_NONE; + + if (c != 0x1B) return (unsigned char)c; + + char seq[4]; + if (read(0, &seq[0], 1) != 1) return KEY_ESC; + if (read(0, &seq[1], 1) != 1) return KEY_ESC; + + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + if (read(0, &seq[2], 1) != 1) return KEY_ESC; + if (seq[2] == '~') { + switch (seq[1]) { + case '1': + case '7': return KEY_HOME; + case '3': return KEY_DEL; + case '4': + case '8': return KEY_END; + case '5': return KEY_PAGE_UP; + case '6': return KEY_PAGE_DOWN; + } + } + } else { + switch (seq[1]) { + case 'A': return KEY_ARROW_UP; + case 'B': return KEY_ARROW_DOWN; + case 'C': return KEY_ARROW_RIGHT; + case 'D': return KEY_ARROW_LEFT; + case 'H': return KEY_HOME; + case 'F': return KEY_END; + } + } + } else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': return KEY_HOME; + case 'F': return KEY_END; + } + } + return KEY_ESC; +} + +static void get_window_size(void) +{ + neo_winsize_t ws; + if (syscall3(SYS_IOCTL, 1, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0 && ws.ws_row > 0) { + E.screencols = ws.ws_col; + E.screenrows = ws.ws_row; + } else { + E.screencols = 80; + E.screenrows = 24; + } + E.screenrows -= 2; + if (E.screenrows < 1) E.screenrows = 1; +} + +static int row_cx_to_rx(neo_row_t *row, int cx) +{ + int rx = 0; + for (int j = 0; j < cx && j < row->size; j++) { + if (row->chars[j] == '\t') rx += (NEO_TABSTOP - (rx % NEO_TABSTOP)); + else rx++; + } + return rx; +} + +static int row_rx_to_cx(neo_row_t *row, int rx) +{ + int cur_rx = 0; + int cx; + for (cx = 0; cx < row->size; cx++) { + if (row->chars[cx] == '\t') cur_rx += (NEO_TABSTOP - (cur_rx % NEO_TABSTOP)); + else cur_rx++; + if (cur_rx > rx) return cx; + } + return cx; +} + +static void row_update(neo_row_t *row) +{ + int tabs = 0; + for (int j = 0; j < row->size; j++) if (row->chars[j] == '\t') tabs++; + free(row->render); + row->render = malloc(row->size + tabs * (NEO_TABSTOP - 1) + 1); + int idx = 0; + for (int j = 0; j < row->size; j++) { + if (row->chars[j] == '\t') { + row->render[idx++] = ' '; + while (idx % NEO_TABSTOP != 0) row->render[idx++] = ' '; + } else { + row->render[idx++] = row->chars[j]; + } + } + row->render[idx] = '\0'; + row->rsize = idx; +} + +static void rows_reserve(int want) +{ + if (want <= E.rowscap) return; + int nc = E.rowscap ? E.rowscap * 2 : 32; + while (nc < want) nc *= 2; + neo_row_t *nr = malloc(sizeof(neo_row_t) * nc); + if (!nr) die("out of memory"); + if (E.row) { + memcpy(nr, E.row, sizeof(neo_row_t) * E.numrows); + } + for (int i = E.numrows; i < nc; i++) { + nr[i].size = 0; nr[i].cap = 0; nr[i].chars = NULL; + nr[i].rsize = 0; nr[i].render = NULL; + } + E.row = nr; + E.rowscap = nc; +} + +static void row_insert_at(int at, const char *s, int len) +{ + if (at < 0 || at > E.numrows) return; + rows_reserve(E.numrows + 1); + for (int i = E.numrows; i > at; i--) E.row[i] = E.row[i - 1]; + + neo_row_t *r = &E.row[at]; + r->size = len; + r->cap = len + 1; + r->chars = malloc(r->cap); + if (!r->chars) die("out of memory"); + if (len > 0) memcpy(r->chars, s, len); + r->chars[len] = '\0'; + r->render = NULL; + r->rsize = 0; + row_update(r); + E.numrows++; + E.dirty = 1; +} + +static void row_free(neo_row_t *r) +{ + free(r->chars); + free(r->render); + r->chars = NULL; r->render = NULL; + r->size = 0; r->cap = 0; r->rsize = 0; +} + +static void row_delete_at(int at) +{ + if (at < 0 || at >= E.numrows) return; + row_free(&E.row[at]); + for (int i = at; i < E.numrows - 1; i++) E.row[i] = E.row[i + 1]; + E.numrows--; + E.dirty = 1; +} + +static void row_reserve(neo_row_t *r, int want) +{ + if (want <= r->cap) return; + int nc = r->cap ? r->cap * 2 : 16; + while (nc < want) nc *= 2; + char *nb = malloc(nc); + if (!nb) die("out of memory"); + if (r->chars) memcpy(nb, r->chars, r->size); + nb[r->size] = '\0'; + free(r->chars); + r->chars = nb; + r->cap = nc; +} + +static void row_insert_char(neo_row_t *r, int at, int ch) +{ + if (at < 0 || at > r->size) at = r->size; + row_reserve(r, r->size + 2); + memmove(&r->chars[at + 1], &r->chars[at], r->size - at + 1); + r->chars[at] = (char)ch; + r->size++; + row_update(r); + E.dirty = 1; +} + +static void row_append_string(neo_row_t *r, const char *s, int len) +{ + row_reserve(r, r->size + len + 1); + memcpy(&r->chars[r->size], s, len); + r->size += len; + r->chars[r->size] = '\0'; + row_update(r); + E.dirty = 1; +} + +static void row_delete_char(neo_row_t *r, int at) +{ + if (at < 0 || at >= r->size) return; + memmove(&r->chars[at], &r->chars[at + 1], r->size - at); + r->size--; + row_update(r); + E.dirty = 1; +} + +static void editor_insert_char(int ch) +{ + if (E.cy == E.numrows) row_insert_at(E.numrows, "", 0); + row_insert_char(&E.row[E.cy], E.cx, ch); + E.cx++; +} + +static void editor_insert_newline(void) +{ + if (E.cx == 0) { + row_insert_at(E.cy, "", 0); + } else { + neo_row_t *r = &E.row[E.cy]; + row_insert_at(E.cy + 1, &r->chars[E.cx], r->size - E.cx); + r = &E.row[E.cy]; + r->size = E.cx; + r->chars[r->size] = '\0'; + row_update(r); + } + E.cy++; + E.cx = 0; +} + +static void editor_delete_char(void) +{ + if (E.cy == E.numrows) return; + if (E.cx == 0 && E.cy == 0) return; + + neo_row_t *r = &E.row[E.cy]; + if (E.cx > 0) { + row_delete_char(r, E.cx - 1); + E.cx--; + } else { + E.cx = E.row[E.cy - 1].size; + row_append_string(&E.row[E.cy - 1], r->chars, r->size); + row_delete_at(E.cy); + E.cy--; + } +} + +static char *rows_to_string(int *len) +{ + int total = 0; + for (int j = 0; j < E.numrows; j++) total += E.row[j].size + 1; + char *buf = malloc(total + 1); + if (!buf) die("out of memory"); + char *p = buf; + for (int j = 0; j < E.numrows; j++) { + memcpy(p, E.row[j].chars, E.row[j].size); + p += E.row[j].size; + *p++ = '\n'; + } + *p = '\0'; + *len = total; + return buf; +} + +static void set_status(const char *fmt, ...); + +static void editor_open(const char *filename) +{ + free(E.filename); + size_t fl = strlen(filename); + E.filename = malloc(fl + 1); + memcpy(E.filename, filename, fl + 1); + + char full[512]; + resolve_path(g_cwd, filename, full, sizeof(full)); + + int fd = open(full, O_RDONLY, 0); + if (fd < 0) { + E.disk_size = -1; + set_status("New file: %s", filename); + return; + } + + struct stat st; + if (fstat(fd, &st) < 0) { close(fd); E.disk_size = -1; return; } + size_t sz = (size_t)st.st_size; + E.disk_size = (long)sz; + char *buf = malloc(sz + 1); + if (!buf) { close(fd); die("out of memory"); } + + size_t total = 0; + while (total < sz) { + ssize_t r = read(fd, buf + total, sz - total); + if (r <= 0) break; + total += (size_t)r; + } + close(fd); + buf[total] = '\0'; + + size_t i = 0; + while (i < total) { + size_t start = i; + while (i < total && buf[i] != '\n' && buf[i] != '\r') i++; + int len = (int)(i - start); + row_insert_at(E.numrows, buf + start, len); + if (i < total && buf[i] == '\r') i++; + if (i < total && buf[i] == '\n') i++; + } + free(buf); + E.dirty = 0; +} + +static char *prompt(const char *prompt_fmt); + +static int editor_save(void) +{ + if (!E.filename) { + char *name = prompt("Save as (ESC to cancel): %s"); + if (!name) { + set_status("Save cancelled"); + return -1; + } + if (name[0] == '\0') { + free(name); + set_status("Save cancelled (empty filename)"); + return -1; + } + E.filename = name; + E.disk_size = -1; + } + + char full[512]; + resolve_path(g_cwd, E.filename, full, sizeof(full)); + + if (E.disk_size >= 0) { + struct stat st; + if (stat(full, &st) == 0 && (long)st.st_size != E.disk_size) { + char *ans = prompt("File changed on disk! Overwrite? [y/N]: %s"); + if (!ans) { set_status("Save cancelled"); return -1; } + int yes = (ans[0] == 'y' || ans[0] == 'Y'); + free(ans); + if (!yes) { set_status("Save cancelled"); return -1; } + } + } + + int len = 0; + char *buf = rows_to_string(&len); + + int fd = open(full, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + free(buf); + set_status("Save failed: errno=%d (%s)", errno, full); + return -1; + } + ssize_t written = 0; + while (written < len) { + ssize_t w = write(fd, buf + written, len - written); + if (w <= 0) { close(fd); free(buf); set_status("Save failed (write err)"); return -1; } + written += w; + } + close(fd); + free(buf); + E.dirty = 0; + E.disk_size = len; + set_status("Saved %d bytes to %s", len, E.filename); + return 0; +} + +typedef struct { char *b; int len; int cap; } abuf_t; + +static void ab_append(abuf_t *ab, const char *s, int len) +{ + if (ab->len + len > ab->cap) { + int nc = ab->cap ? ab->cap * 2 : 1024; + while (nc < ab->len + len) nc *= 2; + char *nb = malloc(nc); + if (!nb) die("out of memory"); + if (ab->b) memcpy(nb, ab->b, ab->len); + free(ab->b); + ab->b = nb; + ab->cap = nc; + } + memcpy(ab->b + ab->len, s, len); + ab->len += len; +} +static void ab_free(abuf_t *ab) { free(ab->b); ab->b = NULL; ab->len = 0; ab->cap = 0; } + +static void scroll(void) +{ + E.rx = 0; + if (E.cy < E.numrows) E.rx = row_cx_to_rx(&E.row[E.cy], E.cx); + + if (E.cy < E.rowoff) E.rowoff = E.cy; + if (E.cy >= E.rowoff + E.screenrows) E.rowoff = E.cy - E.screenrows + 1; + if (E.rx < E.coloff) E.coloff = E.rx; + if (E.rx >= E.coloff + E.screencols) E.coloff = E.rx - E.screencols + 1; +} + +static void draw_rows(abuf_t *ab) +{ + char pos[16]; + int limit = E.screencols - 1; + if (limit < 1) limit = 1; + for (int y = 0; y < E.screenrows; y++) { + int n = snprintf(pos, sizeof(pos), "\x1b[%d;1H", y + 1); + ab_append(ab, pos, n); + + int filerow = y + E.rowoff; + if (filerow >= E.numrows) { + if (E.numrows == 0 && y == E.screenrows / 3) { + char welcome[80]; + int wl = snprintf(welcome, sizeof(welcome), + "neo editor -- version %s -- press ESC to exit", NEO_VERSION); + if (wl > limit) wl = limit; + int padding = (limit - wl) / 2; + if (padding > 0) { ab_append(ab, "~", 1); padding--; } + while (padding-- > 0) ab_append(ab, " ", 1); + ab_append(ab, welcome, wl); + } else { + ab_append(ab, "~", 1); + } + } else { + int len = E.row[filerow].rsize - E.coloff; + if (len < 0) len = 0; + if (len > limit) len = limit; + ab_append(ab, E.row[filerow].render + E.coloff, len); + } + ab_append(ab, "\x1b[K", 3); + } +} + +static void draw_status(abuf_t *ab) +{ + char pos[16]; + int n = snprintf(pos, sizeof(pos), "\x1b[%d;1H", E.screenrows + 1); + ab_append(ab, pos, n); + ab_append(ab, "\x1b[7m", 4); + char status[256], rstatus[80]; + int len = snprintf(status, sizeof(status), " %.40s%s ", + E.filename ? E.filename : "[No Name]", + E.dirty ? " [modified]" : ""); + int rlen = snprintf(rstatus, sizeof(rstatus), "%d/%d ", + E.cy + 1, E.numrows); + + int limit = E.screencols - 1; + if (limit < 1) limit = 1; + + if (len > limit) len = limit; + ab_append(ab, status, len); + while (len < limit) { + if (limit - len == rlen) { ab_append(ab, rstatus, rlen); break; } + ab_append(ab, " ", 1); + len++; + } + ab_append(ab, "\x1b[m", 3); + ab_append(ab, "\x1b[K", 3); +} + +static void draw_message(abuf_t *ab) +{ + char pos[16]; + int n = snprintf(pos, sizeof(pos), "\x1b[%d;1H", E.screenrows + 2); + ab_append(ab, pos, n); + ab_append(ab, "\x1b[K", 3); + if (E.statusmsg_visible) { + int mlen = strlen(E.statusmsg); + if (mlen > E.screencols) mlen = E.screencols; + ab_append(ab, E.statusmsg, mlen); + } else { + const char *hint = " ^S=save ^Q=quit ^F=find ^G=goto ^B=top ^E=end ESC=exit"; + int mlen = strlen(hint); + if (mlen > E.screencols) mlen = E.screencols; + ab_append(ab, hint, mlen); + } +} + +static void refresh_screen(void) +{ + scroll(); + abuf_t ab = {0}; + ab_append(&ab, "\x1b[?25l", 6); + draw_rows(&ab); + draw_status(&ab); + draw_message(&ab); + + int cursor_row = (E.cy - E.rowoff) + 1; + int cursor_col = (E.rx - E.coloff) + 1; + + char curbuf[32]; + int n = snprintf(curbuf, sizeof(curbuf), "\x1b[%d;%dH", cursor_row, cursor_col); + ab_append(&ab, curbuf, n); + + ab_append(&ab, "\x1b[?25h", 6); + + write(1, ab.b, ab.len); + ab_free(&ab); +} + +static void set_status(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap); + va_end(ap); + E.statusmsg_visible = 1; +} + +static char *prompt_cb(const char *prompt_fmt, void (*callback)(char *, int)) +{ + size_t bufcap = 128; + size_t buflen = 0; + char *buf = malloc(bufcap); + buf[0] = '\0'; + + for (;;) { + set_status(prompt_fmt, buf); + refresh_screen(); + int c = read_key(); + if (c == KEY_DEL || c == KEY_CTRL('h') || c == KEY_BACKSPACE) { + if (buflen > 0) buf[--buflen] = '\0'; + } else if (c == KEY_ESC) { + set_status(""); + E.statusmsg_visible = 0; + if (callback) callback(buf, c); + free(buf); + return NULL; + } else if (c == '\r' || c == '\n') { + if (buflen != 0 || callback) { + set_status(""); + E.statusmsg_visible = 0; + if (callback) callback(buf, c); + return buf; + } + } else if (!iscntrl(c) && c < 128) { + if (buflen + 1 >= bufcap) { + bufcap *= 2; + char *nb = malloc(bufcap); + memcpy(nb, buf, buflen); + free(buf); + buf = nb; + } + buf[buflen++] = (char)c; + buf[buflen] = '\0'; + } + if (callback) callback(buf, c); + } +} + +static char *prompt(const char *prompt_fmt) +{ + return prompt_cb(prompt_fmt, NULL); +} + +static void move_cursor(int key) +{ + neo_row_t *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + switch (key) { + case KEY_ARROW_LEFT: + if (E.cx > 0) E.cx--; + else if (E.cy > 0) { E.cy--; E.cx = E.row[E.cy].size; } + break; + case KEY_ARROW_RIGHT: + if (row && E.cx < row->size) E.cx++; + else if (row && E.cx == row->size) { E.cy++; E.cx = 0; } + break; + case KEY_ARROW_UP: + if (E.cy > 0) E.cy--; + break; + case KEY_ARROW_DOWN: + if (E.cy < E.numrows) E.cy++; + break; + } + row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + int rowlen = row ? row->size : 0; + if (E.cx > rowlen) E.cx = rowlen; +} + +static void goto_line(void) +{ + char *p = prompt("Go to line: %s (ESC cancels)"); + if (!p) return; + int n = atoi(p); + free(p); + if (n < 1) n = 1; + if (n > E.numrows) n = E.numrows == 0 ? 1 : E.numrows; + E.cy = n - 1; + E.cx = 0; +} + +static int __find_last_match = -1; +static int __find_direction = 1; +static int __find_saved_cx, __find_saved_cy; +static int __find_saved_rowoff, __find_saved_coloff; +static char *__find_last_query = NULL; + +static void editor_find_callback(char *query, int key) +{ + if (key == '\r' || key == '\n' || key == KEY_ESC) { + __find_last_match = -1; + __find_direction = 1; + return; + } + if (key == KEY_ARROW_DOWN || key == KEY_ARROW_RIGHT) { + __find_direction = 1; + } else if (key == KEY_ARROW_UP || key == KEY_ARROW_LEFT) { + __find_direction = -1; + } else { + __find_last_match = -1; + __find_direction = 1; + } + if (!query || !query[0]) return; + + int current = __find_last_match; + if (current == -1) current = E.cy; + int qlen = (int)strlen(query); + + for (int i = 0; i < E.numrows; i++) { + current += __find_direction; + if (current == -1) current = E.numrows - 1; + else if (current == E.numrows) current = 0; + + neo_row_t *row = &E.row[current]; + char *match = strstr(row->render, query); + if (match) { + __find_last_match = current; + E.cy = current; + int rx = (int)(match - row->render); + E.cx = row_rx_to_cx(row, rx); + E.rowoff = E.numrows; + (void)qlen; + return; + } + } +} + +static void editor_find(void) +{ + __find_saved_cx = E.cx; + __find_saved_cy = E.cy; + __find_saved_rowoff = E.rowoff; + __find_saved_coloff = E.coloff; + __find_last_match = -1; + __find_direction = 1; + + char *query = prompt_cb( + "Search: %s (Up/Down=prev/next, Enter=keep, ESC=cancel)", + editor_find_callback); + + if (query) { + if (query[0] == '\0' && __find_last_query) { + free(query); + query = strdup(__find_last_query); + if (query) { + editor_find_callback(query, 0); + } + } + if (query && query[0]) { + free(__find_last_query); + __find_last_query = strdup(query); + } + if (query) free(query); + } else { + E.cx = __find_saved_cx; + E.cy = __find_saved_cy; + E.rowoff = __find_saved_rowoff; + E.coloff = __find_saved_coloff; + } +} + +static int process_key(void) +{ + int c = read_key(); + + switch (c) { + case '\r': + case '\n': + editor_insert_newline(); + break; + + case KEY_ESC: + if (E.dirty && NEO_QUIT_CONFIRM && !E.quit_pending) { + set_status("Unsaved changes. ESC again to exit without saving, Ctrl-S to save."); + E.quit_pending = 1; + return 1; + } + return 0; + + case KEY_CTRL('s'): + editor_save(); + E.quit_pending = 0; + break; + + case KEY_CTRL('q'): + if (E.dirty && NEO_QUIT_CONFIRM && !E.quit_pending) { + set_status("Unsaved changes. Ctrl-Q again to force quit."); + E.quit_pending = 1; + return 1; + } + return 0; + + case KEY_CTRL('g'): + goto_line(); + break; + + case KEY_CTRL('f'): + editor_find(); + break; + + case KEY_CTRL('b'): + E.cy = 0; + E.cx = 0; + E.rowoff = 0; + E.coloff = 0; + break; + + case KEY_CTRL('e'): + E.cy = E.numrows == 0 ? 0 : E.numrows - 1; + if (E.cy < E.numrows) E.cx = E.row[E.cy].size; + else E.cx = 0; + break; + + case KEY_HOME: + E.cx = 0; + break; + + case KEY_END: + if (E.cy < E.numrows) E.cx = E.row[E.cy].size; + break; + + case KEY_BACKSPACE: + case KEY_CTRL('h'): + editor_delete_char(); + break; + + case KEY_DEL: + move_cursor(KEY_ARROW_RIGHT); + editor_delete_char(); + break; + + case KEY_PAGE_UP: + case KEY_PAGE_DOWN: { + if (c == KEY_PAGE_UP) { + E.cy = E.rowoff; + } else { + E.cy = E.rowoff + E.screenrows - 1; + if (E.cy > E.numrows) E.cy = E.numrows; + } + int times = E.screenrows; + while (times--) move_cursor(c == KEY_PAGE_UP ? KEY_ARROW_UP : KEY_ARROW_DOWN); + break; + } + + case KEY_ARROW_UP: + case KEY_ARROW_DOWN: + case KEY_ARROW_LEFT: + case KEY_ARROW_RIGHT: + move_cursor(c); + break; + + case KEY_CTRL('l'): + case 0: + break; + + default: + if (c >= 32 && c < 127) editor_insert_char(c); + else if (c == '\t') editor_insert_char('\t'); + break; + } + E.quit_pending = 0; + return 1; +} + +static void init_editor(void) +{ + E.cx = 0; E.cy = 0; E.rx = 0; + E.rowoff = 0; E.coloff = 0; + E.numrows = 0; E.rowscap = 0; E.row = NULL; + E.dirty = 0; + E.filename = NULL; + E.statusmsg[0] = '\0'; + E.statusmsg_visible = 0; + E.quit_pending = 0; + E.disk_size = -1; + get_window_size(); +} + +int main(int argc, char **argv) +{ + g_cwd = get_cwd_flag(argc, argv); + + init_editor(); + enable_raw_mode(); + + const char *file_to_open = NULL; + for (int i = 1; i < argc; i++) { + if (is_shell_flag(argv[i])) continue; + file_to_open = argv[i]; + break; + } + + if (file_to_open) editor_open(file_to_open); + + write(1, "\x1b[2J", 4); + write(1, "\x1b[H", 3); + + refresh_screen(); + while (process_key()) { + refresh_screen(); + } + + write(1, "\x1b[2J", 4); + write(1, "\x1b[H", 3); + disable_raw_mode(); + return 0; +} \ No newline at end of file diff --git a/usr/apps/shell.c b/usr/apps/shell.c new file mode 100644 index 0000000..192e006 --- /dev/null +++ b/usr/apps/shell.c @@ -0,0 +1,975 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef VFS_MAX_PATH +#define VFS_MAX_PATH 1024 +#endif + +#define TIOCGWINSZ 0x5413 +#define TIOCGCURSOR 0x5480 + +typedef struct { uint16_t ws_row, ws_col, ws_xpixel, ws_ypixel; } cervus_winsize_t; +typedef struct { uint32_t row, col; } cervus_cursor_pos_t; + +static inline int sh_ioctl(int fd, unsigned long req, void *arg) { + return (int)syscall3(SYS_IOCTL, fd, req, arg); +} + +static int g_cols = 80; +static int g_rows = 25; + +static void term_update_size(void) { + cervus_winsize_t ws; + if (sh_ioctl(1, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 8 && ws.ws_row >= 2) { + g_cols = (int)ws.ws_col; + g_rows = (int)ws.ws_row; + } +} + +static int term_get_cursor_row(void) { + cervus_cursor_pos_t cp; + if (sh_ioctl(1, TIOCGCURSOR, &cp) == 0) return (int)cp.row; + return 0; +} + +static void vt_goto(int row, int col) { + char b[24]; + snprintf(b, sizeof(b), "\x1b[%d;%dH", row + 1, col + 1); + fputs(b, stdout); +} + +static void vt_eol(void) { fputs("\x1b[K", stdout); } + +#define HIST_MAX 1024 +#define LINE_MAX 1024 + +static char history[HIST_MAX][LINE_MAX]; +static int hist_count = 0, hist_head = 0; + +static const char *g_hist_file = NULL; + +static void hist_load(const char *path) { + int fd = open(path, O_RDONLY, 0); + if (fd < 0) return; + char line[LINE_MAX]; + int li = 0; + char ch; + while (read(fd, &ch, 1) > 0) { + if (ch == '\n' || li >= LINE_MAX - 1) { + line[li] = '\0'; + if (li > 0) { + int idx = (hist_head + hist_count) % HIST_MAX; + strncpy(history[idx], line, LINE_MAX - 1); + history[idx][LINE_MAX - 1] = '\0'; + if (hist_count < HIST_MAX) hist_count++; + else hist_head = (hist_head + 1) % HIST_MAX; + } + li = 0; + } else { + line[li++] = ch; + } + } + close(fd); +} + +static void hist_save_entry(const char *path, const char *l) { + int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600); + if (fd < 0) return; + int n = 0; + while (l[n]) n++; + write(fd, l, n); + write(fd, "\n", 1); + close(fd); +} + +static void hist_push(const char *l) { + if (!l[0]) return; + if (hist_count > 0) { + int last = (hist_head + hist_count - 1) % HIST_MAX; + if (strcmp(history[last], l) == 0) return; + } + int idx = (hist_head + hist_count) % HIST_MAX; + strncpy(history[idx], l, LINE_MAX - 1); + history[idx][LINE_MAX - 1] = '\0'; + if (hist_count < HIST_MAX) hist_count++; + else hist_head = (hist_head + 1) % HIST_MAX; + if (g_hist_file) hist_save_entry(g_hist_file, l); +} + +static const char *hist_get(int n) { + if (n < 1 || n > hist_count) return NULL; + return history[(hist_head + hist_count - n) % HIST_MAX]; +} + +#define ENV_MAX_VARS 128 +#define ENV_NAME_MAX 64 +#define ENV_VAL_MAX 512 + +typedef struct { char name[ENV_NAME_MAX]; char value[ENV_VAL_MAX]; } env_var_t; + +static env_var_t g_env[ENV_MAX_VARS]; +static int g_env_count = 0; + +static int env_find(const char *name) { + for (int i = 0; i < g_env_count; i++) + if (strcmp(g_env[i].name, name) == 0) return i; + return -1; +} +static const char *env_get(const char *name) { + int i = env_find(name); + return i >= 0 ? g_env[i].value : ""; +} +static void env_set(const char *name, const char *value) { + int i = env_find(name); + if (i >= 0) { strncpy(g_env[i].value, value, ENV_VAL_MAX - 1); return; } + if (g_env_count >= ENV_MAX_VARS) return; + strncpy(g_env[g_env_count].name, name, ENV_NAME_MAX - 1); + strncpy(g_env[g_env_count].value, value, ENV_VAL_MAX - 1); + g_env_count++; +} +static void env_unset(const char *name) { + int i = env_find(name); + if (i < 0) return; + g_env[i] = g_env[--g_env_count]; +} + +static int g_last_rc = 0; + +static void expand_vars(const char *src, char *dst, size_t dsz) { + size_t di = 0; + for (const char *p = src; *p && di + 1 < dsz; ) { + if (*p != '$') { dst[di++] = *p++; continue; } + p++; + if (*p == '?') { + char tmp[12]; + int len = snprintf(tmp, sizeof(tmp), "%d", g_last_rc); + for (int x = 0; x < len && di + 1 < dsz; x++) dst[di++] = tmp[x]; + p++; continue; + } + int braced = (*p == '{'); + if (braced) p++; + char name[ENV_NAME_MAX]; int ni = 0; + while (*p && ni + 1 < (int)ENV_NAME_MAX) { + char c = *p; + if (braced) { if (c == '}') { p++; break; } } + else if (!isalnum((unsigned char)c) && c != '_') break; + name[ni++] = c; p++; + } + name[ni] = '\0'; + if (ni == 0) { dst[di++] = '$'; continue; } + const char *val = env_get(name); + for (; *val && di + 1 < dsz; val++) dst[di++] = *val; + } + dst[di] = '\0'; +} + +static char cwd[VFS_MAX_PATH]; +static int prompt_len = 0; + +static const char *display_path(void) { + static char dpbuf[VFS_MAX_PATH]; + const char *home = env_get("HOME"); + size_t hlen = home ? strlen(home) : 0; + if (hlen > 1 && strncmp(cwd, home, hlen) == 0 && + (cwd[hlen] == '/' || cwd[hlen] == '\0')) { + dpbuf[0] = '~'; + strncpy(dpbuf + 1, cwd + hlen, sizeof(dpbuf) - 2); + dpbuf[sizeof(dpbuf) - 1] = '\0'; + if (dpbuf[1] == '\0') { dpbuf[0] = '~'; dpbuf[1] = '\0'; } + return dpbuf; + } + return cwd; +} + +static void print_prompt(void) { + const char *dp = display_path(); + fputs(C_GREEN "cervus" C_RESET ":" C_BLUE, stdout); + fputs(dp, stdout); + fputs(C_RESET "$ ", stdout); + prompt_len = 9 + (int)strlen(dp); +} + +static int g_start_row = 0; + +static void sync_start_row(int cur_logical_pos) { + int real_row = term_get_cursor_row(); + int row_offset = (prompt_len + cur_logical_pos) / g_cols; + g_start_row = real_row - row_offset; + if (g_start_row < 0) g_start_row = 0; +} + +static void input_pos_to_screen(int pos, int *row, int *col) { + int abs = prompt_len + pos; + *row = g_start_row + abs / g_cols; + *col = abs % g_cols; +} + +static void cursor_to(int pos) { + int row, col; + input_pos_to_screen(pos, &row, &col); + if (row >= g_rows) row = g_rows - 1; + if (row < 0) row = 0; + vt_goto(row, col); +} + +static int last_row_of(int len) { + int abs = prompt_len + len; + return g_start_row + (abs > 0 ? (abs - 1) : 0) / g_cols; +} + +static void redraw(const char *buf, int from, int new_len, int old_len, int pos) { + cursor_to(from); + if (new_len > from) write(1, buf + from, new_len - from); + sync_start_row(new_len); + if (old_len > new_len) { + int old_last = last_row_of(old_len); + int new_last = last_row_of(new_len); + cursor_to(new_len); vt_eol(); + for (int r = new_last + 1; r <= old_last; r++) { + if (r >= g_rows) break; + vt_goto(r, 0); vt_eol(); + } + } + cursor_to(pos); +} + +static void replace_line(char *buf, int *len, int *pos, const char *newtext, int newlen) { + int old_len = *len; + for (int i = 0; i < newlen; i++) buf[i] = newtext[i]; + buf[newlen] = '\0'; + *len = newlen; + *pos = newlen; + redraw(buf, 0, newlen, old_len, newlen); +} + +static void insert_str(char *buf, int *len, int *pos, int maxlen, const char *s, int slen) { + if (*len + slen >= maxlen) return; + int old_len = *len; + for (int i = *len; i >= *pos; i--) buf[i + slen] = buf[i]; + for (int i = 0; i < slen; i++) buf[*pos + i] = s[i]; + *len += slen; + buf[*len] = '\0'; + cursor_to(*pos); + write(1, buf + *pos, *len - *pos); + sync_start_row(*len); + *pos += slen; + cursor_to(*pos); +} + +static int find_word_start(const char *buf, int pos) { + int p = pos; + while (p > 0 && buf[p - 1] != ' ') p--; + return p; +} + +static void list_dir_matches(const char *dir, const char *prefix, int plen, + char matches[][256], int *nmatch, int max) { + DIR *d = opendir(dir); + if (!d) return; + struct dirent *de; + while (*nmatch < max && (de = readdir(d)) != NULL) { + if (strncmp(de->d_name, prefix, plen) == 0) { + strncpy(matches[*nmatch], de->d_name, 255); + matches[*nmatch][255] = '\0'; + (*nmatch)++; + } + } + closedir(d); +} + +static void do_tab_complete(char *buf, int *len, int *pos, int maxlen) { + int ws_start = find_word_start(buf, *pos); + int wlen = *pos - ws_start; + if (wlen <= 0) { + insert_str(buf, len, pos, maxlen, " ", 4); + return; + } + char word[256]; + if (wlen > 255) wlen = 255; + memcpy(word, buf + ws_start, wlen); + word[wlen] = '\0'; + + int is_first_word = 1; + for (int i = 0; i < ws_start; i++) { + if (buf[i] != ' ') { is_first_word = 0; break; } + } + + char matches[32][256]; + int nmatch = 0; + + if (is_first_word) { + const char *pathvar = env_get("PATH"); + char ptmp[ENV_VAL_MAX]; + strncpy(ptmp, pathvar, sizeof(ptmp) - 1); + char *p = ptmp; + while (*p && nmatch < 32) { + char *seg = p; + while (*p && *p != ':') p++; + if (*p == ':') *p++ = '\0'; + if (seg[0]) list_dir_matches(seg, word, wlen, matches, &nmatch, 32); + } + const char *builtins[] = {"help","exit","cd","export","unset",NULL}; + for (int i = 0; builtins[i] && nmatch < 32; i++) { + if (strncmp(builtins[i], word, wlen) == 0) { + strncpy(matches[nmatch], builtins[i], 255); + nmatch++; + } + } + } else { + char dirp[VFS_MAX_PATH]; + const char *prefix = word; + char *last_slash = NULL; + for (int i = 0; word[i]; i++) if (word[i] == '/') last_slash = &word[i]; + if (last_slash) { + int dlen = (int)(last_slash - word); + char raw_dir[256]; + memcpy(raw_dir, word, dlen); + raw_dir[dlen] = '\0'; + if (raw_dir[0] == '\0') strcpy(raw_dir, "/"); + resolve_path(cwd, raw_dir, dirp, sizeof(dirp)); + prefix = last_slash + 1; + } else { + strncpy(dirp, cwd, sizeof(dirp) - 1); + } + int plen = (int)strlen(prefix); + list_dir_matches(dirp, prefix, plen, matches, &nmatch, 32); + } + + if (nmatch == 0) { + insert_str(buf, len, pos, maxlen, " ", 4); + return; + } + if (nmatch == 1) { + const char *m = matches[0]; + int mlen = (int)strlen(m); + int tail = mlen - wlen; + if (tail > 0) { + const char *suffix = m + wlen; + if (is_first_word) { + int need = tail; + if (*len + need >= maxlen) return; + int old_len = *len; + for (int i = *len; i >= *pos; i--) buf[i + need] = buf[i]; + for (int i = 0; i < tail; i++) buf[*pos + i] = suffix[i]; + *len += need; + buf[*len] = '\0'; + cursor_to(*pos); + write(1, buf + *pos, *len - *pos); + sync_start_row(*len); + *pos += need; + cursor_to(*pos); + } else { + insert_str(buf, len, pos, maxlen, suffix, tail); + } + } + return; + } + int common = (int)strlen(matches[0]); + for (int i = 1; i < nmatch; i++) { + int j = 0; + while (j < common && matches[0][j] == matches[i][j]) j++; + common = j; + } + int extra = common - wlen; + if (extra > 0) { + insert_str(buf, len, pos, maxlen, matches[0] + wlen, extra); + return; + } + putchar(10); + for (int i = 0; i < nmatch; i++) { + fputs(" ", stdout); fputs(matches[i], stdout); + } + putchar(10); + print_prompt(); + write(1, buf, *len); + sync_start_row(*len); + cursor_to(*pos); +} + +static int readline_edit(char *buf, int maxlen) { + term_update_size(); + { + int real_row = term_get_cursor_row(); + g_start_row = real_row - prompt_len / g_cols; + if (g_start_row < 0) g_start_row = 0; + } + int len = 0, pos = 0, hidx = 0; + static char saved[LINE_MAX]; + saved[0] = '\0'; buf[0] = '\0'; + + for (;;) { + char c; + if (read(0, &c, 1) <= 0) return -1; + + if (c == '\x1b') { + char s[4]; + if (read(0, &s[0], 1) <= 0) continue; + if (s[0] != '[') continue; + if (read(0, &s[1], 1) <= 0) continue; + if (s[1] == 'A') { + if (hidx == 0) strncpy(saved, buf, LINE_MAX - 1); + if (hidx < hist_count) { + hidx++; + const char *h = hist_get(hidx); + if (h) { + int hl = (int)strlen(h); + if (hl > maxlen - 1) hl = maxlen - 1; + replace_line(buf, &len, &pos, h, hl); + } + } + continue; + } + if (s[1] == 'B') { + if (hidx > 0) { + hidx--; + const char *h = hidx == 0 ? saved : hist_get(hidx); + if (!h) h = ""; + int hl = (int)strlen(h); + if (hl > maxlen - 1) hl = maxlen - 1; + replace_line(buf, &len, &pos, h, hl); + } + continue; + } + if (s[1] == 'C') { if (pos < len) { pos++; cursor_to(pos); } continue; } + if (s[1] == 'D') { if (pos > 0) { pos--; cursor_to(pos); } continue; } + if (s[1] == 'H') { pos = 0; cursor_to(0); continue; } + if (s[1] == 'F') { pos = len; cursor_to(len); continue; } + if (s[1] >= '1' && s[1] <= '6') { + char tilde; read(0, &tilde, 1); + if (tilde != '~') continue; + if (s[1] == '3' && pos < len) { + for (int i = pos; i < len - 1; i++) buf[i] = buf[i + 1]; + len--; buf[len] = '\0'; + redraw(buf, pos, len, len + 1, pos); + } else if (s[1] == '1') { pos = 0; cursor_to(0); } + else if (s[1] == '4') { pos = len; cursor_to(len); } + } + continue; + } + + if (c == '\n' || c == '\r') { buf[len] = '\0'; cursor_to(len); putchar(10); return len; } + if (c == 3) { fputs("^C", stdout); putchar(10); buf[0] = '\0'; return 0; } + if (c == 4) { if (len == 0) return -1; continue; } + if (c == 1) { pos = 0; cursor_to(0); continue; } + if (c == 5) { pos = len; cursor_to(len); continue; } + if (c == '\t') { + do_tab_complete(buf, &len, &pos, maxlen); + continue; + } + if (c == 11) { + if (pos < len) { int old_len = len; len = pos; buf[len] = '\0'; redraw(buf, pos, len, old_len, pos); } + continue; + } + if (c == 21) { + if (pos > 0) { + int old_len = len, del = pos; + for (int i = 0; i < len - del; i++) buf[i] = buf[i + del]; + len -= del; pos = 0; buf[len] = '\0'; + redraw(buf, 0, len, old_len, 0); + } + continue; + } + if (c == 23) { + if (pos > 0) { + int p = pos; + while (p > 0 && buf[p - 1] == ' ') p--; + while (p > 0 && buf[p - 1] != ' ') p--; + int old_len = len, del = pos - p; + for (int i = p; i < len - del; i++) buf[i] = buf[i + del]; + len -= del; pos = p; buf[len] = '\0'; + redraw(buf, p, len, old_len, p); + } + continue; + } + if (c == '\b' || c == 0x7F) { + if (pos > 0) { + int old_len = len; + for (int i = pos - 1; i < len - 1; i++) buf[i] = buf[i + 1]; + len--; pos--; buf[len] = '\0'; + redraw(buf, pos, len, old_len, pos); + } + continue; + } + if (c >= 0x20 && c < 0x7F) { + if (len >= maxlen - 1) continue; + for (int i = len; i > pos; i--) buf[i] = buf[i - 1]; + buf[pos] = c; len++; buf[len] = '\0'; + cursor_to(pos); write(1, buf + pos, len - pos); + sync_start_row(len); pos++; + cursor_to(pos); + } + } +} + +#define MAX_ARGS 32 + +static int tokenize(char *line, char *argv[], int maxargs) { + int argc = 0; + char *p = line; + while (*p) { + while (isspace((unsigned char)*p)) p++; + if (!*p) break; + if (argc >= maxargs - 1) break; + char *out = p; + argv[argc++] = out; + int in_dquote = 0, in_squote = 0; + while (*p) { + if (in_dquote) { + if (*p == '"') { in_dquote = 0; p++; } + else { *out++ = *p++; } + } else if (in_squote) { + if (*p == '\'') { in_squote = 0; p++; } + else { *out++ = *p++; } + } else { + if (*p == '"') { in_dquote = 1; p++; } + else if (*p == '\'') { in_squote = 1; p++; } + else if (isspace((unsigned char)*p)) { p++; break; } + else { *out++ = *p++; } + } + } + *out = '\0'; + if (in_dquote || in_squote) { argv[argc] = NULL; return -1; } + } + argv[argc] = NULL; + return argc; +} + +static void cmd_help(void) { + putchar(10); + fputs(" " C_CYAN "Cervus Shell" C_RESET " - commands\n", stdout); + fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); + fputs(" " C_BOLD "help" C_RESET " show this message\n", stdout); + fputs(" " C_BOLD "cd" C_RESET " change directory\n", stdout); + fputs(" " C_BOLD "export" C_RESET " N=V set variable N to value V\n", stdout); + fputs(" " C_BOLD "unset" C_RESET " N delete variable N\n", stdout); + fputs(" " C_BOLD "env" C_RESET " list variables\n", stdout); + fputs(" " C_BOLD "exit" C_RESET " quit shell\n", stdout); + fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); + fputs(" " C_BOLD "Programs:" C_RESET " ls, cat, echo, pwd, clear, uname\n", stdout); + fputs(" meminfo, cpuinfo, ps, kill, find, stat, wc, yes, sleep\n", stdout); + fputs(" mount, umount, mkfs, lsblk, mv, rm, mkdir, touch\n", stdout); + fputs(" " C_RED "shutdown" C_RESET ", " C_CYAN "reboot" C_RESET "\n", stdout); + fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); + fputs(" " C_BOLD "Operators:" C_RESET " cmd1 " C_YELLOW ";" C_RESET + " cmd2 " C_YELLOW "&&" C_RESET " " C_YELLOW "||" C_RESET "\n", stdout); + fputs(" " C_BOLD "Tab" C_RESET " auto-complete / 4 spaces\n", stdout); + fputs(" " C_BOLD "Ctrl+C" C_RESET " interrupt\n", stdout); + fputs(" " C_BOLD "Ctrl+A/E" C_RESET " beginning/end of line\n", stdout); + fputs(" " C_BOLD "Ctrl+K/U" C_RESET " delete to end/beginning\n", stdout); + fputs(" " C_BOLD "Ctrl+W" C_RESET " delete word\n", stdout); + fputs(" " C_BOLD "Arrows" C_RESET " cursor / history\n", stdout); + fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); + putchar(10); +} + +static int cmd_cd(const char *path) { + if (!path || !path[0] || strcmp(path, "~") == 0) { + const char *home = env_get("HOME"); + path = (home && home[0]) ? home : "/"; + } + char np[VFS_MAX_PATH]; + resolve_path(cwd, path, np, sizeof(np)); + struct stat st; + if (stat(np, &st) < 0) { fputs(C_RED "cd: not found: " C_RESET, stdout); fputs(path, stdout); putchar(10); return 1; } + if (st.st_type != 1) { fputs(C_RED "cd: not a dir: " C_RESET, stdout); fputs(path, stdout); putchar(10); return 1; } + strncpy(cwd, np, sizeof(cwd) - 1); + cwd[sizeof(cwd) - 1] = '\0'; + chdir(np); + return 0; +} + +static int valid_varname(const char *s) { + if (!s || !*s) return 0; + if (!isalpha((unsigned char)*s) && *s != '_') return 0; + for (s++; *s; s++) + if (!isalnum((unsigned char)*s) && *s != '_') return 0; + return 1; +} + +static int cmd_export(int argc, char *argv[]) { + if (argc < 2) { fputs(C_RED "export: usage: export NAME=VALUE\n" C_RESET, stdout); return 1; } + if (argc > 2) { fputs(C_RED "export: invalid syntax\n" C_RESET, stdout); return 1; } + char *arg = argv[1]; + char *eq_pos = strchr(arg, '='); + if (!eq_pos) { + if (!valid_varname(arg)) { fputs(C_RED "export: not a valid identifier\n" C_RESET, stdout); return 1; } + env_set(arg, ""); return 0; + } + *eq_pos = '\0'; + const char *name = arg, *val = eq_pos + 1; + if (!valid_varname(name)) { *eq_pos = '='; fputs(C_RED "export: not a valid identifier\n" C_RESET, stdout); return 1; } + env_set(name, val); + *eq_pos = '='; + return 0; +} + +static int cmd_unset(int argc, char *argv[]) { + if (argc < 2) { fputs(C_RED "unset: usage: unset NAME\n" C_RESET, stdout); return 1; } + for (int i = 1; i < argc; i++) env_unset(argv[i]); + return 0; +} + +static int find_in_path(const char *cmd, char *out, size_t outsz) { + const char *pathvar = env_get("PATH"); + if (!pathvar || !pathvar[0]) { + path_join("/bin", cmd, out, outsz); + struct stat st; + return stat(out, &st) == 0 && st.st_type != 1; + } + char tmp[ENV_VAL_MAX]; + strncpy(tmp, pathvar, sizeof(tmp) - 1); + char *p = tmp; + while (*p) { + char *seg = p; + while (*p && *p != ':') p++; + if (*p == ':') *p++ = '\0'; + if (!seg[0]) continue; + char candidate[VFS_MAX_PATH]; + path_join(seg, cmd, candidate, sizeof(candidate)); + struct stat st; + if (stat(candidate, &st) == 0 && st.st_type != 1) { strncpy(out, candidate, outsz - 1); return 1; } + } + return 0; +} + +typedef enum { REDIR_NONE = 0, REDIR_OUT, REDIR_APPEND, REDIR_IN } redir_type_t; + +typedef struct { + redir_type_t type; + char path[VFS_MAX_PATH]; +} redir_t; + +static int parse_redirects(char *argv[], int *argc, redir_t redirs[], int max_redirs, int *nredirs) { + *nredirs = 0; + int new_argc = 0; + for (int i = 0; i < *argc; i++) { + const char *a = argv[i]; + redir_type_t rt = REDIR_NONE; + const char *target = NULL; + + if (strcmp(a, ">>") == 0) { + rt = REDIR_APPEND; + if (i + 1 < *argc) target = argv[++i]; + } else if (strcmp(a, ">") == 0) { + rt = REDIR_OUT; + if (i + 1 < *argc) target = argv[++i]; + } else if (strcmp(a, "<") == 0) { + rt = REDIR_IN; + if (i + 1 < *argc) target = argv[++i]; + } else if (a[0] == '>' && a[1] == '>' && a[2] != '\0') { + rt = REDIR_APPEND; target = a + 2; + } else if (a[0] == '>' && a[1] != '>' && a[1] != '\0') { + rt = REDIR_OUT; target = a + 1; + } else if (a[0] == '<' && a[1] != '\0') { + rt = REDIR_IN; target = a + 1; + } else { + argv[new_argc++] = argv[i]; + continue; + } + + if (!target || !target[0]) { + fputs(C_RED "syntax error: missing redirection target\n" C_RESET, stdout); + return -1; + } + if (*nredirs < max_redirs) { + redirs[*nredirs].type = rt; + char resolved[VFS_MAX_PATH]; + resolve_path(cwd, target, resolved, sizeof(resolved)); + strncpy(redirs[*nredirs].path, resolved, VFS_MAX_PATH - 1); + redirs[*nredirs].path[VFS_MAX_PATH - 1] = '\0'; + (*nredirs)++; + } + } + argv[new_argc] = NULL; + *argc = new_argc; + return 0; +} + +static int run_single(char *line) { + char expanded[LINE_MAX]; + expand_vars(line, expanded, sizeof(expanded)); + char buf[LINE_MAX]; + strncpy(buf, expanded, LINE_MAX - 1); + char *argv[MAX_ARGS]; + int argc = tokenize(buf, argv, MAX_ARGS); + if (argc < 0) { fputs(C_RED "syntax error: unclosed quote\n" C_RESET, stdout); return 1; } + if (!argc) return 0; + + redir_t redirs[8]; + int nredirs = 0; + if (parse_redirects(argv, &argc, redirs, 8, &nredirs) < 0) return 1; + if (!argc) return 0; + + const char *cmd = argv[0]; + + if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; } + if (strcmp(cmd, "exit") == 0) { fputs("Goodbye!\n", stdout); exit(0); } + if (strcmp(cmd, "cd") == 0) return cmd_cd(argc > 1 ? argv[1] : NULL); + if (strcmp(cmd, "export") == 0) return cmd_export(argc, argv); + if (strcmp(cmd, "unset") == 0) return cmd_unset(argc, argv); + + char binpath[VFS_MAX_PATH]; + if (cmd[0] == '/') { + strncpy(binpath, cmd, sizeof(binpath) - 1); + binpath[sizeof(binpath) - 1] = '\0'; + } else if (cmd[0] == '.') { + resolve_path(cwd, cmd, binpath, sizeof(binpath)); + } else { + if (!find_in_path(cmd, binpath, sizeof(binpath))) { + char t_cwd[VFS_MAX_PATH]; + resolve_path(cwd, cmd, t_cwd, sizeof(t_cwd)); + struct stat st; + if (stat(t_cwd, &st) == 0 && st.st_type != 1) { + strncpy(binpath, t_cwd, sizeof(binpath) - 1); + binpath[sizeof(binpath) - 1] = '\0'; + } else { + fputs(C_RED "not found: " C_RESET, stdout); fputs(cmd, stdout); putchar(10); return 127; + } + } + } + +#define REAL_ARGV_MAX (MAX_ARGS + ENV_MAX_VARS + 4) + char *real_argv_buf[REAL_ARGV_MAX]; + static char _cwd_flag[VFS_MAX_PATH + 8]; + static char _env_flags[ENV_MAX_VARS][ENV_NAME_MAX + ENV_VAL_MAX + 8]; + + int ri = 0; + real_argv_buf[ri++] = binpath; + for (int i = 1; i < argc; i++) real_argv_buf[ri++] = argv[i]; + snprintf(_cwd_flag, sizeof(_cwd_flag), "--cwd=%s", cwd); + real_argv_buf[ri++] = _cwd_flag; + for (int ei = 0; ei < g_env_count && ri < REAL_ARGV_MAX - 1; ei++) { + snprintf(_env_flags[ei], sizeof(_env_flags[ei]), "--env:%s=%s", + g_env[ei].name, g_env[ei].value); + real_argv_buf[ri++] = _env_flags[ei]; + } + real_argv_buf[ri] = NULL; + + pid_t child = fork(); + if (child < 0) { fputs(C_RED "fork failed" C_RESET "\n", stdout); return 1; } + if (child == 0) { + for (int i = 0; i < nredirs; i++) { + int fd = -1; + int target_fd = -1; + if (redirs[i].type == REDIR_OUT) { + fd = open(redirs[i].path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + target_fd = 1; + } else if (redirs[i].type == REDIR_APPEND) { + fd = open(redirs[i].path, O_WRONLY | O_CREAT | O_APPEND, 0644); + target_fd = 1; + } else if (redirs[i].type == REDIR_IN) { + fd = open(redirs[i].path, O_RDONLY, 0); + target_fd = 0; + } + if (fd < 0) { + fputs(C_RED "redirect: cannot open: " C_RESET, stdout); + fputs(redirs[i].path, stdout); + putchar('\n'); + exit(1); + } + dup2(fd, target_fd); + close(fd); + } + execve(binpath, (char *const *)real_argv_buf, NULL); + fputs(C_RED "exec failed: " C_RESET, stdout); fputs(binpath, stdout); putchar(10); exit(127); + } + int status = 0; + waitpid(child, &status, 0); + return (status >> 8) & 0xFF; +} + +typedef enum { CH_NONE = 0, CH_SEQ, CH_AND, CH_OR } chain_t; + +static void run_command(char *line) { + char work[LINE_MAX]; + strncpy(work, line, LINE_MAX - 1); + char *segs[64]; chain_t ops[64]; int ns = 1; + segs[0] = work; ops[0] = CH_NONE; + char *p = work; + while (*p) { + if (*p == '"') { p++; while (*p && *p != '"') p++; if (*p) p++; continue; } + if (*p == '\'') { p++; while (*p && *p != '\'') p++; if (*p) p++; continue; } + if (*p == '&' && *(p+1) == '&') { *p='\0'; p+=2; while(isspace((unsigned char)*p))p++; ops[ns]=CH_AND; segs[ns]=p; ns++; continue; } + if (*p == '|' && *(p+1) == '|') { *p='\0'; p+=2; while(isspace((unsigned char)*p))p++; ops[ns]=CH_OR; segs[ns]=p; ns++; continue; } + if (*p == ';') { *p='\0'; p++; while(isspace((unsigned char)*p))p++; ops[ns]=CH_SEQ; segs[ns]=p; ns++; continue; } + p++; + } + int rc = 0; + for (int i = 0; i < ns; i++) { + char *s = segs[i]; + while (isspace((unsigned char)*s)) s++; + size_t sl = strlen(s); + while (sl > 0 && isspace((unsigned char)s[sl - 1])) s[--sl] = '\0'; + if (!s[0]) continue; + if (i > 0) { + if (ops[i] == CH_AND && rc != 0) continue; + if (ops[i] == CH_OR && rc == 0) continue; + } + rc = run_single(s); + } + g_last_rc = rc; +} + +static void print_motd(void) { + int fd = open("/mnt/etc/motd", O_RDONLY, 0); + if (fd < 0) fd = open("/etc/motd", O_RDONLY, 0); + if (fd < 0) { putchar(10); fputs(" Cervus OS v0.0.2\n Type 'help' for commands.\n", stdout); putchar(10); return; } + char buf[1024]; + ssize_t n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n > 0) { buf[n] = '\0'; write(1, buf, n); } +} + + +static int g_installed = 0; + +static int sys_disk_mount(const char *dev, const char *path) { + return (int)syscall2(SYS_DISK_MOUNT, dev, path); +} + +static int launch_installer(void) { + const char *path = "/bin/install-on-disk"; + struct stat st; + if (stat(path, &st) != 0) { + fputs(C_RED " install-on-disk not found on system.\n" C_RESET, stdout); + return -1; + } + + const char *argv[4]; + argv[0] = path; + argv[1] = "--env:MODE=live"; + argv[2] = "--cwd=/"; + argv[3] = NULL; + + pid_t child = fork(); + if (child < 0) { + fputs(C_RED " fork failed\n" C_RESET, stdout); + return -1; + } + if (child == 0) { + execve(path, (char *const *)argv, NULL); + fputs(C_RED " exec install-on-disk failed\n" C_RESET, stdout); + exit(127); + } + int status = 0; + waitpid(child, &status, 0); + return (status >> 8) & 0xFF; +} + +static int ask_install_or_live(void) { + fputs("\x1b[2J\x1b[H", stdout); + fputs("\n", stdout); + fputs(C_CYAN " Cervus OS" C_RESET " - Live ISO\n", stdout); + fputs(C_GRAY " -----------------------------------" C_RESET "\n\n", stdout); + fputs(" A disk has been detected on this machine.\n", stdout); + fputs(" What would you like to do?\n\n", stdout); + fputs(" [" C_BOLD "1" C_RESET "] Install Cervus to disk\n", stdout); + fputs(" [" C_BOLD "2" C_RESET "] Continue in Live mode\n\n", stdout); + fputs(" Choice [1-2]: ", stdout); + + char c = 0; + while (1) { + if (read(0, &c, 1) <= 0) continue; + if (c == '1' || c == '2') { putchar(c); putchar(10); break; } + } + return (c == '1') ? 1 : 0; +} + +int main(int argc, char **argv) { + (void)argc; (void)argv; + + struct stat dev_st; + int has_disk = (stat("/dev/hda", &dev_st) == 0); + int has_hda2 = (stat("/dev/hda2", &dev_st) == 0); + int has_hda_legacy = 0; + + int disk_mounted = 0; + + if (has_hda2) { + int mr = sys_disk_mount("hda2", "/mnt"); + if (mr == 0) { + disk_mounted = 1; + g_installed = 1; + } + } else if (has_disk) { + int mr = sys_disk_mount("hda", "/mnt"); + if (mr == 0) { + disk_mounted = 1; + g_installed = 1; + has_hda_legacy = 1; + } + } + (void)has_hda_legacy; + + if (!disk_mounted && has_disk) { + if (ask_install_or_live() == 1) { + launch_installer(); + struct stat retry_st; + if (stat("/dev/hda2", &retry_st) == 0) { + if (sys_disk_mount("hda2", "/mnt") == 0) { + disk_mounted = 1; + g_installed = 1; + } + } + } + } + + if (disk_mounted) { + strncpy(cwd, "/mnt/home", sizeof(cwd)); + env_set("HOME", "/mnt/home"); + env_set("PATH", "/mnt/bin:/mnt/apps:/mnt/usr/bin"); + env_set("SHELL", "/mnt/bin/shell"); + } else { + strncpy(cwd, "/", sizeof(cwd)); + env_set("HOME", "/"); + env_set("PATH", "/bin:/apps:/usr/bin"); + env_set("SHELL", "/bin/shell"); + } + + if (!disk_mounted && has_disk) env_set("MODE", "live"); + else if (!has_disk) env_set("MODE", "live"); + else env_set("MODE", "installed"); + + print_motd(); + + { + static char hist_path[VFS_MAX_PATH]; + const char *h = env_get("HOME"); + if (h && h[0]) { + path_join(h, ".history", hist_path, sizeof(hist_path)); + g_hist_file = hist_path; + hist_load(hist_path); + } + } + + if (!has_disk) { + fputs(C_YELLOW " [Live Mode]" C_RESET " No disk detected. All changes are in RAM.\n\n", stdout); + } else if (!disk_mounted) { + fputs(C_YELLOW " [Live Mode]" C_RESET " Disk not mounted.\n\n", stdout); + } + + char line[LINE_MAX]; + for (;;) { + print_prompt(); + int n = readline_edit(line, LINE_MAX); + if (n < 0) { + fputs("\nSession ended. Restarting shell...\n", stdout); + const char *h = env_get("HOME"); + strncpy(cwd, (h && h[0]) ? h : "/", sizeof(cwd)); + print_motd(); + continue; + } + int len = (int)strlen(line); + while (len > 0 && isspace((unsigned char)line[len - 1])) line[--len] = '\0'; + if (len > 0) { hist_push(line); run_command(line); } + } +} \ No newline at end of file diff --git a/usr/apps/test_execve.c b/usr/apps/test_execve.c new file mode 100644 index 0000000..079908b --- /dev/null +++ b/usr/apps/test_execve.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include + +static int failed = 0; +static void ok(const char *s) { printf(" [OK] %s\n", s); } +static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; } + +static int file_exists(const char *path) +{ + struct stat st; + return stat(path, &st) == 0; +} + +static const char *resolve_app(const char *name, char *buf, size_t bufsz) +{ + const char *prefixes[] = { "/mnt/apps/", "/apps/", "/mnt/bin/", "/bin/", NULL }; + for (int i = 0; prefixes[i]; i++) { + size_t pl = strlen(prefixes[i]); + size_t nl = strlen(name); + if (pl + nl + 1 > bufsz) continue; + memcpy(buf, prefixes[i], pl); + memcpy(buf + pl, name, nl + 1); + if (file_exists(buf)) return buf; + } + return NULL; +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + puts("--- test_execve ---"); + + char target[256]; + if (!resolve_app("execve_target", target, sizeof(target))) { + printf(" [SKIP] execve_target not found\n"); + puts("--- test_execve done ---"); + return 0; + } + + { + char *const cargv[] = { target, (char *)"hello", (char *)"from", (char *)"execve", NULL }; + pid_t child = fork(); + if (child < 0) { fail("fork"); return 1; } + if (child == 0) { + execve(target, cargv, NULL); + printf(" [FATAL] execve failed\n"); + return 127; + } + int status = 0; + waitpid(child, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 99) ok("execve target exits 99"); + else { + printf(" exit status = %d\n", WEXITSTATUS(status)); + fail("execve exit code"); + } + } + + { + pid_t child = fork(); + if (child < 0) { fail("fork"); return 1; } + if (child == 0) { + char *const cargv[] = { (char *)"/no/such/binary", NULL }; + int r = execve("/no/such/binary", cargv, NULL); + return (r < 0) ? 0 : 1; + } + int status = 0; + waitpid(child, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) ok("execve nonexistent returns error"); + else fail("execve nonexistent should return error"); + } + + { + pid_t child = fork(); + if (child < 0) { fail("fork"); return 1; } + if (child == 0) { + execve(target, NULL, NULL); + return 127; + } + int status = 0; + waitpid(child, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 99) ok("execve with NULL argv"); + else fail("execve NULL argv"); + } + + puts("--- test_execve done ---"); + return failed ? 1 : 0; +} diff --git a/usr/apps/test_files.c b/usr/apps/test_files.c new file mode 100644 index 0000000..793cc56 --- /dev/null +++ b/usr/apps/test_files.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include + +static int failed = 0; +static void ok(const char *s) { printf(" [OK] %s\n", s); } +static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; } + +static int file_exists(const char *path) +{ + struct stat st; + return stat(path, &st) == 0; +} + +static const char *resolve_etc(const char *name, char *buf, size_t bufsz) +{ + const char *prefixes[] = { "/mnt/etc/", "/etc/", NULL }; + for (int i = 0; prefixes[i]; i++) { + size_t pl = strlen(prefixes[i]); + size_t nl = strlen(name); + if (pl + nl + 1 > bufsz) continue; + memcpy(buf, prefixes[i], pl); + memcpy(buf + pl, name, nl + 1); + if (file_exists(buf)) return buf; + } + return NULL; +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + puts("--- test_files ---"); + + char hostname_path[256], passwd_path[256]; + const char *hostname = resolve_etc("hostname", hostname_path, sizeof(hostname_path)); + const char *passwd = resolve_etc("passwd", passwd_path, sizeof(passwd_path)); + + if (!hostname) printf(" [WARN] hostname not found in /etc or /mnt/etc\n"); + if (!passwd) printf(" [WARN] passwd not found in /etc or /mnt/etc\n"); + + { + const char *msg = "hello from test_files\n"; + ssize_t w = write(1, msg, strlen(msg)); + if (w == (ssize_t)strlen(msg)) ok("write to stdout"); + else fail("write to stdout"); + } + { + const char *msg = "stderr line\n"; + ssize_t w = write(2, msg, strlen(msg)); + if (w == (ssize_t)strlen(msg)) ok("write to stderr"); + else fail("write to stderr"); + } + + if (hostname) { + int fd = open(hostname, O_RDONLY); + if (fd >= 0) { + ok("open hostname"); + char buf[64]; + memset(buf, 0, sizeof(buf)); + ssize_t r = read(fd, buf, sizeof(buf) - 1); + if (r > 0) { printf(" hostname = '%s'\n", buf); ok("read hostname"); } + else fail("read hostname"); + struct stat st; + if (fstat(fd, &st) == 0) { + printf(" fstat: size=%lu\n", (unsigned long)st.st_size); + ok("fstat"); + } else fail("fstat"); + close(fd); + ok("close"); + } else fail("open hostname"); + } + + if (passwd) { + struct stat st; + if (stat(passwd, &st) == 0) { + printf(" stat: ino=%lu size=%lu\n", + (unsigned long)st.st_ino, (unsigned long)st.st_size); + ok("stat passwd"); + } else fail("stat passwd"); + } + + { + int fd = open("/no/such/file", O_RDONLY); + if (fd < 0) ok("open nonexistent returns error"); + else { close(fd); fail("open nonexistent should fail"); } + } + + if (hostname) { + int fd = open(hostname, O_RDONLY); + if (fd >= 0) { + char buf[4]; memset(buf, 0, sizeof(buf)); + read(fd, buf, 2); + off_t pos = lseek(fd, 0, SEEK_CUR); + if (pos == 2) ok("lseek SEEK_CUR"); + else fail("lseek SEEK_CUR"); + lseek(fd, 0, SEEK_SET); + char buf2[4]; memset(buf2, 0, sizeof(buf2)); + read(fd, buf2, 2); + if (buf[0] == buf2[0] && buf[1] == buf2[1]) ok("lseek SEEK_SET re-read"); + else fail("lseek SEEK_SET re-read"); + close(fd); + } else fail("open for lseek test"); + } + + if (hostname) { + int fd = open(hostname, O_RDONLY); + if (fd >= 0) { + int fd2 = dup(fd); + if (fd2 >= 0 && fd2 != fd) { + ok("dup"); + char buf[32]; memset(buf, 0, sizeof(buf)); + ssize_t r = read(fd2, buf, sizeof(buf) - 1); + if (r > 0) ok("read from dup'd fd"); + else fail("read from dup'd fd"); + close(fd2); + } else fail("dup"); + close(fd); + } else fail("open for dup test"); + } + + if (hostname) { + int fd = open(hostname, O_RDONLY); + if (fd >= 0) { + int r = dup2(fd, 10); + if (r == 10) { + ok("dup2"); + char buf[32]; memset(buf, 0, sizeof(buf)); + ssize_t n = read(10, buf, sizeof(buf) - 1); + if (n > 0) ok("read from dup2 fd"); + else fail("read from dup2 fd"); + close(10); + } else fail("dup2"); + close(fd); + } else fail("open for dup2 test"); + } + + { + int fd = open("/dev/null", O_WRONLY); + if (fd >= 0) { + ssize_t w = write(fd, "garbage", 7); + if (w == 7) ok("write to /dev/null"); + else fail("write to /dev/null"); + close(fd); + } else fail("open /dev/null"); + } + { + int fd = open("/dev/zero", O_RDONLY); + if (fd >= 0) { + char buf[8]; memset(buf, 0xFF, 8); + ssize_t r = read(fd, buf, 8); + int all_zero = 1; + for (int i = 0; i < 8; i++) if (buf[i]) all_zero = 0; + if (r == 8 && all_zero) ok("read from /dev/zero"); + else fail("read from /dev/zero"); + close(fd); + } else fail("open /dev/zero"); + } + + puts("--- test_files done ---"); + return failed ? 1 : 0; +} diff --git a/usr/apps/test_mem.c b/usr/apps/test_mem.c new file mode 100644 index 0000000..98ed382 --- /dev/null +++ b/usr/apps/test_mem.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include + +static int failed = 0; +static void ok(const char *s) { printf(" [OK] %s\n", s); } +static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; } + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + puts("--- test_mem ---"); + + void *brk0 = sbrk(0); + printf(" initial brk = %p\n", brk0); + if ((uintptr_t)brk0 > 0) ok("sbrk(0) returns valid address"); + else fail("sbrk(0)"); + + void *old = sbrk(4096); + if (old == brk0) ok("sbrk(4096) returns old brk"); + else fail("sbrk(4096)"); + + volatile uint8_t *heap = (volatile uint8_t *)brk0; + heap[0] = 0xAB; heap[4095] = 0xCD; + if (heap[0] == 0xAB && heap[4095] == 0xCD) ok("write/read heap page"); + else fail("write/read heap page"); + + void *cur = sbrk(0); + sbrk(-4096); + void *after = sbrk(0); + if ((uintptr_t)after == (uintptr_t)cur - 4096) ok("sbrk shrink"); + else fail("sbrk shrink"); + + void *m = mmap(NULL, 8192, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (m != MAP_FAILED) { + ok("mmap anonymous 8 KiB"); + volatile uint8_t *p = (volatile uint8_t *)m; + p[0] = 0x11; p[8191] = 0x22; + if (p[0] == 0x11 && p[8191] == 0x22) ok("write/read mmap pages"); + else fail("write/read mmap pages"); + if (munmap(m, 8192) == 0) ok("munmap"); + else fail("munmap"); + } else fail("mmap anonymous"); + + void *big = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (big != MAP_FAILED) { + ok("mmap 1 MiB"); + volatile uint8_t *bp = (volatile uint8_t *)big; + int ok_all = 1; + for (int i = 0; i < 256; i++) { + bp[i * 4096] = (uint8_t)i; + if (bp[i * 4096] != (uint8_t)i) ok_all = 0; + } + if (ok_all) ok("touch 256 pages of 1 MiB mmap"); + else fail("touch mmap pages"); + munmap(big, 1024 * 1024); + } else fail("mmap 1 MiB"); + + { + void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr != MAP_FAILED) { + munmap(addr, 4096); + void *fixed = mmap(addr, 4096, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + if (fixed == addr) ok("MAP_FIXED at specific address"); + else fail("MAP_FIXED"); + if (fixed != MAP_FAILED) munmap(fixed, 4096); + } + } + + puts("--- test_mem done ---"); + return failed ? 1 : 0; +} diff --git a/usr/apps/test_pipe.c b/usr/apps/test_pipe.c new file mode 100644 index 0000000..7d4364a --- /dev/null +++ b/usr/apps/test_pipe.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +static int failed = 0; +static void ok(const char *s) { printf(" [OK] %s\n", s); } +static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; } + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + puts("--- test_pipe ---"); + + int fds[2] = { -1, -1 }; + if (pipe(fds) == 0 && fds[0] >= 0 && fds[1] >= 0) ok("pipe() creates two fds"); + else { fail("pipe() failed"); return 1; } + printf(" read_fd=%d write_fd=%d\n", fds[0], fds[1]); + + { + const char *msg = "hello pipe"; + ssize_t w = write(fds[1], msg, strlen(msg)); + if (w == (ssize_t)strlen(msg)) ok("write to pipe"); + else fail("write to pipe"); + + char buf[32]; + memset(buf, 0, sizeof(buf)); + ssize_t r = read(fds[0], buf, sizeof(buf) - 1); + if (r == (ssize_t)strlen(msg) && strcmp(buf, msg) == 0) + ok("read from pipe matches written data"); + else fail("read from pipe"); + } + close(fds[0]); close(fds[1]); + + { + int p[2]; + if (pipe(p) != 0) { fail("pipe for fork test"); return 1; } + pid_t child = fork(); + if (child < 0) { fail("fork"); return 1; } + if (child == 0) { + close(p[0]); + const char *s = "world"; + write(p[1], s, strlen(s)); + close(p[1]); + return 0; + } + close(p[1]); + char buf[32]; memset(buf, 0, sizeof(buf)); + ssize_t r = read(p[0], buf, sizeof(buf) - 1); + close(p[0]); + int status = 0; waitpid(child, &status, 0); + if (r > 0 && strcmp(buf, "world") == 0) ok("pipe IPC parent<-child"); + else fail("pipe IPC"); + } + + { + int p[2]; + if (pipe(p) != 0) { fail("pipe for dup2 test"); return 1; } + pid_t child = fork(); + if (child < 0) { fail("fork for dup2"); return 1; } + if (child == 0) { + close(p[0]); + dup2(p[1], 1); + close(p[1]); + const char *s = "from child stdout\n"; + write(1, s, strlen(s)); + return 0; + } + close(p[1]); + char buf[64]; memset(buf, 0, sizeof(buf)); + ssize_t r = read(p[0], buf, sizeof(buf) - 1); + close(p[0]); + int status = 0; waitpid(child, &status, 0); + if (r > 0 && strncmp(buf, "from child stdout", 17) == 0) + ok("dup2 redirects child stdout through pipe"); + else fail("dup2 redirect"); + } + + puts("--- test_pipe done ---"); + return failed ? 1 : 0; +} diff --git a/usr/apps/test_process.c b/usr/apps/test_process.c new file mode 100644 index 0000000..e375ec9 --- /dev/null +++ b/usr/apps/test_process.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +static int failed = 0; +static void ok(const char *s) { printf(" [OK] %s\n", s); } +static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; } + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + puts("--- test_process ---"); + + pid_t my_pid = getpid(); + if (my_pid > 0) ok("getpid > 0"); else fail("getpid > 0"); + + pid_t my_ppid = getppid(); + if (my_ppid > 0) ok("getppid > 0"); else fail("getppid > 0"); + + pid_t child = fork(); + if (child < 0) { fail("fork"); return 1; } + + if (child == 0) { + if (getppid() == my_pid) ok("child: ppid == parent pid"); + else fail("child: ppid == parent pid"); + return 42; + } + + int status = 0; + pid_t reaped = waitpid(child, &status, 0); + if (reaped == child) ok("waitpid returns child pid"); + else fail("waitpid returns child pid"); + if (WIFEXITED(status)) ok("WIFEXITED"); + else fail("WIFEXITED"); + if (WEXITSTATUS(status) == 42) ok("exit code 42"); + else { printf(" exit code = %d\n", WEXITSTATUS(status)); fail("exit code 42"); } + + pid_t r = waitpid(-1, &status, WNOHANG); + if (r == 0) ok("WNOHANG returns 0 when no zombie"); + else fail("WNOHANG returns 0 when no zombie"); + + if (getuid() == 0) ok("getuid == 0 (root)"); + else fail("getuid == 0"); + + puts("--- test_process done ---"); + return failed ? 1 : 0; +} diff --git a/usr/apps/uptime.c b/usr/apps/uptime.c new file mode 100644 index 0000000..174f8df --- /dev/null +++ b/usr/apps/uptime.c @@ -0,0 +1,22 @@ +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + uint64_t ns = cervus_uptime_ns(); + uint64_t total_s = ns / 1000000000ULL; + uint64_t ms = (ns / 1000000ULL) % 1000ULL; + uint64_t secs = total_s % 60; + uint64_t mins = (total_s / 60) % 60; + uint64_t hours = (total_s / 3600) % 24; + uint64_t days = total_s / 86400; + + fputs(" Uptime: ", stdout); + if (days > 0) + printf("%lu day%s, ", (unsigned long)days, days != 1 ? "s" : ""); + printf("%02lu:%02lu:%02lu (%lus %lums)\n", + (unsigned long)hours, (unsigned long)mins, (unsigned long)secs, + (unsigned long)total_s, (unsigned long)ms); + return 0; +} diff --git a/usr/installer/install-on-disk.c b/usr/installer/install-on-disk.c new file mode 100644 index 0000000..d5753eb --- /dev/null +++ b/usr/installer/install-on-disk.c @@ -0,0 +1,592 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_ENTRIES 128 + +#define MBR_TYPE_FAT32_LBA 0x0C +#define MBR_TYPE_LINUX 0x83 +#define MBR_TYPE_LINUX_SWAP 0x82 + +typedef struct { + char name[32]; + char model[41]; + uint64_t sectors; + uint64_t size_bytes; +} disk_summary_t; + +typedef struct { + char name[64]; + uint8_t type; +} dir_entry_t; + +static void safe_strcpy(char *dst, size_t dsz, const char *src) +{ + if (!dst || dsz == 0) return; + if (!src) { dst[0] = '\0'; return; } + size_t i = 0; + while (i + 1 < dsz && src[i]) { dst[i] = src[i]; i++; } + dst[i] = '\0'; +} + +static void clear_screen(void) +{ + fputs("\x1b[2J\x1b[H", stdout); +} + +static int ensure_dir(const char *path) +{ + struct stat st; + if (stat(path, &st) == 0) return 0; + return mkdir(path, 0755); +} + +static int ensure_parent_dir(const char *path) +{ + char tmp[512]; + size_t len = strlen(path); + if (len >= sizeof(tmp)) return -1; + int last_slash = -1; + for (int i = (int)len - 1; i >= 0; i--) { + if (path[i] == '/') { last_slash = i; break; } + } + if (last_slash <= 0) return 0; + for (int i = 0; i < last_slash; i++) tmp[i] = path[i]; + tmp[last_slash] = '\0'; + int depth = 0; + int starts[32]; + starts[depth++] = 0; + for (int i = 1; i < last_slash && depth < 32; i++) { + if (tmp[i] == '/') starts[depth++] = i; + } + for (int d = 0; d < depth; d++) { + int end = (d + 1 < depth) ? starts[d + 1] : last_slash; + char part[512]; + for (int i = 0; i < end; i++) part[i] = tmp[i]; + part[end] = '\0'; + if (part[0] == '\0') continue; + struct stat st; + if (stat(part, &st) != 0) syscall2(SYS_MKDIR, part, 0755); + } + return 0; +} + +static int copy_one_file_progress(const char *src, const char *dst, const char *display_name) +{ + int sfd = open(src, O_RDONLY, 0); + if (sfd < 0) return sfd; + ensure_parent_dir(dst); + int dfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0755); + if (dfd < 0) { close(sfd); return dfd; } + + struct stat st; + uint64_t total = 0; + if (stat(src, &st) == 0) total = st.st_size; + + static char fbuf[4096]; + ssize_t n; + int rc = 0; + uint64_t written = 0; + int last_pct = -1; + int spinner = 0; + + while ((n = read(sfd, fbuf, sizeof(fbuf))) > 0) { + ssize_t w = write(dfd, fbuf, (size_t)n); + if (w < 0) { rc = (int)w; break; } + written += (uint64_t)w; + if (total > 0 && display_name) { + int pct = (int)((written * 100) / total); + if (pct != last_pct) { + static const char glyphs[4] = { '|', '/', '-', '\\' }; + fputs("\r\033[K ", stdout); + putchar(glyphs[spinner & 3]); + fputs(" ", stdout); + fputs(display_name, stdout); + fputs(" ", stdout); + char pb[8]; + int bi = 0; + if (pct >= 100) { pb[bi++]='1'; pb[bi++]='0'; pb[bi++]='0'; } + else if (pct >= 10) { pb[bi++]=(char)('0'+pct/10); pb[bi++]=(char)('0'+pct%10); } + else { pb[bi++]=(char)('0'+pct); } + pb[bi++]='%'; + pb[bi]='\0'; + fputs(pb, stdout); + spinner++; + last_pct = pct; + } + } + } + close(sfd); + close(dfd); + + if (display_name) { + fputs("\r\033[K ", stdout); + fputs(display_name, stdout); + fputs(rc < 0 ? " FAILED\n" : " done\n", stdout); + } + return rc; +} + +static int copy_one_file(const char *src, const char *dst) +{ + return copy_one_file_progress(src, dst, NULL); +} + +static int read_dir_entries(const char *path, dir_entry_t *out, int max) +{ + DIR *d = opendir(path); + if (!d) return 0; + int count = 0; + struct dirent *de; + while (count < max && (de = readdir(d)) != NULL) { + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || + (de->d_name[1] == '.' && de->d_name[2] == '\0'))) continue; + safe_strcpy(out[count].name, sizeof(out[count].name), de->d_name); + out[count].type = de->d_type; + count++; + } + closedir(d); + return count; +} + +static int should_exclude_from_copy(const char *name) +{ + if (strcmp(name, "install-on-disk") == 0) return 1; + return 0; +} + +static void copy_tree(const char *src_dir, const char *dst_dir) +{ + dir_entry_t entries[MAX_ENTRIES]; + int n = read_dir_entries(src_dir, entries, MAX_ENTRIES); + if (n == 0) return; + ensure_dir(dst_dir); + for (int i = 0; i < n; i++) { + if (should_exclude_from_copy(entries[i].name)) { + fputs(C_GRAY " skip (installer): " C_RESET, stdout); + fputs(entries[i].name, stdout); + putchar(10); + continue; + } + char sp[256], dp[256]; + path_join(src_dir, entries[i].name, sp, sizeof(sp)); + path_join(dst_dir, entries[i].name, dp, sizeof(dp)); + if (entries[i].type == 1) { + ensure_dir(dp); + copy_tree(sp, dp); + } else { + copy_one_file_progress(sp, dp, sp); + } + } +} + +static int list_disks(disk_summary_t out[4]) +{ + int found = 0; + for (int i = 0; i < 4; i++) { + struct { + char name[32]; + uint64_t sectors; + uint64_t size_bytes; + char model[41]; + uint8_t present; + uint8_t _pad[6]; + } info; + memset(&info, 0, sizeof(info)); + int r = (int)syscall2(SYS_DISK_INFO, (uint64_t)i, (uint64_t)&info); + if (r < 0 || !info.present) continue; + size_t nlen = strlen(info.name); + int is_part = 0; + for (size_t k = 0; k < nlen; k++) { + if (info.name[k] >= '0' && info.name[k] <= '9') { is_part = 1; break; } + } + if (is_part) continue; + safe_strcpy(out[found].name, sizeof(out[found].name), info.name); + safe_strcpy(out[found].model, sizeof(out[found].model), info.model); + out[found].sectors = info.sectors; + out[found].size_bytes = info.size_bytes; + found++; + if (found >= 4) break; + } + return found; +} + +static int ask_choose_disk(char *out_name, size_t out_cap) +{ + disk_summary_t disks[4]; + int n = list_disks(disks); + if (n == 0) { + fputs(C_RED " No disks detected!" C_RESET "\n", stdout); + return -1; + } + fputs("\n", stdout); + fputs(C_CYAN " Available disks:" C_RESET "\n", stdout); + for (int i = 0; i < n; i++) { + fputs(" ", stdout); + putchar((char)('1' + i)); + fputs(") ", stdout); + fputs(C_BOLD, stdout); + fputs(disks[i].name, stdout); + fputs(C_RESET, stdout); + fputs(" ", stdout); + uint64_t mb = disks[i].size_bytes / (1024 * 1024); + char buf[32]; + int bi = 0; + if (mb == 0) { + buf[bi++] = '0'; + } else { + char rev[32]; + int ri = 0; + uint64_t v = mb; + while (v) { rev[ri++] = (char)('0' + (v % 10)); v /= 10; } + while (ri) buf[bi++] = rev[--ri]; + } + buf[bi] = '\0'; + fputs(buf, stdout); + fputs(" MB ", stdout); + fputs(disks[i].model, stdout); + fputs("\n", stdout); + } + fputs("\n Select disk [1-", stdout); + putchar((char)('0' + n)); + fputs("] (q to cancel): ", stdout); + char c = 0; + while (1) { + if (read(0, &c, 1) <= 0) continue; + if (c >= '1' && c <= (char)('0' + n)) { putchar(c); putchar(10); break; } + if (c == 'q' || c == 'Q') { putchar(c); putchar(10); return -1; } + } + int idx = c - '1'; + safe_strcpy(out_name, out_cap, disks[idx].name); + return 0; +} + +static void progress_done(const char *msg) +{ + putchar('\r'); + fputs(C_GREEN " ", stdout); + fputs(msg, stdout); + fputs(C_RESET " \n", stdout); +} + +static void progress_fail(const char *msg) +{ + putchar('\r'); + fputs(C_RED " ", stdout); + fputs(msg, stdout); + fputs(C_RESET " \n", stdout); +} + +static int write_limine_conf(const char *path) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return fd; + const char *conf = + "timeout: 5\n" + "default_entry: 1\n" + "interface_branding: Cervus\n" + "wallpaper: boot():/boot/wallpaper.png\n" + "\n" + "/Cervus v0.0.2 Alpha\n" + " protocol: limine\n" + " path: boot():/kernel\n" + " module_path: boot():/shell.elf\n" + " module_cmdline: init\n"; + write(fd, conf, strlen(conf)); + close(fd); + return 0; +} + +static int do_install(void) +{ + clear_screen(); + + fputs(C_CYAN " Cervus OS Installer" C_RESET "\n", stdout); + fputs(C_GRAY " -----------------------------------" C_RESET "\n", stdout); + + char chosen_disk_name[32]; + if (ask_choose_disk(chosen_disk_name, sizeof(chosen_disk_name)) < 0) { + fputs("\n Cancelled.\n\n", stdout); + return 1; + } + + fputs("\n Target disk: " C_BOLD, stdout); + fputs(chosen_disk_name, stdout); + fputs(C_RESET "\n", stdout); + fputs(" Layout: ESP (FAT32, 64 MB) + root (ext2) + swap (16 MB)\n", stdout); + fputs("\n", stdout); + fputs(C_RED " WARNING: This will erase ALL data on " C_RESET, stdout); + fputs(C_BOLD, stdout); + fputs(chosen_disk_name, stdout); + fputs(C_RESET C_RED "!" C_RESET "\n\n", stdout); + fputs(" Continue? [y/n]: ", stdout); + + char c = 0; + while (1) { + if (read(0, &c, 1) <= 0) continue; + if (c == 'y' || c == 'Y' || c == 'n' || c == 'N') { putchar(c); putchar(10); break; } + } + + if (c == 'n' || c == 'N') { + fputs("\n Cancelled.\n\n", stdout); + return 1; + } + + disk_summary_t disks[4]; + int n_disks = list_disks(disks); + uint64_t total_sectors = 0; + for (int i = 0; i < n_disks; i++) { + if (strcmp(disks[i].name, chosen_disk_name) == 0) { + total_sectors = disks[i].sectors; + break; + } + } + if (total_sectors < 300000) { + fputs(C_RED " Disk too small (need at least 150 MB)" C_RESET "\n\n", stdout); + return 1; + } + + uint32_t esp_start = 2048; + uint32_t esp_size = 131072; + uint32_t swap_size = 32768; + uint32_t root_start = esp_start + esp_size; + uint32_t avail = (uint32_t)total_sectors - root_start - swap_size; + uint32_t root_size = avail; + uint32_t swap_start = root_start + root_size; + + fputs("\n [1/8] Writing partition table...\n", stdout); + cervus_mbr_part_t specs[4]; + memset(specs, 0, sizeof(specs)); + specs[0].boot_flag = 1; + specs[0].type = MBR_TYPE_FAT32_LBA; + specs[0].lba_start = esp_start; + specs[0].sector_count = esp_size; + specs[1].boot_flag = 0; + specs[1].type = MBR_TYPE_LINUX; + specs[1].lba_start = root_start; + specs[1].sector_count = root_size; + specs[2].boot_flag = 0; + specs[2].type = MBR_TYPE_LINUX_SWAP; + specs[2].lba_start = swap_start; + specs[2].sector_count = swap_size; + if (cervus_disk_partition(chosen_disk_name, specs, 3) < 0) { + progress_fail("Failed to write partition table!"); + return 1; + } + progress_done("partition table written"); + + char part1[32], part2[32]; + snprintf(part1, sizeof(part1), "%s1", chosen_disk_name); + snprintf(part2, sizeof(part2), "%s2", chosen_disk_name); + + fputs(" [2/8] Formatting ", stdout); + fputs(part1, stdout); + fputs(" as FAT32 (ESP)...\n", stdout); + if (cervus_disk_mkfs_fat32(part1, "CERVUS-ESP") < 0) { + progress_fail("mkfs.fat32 failed!"); + return 1; + } + progress_done("FAT32 ESP created"); + + fputs(" [3/8] Formatting ", stdout); + fputs(part2, stdout); + fputs(" as ext2 (root)...\n", stdout); + if (cervus_disk_format(part2, "cervus-root") < 0) { + progress_fail("mkfs.ext2 failed!"); + return 1; + } + progress_done("ext2 root created"); + + fputs(" [4/8] Mounting partitions...\n", stdout); + ensure_dir("/mnt/esp"); + ensure_dir("/mnt/root"); + if (cervus_disk_mount(part1, "/mnt/esp") < 0) { + progress_fail("mount ESP failed"); + return 1; + } + if (cervus_disk_mount(part2, "/mnt/root") < 0) { + progress_fail("mount root failed"); + cervus_disk_umount("/mnt/esp"); + return 1; + } + progress_done("mounted"); + + fputs(" [5/8] Copying boot files to ESP...\n", stdout); + ensure_dir("/mnt/esp/boot"); + ensure_dir("/mnt/esp/boot/limine"); + ensure_dir("/mnt/esp/EFI"); + ensure_dir("/mnt/esp/EFI/BOOT"); + + struct { const char *src; const char *dst; int required; } boot_files[] = { + { "/boot/kernel", "/mnt/esp/boot/kernel", 1 }, + { "/boot/kernel", "/mnt/esp/kernel", 0 }, + { "/boot/shell.elf", "/mnt/esp/boot/shell.elf", 1 }, + { "/boot/shell.elf", "/mnt/esp/shell.elf", 0 }, + { "/boot/limine-bios.sys", "/mnt/esp/boot/limine/limine-bios.sys", 0 }, + { "/boot/limine-bios-hdd.bin", "/mnt/esp/boot/limine/limine-bios-hdd.bin", 0 }, + { "/boot/BOOTX64.EFI", "/mnt/esp/EFI/BOOT/BOOTX64.EFI", 0 }, + { "/boot/BOOTIA32.EFI", "/mnt/esp/EFI/BOOT/BOOTIA32.EFI", 0 }, + { "/boot/wallpaper.png", "/mnt/esp/boot/wallpaper.png", 0 }, + { "/boot/wallpaper.png", "/mnt/esp/wallpaper.png", 0 }, + { NULL, NULL, 0 } + }; + for (int i = 0; boot_files[i].src; i++) { + struct stat st; + if (stat(boot_files[i].src, &st) != 0) { + if (boot_files[i].required) { + fputs(C_RED " MISSING required: " C_RESET, stdout); + fputs(boot_files[i].src, stdout); + putchar(10); + } else { + fputs(C_YELLOW " skip (missing): " C_RESET, stdout); + fputs(boot_files[i].src, stdout); + putchar(10); + } + continue; + } + if (copy_one_file_progress(boot_files[i].src, boot_files[i].dst, boot_files[i].src) < 0) { + fputs(C_RED " FAILED: " C_RESET, stdout); + fputs(boot_files[i].dst, stdout); + putchar(10); + } else { + fputs(C_GREEN " " C_RESET, stdout); + fputs(boot_files[i].dst, stdout); + putchar(10); + } + } + + fputs(" [6/8] Writing limine.conf...\n", stdout); + int ok1 = write_limine_conf("/mnt/esp/boot/limine/limine.conf"); + int ok2 = write_limine_conf("/mnt/esp/EFI/BOOT/limine.conf"); + int ok3 = write_limine_conf("/mnt/esp/limine.conf"); + if (ok1 < 0 && ok2 < 0 && ok3 < 0) + progress_fail("failed to write limine.conf"); + else + progress_done("limine.conf written (3 locations)"); + + fputs(" [7/8] Populating root filesystem...\n", stdout); + const char *rdirs[] = { + "/mnt/root/bin", "/mnt/root/apps", "/mnt/root/etc", + "/mnt/root/home", "/mnt/root/tmp", "/mnt/root/var", + "/mnt/root/usr", + NULL + }; + for (int i = 0; rdirs[i]; i++) ensure_dir(rdirs[i]); + + fputs(" copying /bin...\n", stdout); + copy_tree("/bin", "/mnt/root/bin"); + fputs(" copying /apps...\n", stdout); + copy_tree("/apps", "/mnt/root/apps"); + + struct stat ust; + if (stat("/usr", &ust) == 0) { + fputs(" copying /usr (sysroot)...\n", stdout); + copy_tree("/usr", "/mnt/root/usr"); + } else { + fputs(" /usr not present in live image — sysroot skipped\n", stdout); + } + + struct stat est; + if (stat("/etc", &est) == 0) { + fputs(" copying /etc...\n", stdout); + static dir_entry_t etc_entries[MAX_ENTRIES]; + int nn = read_dir_entries("/etc", etc_entries, MAX_ENTRIES); + for (int i = 0; i < nn; i++) { + const char *nm = etc_entries[i].name; + size_t nl = strlen(nm); + int is_txt = (nl >= 5 && strcmp(nm + nl - 4, ".txt") == 0); + if (etc_entries[i].type == 0) { + char sp[256], dp[256]; + path_join("/etc", nm, sp, sizeof(sp)); + if (is_txt) path_join("/mnt/root/home", nm, dp, sizeof(dp)); + else path_join("/mnt/root/etc", nm, dp, sizeof(dp)); + copy_one_file(sp, dp); + } else if (etc_entries[i].type == 1) { + char sp[256], dp[256]; + path_join("/etc", nm, sp, sizeof(sp)); + path_join("/mnt/root/etc", nm, dp, sizeof(dp)); + copy_tree(sp, dp); + } + } + } + + struct stat hst; + if (stat("/home", &hst) == 0) { + fputs(" copying /home...\n", stdout); + copy_tree("/home", "/mnt/root/home"); + } + + fputs(" [8/8] Installing BIOS stage1 to MBR...\n", stdout); + { + static uint8_t sys_buf[300 * 1024]; + int fd = open("/mnt/esp/boot/limine/limine-bios-hdd.bin", O_RDONLY, 0); + if (fd < 0) { + progress_fail("limine-bios-hdd.bin not found on ESP"); + } else { + struct stat st; + int sr = stat("/mnt/esp/boot/limine/limine-bios-hdd.bin", &st); + uint32_t sys_size = (sr == 0) ? (uint32_t)st.st_size : 0; + + if (sys_size == 0 || sys_size > sizeof(sys_buf)) { + close(fd); + progress_fail("bad limine-bios-hdd.bin size"); + } else { + uint32_t got = 0; + int ok = 1; + while (got < sys_size) { + ssize_t n = read(fd, sys_buf + got, sys_size - got); + if (n <= 0) { ok = 0; break; } + got += (uint32_t)n; + } + close(fd); + if (!ok || got != sys_size) { + progress_fail("short read on limine-bios-hdd.bin"); + } else { + long r = cervus_disk_bios_install(chosen_disk_name, sys_buf, sys_size); + if (r < 0) progress_fail("BIOS install syscall failed"); + else progress_done("BIOS stage1 installed"); + } + } + } + } + + cervus_disk_umount("/mnt/esp"); + cervus_disk_umount("/mnt/root"); + + clear_screen(); + + fputs("\n" C_GREEN " Installation complete!" C_RESET "\n", stdout); + fputs(" The system will reboot in 3 seconds.\n\n", stdout); + + for (int i = 3; i > 0; i--) { + fputs(" Rebooting in ", stdout); + putchar((char)('0' + i)); + fputs("...\r", stdout); + syscall1(SYS_SLEEP_NS, 1000000000ULL); + } + fputs("\n", stdout); + syscall0(SYS_REBOOT); + return 0; +} + +int main(int argc, char **argv) +{ + const char *mode = getenv_argv(argc, argv, "MODE", ""); + + if (strcmp(mode, "live") != 0) { + fputs(C_RED "install-on-disk: this command is only available in Live mode.\n" C_RESET, stderr); + fputs("The system is already installed on disk.\n", stderr); + return 1; + } + + do_install(); + return 0; +} \ No newline at end of file diff --git a/usr/lib/libcervus/compat.c b/usr/lib/libcervus/compat.c new file mode 100644 index 0000000..cff3e82 --- /dev/null +++ b/usr/lib/libcervus/compat.c @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int __cervus_errno; + +static long __compat_sys_ret(long r) +{ + if (r < 0 && r > -4096) { + __cervus_errno = (int)-r; + return -1; + } + return r; +} + +int access(const char *path, int mode) +{ + struct stat st; + if (!path) { __cervus_errno = EFAULT; return -1; } + long r = syscall2(SYS_STAT, path, &st); + (void)mode; + if (r < 0 && r > -4096) { __cervus_errno = (int)-r; return -1; } + return 0; +} + +static char __cervus_cwd[512] = "/"; + +int fchdir(int fd) +{ + (void)fd; + __cervus_errno = ENOSYS; + return -1; +} + +int symlink(const char *target, const char *linkpath) +{ + (void)target; (void)linkpath; + __cervus_errno = ENOSYS; + return -1; +} + +ssize_t readlink(const char *path, char *buf, size_t bufsiz) +{ + (void)path; (void)buf; (void)bufsiz; + __cervus_errno = ENOSYS; + return -1; +} + +int sched_yield(void) +{ + syscall0(SYS_YIELD); + return 0; +} + +long pathconf(const char *path, int name) +{ + (void)path; + switch (name) { + case 0: return 255; + case 1: return 512; + default: __cervus_errno = EINVAL; return -1; + } +} + +long fpathconf(int fd, int name) +{ + (void)fd; + switch (name) { + case 0: return 255; + case 1: return 512; + default: __cervus_errno = EINVAL; return -1; + } +} + +int mprotect(void *addr, size_t len, int prot) +{ + return (int)__compat_sys_ret(syscall3(SYS_MPROTECT, addr, len, prot)); +} + +char *realpath(const char *path, char *resolved) +{ + if (!path) { __cervus_errno = EINVAL; return NULL; } + static char sbuf[512]; + char *out = resolved ? resolved : sbuf; + + if (path[0] == '/') { + strncpy(out, path, 511); + out[511] = '\0'; + } else { + strncpy(out, __cervus_cwd, 511); + out[511] = '\0'; + size_t bl = strlen(out); + if (bl > 0 && out[bl - 1] != '/' && bl < 510) { + out[bl++] = '/'; + out[bl] = '\0'; + } + strncat(out, path, 511 - strlen(out)); + } + + char tmp[512]; + strncpy(tmp, out, 511); + tmp[511] = '\0'; + char *parts[64]; + int np = 0; + char *p = tmp; + while (*p) { + while (*p == '/') p++; + if (!*p) break; + char *s = p; + while (*p && *p != '/') p++; + if (*p) *p++ = '\0'; + if (strcmp(s, ".") == 0) continue; + if (strcmp(s, "..") == 0) { if (np > 0) np--; continue; } + if (np < 64) parts[np++] = s; + } + size_t ol = 0; + for (int i = 0; i < np; i++) { + out[ol++] = '/'; + size_t pl = strlen(parts[i]); + if (ol + pl >= 511) break; + memcpy(out + ol, parts[i], pl); + ol += pl; + } + out[ol] = '\0'; + if (ol == 0) { out[0] = '/'; out[1] = '\0'; } + return out; +} + +char *mkdtemp(char *tmpl) +{ + if (!tmpl) { __cervus_errno = EINVAL; return NULL; } + size_t len = strlen(tmpl); + if (len < 6 || strcmp(tmpl + len - 6, "XXXXXX") != 0) { + __cervus_errno = EINVAL; + return NULL; + } + static uint64_t seq = 0; + uint64_t pid = (uint64_t)getpid(); + const char *alpha = "0123456789abcdefghijklmnopqrstuvwxyz"; + for (int attempt = 0; attempt < 100; attempt++) { + uint64_t seed = (cervus_uptime_ns() ^ (pid << 32)) + (seq++); + for (int i = 0; i < 6; i++) { + tmpl[len - 6 + i] = alpha[seed % 36]; + seed /= 36; + } + if (mkdir(tmpl, 0700) == 0) return tmpl; + if (__cervus_errno != EEXIST) return NULL; + } + __cervus_errno = EEXIST; + return NULL; +} + +static char **__env_table = NULL; +static int __env_count = 0; +static int __env_cap = 0; + +int putenv(char *str) +{ + if (!str) return -1; + char *eq = strchr(str, '='); + if (!eq) return -1; + size_t nl = (size_t)(eq - str); + for (int i = 0; i < __env_count; i++) { + if (strncmp(__env_table[i], str, nl) == 0 && __env_table[i][nl] == '=') { + __env_table[i] = str; + return 0; + } + } + if (__env_count >= __env_cap) { + int nc = __env_cap ? __env_cap * 2 : 16; + char **nt = (char **)realloc(__env_table, (size_t)nc * sizeof(char *)); + if (!nt) return -1; + __env_table = nt; + __env_cap = nc; + } + __env_table[__env_count++] = str; + return 0; +} + +int setenv(const char *name, const char *value, int overwrite) +{ + if (!name || !value) { __cervus_errno = EINVAL; return -1; } + size_t nl = strlen(name); + size_t vl = strlen(value); + for (int i = 0; i < __env_count; i++) { + if (strncmp(__env_table[i], name, nl) == 0 && __env_table[i][nl] == '=') { + if (!overwrite) return 0; + char *nv = (char *)malloc(nl + vl + 2); + if (!nv) return -1; + memcpy(nv, name, nl); + nv[nl] = '='; + memcpy(nv + nl + 1, value, vl + 1); + __env_table[i] = nv; + return 0; + } + } + char *nv = (char *)malloc(nl + vl + 2); + if (!nv) return -1; + memcpy(nv, name, nl); + nv[nl] = '='; + memcpy(nv + nl + 1, value, vl + 1); + return putenv(nv); +} + +int unsetenv(const char *name) +{ + if (!name) { __cervus_errno = EINVAL; return -1; } + size_t nl = strlen(name); + for (int i = 0; i < __env_count; i++) { + if (strncmp(__env_table[i], name, nl) == 0 && __env_table[i][nl] == '=') { + __env_table[i] = __env_table[--__env_count]; + return 0; + } + } + return 0; +} + +int uname(struct utsname *buf) +{ + if (!buf) { __cervus_errno = EFAULT; return -1; } + strncpy(buf->sysname, "Cervus", _UTSNAME_LENGTH - 1); + strncpy(buf->nodename, "cervus", _UTSNAME_LENGTH - 1); + strncpy(buf->release, "0.0.2", _UTSNAME_LENGTH - 1); + strncpy(buf->version, "#1", _UTSNAME_LENGTH - 1); + strncpy(buf->machine, "x86_64", _UTSNAME_LENGTH - 1); + buf->sysname[_UTSNAME_LENGTH - 1] = '\0'; + buf->nodename[_UTSNAME_LENGTH - 1] = '\0'; + buf->release[_UTSNAME_LENGTH - 1] = '\0'; + buf->version[_UTSNAME_LENGTH - 1] = '\0'; + buf->machine[_UTSNAME_LENGTH - 1] = '\0'; + return 0; +} + +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *)) +{ + const unsigned char *lo = (const unsigned char *)base; + const unsigned char *hi = lo + nmemb * size; + while (lo < hi) { + size_t half = (size_t)((hi - lo) / (ptrdiff_t)size) / 2; + const unsigned char *mid = lo + half * size; + int r = cmp(key, mid); + if (r == 0) return (void *)mid; + if (r < 0) hi = mid; + else lo = mid + size; + } + return NULL; +} + +struct __cervus_FILE { + int fd; + int eof; + int err; + int flags; + char *buf; + size_t buf_size; + size_t buf_pos; +}; + +FILE *fdopen(int fd, const char *mode) +{ + (void)mode; + FILE *f = (FILE *)malloc(sizeof(FILE)); + if (!f) { __cervus_errno = ENOMEM; return NULL; } + f->fd = fd; + f->eof = 0; + f->err = 0; + f->flags = 0; + f->buf = NULL; + f->buf_size = 0; + f->buf_pos = 0; + return f; +} + +FILE *popen(const char *cmd, const char *type) +{ + if (!cmd || !type) { __cervus_errno = EINVAL; return NULL; } + int fds[2]; + if (pipe(fds) < 0) return NULL; + pid_t pid = fork(); + if (pid < 0) { + close(fds[0]); + close(fds[1]); + return NULL; + } + if (pid == 0) { + if (type[0] == 'r') { dup2(fds[1], 1); } + else { dup2(fds[0], 0); } + close(fds[0]); + close(fds[1]); + char *argv[] = { "/bin/sh", "-c", (char *)cmd, NULL }; + execve("/bin/sh", argv, NULL); + _exit(127); + } + if (type[0] == 'r') { + close(fds[1]); + return fdopen(fds[0], "r"); + } else { + close(fds[0]); + return fdopen(fds[1], "w"); + } +} + +int pclose(FILE *f) +{ + if (!f) return -1; + int fd = f->fd; + free(f); + close(fd); + int status = 0; + waitpid(-1, &status, 0); + return status; +} + +int ungetc(int c, FILE *f) +{ + (void)c; (void)f; + return EOF; +} + +void rewind(FILE *f) +{ + if (f) { + syscall3(SYS_SEEK, f->fd, 0, 0); + f->eof = 0; + f->err = 0; + } +} + +char *tmpnam(char *buf) +{ + static char sbuf[32]; + static uint64_t seq = 0; + char *out = buf ? buf : sbuf; + uint64_t seed = cervus_uptime_ns() ^ (seq++); + snprintf(out, 32, "/tmp/tmp%llu", (unsigned long long)seed); + return out; +} \ No newline at end of file diff --git a/usr/lib/libcervus/crt0.asm b/usr/lib/libcervus/crt0.asm new file mode 100644 index 0000000..e34ff9c --- /dev/null +++ b/usr/lib/libcervus/crt0.asm @@ -0,0 +1,41 @@ +BITS 64 +DEFAULT REL + +section .text + global _start + extern main + extern __cervus_argc + extern __cervus_argv + extern __cervus_filter_args + extern __cervus_filtered_argv + +_start: + xor rbp, rbp + + mov rdi, [rsp] + lea rsi, [rsp + 8] + + lea rax, [rel __cervus_argc] + mov dword [rax], edi + lea rax, [rel __cervus_argv] + mov qword [rax], rsi + + and rsp, -16 + + movsxd rdi, dword [rel __cervus_argc] + mov rsi, qword [rel __cervus_argv] + call __cervus_filter_args + + movsxd rdi, eax + lea rsi, [rel __cervus_filtered_argv] + call main + + movsxd rdi, eax + xor rax, rax + syscall + +.hang: + hlt + jmp .hang + +section .note.GNU-stack noalloc noexec nowrite progbits \ No newline at end of file diff --git a/usr/lib/libcervus/ctype/ctype.c b/usr/lib/libcervus/ctype/ctype.c new file mode 100644 index 0000000..debf84f --- /dev/null +++ b/usr/lib/libcervus/ctype/ctype.c @@ -0,0 +1,16 @@ +#include + +int isdigit(int c) { return c >= '0' && c <= '9'; } +int isalpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +int isalnum(int c) { return isdigit(c) || isalpha(c); } +int isupper(int c) { return c >= 'A' && c <= 'Z'; } +int islower(int c) { return c >= 'a' && c <= 'z'; } +int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; } +int isprint(int c) { return (unsigned char)c >= 0x20 && (unsigned char)c < 0x7F; } +int isgraph(int c) { return (unsigned char)c > 0x20 && (unsigned char)c < 0x7F; } +int ispunct(int c) { return isgraph(c) && !isalnum(c); } +int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } +int iscntrl(int c) { return (unsigned char)c < 0x20 || c == 0x7F; } +int isblank(int c) { return c == ' ' || c == '\t'; } +int toupper(int c) { return islower(c) ? c - 'a' + 'A' : c; } +int tolower(int c) { return isupper(c) ? c - 'A' + 'a' : c; } \ No newline at end of file diff --git a/usr/lib/libcervus/dirent/dirent.c b/usr/lib/libcervus/dirent/dirent.c new file mode 100644 index 0000000..6bbaf50 --- /dev/null +++ b/usr/lib/libcervus/dirent/dirent.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + uint64_t d_ino; + uint8_t d_type; + char d_name[256]; +} __kernel_dirent_t; + +struct __cervus_DIR { + int fd; + struct dirent buf; +}; + +DIR *opendir(const char *path) +{ + int fd = open(path, O_RDONLY | O_DIRECTORY, 0); + if (fd < 0) return NULL; + DIR *d = (DIR *)malloc(sizeof(DIR)); + if (!d) { close(fd); return NULL; } + d->fd = fd; + return d; +} + +struct dirent *readdir(DIR *dirp) +{ + if (!dirp) return NULL; + __kernel_dirent_t kde; + int r = (int)syscall2(SYS_READDIR, dirp->fd, &kde); + if (r != 0) return NULL; + dirp->buf.d_ino = kde.d_ino; + dirp->buf.d_type = kde.d_type; + size_t nl = strlen(kde.d_name); + if (nl >= sizeof(dirp->buf.d_name)) nl = sizeof(dirp->buf.d_name) - 1; + memcpy(dirp->buf.d_name, kde.d_name, nl); + dirp->buf.d_name[nl] = '\0'; + return &dirp->buf; +} + +int closedir(DIR *dirp) +{ + if (!dirp) return -1; + int fd = dirp->fd; + free(dirp); + return close(fd); +} + +void rewinddir(DIR *dirp) +{ + if (!dirp) return; + lseek(dirp->fd, 0, SEEK_SET); +} + +int dirfd(DIR *dirp) { return dirp ? dirp->fd : -1; } \ No newline at end of file diff --git a/usr/lib/libcervus/libcervus.c b/usr/lib/libcervus/libcervus.c new file mode 100644 index 0000000..251ab2a --- /dev/null +++ b/usr/lib/libcervus/libcervus.c @@ -0,0 +1,461 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int __cervus_argc; +char **__cervus_argv; + +int __cervus_errno = 0; + +static long __sys_ret(long r) +{ + if (r < 0 && r > -4096) { + __cervus_errno = (int)-r; + return -1; + } + return r; +} + +#define CERVUS_PATH_MAX 512 + +static char __cervus_cwd[CERVUS_PATH_MAX]; +static int __cervus_cwd_inited = 0; + +static const char *__cervus_get_cwd(void) +{ + if (!__cervus_cwd_inited) { + const char *c = get_cwd_flag(__cervus_argc, __cervus_argv); + if (!c || !*c) c = "/"; + size_t n = strlen(c); + if (n >= sizeof(__cervus_cwd)) n = sizeof(__cervus_cwd) - 1; + memcpy(__cervus_cwd, c, n); + __cervus_cwd[n] = '\0'; + __cervus_cwd_inited = 1; + } + return __cervus_cwd; +} + +static const char *__cervus_resolve(const char *path, char *buf, size_t bufsz) +{ + if (!path) return path; + if (path[0] == '/') return path; + resolve_path(__cervus_get_cwd(), path, buf, bufsz); + return buf; +} + +ssize_t read(int fd, void *buf, size_t n) +{ + return (ssize_t)__sys_ret(syscall3(SYS_READ, fd, buf, n)); +} +ssize_t write(int fd, const void *buf, size_t n) +{ + return (ssize_t)__sys_ret(syscall3(SYS_WRITE, fd, buf, n)); +} +int close(int fd) +{ + return (int)__sys_ret(syscall1(SYS_CLOSE, fd)); +} +off_t lseek(int fd, off_t off, int whence) +{ + return (off_t)__sys_ret(syscall3(SYS_SEEK, fd, (uint64_t)off, whence)); +} +int dup(int fd) +{ + return (int)__sys_ret(syscall1(SYS_DUP, fd)); +} +int dup2(int oldfd, int newfd) +{ + return (int)__sys_ret(syscall2(SYS_DUP2, oldfd, newfd)); +} +int pipe(int fds[2]) +{ + return (int)__sys_ret(syscall1(SYS_PIPE, fds)); +} + +int ioctl(int fd, unsigned long request, ...) +{ + va_list ap; + va_start(ap, request); + void *arg = va_arg(ap, void *); + va_end(ap); + return (int)__sys_ret(syscall3(SYS_IOCTL, fd, request, arg)); +} + +int isatty(int fd) +{ + struct winsize ws; + long r = syscall3(SYS_IOCTL, fd, TIOCGWINSZ, &ws); + if (r < 0) { + __cervus_errno = (int)-r; + return 0; + } + return 1; +} + +int unlink(const char *path) +{ + char abs[CERVUS_PATH_MAX]; + path = __cervus_resolve(path, abs, sizeof(abs)); + return (int)__sys_ret(syscall1(SYS_UNLINK, path)); +} +int rmdir(const char *path) +{ + char abs[CERVUS_PATH_MAX]; + path = __cervus_resolve(path, abs, sizeof(abs)); + return (int)__sys_ret(syscall1(SYS_RMDIR, path)); +} + +pid_t getpid(void) { return (pid_t)syscall0(SYS_GETPID); } +pid_t getppid(void) { return (pid_t)syscall0(SYS_GETPPID); } +uid_t getuid(void) { return (uid_t)syscall0(SYS_GETUID); } +gid_t getgid(void) { return (gid_t)syscall0(SYS_GETGID); } +int setuid(uid_t u) { return (int)__sys_ret(syscall1(SYS_SETUID, u)); } +int setgid(gid_t g) { return (int)__sys_ret(syscall1(SYS_SETGID, g)); } +pid_t fork(void) { return (pid_t)__sys_ret(syscall0(SYS_FORK)); } + +int execve(const char *path, char *const argv[], char *const envp[]) +{ + char abs[CERVUS_PATH_MAX]; + path = __cervus_resolve(path, abs, sizeof(abs)); + return (int)__sys_ret(syscall3(SYS_EXECVE, path, argv, envp)); +} + +int execv(const char *path, char *const argv[]) +{ + char *empty[] = { NULL }; + return execve(path, argv, empty); +} + +int execvp(const char *file, char *const argv[]) +{ + if (!file || !*file) { __cervus_errno = ENOENT; return -1; } + + int has_slash = 0; + for (const char *p = file; *p; p++) if (*p == '/') { has_slash = 1; break; } + if (has_slash) return execve(file, argv, NULL); + + const char *path = getenv("PATH"); + if (!path || !*path) path = "/bin:/apps"; + + char buf[512]; + const char *p = path; + while (*p) { + const char *colon = p; + while (*colon && *colon != ':') colon++; + size_t dlen = (size_t)(colon - p); + size_t flen = strlen(file); + if (dlen + 1 + flen + 1 <= sizeof(buf)) { + memcpy(buf, p, dlen); + buf[dlen] = '/'; + memcpy(buf + dlen + 1, file, flen); + buf[dlen + 1 + flen] = '\0'; + execve(buf, argv, NULL); + } + p = colon; + if (*p == ':') p++; + } + __cervus_errno = ENOENT; + return -1; +} + +void _exit(int status) { syscall1(SYS_EXIT, status); __builtin_unreachable(); } + +unsigned int sleep(unsigned int sec) +{ + cervus_nanosleep((uint64_t)sec * 1000000000ULL); + return 0; +} +int usleep(unsigned int usec) +{ + return cervus_nanosleep((uint64_t)usec * 1000ULL); +} + +void sched_yield_cervus(void) { syscall0(SYS_YIELD); } + +char *getcwd(char *buf, size_t size) +{ + if (!buf || size < 2) { __cervus_errno = EINVAL; return NULL; } + const char *c = __cervus_get_cwd(); + size_t n = strlen(c); + if (n + 1 > size) { __cervus_errno = ERANGE; return NULL; } + memcpy(buf, c, n + 1); + return buf; +} + +int chdir(const char *path) +{ + if (!path || !*path) { __cervus_errno = ENOENT; return -1; } + + char abs[CERVUS_PATH_MAX]; + const char *p = __cervus_resolve(path, abs, sizeof(abs)); + + struct stat st; + if ((int)__sys_ret(syscall2(SYS_STAT, p, &st)) < 0) return -1; + if (!S_ISDIR(st.st_mode)) { __cervus_errno = ENOTDIR; return -1; } + + size_t n = strlen(p); + if (n >= sizeof(__cervus_cwd)) { __cervus_errno = ENAMETOOLONG; return -1; } + memcpy(__cervus_cwd, p, n + 1); + __cervus_cwd_inited = 1; + return 0; +} + +void *sbrk(intptr_t incr) +{ + uintptr_t cur = (uintptr_t)syscall1(SYS_BRK, 0); + if (incr == 0) return (void *)cur; + uintptr_t nw = (uintptr_t)syscall1(SYS_BRK, cur + (uintptr_t)incr); + if (nw != cur + (uintptr_t)incr) { + __cervus_errno = ENOMEM; + return (void *)-1; + } + return (void *)cur; +} +int brk(void *addr) +{ + uintptr_t r = (uintptr_t)syscall1(SYS_BRK, (uintptr_t)addr); + if (r != (uintptr_t)addr) { __cervus_errno = ENOMEM; return -1; } + return 0; +} + +int open(const char *path, int flags, ...) +{ + mode_t mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + } + char abs[CERVUS_PATH_MAX]; + path = __cervus_resolve(path, abs, sizeof(abs)); + return (int)__sys_ret(syscall3(SYS_OPEN, path, flags, mode)); +} +int creat(const char *path, mode_t mode) +{ + return open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); +} +int fcntl(int fd, int cmd, ...) +{ + va_list ap; + va_start(ap, cmd); + long arg = va_arg(ap, long); + va_end(ap); + return (int)__sys_ret(syscall3(SYS_FCNTL, fd, cmd, arg)); +} + +int stat(const char *path, struct stat *out) +{ + char abs[CERVUS_PATH_MAX]; + path = __cervus_resolve(path, abs, sizeof(abs)); + return (int)__sys_ret(syscall2(SYS_STAT, path, out)); +} +int fstat(int fd, struct stat *out) +{ + return (int)__sys_ret(syscall2(SYS_FSTAT, fd, out)); +} +int mkdir(const char *path, mode_t mode) +{ + char abs[CERVUS_PATH_MAX]; + path = __cervus_resolve(path, abs, sizeof(abs)); + return (int)__sys_ret(syscall2(SYS_MKDIR, path, mode)); +} +int chmod(const char *path, mode_t mode) +{ + (void)path; (void)mode; + return 0; +} +int rename(const char *oldp, const char *newp) +{ + char absa[CERVUS_PATH_MAX], absb[CERVUS_PATH_MAX]; + oldp = __cervus_resolve(oldp, absa, sizeof(absa)); + newp = __cervus_resolve(newp, absb, sizeof(absb)); + return (int)__sys_ret(syscall2(SYS_RENAME, oldp, newp)); +} + +void *mmap(void *a, size_t l, int p, int f, int fd, off_t o) +{ + long r = syscall6(SYS_MMAP, a, l, p, f, fd, (uint64_t)o); + if (r < 0 && r > -4096) { __cervus_errno = (int)-r; return MAP_FAILED; } + return (void *)r; +} +int munmap(void *a, size_t l) { return (int)__sys_ret(syscall2(SYS_MUNMAP, a, l)); } + +pid_t waitpid(pid_t p, int *s, int f) { return (pid_t)__sys_ret(syscall3(SYS_WAIT, p, s, f)); } +pid_t wait(int *s) { return waitpid(-1, s, 0); } + +ssize_t cervus_dbg_print(const char *b, size_t n) { return (ssize_t)syscall2(SYS_DBG_PRINT, b, n); } + +char *optarg = NULL; +int optind = 1; +int optopt = 0; +int opterr = 1; + +static int __opt_subidx = 1; + +int getopt(int argc, char *const argv[], const char *optstring) +{ + if (!optstring) optstring = ""; + int colon_mode = (optstring[0] == ':'); + const char *opts = colon_mode ? optstring + 1 : optstring; + + optarg = NULL; + + if (optind >= argc) return -1; + + char *cur = argv[optind]; + if (!cur || cur[0] != '-' || cur[1] == '\0') return -1; + if (cur[0] == '-' && cur[1] == '-' && cur[2] == '\0') { + optind++; + return -1; + } + + char ch = cur[__opt_subidx]; + if (ch == '\0') { + optind++; + __opt_subidx = 1; + return getopt(argc, argv, optstring); + } + + const char *pp = opts; + while (*pp && *pp != ch) pp++; + if (*pp == '\0' || ch == ':') { + optopt = ch; + if (opterr && !colon_mode) { + const char *prog = argv[0] ? argv[0] : "?"; + fprintf(stderr, "%s: invalid option -- '%c'\n", prog, ch); + } + __opt_subidx++; + if (cur[__opt_subidx] == '\0') { + optind++; + __opt_subidx = 1; + } + return '?'; + } + + if (pp[1] == ':') { + if (cur[__opt_subidx + 1] != '\0') { + optarg = &cur[__opt_subidx + 1]; + optind++; + __opt_subidx = 1; + return ch; + } + if (optind + 1 >= argc) { + optopt = ch; + optind++; + __opt_subidx = 1; + if (opterr && !colon_mode) { + const char *prog = argv[0] ? argv[0] : "?"; + fprintf(stderr, "%s: option requires an argument -- '%c'\n", prog, ch); + } + return colon_mode ? ':' : '?'; + } + optarg = argv[optind + 1]; + optind += 2; + __opt_subidx = 1; + return ch; + } + + __opt_subidx++; + if (cur[__opt_subidx] == '\0') { + optind++; + __opt_subidx = 1; + } + return ch; +} + +int tcgetattr(int fd, struct termios *t) +{ + if (!t) { __cervus_errno = EINVAL; return -1; } + return (int)__sys_ret(syscall3(SYS_IOCTL, fd, TCGETS, t)); +} + +int tcsetattr(int fd, int optional_actions, const struct termios *t) +{ + if (!t) { __cervus_errno = EINVAL; return -1; } + unsigned long req; + switch (optional_actions) { + case TCSADRAIN: req = TCSETSW; break; + case TCSAFLUSH: req = TCSETSF; break; + case TCSANOW: + default: req = TCSETS; break; + } + return (int)__sys_ret(syscall3(SYS_IOCTL, fd, req, t)); +} + +int cervus_task_info(pid_t p, cervus_task_info_t *o) { return (int)__sys_ret(syscall2(SYS_TASK_INFO, p, o)); } +int cervus_task_kill(pid_t p) { return (int)__sys_ret(syscall1(SYS_TASK_KILL, p)); } +uint64_t cervus_cap_get(void) { return (uint64_t)syscall0(SYS_CAP_GET); } +int cervus_cap_drop(uint64_t m) { return (int)__sys_ret(syscall1(SYS_CAP_DROP, m)); } + +int cervus_meminfo(cervus_meminfo_t *m) { return (int)__sys_ret(syscall1(SYS_MEMINFO, m)); } +uint64_t cervus_uptime_ns(void) { return (uint64_t)syscall0(SYS_UPTIME); } +int cervus_clock_gettime(int id, cervus_timespec_t *t){ return (int)__sys_ret(syscall2(SYS_CLOCK_GET, id, t)); } +int cervus_nanosleep(uint64_t ns) { return (int)__sys_ret(syscall1(SYS_SLEEP_NS, ns)); } + +int cervus_shutdown(void) { return (int)__sys_ret(syscall0(SYS_SHUTDOWN)); } +int cervus_reboot(void) { return (int)__sys_ret(syscall0(SYS_REBOOT)); } + +int cervus_disk_info(int i, cervus_disk_info_t *o) { return (int)__sys_ret(syscall2(SYS_DISK_INFO, (uint64_t)i, o)); } +int cervus_disk_mount(const char *d, const char *p) { return (int)__sys_ret(syscall2(SYS_DISK_MOUNT, d, p)); } +int cervus_disk_umount(const char *p) { return (int)__sys_ret(syscall1(SYS_DISK_UMOUNT, p)); } +int cervus_disk_format(const char *d, const char *l) { return (int)__sys_ret(syscall2(SYS_DISK_FORMAT, d, l)); } +int cervus_disk_mkfs_fat32(const char *d, const char *l) { return (int)__sys_ret(syscall2(SYS_DISK_MKFS_FAT32, d, l)); } +int cervus_disk_partition(const char *d, const cervus_mbr_part_t *s, uint64_t n) { return (int)__sys_ret(syscall3(SYS_DISK_PARTITION, d, s, n)); } +int cervus_disk_read_raw(const char *d, uint64_t lba, uint64_t c, void *b) { return (int)__sys_ret(syscall4(SYS_DISK_READ_RAW, d, lba, c, b)); } +int cervus_disk_write_raw(const char *d, uint64_t lba, uint64_t c, const void *b){ return (int)__sys_ret(syscall4(SYS_DISK_WRITE_RAW, d, lba, c, b)); } +long cervus_disk_list_parts(cervus_part_info_t *o, int m) { return __sys_ret(syscall2(SYS_DISK_LIST_PARTS, o, m)); } +long cervus_disk_bios_install(const char *d, const void *sd, uint32_t ss) { return __sys_ret(syscall3(SYS_DISK_BIOS_INSTALL, d, sd, ss)); } + +long cervus_list_mounts(cervus_mount_info_t *o, int m) { return __sys_ret(syscall2(SYS_LIST_MOUNTS, o, m)); } +long cervus_statvfs(const char *p, cervus_statvfs_t *o) { return __sys_ret(syscall2(SYS_STATVFS, p, o)); } + +uint32_t cervus_ioport_read(uint16_t p, int w) { return (uint32_t)syscall2(SYS_IOPORT_READ, p, w); } +int cervus_ioport_write(uint16_t p, int w, uint32_t v) { return (int)__sys_ret(syscall3(SYS_IOPORT_WRITE, p, w, v)); } + +void __cervus_assert_fail(const char *expr, const char *file, int line, const char *func) +{ + printf("assertion failed: %s (%s:%d, %s)\n", + expr ? expr : "(null)", + file ? file : "(null)", + line, + func ? func : "(null)"); + syscall1(SYS_EXIT, 134); + for (;;) { } +} + +#define _CERVUS_FILT_MAX 128 +char *__cervus_filtered_argv[_CERVUS_FILT_MAX + 1]; + +int __cervus_filter_args(int argc, char **argv) +{ + int out = 0; + for (int i = 0; i < argc && out < _CERVUS_FILT_MAX; i++) { + const char *a = argv[i]; + if (i > 0 && a && a[0] == '-' && a[1] == '-' && + ((a[2]=='c' && a[3]=='w' && a[4]=='d' && a[5]=='=') || + (a[2]=='e' && a[3]=='n' && a[4]=='v' && a[5]==':'))) { + continue; + } + __cervus_filtered_argv[out++] = (char *)a; + } + __cervus_filtered_argv[out] = NULL; + return out; +} \ No newline at end of file diff --git a/usr/lib/libcervus/math/abs.c b/usr/lib/libcervus/math/abs.c new file mode 100644 index 0000000..a499d2c --- /dev/null +++ b/usr/lib/libcervus/math/abs.c @@ -0,0 +1,5 @@ +#include + +int abs(int n) { + return n >= 0 ? n : -n; +} diff --git a/usr/lib/libcervus/math/fabs.c b/usr/lib/libcervus/math/fabs.c new file mode 100644 index 0000000..16e12de --- /dev/null +++ b/usr/lib/libcervus/math/fabs.c @@ -0,0 +1,12 @@ +#include +#include + +double fabs(double x) { + union { + double d; + uint64_t i; + } u = { .d = x }; + + u.i &= 0x7FFFFFFFFFFFFFFFULL; + return u.d; +} \ No newline at end of file diff --git a/usr/lib/libcervus/math/isinf.c b/usr/lib/libcervus/math/isinf.c new file mode 100644 index 0000000..12a6a00 --- /dev/null +++ b/usr/lib/libcervus/math/isinf.c @@ -0,0 +1,14 @@ +#include +#include + +int isinf(double x) { + union { + double f; + uint64_t i; + } u = { .f = x }; + + uint64_t exp = (u.i >> 52) & 0x7FF; + uint64_t mantissa = u.i & 0xFFFFFFFFFFFFFULL; + + return (exp == 0x7FF) && (mantissa == 0); +} \ No newline at end of file diff --git a/usr/lib/libcervus/math/isnan.c b/usr/lib/libcervus/math/isnan.c new file mode 100644 index 0000000..66afe95 --- /dev/null +++ b/usr/lib/libcervus/math/isnan.c @@ -0,0 +1,14 @@ +#include +#include + +int isnan(double x) { + union { + double f; + uint64_t i; + } u = { .f = x }; + + uint64_t exp = (u.i >> 52) & 0x7FF; + uint64_t mantissa = u.i & 0xFFFFFFFFFFFFFULL; + + return (exp == 0x7FF) && (mantissa != 0); +} \ No newline at end of file diff --git a/usr/lib/libcervus/math/pow.c b/usr/lib/libcervus/math/pow.c new file mode 100644 index 0000000..2f0acc7 --- /dev/null +++ b/usr/lib/libcervus/math/pow.c @@ -0,0 +1,37 @@ +#include + +double pow(double x, double y) { + if (y == 0.0) return 1.0; + + if (y == (int)y) { + int n = (int)y; + double result = 1.0; + + if (n > 0) { + for (int i = 0; i < n; i++) { + result *= x; + } + } else { + for (int i = 0; i < -n; i++) { + result /= x; + } + } + + return result; + } + + if (x > 0.0) { + int int_part = (int)y; + double frac_part = y - int_part; + + double int_pow = pow(x, int_part); + + if (frac_part > 0.0) { + return int_pow * (1.0 + frac_part * (x - 1.0)); + } else { + return int_pow / (1.0 - frac_part * (x - 1.0)); + } + } + + return NAN; +} \ No newline at end of file diff --git a/usr/lib/libcervus/math/pow10.c b/usr/lib/libcervus/math/pow10.c new file mode 100644 index 0000000..389fd43 --- /dev/null +++ b/usr/lib/libcervus/math/pow10.c @@ -0,0 +1,17 @@ +#include + +double pow10(int n) { + double result = 1.0; + + if (n >= 0) { + for (int i = 0; i < n; i++) { + result *= 10.0; + } + } else { + for (int i = 0; i < -n; i++) { + result /= 10.0; + } + } + + return result; +} \ No newline at end of file diff --git a/usr/lib/libcervus/memory/memory.c b/usr/lib/libcervus/memory/memory.c new file mode 100644 index 0000000..c3cddf7 --- /dev/null +++ b/usr/lib/libcervus/memory/memory.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include + +extern int __cervus_errno; + +typedef struct __mblock { + size_t size; + size_t prev_size; +} __mblock_t; + +#define MB_HDR_SZ (sizeof(__mblock_t)) +#define MB_ALIGN 16 +#define MB_MIN_TOTAL 32 +#define MB_FREE_BIT ((size_t)1) +#define MB_SIZE(b) ((b)->size & ~MB_FREE_BIT) +#define MB_IS_FREE(b) (((b)->size & MB_FREE_BIT) != 0) +#define MB_USER(b) ((void *)((char *)(b) + MB_HDR_SZ)) +#define MB_FROM_USER(p) ((__mblock_t *)((char *)(p) - MB_HDR_SZ)) + +static __mblock_t *__heap_start = NULL; +static __mblock_t *__heap_end = NULL; + +static inline size_t __align_up(size_t n, size_t a) { + return (n + a - 1) & ~(a - 1); +} + +static inline __mblock_t *__mb_next(__mblock_t *b) { + return (__mblock_t *)((char *)b + MB_SIZE(b)); +} + +static inline __mblock_t *__mb_prev(__mblock_t *b) { + if (b->prev_size == 0) return NULL; + return (__mblock_t *)((char *)b - b->prev_size); +} + +static __mblock_t *__heap_grow(size_t need) +{ + size_t chunk = __align_up(need + MB_HDR_SZ, 65536); + + if (!__heap_start) { + void *base = sbrk((intptr_t)chunk); + if (base == (void *)-1) return NULL; + + uintptr_t addr = (uintptr_t)base; + uintptr_t aligned = (addr + MB_ALIGN - 1) & ~(uintptr_t)(MB_ALIGN - 1); + size_t lost = aligned - addr; + if (lost >= chunk - MB_MIN_TOTAL - MB_HDR_SZ) { + __cervus_errno = ENOMEM; + return NULL; + } + + __heap_start = (__mblock_t *)aligned; + size_t usable = chunk - lost; + + __mblock_t *first = __heap_start; + size_t first_sz = usable - MB_HDR_SZ; + first->size = first_sz | MB_FREE_BIT; + first->prev_size = 0; + + __heap_end = (__mblock_t *)((char *)first + first_sz); + __heap_end->size = 0; + __heap_end->prev_size = first_sz; + + return first; + } + + void *p = sbrk((intptr_t)chunk); + if (p == (void *)-1) return NULL; + if ((uintptr_t)p != (uintptr_t)__heap_end + MB_HDR_SZ) { + __cervus_errno = ENOMEM; + return NULL; + } + + __mblock_t *new_block = __heap_end; + new_block->size = chunk | MB_FREE_BIT; + + __mblock_t *new_end = (__mblock_t *)((char *)new_block + MB_SIZE(new_block)); + new_end->size = 0; + new_end->prev_size = MB_SIZE(new_block); + __heap_end = new_end; + + __mblock_t *prev = __mb_prev(new_block); + if (prev && MB_IS_FREE(prev)) { + size_t merged_sz = MB_SIZE(prev) + MB_SIZE(new_block); + prev->size = merged_sz | MB_FREE_BIT; + __heap_end->prev_size = merged_sz; + return prev; + } + return new_block; +} + +static void __mb_split(__mblock_t *b, size_t need) +{ + size_t cur = MB_SIZE(b); + if (cur < need + MB_MIN_TOTAL) { + b->size = cur; + return; + } + b->size = need; + + __mblock_t *rest = (__mblock_t *)((char *)b + need); + rest->size = (cur - need) | MB_FREE_BIT; + rest->prev_size = need; + + __mblock_t *after = __mb_next(rest); + if (after) after->prev_size = MB_SIZE(rest); +} + +void *malloc(size_t n) +{ + if (n == 0) n = 1; + size_t need = __align_up(n + MB_HDR_SZ, MB_ALIGN); + if (need < MB_MIN_TOTAL) need = MB_MIN_TOTAL; + + for (__mblock_t *b = __heap_start; b && b != __heap_end; b = __mb_next(b)) { + if (MB_IS_FREE(b) && MB_SIZE(b) >= need) { + __mb_split(b, need); + return MB_USER(b); + } + } + + __mblock_t *grown = __heap_grow(need); + if (!grown) return NULL; + if (MB_SIZE(grown) < need) { + __cervus_errno = ENOMEM; + return NULL; + } + __mb_split(grown, need); + return MB_USER(grown); +} + +void *calloc(size_t nm, size_t sz) +{ + size_t t = nm * sz; + if (nm && t / nm != sz) { __cervus_errno = ENOMEM; return NULL; } + void *p = malloc(t); + if (p) memset(p, 0, t); + return p; +} + +void free(void *p) +{ + if (!p) return; + __mblock_t *b = MB_FROM_USER(p); + b->size = MB_SIZE(b) | MB_FREE_BIT; + + __mblock_t *next = __mb_next(b); + if (next != __heap_end && MB_IS_FREE(next)) { + size_t merged = MB_SIZE(b) + MB_SIZE(next); + b->size = merged | MB_FREE_BIT; + __mblock_t *after = __mb_next(b); + if (after) after->prev_size = merged; + } + __mblock_t *prev = __mb_prev(b); + if (prev && MB_IS_FREE(prev)) { + size_t merged = MB_SIZE(prev) + MB_SIZE(b); + prev->size = merged | MB_FREE_BIT; + __mblock_t *after = __mb_next(prev); + if (after) after->prev_size = merged; + } +} + +void *realloc(void *p, size_t n) +{ + if (!p) return malloc(n); + if (n == 0) { free(p); return NULL; } + + __mblock_t *b = MB_FROM_USER(p); + size_t cur_total = MB_SIZE(b); + size_t cur_user = cur_total - MB_HDR_SZ; + size_t need = __align_up(n + MB_HDR_SZ, MB_ALIGN); + if (need < MB_MIN_TOTAL) need = MB_MIN_TOTAL; + + if (need <= cur_total) { + if (cur_total >= need + MB_MIN_TOTAL) { + b->size = need; + __mblock_t *rest = (__mblock_t *)((char *)b + need); + rest->size = (cur_total - need) | MB_FREE_BIT; + rest->prev_size = need; + __mblock_t *after = __mb_next(rest); + if (after) after->prev_size = MB_SIZE(rest); + if (after != __heap_end && MB_IS_FREE(after)) { + size_t merged = MB_SIZE(rest) + MB_SIZE(after); + rest->size = merged | MB_FREE_BIT; + __mblock_t *aft2 = __mb_next(rest); + if (aft2) aft2->prev_size = merged; + } + } + return p; + } + + __mblock_t *next = __mb_next(b); + if (next != __heap_end && MB_IS_FREE(next) && + cur_total + MB_SIZE(next) >= need) + { + size_t combined = cur_total + MB_SIZE(next); + b->size = combined; + __mblock_t *after = __mb_next(b); + if (after) after->prev_size = combined; + if (combined >= need + MB_MIN_TOTAL) { + b->size = need; + __mblock_t *rest = (__mblock_t *)((char *)b + need); + rest->size = (combined - need) | MB_FREE_BIT; + rest->prev_size = need; + __mblock_t *aft = __mb_next(rest); + if (aft) aft->prev_size = MB_SIZE(rest); + } + return p; + } + + void *np = malloc(n); + if (!np) return NULL; + memcpy(np, p, cur_user); + free(p); + return np; +} \ No newline at end of file diff --git a/usr/lib/libcervus/setjmp.asm b/usr/lib/libcervus/setjmp.asm new file mode 100644 index 0000000..e47db57 --- /dev/null +++ b/usr/lib/libcervus/setjmp.asm @@ -0,0 +1,38 @@ +BITS 64 +DEFAULT REL + +section .text + +global setjmp +global longjmp + +setjmp: + mov [rdi + 0x00], rbx + mov [rdi + 0x08], rbp + mov [rdi + 0x10], r12 + mov [rdi + 0x18], r13 + mov [rdi + 0x20], r14 + mov [rdi + 0x28], r15 + lea rax, [rsp + 8] + mov [rdi + 0x30], rax + mov rax, [rsp] + mov [rdi + 0x38], rax + xor eax, eax + ret + +longjmp: + mov rbx, [rdi + 0x00] + mov rbp, [rdi + 0x08] + mov r12, [rdi + 0x10] + mov r13, [rdi + 0x18] + mov r14, [rdi + 0x20] + mov r15, [rdi + 0x28] + mov rsp, [rdi + 0x30] + mov eax, esi + test eax, eax + jnz .done + mov eax, 1 +.done: + jmp qword [rdi + 0x38] + +section .note.GNU-stack noalloc noexec nowrite progbits \ No newline at end of file diff --git a/usr/lib/libcervus/signal/signal.c b/usr/lib/libcervus/signal/signal.c new file mode 100644 index 0000000..d114464 --- /dev/null +++ b/usr/lib/libcervus/signal/signal.c @@ -0,0 +1,39 @@ +#include +#include + +sighandler_t signal(int signum, sighandler_t handler) +{ + (void)signum; + (void)handler; + return SIG_DFL; +} + +int raise(int sig) +{ + (void)sig; + return 0; +} + +int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) +{ + (void)sig; (void)act; + if (oldact) { + oldact->sa_handler = SIG_DFL; + memset(&oldact->sa_mask, 0, sizeof(oldact->sa_mask)); + oldact->sa_flags = 0; + } + return 0; +} + +int sigemptyset(sigset_t *set) { if (set) memset(set, 0, sizeof(*set)); return 0; } +int sigfillset(sigset_t *set) { if (set) memset(set, 0xFF, sizeof(*set)); return 0; } +int sigaddset(sigset_t *set, int sig) { (void)set; (void)sig; return 0; } +int sigdelset(sigset_t *set, int sig) { (void)set; (void)sig; return 0; } +int sigismember(const sigset_t *set, int sig) { (void)set; (void)sig; return 0; } + +int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) +{ + (void)how; (void)set; + if (oldset) memset(oldset, 0, sizeof(*oldset)); + return 0; +} \ No newline at end of file diff --git a/usr/lib/libcervus/stdio/scanf.c b/usr/lib/libcervus/stdio/scanf.c new file mode 100644 index 0000000..fdb15de --- /dev/null +++ b/usr/lib/libcervus/stdio/scanf.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include + +extern uint64_t __cervus_strtod_bits(const char *s, char **endptr); + +static int __is_space(int c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +int vsscanf(const char *str, const char *fmt, va_list ap) +{ + if (!str || !fmt) return -1; + const char *s = str; + const char *f = fmt; + int matched = 0; + int saw_anything = 0; + + while (*f) { + if (__is_space((unsigned char)*f)) { + while (__is_space((unsigned char)*s)) s++; + f++; + continue; + } + if (*f != '%') { + if (*s != *f) return saw_anything ? matched : (matched ? matched : -1); + s++; f++; + continue; + } + f++; + + int suppress = 0; + if (*f == '*') { suppress = 1; f++; } + + int width = 0; + int has_width = 0; + while (*f >= '0' && *f <= '9') { + width = width * 10 + (*f - '0'); + has_width = 1; + f++; + } + if (!has_width) width = 0; + + int len_mod = 0; + if (*f == 'h') { f++; if (*f == 'h') { f++; len_mod = 2; } else len_mod = 1; } + else if (*f == 'l') { f++; if (*f == 'l') { f++; len_mod = 4; } else len_mod = 3; } + else if (*f == 'z') { f++; len_mod = 5; } + else if (*f == 'j' || *f == 't') { f++; len_mod = 4; } + + char conv = *f; + if (!conv) break; + f++; + + if (conv == '%') { + if (*s != '%') return matched ? matched : -1; + s++; + continue; + } + + if (conv == 'n') { + if (!suppress) { + int read = (int)(s - str); + if (len_mod == 3 || len_mod == 5) *va_arg(ap, long *) = read; + else if (len_mod == 4) *va_arg(ap, long long *) = read; + else *va_arg(ap, int *) = read; + } + continue; + } + + if (conv == 'c') { + int w = has_width ? width : 1; + char *out = suppress ? NULL : va_arg(ap, char *); + for (int i = 0; i < w; i++) { + if (!*s) return matched ? matched : -1; + if (out) out[i] = *s; + s++; + } + if (!suppress) matched++; + saw_anything = 1; + continue; + } + + if (conv != '[') { + while (__is_space((unsigned char)*s)) s++; + } + + if (conv == 's') { + int w = has_width ? width : 0x7FFFFFFF; + char *out = suppress ? NULL : va_arg(ap, char *); + int wrote = 0; + if (!*s) return matched ? matched : -1; + while (*s && !__is_space((unsigned char)*s) && wrote < w) { + if (out) out[wrote] = *s; + s++; wrote++; + } + if (out) out[wrote] = '\0'; + if (!suppress) matched++; + saw_anything = 1; + continue; + } + + if (conv == '[') { + int negate = 0; + if (*f == '^') { negate = 1; f++; } + char set[256]; + memset(set, 0, sizeof(set)); + if (*f == ']') { set[(unsigned char)']'] = 1; f++; } + while (*f && *f != ']') { set[(unsigned char)*f] = 1; f++; } + if (*f == ']') f++; + int w = has_width ? width : 0x7FFFFFFF; + char *out = suppress ? NULL : va_arg(ap, char *); + int wrote = 0; + while (*s && wrote < w) { + int in = set[(unsigned char)*s] != 0; + if (negate) in = !in; + if (!in) break; + if (out) out[wrote] = *s; + s++; wrote++; + } + if (out) out[wrote] = '\0'; + if (wrote == 0) return matched ? matched : -1; + if (!suppress) matched++; + saw_anything = 1; + continue; + } + + if (conv == 'd' || conv == 'i' || conv == 'u' || + conv == 'o' || conv == 'x' || conv == 'X') + { + int base = 10; + int allow_neg = (conv == 'd' || conv == 'i'); + if (conv == 'o') base = 8; + else if (conv == 'x' || conv == 'X') base = 16; + + const char *start = s; + int neg = 0; + if (allow_neg && (*s == '+' || *s == '-')) { neg = (*s == '-'); s++; } + else if (conv == 'u' && (*s == '+')) s++; + if (conv == 'i' || conv == 'x' || conv == 'X') { + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + base = 16; s += 2; + } else if (conv == 'i' && s[0] == '0' && s[1] >= '0' && s[1] <= '7') { + base = 8; s++; + } + } + unsigned long long val = 0; + int got_digit = 0; + int wleft = has_width ? width - (int)(s - start) : 0x7FFFFFFF; + while (*s && wleft > 0) { + int d = -1; + if (*s >= '0' && *s <= '9') d = *s - '0'; + else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; + if (d < 0 || d >= base) break; + val = val * base + (unsigned)d; + s++; wleft--; + got_digit = 1; + } + if (!got_digit) return matched ? matched : -1; + if (!suppress) { + if (allow_neg && neg) { + long long sv = -(long long)val; + if (len_mod == 4) *va_arg(ap, long long *) = sv; + else if (len_mod == 3 || len_mod == 5) *va_arg(ap, long *) = (long)sv; + else if (len_mod == 1) *va_arg(ap, short *) = (short)sv; + else if (len_mod == 2) *va_arg(ap, signed char *) = (signed char)sv; + else *va_arg(ap, int *) = (int)sv; + } else { + if (len_mod == 4) *va_arg(ap, unsigned long long *) = val; + else if (len_mod == 3 || len_mod == 5) *va_arg(ap, unsigned long *) = (unsigned long)val; + else if (len_mod == 1) *va_arg(ap, unsigned short *) = (unsigned short)val; + else if (len_mod == 2) *va_arg(ap, unsigned char *) = (unsigned char)val; + else *va_arg(ap, unsigned int *) = (unsigned int)val; + } + matched++; + } + saw_anything = 1; + continue; + } + + if (conv == 'f' || conv == 'e' || conv == 'g' || + conv == 'E' || conv == 'G' || conv == 'a' || conv == 'A') + { + char *endp; + uint64_t bits = __cervus_strtod_bits(s, &endp); + if (endp == s) return matched ? matched : -1; + if (!suppress) { + if (len_mod == 3) { + double v; + __builtin_memcpy(&v, &bits, sizeof(double)); + *va_arg(ap, double *) = v; + } else { + double dv; + __builtin_memcpy(&dv, &bits, sizeof(double)); + *va_arg(ap, float *) = (float)dv; + } + matched++; + } + s = endp; + saw_anything = 1; + continue; + } + + return matched ? matched : -1; + } + return matched; +} + +int sscanf(const char *str, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int n = vsscanf(str, fmt, ap); + va_end(ap); + return n; +} + +int vscanf(const char *fmt, va_list ap) +{ + char buf[1024]; + if (!fgets(buf, (int)sizeof(buf), stdin)) return EOF; + return vsscanf(buf, fmt, ap); +} + +int scanf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int n = vscanf(fmt, ap); + va_end(ap); + return n; +} + +int vfscanf(FILE *stream, const char *fmt, va_list ap) +{ + char buf[1024]; + if (!fgets(buf, (int)sizeof(buf), stream)) return EOF; + return vsscanf(buf, fmt, ap); +} + +int fscanf(FILE *stream, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int n = vfscanf(stream, fmt, ap); + va_end(ap); + return n; +} \ No newline at end of file diff --git a/usr/lib/libcervus/stdio/stdio.c b/usr/lib/libcervus/stdio/stdio.c new file mode 100644 index 0000000..97d023b --- /dev/null +++ b/usr/lib/libcervus/stdio/stdio.c @@ -0,0 +1,430 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int __cervus_errno; + +struct __cervus_FILE { + int fd; + int eof; + int err; + int flags; + char *buf; + size_t buf_size; + size_t buf_pos; +}; + +static struct __cervus_FILE __stdin_s = { 0, 0, 0, 0, NULL, 0, 0 }; +static struct __cervus_FILE __stdout_s = { 1, 0, 0, 0, NULL, 0, 0 }; +static struct __cervus_FILE __stderr_s = { 2, 0, 0, 0, NULL, 0, 0 }; + +FILE *stdin = &__stdin_s; +FILE *stdout = &__stdout_s; +FILE *stderr = &__stderr_s; + +int fileno(FILE *s) { return s ? s->fd : -1; } +int feof(FILE *s) { return s ? s->eof : 1; } +int ferror(FILE *s) { return s ? s->err : 1; } +void clearerr(FILE *s) { if (s) { s->eof = 0; s->err = 0; } } + +FILE *fopen(const char *path, const char *mode) +{ + if (!path || !mode) return NULL; + int flags = 0; + int has_plus = 0; + for (const char *m = mode + 1; *m; m++) if (*m == '+') has_plus = 1; + switch (mode[0]) { + case 'r': flags = has_plus ? O_RDWR : O_RDONLY; break; + case 'w': flags = (has_plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC; break; + case 'a': flags = (has_plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND; break; + default: return NULL; + } + int fd = open(path, flags, 0644); + if (fd < 0) return NULL; + FILE *f = (FILE *)malloc(sizeof(FILE)); + if (!f) { close(fd); return NULL; } + f->fd = fd; + f->eof = 0; + f->err = 0; + f->flags = 1; + f->buf = NULL; + f->buf_size = 0; + f->buf_pos = 0; + return f; +} + +int fclose(FILE *s) +{ + if (!s) return EOF; + int fd = s->fd; + int owned = s->flags & 1; + close(fd); + if (owned) free(s); + return 0; +} + +int fflush(FILE *s) { (void)s; return 0; } + +size_t fread(void *buf, size_t size, size_t nmemb, FILE *s) +{ + if (!s || size == 0 || nmemb == 0) return 0; + size_t total = size * nmemb; + size_t got = 0; + while (got < total) { + ssize_t r = read(s->fd, (char *)buf + got, total - got); + if (r < 0) { s->err = 1; break; } + if (r == 0) { s->eof = 1; break; } + got += (size_t)r; + } + return got / size; +} + +size_t fwrite(const void *buf, size_t size, size_t nmemb, FILE *s) +{ + if (!s || size == 0 || nmemb == 0) return 0; + size_t total = size * nmemb; + size_t sent = 0; + while (sent < total) { + ssize_t w = write(s->fd, (const char *)buf + sent, total - sent); + if (w < 0) { s->err = 1; break; } + if (w == 0) break; + sent += (size_t)w; + } + return sent / size; +} + +int fseek(FILE *s, long off, int whence) +{ + if (!s) return -1; + off_t r = lseek(s->fd, (off_t)off, whence); + if (r == (off_t)-1) { s->err = 1; return -1; } + s->eof = 0; + return 0; +} + +long ftell(FILE *s) +{ + if (!s) return -1; + return (long)lseek(s->fd, 0, SEEK_CUR); +} + +int fputc(int c, FILE *s) +{ + unsigned char ch = (unsigned char)c; + if (fwrite(&ch, 1, 1, s) != 1) return EOF; + return (int)ch; +} + +int fgetc(FILE *s) +{ + unsigned char ch; + if (fread(&ch, 1, 1, s) != 1) return EOF; + return (int)ch; +} + +int fputs(const char *str, FILE *s) +{ + if (!str) return EOF; + size_t n = strlen(str); + if (fwrite(str, 1, n, s) != n) return EOF; + return 0; +} + +char *fgets(char *str, int n, FILE *s) +{ + if (!str || n <= 0 || !s) return NULL; + int i = 0; + while (i < n - 1) { + int c = fgetc(s); + if (c == EOF) { + if (i == 0) return NULL; + break; + } + str[i++] = (char)c; + if (c == '\n') break; + } + str[i] = '\0'; + return str; +} + +int putchar(int c) { return fputc(c, stdout); } +int getchar(void) { return fgetc(stdin); } + +int puts(const char *s) +{ + if (fputs(s, stdout) == EOF) return EOF; + if (fputc('\n', stdout) == EOF) return EOF; + return 1; +} + +static void __u64_to_str(uint64_t v, char *out, int base, int upper) +{ + char tmp[32]; + int i = 0; + const char *digs = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + if (v == 0) { out[0] = '0'; out[1] = 0; return; } + while (v) { tmp[i++] = digs[v % (uint64_t)base]; v /= (uint64_t)base; } + int j = 0; + while (i > 0) out[j++] = tmp[--i]; + out[j] = 0; +} + +int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap) +{ + size_t pos = 0; +#define __PUT(s, n) do { \ + size_t __n = (n); const char *__s = (s); \ + for (size_t __i = 0; __i < __n; __i++) { \ + if (pos + 1 < sz) buf[pos] = __s[__i]; \ + pos++; \ + } \ +} while (0) + + while (*fmt) { + if (*fmt != '%') { __PUT(fmt, 1); fmt++; continue; } + fmt++; + int pad_zero = 0, left_align = 0, plus_flag = 0; + while (*fmt == '0' || *fmt == '-' || *fmt == '+' || *fmt == ' ' || *fmt == '#') { + if (*fmt == '0') pad_zero = 1; + else if (*fmt == '-') left_align = 1; + else if (*fmt == '+') plus_flag = 1; + fmt++; + } + int width = 0; + if (*fmt == '*') { width = va_arg(ap, int); if (width < 0) { left_align = 1; width = -width; } fmt++; } + else while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; } + int prec = -1; + if (*fmt == '.') { + fmt++; + prec = 0; + if (*fmt == '*') { prec = va_arg(ap, int); if (prec < 0) prec = 0; fmt++; } + else while (*fmt >= '0' && *fmt <= '9') { prec = prec * 10 + (*fmt - '0'); fmt++; } + } + int is_long = 0, is_size_t = 0; + while (*fmt == 'l') { is_long++; fmt++; } + if (*fmt == 'z') { is_size_t = 1; fmt++; } + if (*fmt == 'h') { fmt++; } + + char nb[40]; + switch (*fmt) { + case 's': { + const char *s = va_arg(ap, const char *); + if (!s) s = "(null)"; + size_t l = strlen(s); + if (prec >= 0 && (size_t)prec < l) l = (size_t)prec; + int pad = (int)(width > (int)l ? width - (int)l : 0); + if (!left_align) for (int i = 0; i < pad; i++) __PUT(" ", 1); + __PUT(s, l); + if (left_align) for (int i = 0; i < pad; i++) __PUT(" ", 1); + break; + } + case 'd': case 'i': { + int64_t v; + if (is_long >= 2) v = va_arg(ap, long long); + else if (is_long) v = va_arg(ap, long); + else if (is_size_t) v = (int64_t)va_arg(ap, size_t); + else v = va_arg(ap, int); + int neg = v < 0; + uint64_t u = neg ? (uint64_t)(-v) : (uint64_t)v; + __u64_to_str(u, nb, 10, 0); + int numlen = (int)strlen(nb) + (neg || plus_flag ? 1 : 0); + int pad = width > numlen ? width - numlen : 0; + if (!left_align && !pad_zero) for (int i = 0; i < pad; i++) __PUT(" ", 1); + if (neg) __PUT("-", 1); + else if (plus_flag) __PUT("+", 1); + if (!left_align && pad_zero) for (int i = 0; i < pad; i++) __PUT("0", 1); + __PUT(nb, strlen(nb)); + if (left_align) for (int i = 0; i < pad; i++) __PUT(" ", 1); + break; + } + case 'u': { + uint64_t v; + if (is_long >= 2) v = va_arg(ap, unsigned long long); + else if (is_long) v = va_arg(ap, unsigned long); + else if (is_size_t) v = va_arg(ap, size_t); + else v = va_arg(ap, unsigned); + __u64_to_str(v, nb, 10, 0); + int numlen = (int)strlen(nb); + int pad = width > numlen ? width - numlen : 0; + if (!left_align) for (int i = 0; i < pad; i++) __PUT(pad_zero ? "0" : " ", 1); + __PUT(nb, strlen(nb)); + if (left_align) for (int i = 0; i < pad; i++) __PUT(" ", 1); + break; + } + case 'x': case 'X': { + uint64_t v; + if (is_long >= 2) v = va_arg(ap, unsigned long long); + else if (is_long) v = va_arg(ap, unsigned long); + else if (is_size_t) v = va_arg(ap, size_t); + else v = va_arg(ap, unsigned); + __u64_to_str(v, nb, 16, *fmt == 'X'); + int numlen = (int)strlen(nb); + int pad = width > numlen ? width - numlen : 0; + if (!left_align) for (int i = 0; i < pad; i++) __PUT(pad_zero ? "0" : " ", 1); + __PUT(nb, strlen(nb)); + if (left_align) for (int i = 0; i < pad; i++) __PUT(" ", 1); + break; + } + case 'o': { + uint64_t v; + if (is_long >= 2) v = va_arg(ap, unsigned long long); + else if (is_long) v = va_arg(ap, unsigned long); + else v = va_arg(ap, unsigned); + __u64_to_str(v, nb, 8, 0); + __PUT(nb, strlen(nb)); + break; + } + case 'p': { + uint64_t v = (uint64_t)(uintptr_t)va_arg(ap, void *); + __PUT("0x", 2); + __u64_to_str(v, nb, 16, 0); + __PUT(nb, strlen(nb)); + break; + } + case 'c': { + char c = (char)va_arg(ap, int); + __PUT(&c, 1); + break; + } + case '%': __PUT("%", 1); break; + default: { + char c = *fmt; + __PUT("%", 1); + __PUT(&c, 1); + break; + } + } + fmt++; + } + if (sz > 0) buf[pos < sz ? pos : sz - 1] = '\0'; +#undef __PUT + return (int)pos; +} + +int snprintf(char *buf, size_t sz, const char *fmt, ...) +{ + va_list ap; va_start(ap, fmt); + int n = vsnprintf(buf, sz, fmt, ap); + va_end(ap); + return n; +} + +int sprintf(char *buf, const char *fmt, ...) +{ + va_list ap; va_start(ap, fmt); + int n = vsnprintf(buf, (size_t)-1, fmt, ap); + va_end(ap); + return n; +} + +int vsprintf(char *buf, const char *fmt, va_list ap) +{ + return vsnprintf(buf, (size_t)-1, fmt, ap); +} + +int vfprintf(FILE *s, const char *fmt, va_list ap) +{ + char small[512]; + va_list ap2; + va_copy(ap2, ap); + int needed = vsnprintf(small, sizeof(small), fmt, ap2); + va_end(ap2); + if (needed < (int)sizeof(small)) { + fwrite(small, 1, (size_t)needed, s); + return needed; + } + char *big = (char *)malloc((size_t)needed + 1); + if (!big) { + fwrite(small, 1, sizeof(small) - 1, s); + return (int)sizeof(small) - 1; + } + vsnprintf(big, (size_t)needed + 1, fmt, ap); + fwrite(big, 1, (size_t)needed, s); + free(big); + return needed; +} + +int fprintf(FILE *s, const char *fmt, ...) +{ + va_list ap; va_start(ap, fmt); + int n = vfprintf(s, fmt, ap); + va_end(ap); + return n; +} + +int vprintf(const char *fmt, va_list ap) { return vfprintf(stdout, fmt, ap); } + +int printf(const char *fmt, ...) +{ + va_list ap; va_start(ap, fmt); + int n = vfprintf(stdout, fmt, ap); + va_end(ap); + return n; +} + +void perror(const char *msg) +{ + if (msg && *msg) { fputs(msg, stderr); fputs(": ", stderr); } + fputs(strerror(__cervus_errno), stderr); + fputc('\n', stderr); +} + +int remove(const char *path) { return unlink(path); } + +int mkstemp(char *template) +{ + if (!template) { __cervus_errno = EINVAL; return -1; } + size_t len = strlen(template); + if (len < 6) { __cervus_errno = EINVAL; return -1; } + char *suf = template + len - 6; + for (int i = 0; i < 6; i++) { + if (suf[i] != 'X') { __cervus_errno = EINVAL; return -1; } + } + static uint64_t __mkstemp_seq = 0; + uint64_t pid = (uint64_t)getpid(); + for (int attempt = 0; attempt < 100; attempt++) { + uint64_t seed = (cervus_uptime_ns() ^ (pid << 32)) + (__mkstemp_seq++); + const char *alpha = "0123456789abcdefghijklmnopqrstuvwxyz"; + for (int i = 0; i < 6; i++) { + suf[i] = alpha[seed % 36]; + seed /= 36; + } + struct stat st; + if (stat(template, &st) == 0) continue; + int fd = open(template, O_RDWR | O_CREAT, 0600); + if (fd >= 0) return fd; + } + __cervus_errno = EEXIST; + return -1; +} + +FILE *tmpfile(void) +{ + char tmpl[64]; + strcpy(tmpl, "/mnt/tmp/tmpXXXXXX"); + int fd = mkstemp(tmpl); + if (fd < 0) { + strcpy(tmpl, "/tmp/tmpXXXXXX"); + fd = mkstemp(tmpl); + if (fd < 0) return NULL; + } + unlink(tmpl); + FILE *f = (FILE *)malloc(sizeof(FILE)); + if (!f) { close(fd); return NULL; } + f->fd = fd; + f->eof = 0; + f->err = 0; + f->flags = 1; + f->buf = NULL; + f->buf_size = 0; + f->buf_pos = 0; + return f; +} \ No newline at end of file diff --git a/usr/lib/libcervus/stdlib/stdlib.c b/usr/lib/libcervus/stdlib/stdlib.c new file mode 100644 index 0000000..aa61b45 --- /dev/null +++ b/usr/lib/libcervus/stdlib/stdlib.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include + +extern int __cervus_errno; +extern int __cervus_argc; +extern char **__cervus_argv; + +int abs(int x) { return x < 0 ? -x : x; } +long labs(long x) { return x < 0 ? -x : x; } +long long llabs(long long x){ return x < 0 ? -x : x; } + +static long long __parse_signed(const char *s, char **end, int base, int is_unsigned) +{ + while (isspace((unsigned char)*s)) s++; + int neg = 0; + if (*s == '-') { neg = 1; s++; } + else if (*s == '+') s++; + if ((base == 0 || base == 16) && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; base = 16; + } else if (base == 0 && *s == '0') { + s++; base = 8; + } else if (base == 0) { + base = 10; + } + unsigned long long v = 0; + while (*s) { + int d; + if (isdigit((unsigned char)*s)) d = *s - '0'; + else if (*s >= 'a' && *s <= 'z') d = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'Z') d = *s - 'A' + 10; + else break; + if (d >= base) break; + v = v * (unsigned long long)base + (unsigned long long)d; + s++; + } + if (end) *end = (char *)s; + if (is_unsigned) return (long long)v; + return neg ? -(long long)v : (long long)v; +} + +long strtol(const char *s, char **e, int b) { return (long)__parse_signed(s, e, b, 0); } +long long strtoll(const char *s, char **e, int b) { return __parse_signed(s, e, b, 0); } +unsigned long strtoul(const char *s, char **e, int b) { return (unsigned long)__parse_signed(s, e, b, 1); } +unsigned long long strtoull(const char *s, char **e, int b) { return (unsigned long long)__parse_signed(s, e, b, 1); } + +int atoi(const char *s) { return (int)strtol(s, NULL, 10); } +long atol(const char *s) { return strtol(s, NULL, 10); } +long long atoll(const char *s) { return strtoll(s, NULL, 10); } + +static unsigned long __rand_state = 1; +int rand(void) { __rand_state = __rand_state * 1103515245UL + 12345UL; return (int)((__rand_state >> 16) & 0x7FFF); } +void srand(unsigned int seed) { __rand_state = seed; } + +static void __qswap(void *a, void *b, size_t sz) +{ + unsigned char tmp; + unsigned char *pa = (unsigned char *)a; + unsigned char *pb = (unsigned char *)b; + while (sz--) { tmp = *pa; *pa++ = *pb; *pb++ = tmp; } +} + +void qsort(void *base, size_t nmemb, size_t sz, int (*cmp)(const void *, const void *)) +{ + if (nmemb < 2) return; + unsigned char *arr = (unsigned char *)base; + unsigned char *pivot = arr + (nmemb - 1) * sz; + size_t i = 0; + for (size_t j = 0; j < nmemb - 1; j++) { + if (cmp(arr + j * sz, pivot) <= 0) { + if (i != j) __qswap(arr + i * sz, arr + j * sz, sz); + i++; + } + } + __qswap(arr + i * sz, pivot, sz); + qsort(arr, i, sz, cmp); + qsort(arr + (i + 1) * sz, nmemb - i - 1, sz, cmp); +} + +char *getenv(const char *name) +{ + if (!name) return NULL; + size_t nl = strlen(name); + for (int i = 1; i < __cervus_argc; i++) { + const char *a = __cervus_argv[i]; + if (a && a[0] == '-' && a[1] == '-' && + a[2] == 'e' && a[3] == 'n' && a[4] == 'v' && a[5] == ':') { + const char *kv = a + 6; + if (strncmp(kv, name, nl) == 0 && kv[nl] == '=') + return (char *)(kv + nl + 1); + } + } + return NULL; +} + +int system(const char *cmd) +{ + (void)cmd; + __cervus_errno = ENOSYS; + return -1; +} + +#define ATEXIT_MAX 32 +static void (*__atexit_fns[ATEXIT_MAX])(void); +static int __atexit_cnt = 0; + +int atexit(void (*fn)(void)) +{ + if (__atexit_cnt >= ATEXIT_MAX) return -1; + __atexit_fns[__atexit_cnt++] = fn; + return 0; +} + +int fflush(FILE *stream); + +void exit(int status) +{ + while (__atexit_cnt > 0) { + __atexit_cnt--; + if (__atexit_fns[__atexit_cnt]) __atexit_fns[__atexit_cnt](); + } + extern FILE *stdout; + extern FILE *stderr; + fflush(stdout); + fflush(stderr); + syscall1(SYS_EXIT, status); + __builtin_unreachable(); +} + +void abort(void) +{ + syscall1(SYS_EXIT, 134); + __builtin_unreachable(); +} \ No newline at end of file diff --git a/usr/lib/libcervus/stdlib/strtod.c b/usr/lib/libcervus/stdlib/strtod.c new file mode 100644 index 0000000..c1b0dfc --- /dev/null +++ b/usr/lib/libcervus/stdlib/strtod.c @@ -0,0 +1,170 @@ +#include +#include + +uint64_t __cervus_strtod_bits(const char *s, char **endptr) +{ + if (!s) { + if (endptr) *endptr = (char *)s; + return 0; + } + const char *p = s; + while (*p == ' ' || *p == '\t' || *p == '\n' || + *p == '\r' || *p == '\f' || *p == '\v') p++; + + int sign = 0; + if (*p == '+') p++; + else if (*p == '-') { sign = 1; p++; } + + if ((p[0] == 'i' || p[0] == 'I') && + (p[1] == 'n' || p[1] == 'N') && + (p[2] == 'f' || p[2] == 'F')) { + p += 3; + if ((p[0] == 'i' || p[0] == 'I') && + (p[1] == 'n' || p[1] == 'N') && + (p[2] == 'i' || p[2] == 'I') && + (p[3] == 't' || p[3] == 'T') && + (p[4] == 'y' || p[4] == 'Y')) p += 5; + if (endptr) *endptr = (char *)p; + return ((uint64_t)sign << 63) | 0x7FF0000000000000ULL; + } + if ((p[0] == 'n' || p[0] == 'N') && + (p[1] == 'a' || p[1] == 'A') && + (p[2] == 'n' || p[2] == 'N')) { + p += 3; + if (endptr) *endptr = (char *)p; + return 0x7FF8000000000000ULL; + } + + uint64_t mant = 0; + int dec_exp = 0; + int seen_digit = 0; + + while (*p >= '0' && *p <= '9') { + seen_digit = 1; + if (mant <= (UINT64_MAX - 9) / 10) { + mant = mant * 10 + (uint64_t)(*p - '0'); + } else { + dec_exp++; + } + p++; + } + if (*p == '.') { + p++; + while (*p >= '0' && *p <= '9') { + seen_digit = 1; + if (mant <= (UINT64_MAX - 9) / 10) { + mant = mant * 10 + (uint64_t)(*p - '0'); + dec_exp--; + } + p++; + } + } + if (!seen_digit) { + if (endptr) *endptr = (char *)s; + return 0; + } + + if (*p == 'e' || *p == 'E') { + const char *ep = p + 1; + int esign = 0; + if (*ep == '+') ep++; + else if (*ep == '-') { esign = 1; ep++; } + if (*ep >= '0' && *ep <= '9') { + int eval = 0; + while (*ep >= '0' && *ep <= '9') { + if (eval < 10000) eval = eval * 10 + (*ep - '0'); + ep++; + } + dec_exp += esign ? -eval : eval; + p = ep; + } + } + if (endptr) *endptr = (char *)p; + + if (mant == 0) { + return (uint64_t)sign << 63; + } + + int bin_exp = 0; + while ((mant >> 63) == 0) { + mant <<= 1; + bin_exp--; + } + + while (dec_exp > 0) { + uint64_t a_hi = mant >> 32; + uint64_t a_lo = mant & 0xFFFFFFFFULL; + uint64_t p_lo = a_lo * 10; + uint64_t p_hi = a_hi * 10; + uint64_t mid_carry = p_lo >> 32; + uint64_t lo = ((p_hi + mid_carry) << 32) | (p_lo & 0xFFFFFFFFULL); + uint64_t hi = (p_hi + mid_carry) >> 32; + + while (hi != 0) { + lo = (lo >> 1) | (hi << 63); + hi >>= 1; + bin_exp++; + } + mant = lo; + while ((mant >> 63) == 0) { + mant <<= 1; + bin_exp--; + } + dec_exp--; + } + while (dec_exp < 0) { + mant = mant / 10; + if (mant == 0) break; + while ((mant >> 63) == 0) { + mant <<= 1; + bin_exp--; + } + dec_exp++; + } + + int ieee_exp = bin_exp + 63 + 1023; + uint64_t no_implicit = mant & 0x7FFFFFFFFFFFFFFFULL; + uint64_t round_bit = (no_implicit >> 10) & 1; + uint64_t sticky = (no_implicit & 0x3FFULL) ? 1 : 0; + uint64_t frac = no_implicit >> 11; + if (round_bit && (sticky || (frac & 1))) { + frac++; + if (frac == (1ULL << 52)) { + frac = 0; + ieee_exp++; + } + } + + if (ieee_exp >= 0x7FF) { + return ((uint64_t)sign << 63) | 0x7FF0000000000000ULL; + } + if (ieee_exp <= 0) { + return (uint64_t)sign << 63; + } + return ((uint64_t)sign << 63) | + ((uint64_t)ieee_exp << 52) | + (frac & 0xFFFFFFFFFFFFFULL); +} + +double strtod(const char *s, char **endptr) +{ + uint64_t bits = __cervus_strtod_bits(s, endptr); + double result; + __builtin_memcpy(&result, &bits, sizeof(double)); + return result; +} + +float strtof(const char *s, char **endptr) +{ + return (float)strtod(s, endptr); +} + +long double strtold(const char *s, char **endptr) +{ + return (long double)strtod(s, endptr); +} + +double atof(const char *s) +{ + return strtod(s, NULL); +} \ No newline at end of file diff --git a/usr/lib/libcervus/string/string.c b/usr/lib/libcervus/string/string.c new file mode 100644 index 0000000..0c71656 --- /dev/null +++ b/usr/lib/libcervus/string/string.c @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include + +void *memset(void *d, int c, size_t n) +{ + unsigned char *p = (unsigned char *)d; + while (n--) *p++ = (unsigned char)c; + return d; +} + +void *memcpy(void *d, const void *s, size_t n) +{ + unsigned char *dd = (unsigned char *)d; + const unsigned char *ss = (const unsigned char *)s; + while (n--) *dd++ = *ss++; + return d; +} + +void *memmove(void *d, const void *s, size_t n) +{ + unsigned char *dd = (unsigned char *)d; + const unsigned char *ss = (const unsigned char *)s; + if (dd < ss) { while (n--) *dd++ = *ss++; } + else { dd += n; ss += n; while (n--) *--dd = *--ss; } + return d; +} + +int memcmp(const void *a, const void *b, size_t n) +{ + const unsigned char *x = (const unsigned char *)a; + const unsigned char *y = (const unsigned char *)b; + for (size_t i = 0; i < n; i++) + if (x[i] != y[i]) return x[i] - y[i]; + return 0; +} + +void *memchr(const void *s, int c, size_t n) +{ + const unsigned char *p = (const unsigned char *)s; + while (n--) { if (*p == (unsigned char)c) return (void *)p; p++; } + return NULL; +} + +void *memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen) +{ + if (nlen == 0) return (void *)haystack; + if (hlen < nlen) return NULL; + const unsigned char *h = (const unsigned char *)haystack; + const unsigned char *n = (const unsigned char *)needle; + size_t last = hlen - nlen; + for (size_t i = 0; i <= last; i++) { + if (h[i] == n[0] && memcmp(h + i, n, nlen) == 0) + return (void *)(h + i); + } + return NULL; +} + +size_t strlen(const char *s) { size_t n = 0; while (s[n]) n++; return n; } + +size_t strnlen(const char *s, size_t max) +{ + size_t n = 0; + while (n < max && s[n]) n++; + return n; +} + +int strcmp(const char *a, const char *b) +{ + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +int strncmp(const char *a, const char *b, size_t n) +{ + for (size_t i = 0; i < n; i++) { + if (a[i] != b[i]) return (unsigned char)a[i] - (unsigned char)b[i]; + if (!a[i]) return 0; + } + return 0; +} + +int strcasecmp(const char *a, const char *b) +{ + while (*a && tolower((unsigned char)*a) == tolower((unsigned char)*b)) { + a++; b++; + } + return tolower((unsigned char)*a) - tolower((unsigned char)*b); +} + +int strncasecmp(const char *a, const char *b, size_t n) +{ + for (size_t i = 0; i < n; i++) { + int ca = tolower((unsigned char)a[i]); + int cb = tolower((unsigned char)b[i]); + if (ca != cb) return ca - cb; + if (!a[i]) return 0; + } + return 0; +} + +char *strcpy(char *d, const char *s) +{ + char *r = d; + while ((*d++ = *s++)) { } + return r; +} + +char *strncpy(char *d, const char *s, size_t n) +{ + size_t i; + for (i = 0; i < n && s[i]; i++) d[i] = s[i]; + for (; i < n; i++) d[i] = 0; + return d; +} + +char *strcat(char *d, const char *s) +{ + char *r = d; + while (*d) d++; + while ((*d++ = *s++)) { } + return r; +} + +char *strncat(char *d, const char *s, size_t n) +{ + char *r = d; + while (*d) d++; + for (size_t i = 0; i < n && s[i]; i++) *d++ = s[i]; + *d = '\0'; + return r; +} + +char *strchr(const char *s, int c) +{ + for (; *s; s++) if (*s == (char)c) return (char *)s; + return c == 0 ? (char *)s : NULL; +} + +char *strrchr(const char *s, int c) +{ + const char *r = NULL; + for (; *s; s++) if (*s == (char)c) r = s; + return (char *)(c == 0 ? s : r); +} + +char *strstr(const char *h, const char *n) +{ + if (!*n) return (char *)h; + for (; *h; h++) { + const char *a = h, *b = n; + while (*a && *b && *a == *b) { a++; b++; } + if (!*b) return (char *)h; + } + return NULL; +} + +size_t strspn(const char *s, const char *accept) +{ + size_t n = 0; + while (s[n]) { + int found = 0; + for (size_t i = 0; accept[i]; i++) + if (s[n] == accept[i]) { found = 1; break; } + if (!found) break; + n++; + } + return n; +} + +size_t strcspn(const char *s, const char *reject) +{ + size_t n = 0; + while (s[n]) { + for (size_t i = 0; reject[i]; i++) + if (s[n] == reject[i]) return n; + n++; + } + return n; +} + +char *strpbrk(const char *s, const char *accept) +{ + while (*s) { + for (const char *a = accept; *a; a++) + if (*s == *a) return (char *)s; + s++; + } + return NULL; +} + +char *strdup(const char *s) +{ + size_t n = strlen(s) + 1; + char *p = (char *)malloc(n); + if (!p) return NULL; + memcpy(p, s, n); + return p; +} + +char *strndup(const char *s, size_t n) +{ + size_t len = strnlen(s, n); + char *p = (char *)malloc(len + 1); + if (!p) return NULL; + memcpy(p, s, len); + p[len] = '\0'; + return p; +} + +char *strtok_r(char *str, const char *delim, char **saveptr) +{ + char *s = str ? str : (saveptr ? *saveptr : NULL); + if (!s || !delim) return NULL; + + while (*s) { + const char *d; + for (d = delim; *d; d++) if (*s == *d) break; + if (!*d) break; + s++; + } + if (!*s) { + if (saveptr) *saveptr = NULL; + return NULL; + } + char *tok = s; + + while (*s) { + const char *d; + for (d = delim; *d; d++) if (*s == *d) break; + if (*d) { + *s = '\0'; + if (saveptr) *saveptr = s + 1; + return tok; + } + s++; + } + if (saveptr) *saveptr = NULL; + return tok; +} + +static char *__strtok_save = NULL; +char *strtok(char *str, const char *delim) +{ + return strtok_r(str, delim, &__strtok_save); +} + +char *strerror(int err) +{ + switch (err) { + case 0: return "Success"; + case EPERM: return "Operation not permitted"; + case ENOENT: return "No such file or directory"; + case ESRCH: return "No such process"; + case EINTR: return "Interrupted system call"; + case EIO: return "Input/output error"; + case EBADF: return "Bad file descriptor"; + case ECHILD: return "No child processes"; + case EAGAIN: return "Resource temporarily unavailable"; + case ENOMEM: return "Cannot allocate memory"; + case EACCES: return "Permission denied"; + case EFAULT: return "Bad address"; + case EBUSY: return "Device or resource busy"; + case EEXIST: return "File exists"; + case ENODEV: return "No such device"; + case ENOTDIR: return "Not a directory"; + case EISDIR: return "Is a directory"; + case EINVAL: return "Invalid argument"; + case EMFILE: return "Too many open files"; + case ENOTTY: return "Inappropriate ioctl for device"; + case ENOSPC: return "No space left on device"; + case EPIPE: return "Broken pipe"; + case ENOSYS: return "Function not implemented"; + default: return "Unknown error"; + } +} + +char *strsignal(int sig) +{ + switch (sig) { + case 1: return "Hangup"; + case 2: return "Interrupt"; + case 3: return "Quit"; + case 4: return "Illegal instruction"; + case 6: return "Aborted"; + case 8: return "Floating point exception"; + case 9: return "Killed"; + case 11: return "Segmentation fault"; + case 13: return "Broken pipe"; + case 14: return "Alarm clock"; + case 15: return "Terminated"; + default: return "Unknown signal"; + } +} \ No newline at end of file diff --git a/usr/lib/libcervus/time/time.c b/usr/lib/libcervus/time/time.c new file mode 100644 index 0000000..c2a9d05 --- /dev/null +++ b/usr/lib/libcervus/time/time.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include +#include + +extern int __cervus_errno; + +typedef struct { int64_t tv_sec; int64_t tv_nsec; } __cervus_ts_raw_t; + +time_t time(time_t *t) +{ + __cervus_ts_raw_t ts = {0, 0}; + syscall2(SYS_CLOCK_GET, 0, &ts); + time_t v = (time_t)ts.tv_sec; + if (t) *t = v; + return v; +} + +int clock_gettime(int clk, struct timespec *tp) +{ + if (!tp) { __cervus_errno = EINVAL; return -1; } + __cervus_ts_raw_t ts = {0, 0}; + long r = syscall2(SYS_CLOCK_GET, clk, &ts); + if (r < 0 && r > -4096) { __cervus_errno = (int)-r; return -1; } + tp->tv_sec = (time_t)ts.tv_sec; + tp->tv_nsec = (long)ts.tv_nsec; + return 0; +} + +clock_t clock(void) +{ + uint64_t up_ns = (uint64_t)syscall0(SYS_UPTIME); + return (clock_t)(up_ns / 1000ULL); +} + +int gettimeofday(struct timeval *tv, struct timezone *tz) +{ + (void)tz; + if (!tv) { __cervus_errno = EINVAL; return -1; } + __cervus_ts_raw_t ts = {0, 0}; + syscall2(SYS_CLOCK_GET, 0, &ts); + tv->tv_sec = (time_t)ts.tv_sec; + tv->tv_usec = (long)(ts.tv_nsec / 1000); + return 0; +} + +int nanosleep(const struct timespec *req, struct timespec *rem) +{ + if (!req) { __cervus_errno = EINVAL; return -1; } + uint64_t ns = (uint64_t)req->tv_sec * 1000000000ULL + (uint64_t)req->tv_nsec; + long r = syscall1(SYS_SLEEP_NS, ns); + if (rem) { rem->tv_sec = 0; rem->tv_nsec = 0; } + if (r < 0 && r > -4096) { __cervus_errno = (int)-r; return -1; } + return 0; +} + +static int __is_leap(int y) +{ + return ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0); +} + +static const int __days_in_mon[2][12] = { + {31,28,31,30,31,30,31,31,30,31,30,31}, + {31,29,31,30,31,30,31,31,30,31,30,31}, +}; + +static struct tm __tm_buf; + +struct tm *gmtime(const time_t *t) +{ + if (!t) return NULL; + long long sec = (long long)*t; + long days = (long)(sec / 86400); + long rem = (long)(sec % 86400); + if (rem < 0) { rem += 86400; days--; } + + __tm_buf.tm_hour = rem / 3600; + rem -= __tm_buf.tm_hour * 3600; + __tm_buf.tm_min = rem / 60; + __tm_buf.tm_sec = rem - __tm_buf.tm_min * 60; + + __tm_buf.tm_wday = (int)((days + 4) % 7); + if (__tm_buf.tm_wday < 0) __tm_buf.tm_wday += 7; + + int year = 1970; + while (1) { + int ly = __is_leap(year); + int dy = ly ? 366 : 365; + if (days >= dy) { days -= dy; year++; } + else if (days < 0) { year--; days += __is_leap(year) ? 366 : 365; } + else break; + } + __tm_buf.tm_year = year - 1900; + __tm_buf.tm_yday = (int)days; + int ly = __is_leap(year); + int m = 0; + while (m < 12 && days >= __days_in_mon[ly][m]) { + days -= __days_in_mon[ly][m]; + m++; + } + __tm_buf.tm_mon = m; + __tm_buf.tm_mday = (int)days + 1; + __tm_buf.tm_isdst = 0; + return &__tm_buf; +} + +struct tm *localtime(const time_t *t) { return gmtime(t); } + +time_t mktime(struct tm *tm) +{ + if (!tm) return (time_t)-1; + int year = tm->tm_year + 1900; + int mon = tm->tm_mon; + long days = 0; + for (int y = 1970; y < year; y++) days += __is_leap(y) ? 366 : 365; + int ly = __is_leap(year); + for (int m = 0; m < mon; m++) days += __days_in_mon[ly][m]; + days += tm->tm_mday - 1; + long long sec = (long long)days * 86400LL + + (long long)tm->tm_hour * 3600LL + + (long long)tm->tm_min * 60LL + + (long long)tm->tm_sec; + return (time_t)sec; +} + +static const char *__wday_name[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; +static const char *__mon_name[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + +static char __asctime_buf[32]; + +char *asctime(const struct tm *tm) +{ + if (!tm) return NULL; + int wday = tm->tm_wday; if (wday < 0 || wday > 6) wday = 0; + int mon = tm->tm_mon; if (mon < 0 || mon > 11) mon = 0; + int y = tm->tm_year + 1900; + int pos = 0; + const char *w = __wday_name[wday]; + const char *mn = __mon_name[mon]; + __asctime_buf[pos++] = w[0]; __asctime_buf[pos++] = w[1]; __asctime_buf[pos++] = w[2]; + __asctime_buf[pos++] = ' '; + __asctime_buf[pos++] = mn[0]; __asctime_buf[pos++] = mn[1]; __asctime_buf[pos++] = mn[2]; + __asctime_buf[pos++] = ' '; + int md = tm->tm_mday; + __asctime_buf[pos++] = (char)('0' + (md/10 % 10)); + __asctime_buf[pos++] = (char)('0' + (md % 10)); + __asctime_buf[pos++] = ' '; + int hh = tm->tm_hour, mm = tm->tm_min, ss = tm->tm_sec; + __asctime_buf[pos++] = (char)('0' + (hh/10 % 10)); + __asctime_buf[pos++] = (char)('0' + (hh % 10)); + __asctime_buf[pos++] = ':'; + __asctime_buf[pos++] = (char)('0' + (mm/10 % 10)); + __asctime_buf[pos++] = (char)('0' + (mm % 10)); + __asctime_buf[pos++] = ':'; + __asctime_buf[pos++] = (char)('0' + (ss/10 % 10)); + __asctime_buf[pos++] = (char)('0' + (ss % 10)); + __asctime_buf[pos++] = ' '; + __asctime_buf[pos++] = (char)('0' + (y/1000 % 10)); + __asctime_buf[pos++] = (char)('0' + (y/100 % 10)); + __asctime_buf[pos++] = (char)('0' + (y/10 % 10)); + __asctime_buf[pos++] = (char)('0' + (y % 10)); + __asctime_buf[pos++] = '\n'; + __asctime_buf[pos] = '\0'; + return __asctime_buf; +} + +char *ctime(const time_t *t) { return asctime(gmtime(t)); } + +size_t strftime(char *s, size_t max, const char *fmt, const struct tm *tm) +{ + if (!s || !fmt || !tm || max == 0) return 0; + size_t i = 0; + while (*fmt && i + 1 < max) { + if (*fmt != '%') { s[i++] = *fmt++; continue; } + fmt++; + char tmp[16]; + int n = 0; + switch (*fmt) { + case 'Y': { + int y = tm->tm_year + 1900; + n = 4; + tmp[0] = (char)('0' + (y/1000)%10); + tmp[1] = (char)('0' + (y/100)%10); + tmp[2] = (char)('0' + (y/10)%10); + tmp[3] = (char)('0' + y%10); + break; + } + case 'm': { int v=tm->tm_mon+1; tmp[0]=(char)('0'+v/10); tmp[1]=(char)('0'+v%10); n=2; break; } + case 'd': { int v=tm->tm_mday; tmp[0]=(char)('0'+v/10); tmp[1]=(char)('0'+v%10); n=2; break; } + case 'H': { int v=tm->tm_hour; tmp[0]=(char)('0'+v/10); tmp[1]=(char)('0'+v%10); n=2; break; } + case 'M': { int v=tm->tm_min; tmp[0]=(char)('0'+v/10); tmp[1]=(char)('0'+v%10); n=2; break; } + case 'S': { int v=tm->tm_sec; tmp[0]=(char)('0'+v/10); tmp[1]=(char)('0'+v%10); n=2; break; } + case '%': tmp[0]='%'; n=1; break; + default: tmp[0]='%'; tmp[1]=*fmt; n = (*fmt ? 2 : 1); break; + } + for (int k = 0; k < n && i + 1 < max; k++) s[i++] = tmp[k]; + if (*fmt) fmt++; + } + s[i] = '\0'; + return i; +} \ No newline at end of file diff --git a/usr/sysroot/usr/include/assert.h b/usr/sysroot/usr/include/assert.h new file mode 100644 index 0000000..2cdfb3f --- /dev/null +++ b/usr/sysroot/usr/include/assert.h @@ -0,0 +1,16 @@ +#ifndef _ASSERT_H +#define _ASSERT_H + +#ifdef NDEBUG +#define assert(cond) ((void)0) +#else + +void __cervus_assert_fail(const char *expr, const char *file, int line, const char *func) + __attribute__((noreturn)); + +#define assert(cond) \ + ((cond) ? (void)0 : __cervus_assert_fail(#cond, __FILE__, __LINE__, __func__)) + +#endif + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/cervus_util.h b/usr/sysroot/usr/include/cervus_util.h new file mode 100644 index 0000000..5ca46eb --- /dev/null +++ b/usr/sysroot/usr/include/cervus_util.h @@ -0,0 +1,162 @@ +#ifndef _CERVUS_UTIL_H +#define _CERVUS_UTIL_H + +#include +#include +#include + +#define C_RESET "\x1b[0m" +#define C_BOLD "\x1b[1m" +#define C_RED "\x1b[1;31m" +#define C_GREEN "\x1b[1;32m" +#define C_YELLOW "\x1b[1;33m" +#define C_BLUE "\x1b[1;34m" +#define C_MAGENTA "\x1b[1;35m" +#define C_CYAN "\x1b[1;36m" +#define C_GRAY "\x1b[90m" + +static inline void path_join(const char *base, const char *name, + char *out, size_t sz) +{ + if (!name || !name[0]) { + strncpy(out, base ? base : "", sz - 1); + out[sz - 1] = '\0'; + return; + } + if (name[0] == '/') { + strncpy(out, name, sz - 1); + out[sz - 1] = '\0'; + return; + } + strncpy(out, base ? base : "", sz - 1); + out[sz - 1] = '\0'; + size_t bl = strlen(out); + if (bl > 0 && out[bl - 1] != '/' && bl + 1 < sz) { + out[bl++] = '/'; + out[bl] = '\0'; + } + size_t nl = strlen(name); + if (bl + nl + 1 < sz) memcpy(out + bl, name, nl + 1); + else out[sz - 1] = '\0'; +} + +static inline void path_norm(char *path) +{ + char tmp[512]; + strncpy(tmp, path, 511); + tmp[511] = '\0'; + + char *parts[64]; + int np = 0; + char *p = tmp; + while (*p) { + while (*p == '/') p++; + if (!*p) break; + char *s = p; + while (*p && *p != '/') p++; + if (*p) *p++ = '\0'; + if (strcmp(s, ".") == 0) continue; + if (strcmp(s, "..") == 0) { if (np > 0) np--; continue; } + if (np < 64) parts[np++] = s; + } + char out[512]; + size_t ol = 0; + for (int i = 0; i < np; i++) { + if (ol + 1 >= sizeof(out)) break; + out[ol++] = '/'; + size_t pl = strlen(parts[i]); + if (ol + pl >= sizeof(out)) pl = sizeof(out) - 1 - ol; + memcpy(out + ol, parts[i], pl); + ol += pl; + } + out[ol] = '\0'; + if (ol == 0) { out[0] = '/'; out[1] = '\0'; } + strncpy(path, out, 512); +} + +static inline void resolve_path(const char *cwd, const char *path, + char *out, size_t sz) +{ + if (!path || !path[0]) { + strncpy(out, cwd ? cwd : "/", sz - 1); + out[sz - 1] = '\0'; + return; + } + if (path[0] == '/') { + strncpy(out, path, sz - 1); + out[sz - 1] = '\0'; + return; + } + path_join(cwd, path, out, sz); + path_norm(out); +} + +extern int __cervus_argc; +extern char **__cervus_argv; + +static inline int is_shell_flag(const char *a) +{ + if (!a) return 0; + if (a[0] == '-' && a[1] == '-' && + a[2] == 'c' && a[3] == 'w' && a[4] == 'd' && a[5] == '=') return 1; + if (a[0] == '-' && a[1] == '-' && + a[2] == 'e' && a[3] == 'n' && a[4] == 'v' && a[5] == ':') return 1; + return 0; +} + +static inline const char *get_cwd_flag(int argc, char **argv) +{ + (void)argc; (void)argv; + for (int i = 1; i < __cervus_argc; i++) { + char *a = __cervus_argv[i]; + if (a && a[0] == '-' && a[1] == '-' && + a[2] == 'c' && a[3] == 'w' && a[4] == 'd' && a[5] == '=') + return a + 6; + } + return "/"; +} + +static inline const char *getenv_argv(int argc, char **argv, + const char *name, const char *def) +{ + (void)argc; (void)argv; + if (!name) return def; + size_t nl = strlen(name); + for (int i = 1; i < __cervus_argc; i++) { + const char *a = __cervus_argv[i]; + if (!a) continue; + if (a[0] == '-' && a[1] == '-' && + a[2] == 'e' && a[3] == 'n' && a[4] == 'v' && a[5] == ':') { + const char *kv = a + 6; + if (strncmp(kv, name, nl) == 0 && kv[nl] == '=') + return kv + nl + 1; + } + } + return def; +} + +static inline int util_readline(int fd, char *buf, int maxlen) +{ + int i = 0; + while (i < maxlen - 1) { + char c; + ssize_t r = read(fd, &c, 1); + if (r <= 0) { buf[i] = '\0'; return i > 0 ? i : -1; } + if (c == '\r') continue; + buf[i++] = c; + if (c == '\n') break; + } + buf[i] = '\0'; + return i; +} + +static inline void util_write_stdout(const char *s) +{ + write(1, s, strlen(s)); +} +static inline void util_write_stderr(const char *s) +{ + write(2, s, strlen(s)); +} + +#endif diff --git a/usr/sysroot/usr/include/ctype.h b/usr/sysroot/usr/include/ctype.h new file mode 100644 index 0000000..2002e1d --- /dev/null +++ b/usr/sysroot/usr/include/ctype.h @@ -0,0 +1,19 @@ +#ifndef _CTYPE_H +#define _CTYPE_H + +int isdigit(int c); +int isalpha(int c); +int isalnum(int c); +int isupper(int c); +int islower(int c); +int isspace(int c); +int isprint(int c); +int isgraph(int c); +int ispunct(int c); +int isxdigit(int c); +int iscntrl(int c); +int isblank(int c); +int toupper(int c); +int tolower(int c); + +#endif diff --git a/usr/sysroot/usr/include/dirent.h b/usr/sysroot/usr/include/dirent.h new file mode 100644 index 0000000..4c3aba0 --- /dev/null +++ b/usr/sysroot/usr/include/dirent.h @@ -0,0 +1,30 @@ +#ifndef _DIRENT_H +#define _DIRENT_H + +#include + +#define DT_UNKNOWN 0 +#define DT_FILE 0 +#define DT_REG 0 +#define DT_DIR 1 +#define DT_CHR 2 +#define DT_BLK 3 +#define DT_LNK 4 +#define DT_PIPE 5 +#define DT_FIFO 5 + +struct dirent { + ino_t d_ino; + uint8_t d_type; + char d_name[256]; +}; + +typedef struct __cervus_DIR DIR; + +DIR *opendir(const char *path); +struct dirent *readdir(DIR *dirp); +int closedir(DIR *dirp); +void rewinddir(DIR *dirp); +int dirfd(DIR *dirp); + +#endif diff --git a/usr/sysroot/usr/include/errno.h b/usr/sysroot/usr/include/errno.h new file mode 100644 index 0000000..be05090 --- /dev/null +++ b/usr/sysroot/usr/include/errno.h @@ -0,0 +1,45 @@ +#ifndef _ERRNO_H +#define _ERRNO_H + +extern int __cervus_errno; +#define errno __cervus_errno + +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define EINVAL 22 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define EDOM 33 +#define ERANGE 34 +#define ENAMETOOLONG 36 +#define ENOSYS 38 +#define ENOTEMPTY 39 +#define ELOOP 40 +#define EOVERFLOW 75 + +#endif diff --git a/usr/sysroot/usr/include/fcntl.h b/usr/sysroot/usr/include/fcntl.h new file mode 100644 index 0000000..d56082b --- /dev/null +++ b/usr/sysroot/usr/include/fcntl.h @@ -0,0 +1,27 @@ +#ifndef _FCNTL_H +#define _FCNTL_H + +#include + +#define O_RDONLY 0x000 +#define O_WRONLY 0x001 +#define O_RDWR 0x002 +#define O_ACCMODE 0x003 +#define O_CREAT 0x040 +#define O_TRUNC 0x200 +#define O_APPEND 0x400 +#define O_NONBLOCK 0x800 +#define O_DIRECTORY 0x10000 +#define O_CLOEXEC 0x80000 + +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 +#define FD_CLOEXEC 1 + +int open(const char *path, int flags, ...); +int fcntl(int fd, int cmd, ...); +int creat(const char *path, mode_t mode); + +#endif diff --git a/usr/sysroot/usr/include/inttypes.h b/usr/sysroot/usr/include/inttypes.h new file mode 100644 index 0000000..a4d450d --- /dev/null +++ b/usr/sysroot/usr/include/inttypes.h @@ -0,0 +1,76 @@ +#ifndef _INTTYPES_H +#define _INTTYPES_H + +#include + +#define PRId8 "d" +#define PRId16 "d" +#define PRId32 "d" +#define PRId64 "ld" + +#define PRIu8 "u" +#define PRIu16 "u" +#define PRIu32 "u" +#define PRIu64 "lu" + +#define PRIx8 "x" +#define PRIx16 "x" +#define PRIx32 "x" +#define PRIx64 "lx" + +#define PRIX8 "X" +#define PRIX16 "X" +#define PRIX32 "X" +#define PRIX64 "lX" + +#define PRIi8 "i" +#define PRIi16 "i" +#define PRIi32 "i" +#define PRIi64 "li" + +#define PRIo8 "o" +#define PRIo16 "o" +#define PRIo32 "o" +#define PRIo64 "lo" + +#define PRIdMAX "ld" +#define PRIuMAX "lu" +#define PRIxMAX "lx" +#define PRIiMAX "li" + +#define PRIdPTR "ld" +#define PRIuPTR "lu" +#define PRIxPTR "lx" + +#define SCNd8 "hhd" +#define SCNd16 "hd" +#define SCNd32 "d" +#define SCNd64 "ld" + +#define SCNu8 "hhu" +#define SCNu16 "hu" +#define SCNu32 "u" +#define SCNu64 "lu" + +#define SCNx8 "hhx" +#define SCNx16 "hx" +#define SCNx32 "x" +#define SCNx64 "lx" + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +static inline intmax_t imaxabs(intmax_t j) { + return j < 0 ? -j : j; +} + +static inline imaxdiv_t imaxdiv(intmax_t numer, intmax_t denom) { + imaxdiv_t r; + r.quot = numer / denom; + r.rem = numer % denom; + return r; +} + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/limits.h b/usr/sysroot/usr/include/limits.h new file mode 100644 index 0000000..a216445 --- /dev/null +++ b/usr/sysroot/usr/include/limits.h @@ -0,0 +1,30 @@ +#ifndef _LIMITS_H +#define _LIMITS_H + +#define CHAR_BIT 8 +#define SCHAR_MIN (-128) +#define SCHAR_MAX (127) +#define UCHAR_MAX (255) +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX + +#define SHRT_MIN (-32768) +#define SHRT_MAX (32767) +#define USHRT_MAX (65535) + +#define INT_MIN (-2147483647 - 1) +#define INT_MAX (2147483647) +#define UINT_MAX (4294967295u) + +#define LONG_MIN (-9223372036854775807L - 1) +#define LONG_MAX (9223372036854775807L) +#define ULONG_MAX (18446744073709551615UL) + +#define LLONG_MIN LONG_MIN +#define LLONG_MAX LONG_MAX +#define ULLONG_MAX ULONG_MAX + +#define PATH_MAX 512 +#define NAME_MAX 255 + +#endif diff --git a/usr/sysroot/usr/include/math.h b/usr/sysroot/usr/include/math.h new file mode 100644 index 0000000..0a649c9 --- /dev/null +++ b/usr/sysroot/usr/include/math.h @@ -0,0 +1,77 @@ +#ifndef _MATH_H +#define _MATH_H + +#include + +#define INFINITY (1.0/0.0) +#define NAN (0.0/0.0) + +int abs(int x); +double fabs(double x); +double pow(double base, double exp); +double pow10(int n); +int isinf(double x); +int isnan(double x); + +static inline double ldexp(double x, int exp) { + union { double d; uint64_t u; } v; + v.d = x; + int e = (int)((v.u >> 52) & 0x7FF); + if (e == 0 || e == 0x7FF) + return x; + e += exp; + if (e <= 0) + return 0.0; + if (e >= 0x7FF) + return INFINITY; + v.u = (v.u & ~((uint64_t)0x7FF << 52)) | ((uint64_t)e << 52); + return v.d; +} + +static inline double floor(double x) { + int64_t i = (int64_t)x; + return (double)(i - (x < (double)i)); +} + +static inline double ceil(double x) { + int64_t i = (int64_t)x; + return (double)(i + (x > (double)i)); +} + +static inline double round(double x) { + return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5); +} + +static inline double sqrt(double x) { + double result; + asm volatile ("sqrtsd %1, %0" : "=x"(result) : "x"(x)); + return result; +} + +static inline float sqrtf(float x) { + float result; + asm volatile ("sqrtss %1, %0" : "=x"(result) : "x"(x)); + return result; +} + +static inline double log2(double x) { + double result; + asm volatile ( + "fld1\n\t" + "fld %1\n\t" + "fyl2x\n\t" + "fstp %0\n\t" + : "=m"(result) : "m"(x) + ); + return result; +} + +#define MIN(a, b) ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a < _b ? _a : _b; }) +#define MAX(a, b) ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a > _b ? _a : _b; }) + +#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1)) +#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1)) + +#define IS_POWER_OF_TWO(x) ((x) != 0 && (((x) & ((x) - 1)) == 0)) + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/setjmp.h b/usr/sysroot/usr/include/setjmp.h new file mode 100644 index 0000000..1585cb3 --- /dev/null +++ b/usr/sysroot/usr/include/setjmp.h @@ -0,0 +1,13 @@ +#ifndef _SETJMP_H +#define _SETJMP_H + +typedef long jmp_buf[8]; + +int setjmp(jmp_buf env); +void longjmp(jmp_buf env, int val) __attribute__((noreturn)); + +#define sigjmp_buf jmp_buf +#define sigsetjmp(env, savesigs) setjmp(env) +#define siglongjmp(env, val) longjmp(env, val) + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/signal.h b/usr/sysroot/usr/include/signal.h new file mode 100644 index 0000000..7d4eb1f --- /dev/null +++ b/usr/sysroot/usr/include/signal.h @@ -0,0 +1,169 @@ +#ifndef _SIGNAL_H +#define _SIGNAL_H + +#include +#include +#include + +typedef int sig_atomic_t; + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT SIGABRT +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGWINCH 28 + +#define NSIG 32 + +typedef void (*sighandler_t)(int); + +#define SIG_DFL ((sighandler_t)0) +#define SIG_IGN ((sighandler_t)1) +#define SIG_ERR ((sighandler_t)-1) + +#define SA_NOCLDSTOP 0x00000001 +#define SA_NOCLDWAIT 0x00000002 +#define SA_SIGINFO 0x00000004 +#define SA_ONSTACK 0x08000000 +#define SA_RESTART 0x10000000 +#define SA_NODEFER 0x40000000 +#define SA_RESETHAND 0x80000000 + +#define SI_USER 0 +#define SI_KERNEL 0x80 +#define SI_QUEUE -1 +#define SI_TIMER -2 +#define SI_MESGQ -3 +#define SI_ASYNCIO -4 +#define SI_SIGIO -5 +#define SI_TKILL -6 + +#define FPE_INTDIV 1 +#define FPE_INTOVF 2 +#define FPE_FLTDIV 3 +#define FPE_FLTOVF 4 +#define FPE_FLTUND 5 +#define FPE_FLTRES 6 +#define FPE_FLTINV 7 +#define FPE_FLTSUB 8 + +#define ILL_ILLOPC 1 +#define ILL_ILLOPN 2 +#define ILL_ILLADR 3 +#define ILL_ILLTRP 4 +#define ILL_PRVOPC 5 +#define ILL_PRVREG 6 +#define ILL_COPROC 7 +#define ILL_BADSTK 8 + +#define SEGV_MAPERR 1 +#define SEGV_ACCERR 2 + +#define BUS_ADRALN 1 +#define BUS_ADRERR 2 +#define BUS_OBJERR 3 + +typedef struct { + unsigned long __bits[2]; +} sigset_t; + +union sigval { + int sival_int; + void *sival_ptr; +}; + +typedef struct { + int si_signo; + int si_errno; + int si_code; + union { + struct { + pid_t si_pid; + uid_t si_uid; + } _kill; + struct { + int si_tid; + int si_overrun; + union sigval si_sigval; + } _timer; + struct { + pid_t si_pid; + uid_t si_uid; + union sigval si_sigval; + } _rt; + struct { + pid_t si_pid; + uid_t si_uid; + int si_status; + } _sigchld; + struct { + void *si_addr; + } _sigfault; + struct { + long si_band; + int si_fd; + } _sigpoll; + } _sifields; +} siginfo_t; + +#define si_pid _sifields._kill.si_pid +#define si_uid _sifields._kill.si_uid +#define si_status _sifields._sigchld.si_status +#define si_addr _sifields._sigfault.si_addr +#define si_band _sifields._sigpoll.si_band +#define si_fd _sifields._sigpoll.si_fd + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; + +#define SS_ONSTACK 1 +#define SS_DISABLE 2 + +struct sigaction { + union { + sighandler_t sa_handler; + void (*sa_sigaction)(int, siginfo_t *, void *); + }; + sigset_t sa_mask; + int sa_flags; + void (*sa_restorer)(void); +}; + +sighandler_t signal(int signum, sighandler_t handler); +int raise(int sig); + +int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact); +int sigemptyset(sigset_t *set); +int sigfillset(sigset_t *set); +int sigaddset(sigset_t *set, int sig); +int sigdelset(sigset_t *set, int sig); +int sigismember(const sigset_t *set, int sig); +int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); +int sigaltstack(const stack_t *ss, stack_t *oss); + +#define SIG_BLOCK 0 +#define SIG_UNBLOCK 1 +#define SIG_SETMASK 2 + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/stdarg.h b/usr/sysroot/usr/include/stdarg.h new file mode 100644 index 0000000..92c990c --- /dev/null +++ b/usr/sysroot/usr/include/stdarg.h @@ -0,0 +1,11 @@ +#ifndef _STDARG_H +#define _STDARG_H + +typedef __builtin_va_list va_list; + +#define va_start(ap, last) __builtin_va_start(ap, last) +#define va_arg(ap, t) __builtin_va_arg(ap, t) +#define va_end(ap) __builtin_va_end(ap) +#define va_copy(d, s) __builtin_va_copy(d, s) + +#endif diff --git a/usr/sysroot/usr/include/stdbool.h b/usr/sysroot/usr/include/stdbool.h new file mode 100644 index 0000000..848dd60 --- /dev/null +++ b/usr/sysroot/usr/include/stdbool.h @@ -0,0 +1,9 @@ +#ifndef _STDBOOL_H +#define _STDBOOL_H +#ifndef __cplusplus +#define bool _Bool +#define true 1 +#define false 0 +#endif +#define __bool_true_false_are_defined 1 +#endif diff --git a/usr/sysroot/usr/include/stddef.h b/usr/sysroot/usr/include/stddef.h new file mode 100644 index 0000000..3e4cdab --- /dev/null +++ b/usr/sysroot/usr/include/stddef.h @@ -0,0 +1,15 @@ +#ifndef _STDDEF_H +#define _STDDEF_H + +#ifndef NULL +#define NULL ((void*)0) +#endif + +typedef unsigned long size_t; +typedef long ssize_t; +typedef long ptrdiff_t; +typedef int wchar_t; + +#define offsetof(t, m) __builtin_offsetof(t, m) + +#endif diff --git a/usr/sysroot/usr/include/stdint.h b/usr/sysroot/usr/include/stdint.h new file mode 100644 index 0000000..5256dfe --- /dev/null +++ b/usr/sysroot/usr/include/stdint.h @@ -0,0 +1,48 @@ +#ifndef _STDINT_H +#define _STDINT_H + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long int64_t; +typedef unsigned long uint64_t; + +typedef long intptr_t; +typedef unsigned long uintptr_t; + +typedef long intmax_t; +typedef unsigned long uintmax_t; + +#define INT8_MIN (-128) +#define INT8_MAX (127) +#define INT16_MIN (-32768) +#define INT16_MAX (32767) +#define INT32_MIN (-2147483647 - 1) +#define INT32_MAX (2147483647) +#define INT64_MIN (-9223372036854775807L - 1) +#define INT64_MAX (9223372036854775807L) + +#define UINT8_MAX (0xFFu) +#define UINT16_MAX (0xFFFFu) +#define UINT32_MAX (0xFFFFFFFFu) +#define UINT64_MAX (0xFFFFFFFFFFFFFFFFUL) + +#define INTPTR_MIN INT64_MIN +#define INTPTR_MAX INT64_MAX +#define UINTPTR_MAX UINT64_MAX + +#define SIZE_MAX UINT64_MAX + +#define INT8_C(v) v +#define INT16_C(v) v +#define INT32_C(v) v +#define INT64_C(v) v ## L +#define UINT8_C(v) v ## u +#define UINT16_C(v) v ## u +#define UINT32_C(v) v ## u +#define UINT64_C(v) v ## UL + +#endif diff --git a/usr/sysroot/usr/include/stdio.h b/usr/sysroot/usr/include/stdio.h new file mode 100644 index 0000000..be0a90c --- /dev/null +++ b/usr/sysroot/usr/include/stdio.h @@ -0,0 +1,66 @@ +#ifndef _STDIO_H +#define _STDIO_H + +#include +#include + +#define EOF (-1) + +#define BUFSIZ 1024 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +typedef struct __cervus_FILE FILE; + +extern FILE *stdin; +extern FILE *stdout; +extern FILE *stderr; + +int putchar(int c); +int getchar(void); +int puts(const char *s); +int fputs(const char *s, FILE *stream); +int fputc(int c, FILE *stream); +int fgetc(FILE *stream); +char *fgets(char *s, int n, FILE *stream); +int ungetc(int c, FILE *stream); + +int printf(const char *fmt, ...); +int fprintf(FILE *stream, const char *fmt, ...); +int sprintf(char *buf, const char *fmt, ...); +int snprintf(char *buf, size_t sz, const char *fmt, ...); +int vsprintf(char *buf, const char *fmt, va_list ap); +int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap); +int vfprintf(FILE *stream, const char *fmt, va_list ap); +int vprintf(const char *fmt, va_list ap); +int sscanf(const char *str, const char *fmt, ...); +int vsscanf(const char *str, const char *fmt, va_list ap); +int fscanf(FILE *stream, const char *fmt, ...); +int scanf(const char *fmt, ...); + +FILE *fopen(const char *path, const char *mode); +FILE *fdopen(int fd, const char *mode); +FILE *tmpfile(void); +int fclose(FILE *stream); +size_t fread(void *buf, size_t size, size_t nmemb, FILE *stream); +size_t fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream); +int fflush(FILE *stream); +int fseek(FILE *stream, long off, int whence); +long ftell(FILE *stream); +void rewind(FILE *stream); +int feof(FILE *stream); +int ferror(FILE *stream); +void clearerr(FILE *stream); +int fileno(FILE *stream); + +FILE *popen(const char *cmd, const char *type); +int pclose(FILE *stream); + +void perror(const char *msg); +int rename(const char *oldp, const char *newp); +int remove(const char *path); +char *tmpnam(char *buf); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/stdlib.h b/usr/sysroot/usr/include/stdlib.h new file mode 100644 index 0000000..eb86b54 --- /dev/null +++ b/usr/sysroot/usr/include/stdlib.h @@ -0,0 +1,76 @@ +#ifndef _STDLIB_H +#define _STDLIB_H + +#include + +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 + +#define RAND_MAX 0x7FFFFFFF + +void *malloc(size_t n); +void *calloc(size_t nmemb, size_t size); +void *realloc(void *p, size_t n); +void free(void *p); + +void exit(int status) __attribute__((noreturn)); +void abort(void) __attribute__((noreturn)); +int atexit(void (*fn)(void)); + +int atoi(const char *s); +long atol(const char *s); +long long atoll(const char *s); +long strtol(const char *s, char **end, int base); +unsigned long strtoul(const char *s, char **end, int base); +long long strtoll(const char *s, char **end, int base); +unsigned long long strtoull(const char *s, char **end, int base); + +#include +uint64_t __cervus_strtod_bits(const char *s, char **endptr); + +static __inline__ double strtod(const char *s, char **endp) +{ + uint64_t b = __cervus_strtod_bits(s, endp); + double d; + __builtin_memcpy(&d, &b, sizeof(d)); + return d; +} + +static __inline__ float strtof(const char *s, char **endp) +{ + return (float)strtod(s, endp); +} + +static __inline__ long double strtold(const char *s, char **endp) +{ + return (long double)strtod(s, endp); +} + +static __inline__ double atof(const char *s) +{ + return strtod(s, (char **)0); +} + +int abs(int x); +long labs(long x); +long long llabs(long long x); + +int rand(void); +void srand(unsigned int seed); + +char *getenv(const char *name); +int putenv(char *str); +int setenv(const char *name, const char *value, int overwrite); +int unsetenv(const char *name); + +void qsort(void *base, size_t nmemb, size_t size, int (*cmp)(const void *, const void *)); +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *)); + +int system(const char *cmd); + +int mkstemp(char *template); +char *mkdtemp(char *template); +char *realpath(const char *path, char *resolved); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/string.h b/usr/sysroot/usr/include/string.h new file mode 100644 index 0000000..61a1d27 --- /dev/null +++ b/usr/sysroot/usr/include/string.h @@ -0,0 +1,36 @@ +#ifndef _STRING_H +#define _STRING_H + +#include + +void *memset(void *dst, int c, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +void *memmove(void *dst, const void *src, size_t n); +int memcmp(const void *a, const void *b, size_t n); +void *memchr(const void *s, int c, size_t n); +void *memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen); + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t max); +int strcmp(const char *a, const char *b); +int strncmp(const char *a, const char *b, size_t n); +int strcasecmp(const char *a, const char *b); +int strncasecmp(const char *a, const char *b, size_t n); +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t n); +char *strcat(char *dst, const char *src); +char *strncat(char *dst, const char *src, size_t n); +char *strchr(const char *s, int c); +char *strrchr(const char *s, int c); +char *strstr(const char *hay, const char *needle); +char *strdup(const char *s); +char *strndup(const char *s, size_t n); +size_t strspn(const char *s, const char *accept); +size_t strcspn(const char *s, const char *reject); +char *strpbrk(const char *s, const char *accept); +char *strtok(char *str, const char *delim); +char *strtok_r(char *str, const char *delim, char **saveptr); +char *strerror(int errnum); +char *strsignal(int sig); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/sys/cervus.h b/usr/sysroot/usr/include/sys/cervus.h new file mode 100644 index 0000000..edd1337 --- /dev/null +++ b/usr/sysroot/usr/include/sys/cervus.h @@ -0,0 +1,140 @@ +#ifndef _SYS_CERVUS_H +#define _SYS_CERVUS_H + +#include +#include +#include + +#define CAP_IOPORT (1ULL << 0) +#define CAP_RAWMEM (1ULL << 1) +#define CAP_KILL_ANY (1ULL << 4) +#define CAP_SET_PRIO (1ULL << 5) +#define CAP_TASK_SPAWN (1ULL << 6) +#define CAP_TASK_INFO (1ULL << 7) +#define CAP_MMAP_EXEC (1ULL << 8) +#define CAP_SETUID (1ULL << 17) +#define CAP_DBG_SERIAL (1ULL << 20) + +typedef struct __attribute__((packed)) { + uint8_t boot_flag; + uint8_t type; + uint32_t lba_start; + uint32_t sector_count; +} cervus_mbr_part_t; + +typedef struct __attribute__((packed)) { + char disk_name[32]; + char part_name[32]; + uint32_t part_num; + uint8_t type; + uint8_t bootable; + uint64_t lba_start; + uint64_t sector_count; + uint64_t size_bytes; +} cervus_part_info_t; + +typedef struct { + char name[32]; + uint64_t sectors; + uint64_t size_bytes; + char model[41]; + uint8_t present; + uint8_t _pad[6]; +} cervus_disk_info_t; + +typedef struct { + uint32_t pid, ppid, uid, gid; + uint64_t capabilities; + char name[32]; + uint32_t state; + uint32_t priority; + uint64_t total_runtime_ns; +} cervus_task_info_t; + +typedef struct { + uint64_t total_bytes; + uint64_t free_bytes; + uint64_t used_bytes; + uint64_t usable_bytes; + uint64_t page_size; +} cervus_meminfo_t; + +typedef struct { + int64_t tv_sec; + int64_t tv_nsec; +} cervus_timespec_t; + +typedef struct { + char path[512]; + char device[32]; + char fstype[16]; + uint32_t flags; +} cervus_mount_info_t; + +typedef struct { + uint64_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; + uint32_t f_flag; + uint32_t f_namemax; +} cervus_statvfs_t; + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 + +#define WNOHANG 0x1 +#define WEXITSTATUS(s) (((s) >> 8) & 0xFF) +#define WIFEXITED(s) (((s) & 0x7F) == 0) + +int cervus_task_info(pid_t pid, cervus_task_info_t *out); +int cervus_task_kill(pid_t pid); +uint64_t cervus_cap_get(void); +int cervus_cap_drop(uint64_t mask); + +int cervus_meminfo(cervus_meminfo_t *out); +uint64_t cervus_uptime_ns(void); +int cervus_clock_gettime(int id, cervus_timespec_t *ts); +int cervus_nanosleep(uint64_t ns); + +int cervus_shutdown(void); +int cervus_reboot(void); + +int cervus_disk_info(int index, cervus_disk_info_t *out); +int cervus_disk_mount(const char *dev, const char *path); +int cervus_disk_umount(const char *path); +int cervus_disk_format(const char *dev, const char *label); +int cervus_disk_mkfs_fat32(const char *dev, const char *label); +int cervus_disk_partition(const char *dev, const cervus_mbr_part_t *specs, uint64_t n); +int cervus_disk_read_raw(const char *dev, uint64_t lba, uint64_t count, void *buf); +int cervus_disk_write_raw(const char *dev, uint64_t lba, uint64_t count, const void *buf); +long cervus_disk_list_parts(cervus_part_info_t *out, int max); +long cervus_disk_bios_install(const char *disk, const void *sys_data, uint32_t sys_size); + +long cervus_list_mounts(cervus_mount_info_t *out, int max); +long cervus_statvfs(const char *path, cervus_statvfs_t *out); + +uint32_t cervus_ioport_read(uint16_t port, int width); +int cervus_ioport_write(uint16_t port, int width, uint32_t val); + +#define PROT_NONE 0x0 +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define PROT_EXEC 0x4 + +#define MAP_PRIVATE 0x02 +#define MAP_ANONYMOUS 0x20 +#define MAP_FIXED 0x10 +#define MAP_FAILED ((void *)-1) + +void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off); +int munmap(void *addr, size_t len); + +pid_t waitpid(pid_t pid, int *status, int options); +pid_t wait(int *status); + +ssize_t cervus_dbg_print(const char *buf, size_t n); + +#endif diff --git a/usr/sysroot/usr/include/sys/ioctl.h b/usr/sysroot/usr/include/sys/ioctl.h new file mode 100644 index 0000000..416be0e --- /dev/null +++ b/usr/sysroot/usr/include/sys/ioctl.h @@ -0,0 +1,29 @@ +#ifndef _SYS_IOCTL_H +#define _SYS_IOCTL_H + +#include +#include + +#define TIOCGWINSZ 0x5413 +#define TIOCSWINSZ 0x5414 +#define TIOCGPGRP 0x540F +#define TIOCSPGRP 0x5410 +#define TIOCGPTN 0x80045430 +#define TIOCSPTLCK 0x40045431 + +#define FIOCLEX 0x5451 +#define FIONCLEX 0x5450 +#define FIONREAD 0x541B +#define FIONBIO 0x5421 +#define FIOASYNC 0x5452 + +struct winsize { + uint16_t ws_row; + uint16_t ws_col; + uint16_t ws_xpixel; + uint16_t ws_ypixel; +}; + +int ioctl(int fd, unsigned long request, ...); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/sys/mman.h b/usr/sysroot/usr/include/sys/mman.h new file mode 100644 index 0000000..f653977 --- /dev/null +++ b/usr/sysroot/usr/include/sys/mman.h @@ -0,0 +1,24 @@ +#ifndef _SYS_MMAN_H +#define _SYS_MMAN_H + +#include +#include + +#define PROT_NONE 0x0 +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define PROT_EXEC 0x4 + +#define MAP_SHARED 0x01 +#define MAP_PRIVATE 0x02 +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS + +#define MAP_FAILED ((void *)-1) + +void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off); +int munmap(void *addr, size_t len); +int mprotect(void *addr, size_t len, int prot); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/sys/stat.h b/usr/sysroot/usr/include/sys/stat.h new file mode 100644 index 0000000..6b898f1 --- /dev/null +++ b/usr/sysroot/usr/include/sys/stat.h @@ -0,0 +1,50 @@ +#ifndef _SYS_STAT_H +#define _SYS_STAT_H + +#include + +#define S_IFMT 0170000 +#define S_IFREG 0100000 +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFBLK 0060000 +#define S_IFIFO 0010000 +#define S_IFLNK 0120000 +#define S_IFSOCK 0140000 + +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) + +#define S_IRWXU 00700 +#define S_IRUSR 00400 +#define S_IWUSR 00200 +#define S_IXUSR 00100 +#define S_IRWXG 00070 +#define S_IRGRP 00040 +#define S_IWGRP 00020 +#define S_IXGRP 00010 +#define S_IRWXO 00007 +#define S_IROTH 00004 +#define S_IWOTH 00002 +#define S_IXOTH 00001 + +struct stat { + ino_t st_ino; + uint32_t st_type; + mode_t st_mode; + uid_t st_uid; + gid_t st_gid; + off_t st_size; + blkcnt_t st_blocks; +}; + +int stat(const char *path, struct stat *out); +int fstat(int fd, struct stat *out); +int mkdir(const char *path, mode_t mode); +int chmod(const char *path, mode_t mode); + +#endif diff --git a/usr/sysroot/usr/include/sys/syscall.h b/usr/sysroot/usr/include/sys/syscall.h new file mode 100644 index 0000000..d6adff0 --- /dev/null +++ b/usr/sysroot/usr/include/sys/syscall.h @@ -0,0 +1,98 @@ +#ifndef _SYS_SYSCALL_H +#define _SYS_SYSCALL_H + +#include + +#define SYS_EXIT 0 +#define SYS_EXIT_GROUP 1 +#define SYS_GETPID 2 +#define SYS_GETPPID 3 +#define SYS_FORK 4 +#define SYS_WAIT 5 +#define SYS_YIELD 6 +#define SYS_GETUID 7 +#define SYS_GETGID 8 +#define SYS_SETUID 9 +#define SYS_SETGID 10 +#define SYS_CAP_GET 11 +#define SYS_CAP_DROP 12 +#define SYS_TASK_INFO 13 +#define SYS_EXECVE 14 + +#define SYS_READ 20 +#define SYS_WRITE 21 +#define SYS_OPEN 22 +#define SYS_CLOSE 23 +#define SYS_SEEK 24 +#define SYS_STAT 25 +#define SYS_FSTAT 26 +#define SYS_IOCTL 27 +#define SYS_DUP 28 +#define SYS_DUP2 29 +#define SYS_PIPE 30 +#define SYS_FCNTL 31 +#define SYS_READDIR 32 + +#define SYS_MMAP 40 +#define SYS_MUNMAP 41 +#define SYS_MPROTECT 42 +#define SYS_BRK 43 + +#define SYS_CLOCK_GET 60 +#define SYS_SLEEP_NS 61 +#define SYS_UPTIME 62 +#define SYS_MEMINFO 63 + +#define SYS_FUTEX_WAIT 80 +#define SYS_FUTEX_WAKE 81 + +#define SYS_DBG_PRINT 512 +#define SYS_TASK_KILL 515 +#define SYS_IOPORT_READ 521 +#define SYS_IOPORT_WRITE 522 +#define SYS_SHUTDOWN 523 +#define SYS_REBOOT 524 + +#define SYS_DISK_MOUNT 530 +#define SYS_DISK_UMOUNT 531 +#define SYS_DISK_FORMAT 532 +#define SYS_DISK_INFO 533 +#define SYS_UNLINK 534 +#define SYS_RMDIR 535 +#define SYS_MKDIR 536 +#define SYS_RENAME 537 + +#define SYS_DISK_READ_RAW 540 +#define SYS_DISK_WRITE_RAW 541 +#define SYS_DISK_PARTITION 542 +#define SYS_DISK_MKFS_FAT32 543 +#define SYS_DISK_LIST_PARTS 544 +#define SYS_DISK_BIOS_INSTALL 545 + +#define SYS_LIST_MOUNTS 546 +#define SYS_STATVFS 547 + +static inline int64_t +__syscall6(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, + uint64_t a4, uint64_t a5, uint64_t a6) +{ + int64_t ret; + register uint64_t r10 asm("r10") = a4; + register uint64_t r8 asm("r8") = a5; + register uint64_t r9 asm("r9") = a6; + asm volatile ("syscall" + : "=a"(ret) + : "0"(nr), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) + : "rcx", "r11", "memory"); + return ret; +} + +#define syscall0(n) __syscall6((n), 0, 0, 0, 0, 0, 0) +#define syscall1(n,a) __syscall6((n), (uint64_t)(a), 0, 0, 0, 0, 0) +#define syscall2(n,a,b) __syscall6((n), (uint64_t)(a), (uint64_t)(b), 0, 0, 0, 0) +#define syscall3(n,a,b,c) __syscall6((n), (uint64_t)(a), (uint64_t)(b), (uint64_t)(c), 0, 0, 0) +#define syscall4(n,a,b,c,d) __syscall6((n), (uint64_t)(a), (uint64_t)(b), (uint64_t)(c), (uint64_t)(d), 0, 0) +#define syscall5(n,a,b,c,d,e) __syscall6((n), (uint64_t)(a), (uint64_t)(b), (uint64_t)(c), (uint64_t)(d), (uint64_t)(e), 0) +#define syscall6(n,a,b,c,d,e,f) __syscall6((n), (uint64_t)(a), (uint64_t)(b), (uint64_t)(c), (uint64_t)(d), (uint64_t)(e), (uint64_t)(f)) + +#endif diff --git a/usr/sysroot/usr/include/sys/time.h b/usr/sysroot/usr/include/sys/time.h new file mode 100644 index 0000000..7e507ce --- /dev/null +++ b/usr/sysroot/usr/include/sys/time.h @@ -0,0 +1,19 @@ +#ifndef _SYS_TIME_H +#define _SYS_TIME_H + +#include +#include + +struct timeval { + time_t tv_sec; + long tv_usec; +}; + +struct timezone { + int tz_minuteswest; + int tz_dsttime; +}; + +int gettimeofday(struct timeval *tv, struct timezone *tz); + +#endif diff --git a/usr/sysroot/usr/include/sys/types.h b/usr/sysroot/usr/include/sys/types.h new file mode 100644 index 0000000..dc1136a --- /dev/null +++ b/usr/sysroot/usr/include/sys/types.h @@ -0,0 +1,18 @@ +#ifndef _SYS_TYPES_H +#define _SYS_TYPES_H + +#include +#include + +typedef int32_t pid_t; +typedef uint32_t uid_t; +typedef uint32_t gid_t; +typedef int64_t off_t; +typedef uint64_t ino_t; +typedef uint32_t mode_t; +typedef uint32_t dev_t; +typedef uint64_t blkcnt_t; +typedef uint64_t blksize_t; +typedef int64_t time_t; + +#endif diff --git a/usr/sysroot/usr/include/sys/ucontext.h b/usr/sysroot/usr/include/sys/ucontext.h new file mode 100644 index 0000000..c18a33c --- /dev/null +++ b/usr/sysroot/usr/include/sys/ucontext.h @@ -0,0 +1,67 @@ +#ifndef _SYS_UCONTEXT_H +#define _SYS_UCONTEXT_H + +#include +#include + +typedef unsigned long greg_t; + +#define NGREG 23 + +typedef greg_t gregset_t[NGREG]; + +enum { + REG_R8 = 0, + REG_R9 = 1, + REG_R10 = 2, + REG_R11 = 3, + REG_R12 = 4, + REG_R13 = 5, + REG_R14 = 6, + REG_R15 = 7, + REG_RDI = 8, + REG_RSI = 9, + REG_RBP = 10, + REG_RBX = 11, + REG_RDX = 12, + REG_RAX = 13, + REG_RCX = 14, + REG_RSP = 15, + REG_RIP = 16, + REG_EFL = 17, + REG_CSGSFS = 18, + REG_ERR = 19, + REG_TRAPNO = 20, + REG_OLDMASK = 21, + REG_CR2 = 22, +}; + +typedef struct { + uint16_t cwd; + uint16_t swd; + uint16_t ftw; + uint16_t fop; + uint64_t rip; + uint64_t rdp; + uint32_t mxcsr; + uint32_t mxcr_mask; + uint32_t st_space[32]; + uint32_t xmm_space[64]; + uint32_t padding[24]; +} fpregset_t; + +typedef struct { + gregset_t gregs; + fpregset_t *fpregs; + uint64_t __reserved[8]; +} mcontext_t; + +typedef struct ucontext { + uint64_t uc_flags; + struct ucontext *uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; + sigset_t uc_sigmask; +} ucontext_t; + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/sys/utsname.h b/usr/sysroot/usr/include/sys/utsname.h new file mode 100644 index 0000000..affde89 --- /dev/null +++ b/usr/sysroot/usr/include/sys/utsname.h @@ -0,0 +1,16 @@ +#ifndef _SYS_UTSNAME_H +#define _SYS_UTSNAME_H + +#define _UTSNAME_LENGTH 65 + +struct utsname { + char sysname[_UTSNAME_LENGTH]; + char nodename[_UTSNAME_LENGTH]; + char release[_UTSNAME_LENGTH]; + char version[_UTSNAME_LENGTH]; + char machine[_UTSNAME_LENGTH]; +}; + +int uname(struct utsname *buf); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/sys/wait.h b/usr/sysroot/usr/include/sys/wait.h new file mode 100644 index 0000000..924a788 --- /dev/null +++ b/usr/sysroot/usr/include/sys/wait.h @@ -0,0 +1,19 @@ +#ifndef _SYS_WAIT_H +#define _SYS_WAIT_H + +#include + +#define WNOHANG 0x1 +#define WUNTRACED 0x2 + +#define WIFEXITED(s) (((s) & 0x7F) == 0) +#define WEXITSTATUS(s) (((s) >> 8) & 0xFF) +#define WIFSIGNALED(s) (((s) & 0x7F) != 0 && ((s) & 0x7F) != 0x7F) +#define WTERMSIG(s) ((s) & 0x7F) +#define WIFSTOPPED(s) (((s) & 0xFF) == 0x7F) +#define WSTOPSIG(s) (((s) >> 8) & 0xFF) + +pid_t wait(int *status); +pid_t waitpid(pid_t pid, int *status, int options); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/termios.h b/usr/sysroot/usr/include/termios.h new file mode 100644 index 0000000..6839057 --- /dev/null +++ b/usr/sysroot/usr/include/termios.h @@ -0,0 +1,58 @@ +#ifndef _TERMIOS_H +#define _TERMIOS_H + +#include + +typedef unsigned int tcflag_t; +typedef unsigned char cc_t; +typedef unsigned int speed_t; + +#define NCCS 32 + +struct termios { + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_cflag; + tcflag_t c_lflag; + cc_t c_cc[NCCS]; +}; + +#define BRKINT 0x0001 +#define ICRNL 0x0002 +#define INPCK 0x0004 +#define ISTRIP 0x0008 +#define IXON 0x0010 + +#define OPOST 0x0001 + +#define CS8 0x0030 + +#define ISIG 0x0001 +#define ICANON 0x0002 +#define ECHO 0x0008 +#define ECHOE 0x0010 +#define ECHOK 0x0020 +#define ECHONL 0x0040 +#define IEXTEN 0x0100 + +#define VMIN 6 +#define VTIME 5 +#define VINTR 0 +#define VQUIT 1 +#define VERASE 2 +#define VKILL 3 +#define VEOF 4 + +#define TCSANOW 0 +#define TCSADRAIN 1 +#define TCSAFLUSH 2 + +#define TCGETS 0x5401 +#define TCSETS 0x5402 +#define TCSETSW 0x5403 +#define TCSETSF 0x5404 + +int tcgetattr(int fd, struct termios *t); +int tcsetattr(int fd, int optional_actions, const struct termios *t); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/time.h b/usr/sysroot/usr/include/time.h new file mode 100644 index 0000000..a6fa584 --- /dev/null +++ b/usr/sysroot/usr/include/time.h @@ -0,0 +1,47 @@ +#ifndef _TIME_H +#define _TIME_H + +#include +#include + +typedef long clock_t; + +#define CLOCKS_PER_SEC 1000000L + +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +struct timespec { + time_t tv_sec; + long tv_nsec; +}; + +time_t time(time_t *t); +clock_t clock(void); + +struct tm *localtime(const time_t *t); +struct tm *gmtime(const time_t *t); +time_t mktime(struct tm *tm); + +size_t strftime(char *s, size_t max, const char *fmt, const struct tm *tm); +char *asctime(const struct tm *tm); +char *ctime(const time_t *t); + +int nanosleep(const struct timespec *req, struct timespec *rem); + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 +int clock_gettime(int clk, struct timespec *tp); + +static inline long difftime_l(time_t a, time_t b) { return (long)(a - b); } + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/include/unistd.h b/usr/sysroot/usr/include/unistd.h new file mode 100644 index 0000000..0716e18 --- /dev/null +++ b/usr/sysroot/usr/include/unistd.h @@ -0,0 +1,68 @@ +#ifndef _UNISTD_H +#define _UNISTD_H + +#include +#include + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 + +ssize_t read(int fd, void *buf, size_t n); +ssize_t write(int fd, const void *buf, size_t n); +int close(int fd); +off_t lseek(int fd, off_t off, int whence); +int dup(int fd); +int dup2(int oldfd, int newfd); +int pipe(int fds[2]); +int unlink(const char *path); +int rmdir(const char *path); +int access(const char *path, int mode); +int chdir(const char *path); +int fchdir(int fd); +char *getcwd(char *buf, size_t size); + +int symlink(const char *target, const char *linkpath); +ssize_t readlink(const char *path, char *buf, size_t bufsiz); + +pid_t getpid(void); +pid_t getppid(void); +uid_t getuid(void); +gid_t getgid(void); +int setuid(uid_t uid); +int setgid(gid_t gid); +pid_t fork(void); +int execve(const char *path, char *const argv[], char *const envp[]); +int execv(const char *path, char *const argv[]); +int execvp(const char *file, char *const argv[]); +void _exit(int status) __attribute__((noreturn)); + +unsigned int sleep(unsigned int sec); +int usleep(unsigned int usec); + +void *sbrk(intptr_t increment); +int brk(void *addr); + +void sched_yield_cervus(void); +int sched_yield(void); + +int isatty(int fd); +long pathconf(const char *path, int name); +long fpathconf(int fd, int name); + +extern char *optarg; +extern int optind; +extern int optopt; +extern int opterr; +int getopt(int argc, char *const argv[], const char *optstring); + +#endif \ No newline at end of file diff --git a/usr/sysroot/usr/share/cervus/README.sysroot b/usr/sysroot/usr/share/cervus/README.sysroot new file mode 100644 index 0000000..cfbef02 --- /dev/null +++ b/usr/sysroot/usr/share/cervus/README.sysroot @@ -0,0 +1 @@ +Hello from sysroot! diff --git a/wallpapers/cervus1024x768.png b/wallpapers/cervus1024x768.png new file mode 100644 index 0000000..a96e0a3 Binary files /dev/null and b/wallpapers/cervus1024x768.png differ diff --git a/wallpapers/cervus1280x720.png b/wallpapers/cervus1280x720.png new file mode 100644 index 0000000..afcdd44 Binary files /dev/null and b/wallpapers/cervus1280x720.png differ diff --git a/wallpapers/cervus1280x800.png b/wallpapers/cervus1280x800.png new file mode 100644 index 0000000..62efb4c Binary files /dev/null and b/wallpapers/cervus1280x800.png differ diff --git a/wallpapers/cervus1366x768.png b/wallpapers/cervus1366x768.png new file mode 100644 index 0000000..62f9586 Binary files /dev/null and b/wallpapers/cervus1366x768.png differ diff --git a/wallpapers/cervus1600x900.png b/wallpapers/cervus1600x900.png new file mode 100644 index 0000000..2dea10d Binary files /dev/null and b/wallpapers/cervus1600x900.png differ diff --git a/wallpapers/cervus1920x1080.png b/wallpapers/cervus1920x1080.png new file mode 100644 index 0000000..c596809 Binary files /dev/null and b/wallpapers/cervus1920x1080.png differ diff --git a/wallpapers/cervus800x600.png b/wallpapers/cervus800x600.png new file mode 100644 index 0000000..c8d858b Binary files /dev/null and b/wallpapers/cervus800x600.png differ diff --git a/wallpapers/cervus_logo.jpg b/wallpapers/cervus_logo.jpg new file mode 100644 index 0000000..b61d5ce Binary files /dev/null and b/wallpapers/cervus_logo.jpg differ