├── .cargo └── config.toml ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── rust-toolchain.toml ├── tests ├── .gitignore ├── Cargo.toml ├── multiboot1 │ ├── Makefile │ ├── README.md │ ├── boot.S │ ├── kernel.c │ ├── multiboot.h │ └── towboot.toml ├── multiboot2 │ ├── Makefile │ ├── README.md │ ├── boot.S │ ├── kernel.c │ ├── multiboot2.h │ └── towboot.toml ├── multiboot2_x64 │ ├── Makefile │ ├── README.md │ ├── boot.S │ ├── kernel.c │ ├── multiboot2.h │ └── towboot.toml └── src │ └── lib.rs ├── towboot.toml ├── towboot ├── Cargo.toml ├── build.rs └── src │ ├── boot │ ├── config_tables.rs │ ├── elf.rs │ ├── mod.rs │ └── video.rs │ ├── config.rs │ ├── file.rs │ ├── main.rs │ ├── mem.rs │ └── menu.rs ├── towboot_config ├── Cargo.toml └── src │ ├── config.rs │ ├── lib.rs │ └── options.rs ├── towboot_ia32 ├── Cargo.toml └── src │ └── lib.rs ├── towboot_x64 ├── Cargo.toml └── src │ └── lib.rs ├── towbootctl ├── Cargo.toml ├── build.rs └── src │ ├── bochs.rs │ ├── config.rs │ ├── firmware.rs │ ├── image.rs │ ├── lib.rs │ └── main.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | 4 | [unstable] 5 | bindeps = true 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | jobs: 3 | test: 4 | # Make sure that everything works on the newest Ubuntu. 5 | runs-on: ubuntu-24.04 6 | steps: 7 | - uses: actions/checkout@v4 8 | - name: Install Rust 9 | uses: dtolnay/rust-toolchain@nightly 10 | - name: Cache Cargo 11 | uses: Swatinem/rust-cache@v2 12 | with: 13 | shared-key: cargo-${{ hashFiles('**/Cargo.lock') }} 14 | cache-all-crates: true 15 | cache-on-failure: true 16 | - name: Install qemu 17 | run: sudo apt-get update && sudo apt-get install -y --no-install-recommends qemu-system-x86 18 | - name: Enable KVM group perms 19 | run: | 20 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 21 | sudo udevadm control --reload-rules 22 | sudo udevadm trigger --name-match=kvm 23 | # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ 24 | - name: Run tests 25 | uses: clechasseur/rs-cargo@v2 26 | with: 27 | command: test 28 | args: --package tests 29 | build: 30 | # Applications linked against glibc only work on that version of glibc 31 | # (or newer ones), so this is effectively the oldest version the release 32 | # is going to run on. 33 | # In theory, this should be the oldest supported version of Ubuntu 34 | # according to , 35 | # but Ubuntu 20.04 has OpenSSL 1.1 which newer ones don't have, so we need 36 | # to go with at least 22.04. 37 | runs-on: ubuntu-22.04 38 | needs: test 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Install Rust 42 | uses: dtolnay/rust-toolchain@nightly 43 | - name: Cache Cargo 44 | uses: Swatinem/rust-cache@v2 45 | with: 46 | shared-key: cargo-${{ hashFiles('**/Cargo.lock') }} 47 | cache-all-crates: true 48 | cache-on-failure: true 49 | - name: Build for i686 50 | uses: clechasseur/rs-cargo@v2 51 | with: 52 | command: build 53 | args: --package towboot --target i686-unknown-uefi 54 | - name: Upload i686 artifact 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: towboot-debug-i686.efi 58 | path: target/i686-unknown-uefi/debug/towboot.efi 59 | - name: Build for x86_64 60 | uses: clechasseur/rs-cargo@v2 61 | with: 62 | command: build 63 | args: --package towboot --target x86_64-unknown-uefi 64 | - name: Upload x86_64 artifact 65 | uses: actions/upload-artifact@v4 66 | with: 67 | name: towboot-debug-x86_64.efi 68 | path: target/x86_64-unknown-uefi/debug/towboot.efi 69 | - name: Build towbootctl for x86_64-linux 70 | uses: clechasseur/rs-cargo@v2 71 | with: 72 | command: build 73 | args: --package towbootctl --target x86_64-unknown-linux-gnu --features=binary 74 | - name: Upload x86_64-linux artifact 75 | uses: actions/upload-artifact@v4 76 | with: 77 | name: towbootctl-debug-x86_64-linux 78 | path: target/x86_64-unknown-linux-gnu/debug/towbootctl 79 | - name: Install compiler for x86_64-windows 80 | run: sudo apt-get update && sudo apt-get install gcc-mingw-w64-x86-64-win32 81 | - name: Install Rust for x86_64-windows 82 | uses: dtolnay/rust-toolchain@nightly 83 | with: 84 | targets: x86_64-pc-windows-gnu 85 | - name: Build towbootctl for x86_64-windows 86 | uses: clechasseur/rs-cargo@v2 87 | with: 88 | command: build 89 | args: --package towbootctl --target x86_64-pc-windows-gnu --features=binary 90 | - name: Upload x86_64-windows artifact 91 | uses: actions/upload-artifact@v4 92 | with: 93 | name: towbootctl-debug-x86_64-windows.exe 94 | path: target/x86_64-pc-windows-gnu/debug/towbootctl.exe 95 | build-mac: 96 | # We could cross-compile from Linux instead, 97 | # but we'd still need the Xcode Command Line Tools. 98 | # Downloading them requires logging in with an Apple ID, 99 | # which is not possible in the CI. The macOS runners include it. 100 | runs-on: macos-latest 101 | needs: test 102 | steps: 103 | - uses: actions/checkout@v4 104 | - name: Install Rust for x86_64-apple and aarch64-apple 105 | uses: dtolnay/rust-toolchain@nightly 106 | with: 107 | targets: x86_64-apple-darwin, aarch64-apple-darwin 108 | - name: Cache Cargo 109 | uses: Swatinem/rust-cache@v2 110 | with: 111 | shared-key: cargo-${{ hashFiles('**/Cargo.lock') }} 112 | cache-all-crates: true 113 | cache-on-failure: true 114 | - name: Build towbootctl for x86_64-apple 115 | uses: clechasseur/rs-cargo@v2 116 | with: 117 | command: build 118 | args: --package towbootctl --target x86_64-apple-darwin --features=binary 119 | - name: Build towbootctl for aarch64-apple 120 | uses: clechasseur/rs-cargo@v2 121 | with: 122 | command: build 123 | args: --package towbootctl --target aarch64-apple-darwin --features=binary 124 | - name: Build universal binary for macOS 125 | run: mkdir -p target/apple-darwin/debug && lipo -create -output target/apple-darwin/debug/towbootctl target/x86_64-apple-darwin/debug/towbootctl target/aarch64-apple-darwin/debug/towbootctl 126 | - name: Upload apple artifact 127 | uses: actions/upload-artifact@v4 128 | with: 129 | name: towbootctl-debug-macos 130 | path: target/apple-darwin/debug/towbootctl 131 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 'v*' 4 | jobs: 5 | test: 6 | # Make sure that everything works on the newest Ubuntu. 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Install qemu 11 | run: sudo apt-get update && sudo apt-get install -y --no-install-recommends qemu-system-x86 12 | - name: Enable KVM group perms 13 | run: | 14 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 15 | sudo udevadm control --reload-rules 16 | sudo udevadm trigger --name-match=kvm 17 | # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ 18 | - name: Run tests 19 | uses: clechasseur/rs-cargo@v2 20 | with: 21 | command: test 22 | args: --package tests --release 23 | release: 24 | # Applications linked against glibc only work on that version of glibc 25 | # (or newer ones), so this is effectively the oldest version the release 26 | # is going to run on. 27 | # In theory, this should be the oldest supported version of Ubuntu 28 | # according to , 29 | # but Ubuntu 20.04 has OpenSSL 1.1 which newer ones don't have, so we need 30 | # to go with at least 22.04. 31 | runs-on: ubuntu-22.04 32 | needs: test 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Build for i686 36 | uses: clechasseur/rs-cargo@v2 37 | with: 38 | command: build 39 | args: --package towboot --target i686-unknown-uefi --release 40 | - name: Build for x86_64 41 | uses: clechasseur/rs-cargo@v2 42 | with: 43 | command: build 44 | args: --package towboot --target x86_64-unknown-uefi --release 45 | - name: Build towbootctl for x86_64-linux 46 | uses: clechasseur/rs-cargo@v2 47 | with: 48 | command: build 49 | args: --package towbootctl --target x86_64-unknown-linux-gnu --features=binary --release 50 | - name: Install compiler for x86_64-windows 51 | run: sudo apt-get update && sudo apt-get install gcc-mingw-w64-x86-64-win32 52 | - name: Install Rust for x86_64-windows 53 | uses: dtolnay/rust-toolchain@nightly 54 | with: 55 | targets: x86_64-pc-windows-gnu 56 | - name: Build towbootctl for x86_64-windows 57 | uses: clechasseur/rs-cargo@v2 58 | with: 59 | command: build 60 | args: --package towbootctl --target x86_64-pc-windows-gnu --features=binary --release 61 | - name: Rename files (1) 62 | run: cp target/i686-unknown-uefi/release/towboot.efi towboot-$(git describe --always --tags)-i686.efi 63 | - name: Rename files (2) 64 | run: cp target/x86_64-unknown-uefi/release/towboot.efi towboot-$(git describe --always --tags)-x86_64.efi 65 | - name: Rename files (2) 66 | run: cp target/x86_64-unknown-linux-gnu/release/towbootctl towbootctl-$(git describe --always --tags)-x86_64-linux 67 | - name: Rename files (3) 68 | run: cp target/x86_64-pc-windows-gnu/release/towbootctl.exe towbootctl-$(git describe --always --tags)-x86_64-windows.exe 69 | - name: Publish release 70 | uses: softprops/action-gh-release@v2 71 | with: 72 | files: | 73 | towboot-*-i686.efi 74 | towboot-*-x86_64.efi 75 | towbootctl-*-x86_64-linux 76 | towbootctl-*-x86_64-windows.exe 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | release-macos: 80 | # We could cross-compile from Linux instead, 81 | # but we'd still need the Xcode Command Line Tools. 82 | # Downloading them requires logging in with an Apple ID, 83 | # which is not possible in the CI. The macOS runners include it. 84 | runs-on: macos-latest 85 | needs: release 86 | steps: 87 | - uses: actions/checkout@v4 88 | - name: Install Rust for x86_64-apple and aarch64-apple 89 | uses: dtolnay/rust-toolchain@nightly 90 | with: 91 | targets: x86_64-apple-darwin, aarch64-apple-darwin 92 | - name: Build towbootctl for x86_64-apple 93 | uses: clechasseur/rs-cargo@v2 94 | with: 95 | command: build 96 | args: --package towbootctl --target x86_64-apple-darwin --features=binary --release 97 | - name: Build towbootctl for aarch64-apple 98 | uses: clechasseur/rs-cargo@v2 99 | with: 100 | command: build 101 | args: --package towbootctl --target aarch64-apple-darwin --features=binary --release 102 | - name: Build universal binary for macOS 103 | run: mkdir -p target/apple-darwin/release && lipo -create -output target/apple-darwin/release/towbootctl target/x86_64-apple-darwin/release/towbootctl target/aarch64-apple-darwin/release/towbootctl 104 | - name: Upload macOS binary to release 105 | uses: svenstaro/upload-release-action@v2 106 | with: 107 | repo_token: ${{ secrets.GITHUB_TOKEN }} 108 | file: target/apple-darwin/release/towbootctl 109 | asset_name: towbootctl-${{ github.ref_name }}-macos 110 | tag: ${{ github.ref }} 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/rust,clion 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust,clion 4 | 5 | ### CLion ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # AWS User-specific 17 | .idea/**/aws.xml 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | # .idea/artifacts 40 | # .idea/compiler.xml 41 | # .idea/jarRepositories.xml 42 | # .idea/modules.xml 43 | # .idea/*.iml 44 | # .idea/modules 45 | # *.iml 46 | # *.ipr 47 | 48 | # CMake 49 | cmake-build-*/ 50 | 51 | # Mongo Explorer plugin 52 | .idea/**/mongoSettings.xml 53 | 54 | # File-based project format 55 | *.iws 56 | 57 | # IntelliJ 58 | out/ 59 | 60 | # mpeltonen/sbt-idea plugin 61 | .idea_modules/ 62 | 63 | # JIRA plugin 64 | atlassian-ide-plugin.xml 65 | 66 | # Cursive Clojure plugin 67 | .idea/replstate.xml 68 | 69 | # Crashlytics plugin (for Android Studio and IntelliJ) 70 | com_crashlytics_export_strings.xml 71 | crashlytics.properties 72 | crashlytics-build.properties 73 | fabric.properties 74 | 75 | # Editor-based Rest Client 76 | .idea/httpRequests 77 | 78 | # Android studio 3.1+ serialized cache file 79 | .idea/caches/build_file_checksums.ser 80 | 81 | ### CLion Patch ### 82 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 83 | 84 | # *.iml 85 | # modules.xml 86 | # .idea/misc.xml 87 | # *.ipr 88 | 89 | # Sonarlint plugin 90 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 91 | .idea/**/sonarlint/ 92 | 93 | # SonarQube Plugin 94 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 95 | .idea/**/sonarIssues.xml 96 | 97 | # Markdown Navigator plugin 98 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 99 | .idea/**/markdown-navigator.xml 100 | .idea/**/markdown-navigator-enh.xml 101 | .idea/**/markdown-navigator/ 102 | 103 | # Cache file creation bug 104 | # See https://youtrack.jetbrains.com/issue/JBR-2257 105 | .idea/$CACHE_FILE$ 106 | 107 | # CodeStream plugin 108 | # https://plugins.jetbrains.com/plugin/12206-codestream 109 | .idea/codestream.xml 110 | 111 | ### Rust ### 112 | # Generated by Cargo 113 | # will have compiled files and executables 114 | debug/ 115 | target/ 116 | 117 | # These are backup files generated by rustfmt 118 | **/*.rs.bk 119 | 120 | # MSVC Windows builds of rustc generate these, which store debugging information 121 | *.pdb 122 | 123 | # End of https://www.toptal.com/developers/gitignore/api/rust,clion 124 | 125 | /mkgpt 126 | image.img 127 | part.img 128 | OVMF.fd 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "towboot", 5 | "towboot_ia32", 6 | "towboot_x64", 7 | "xtask", 8 | "towboot_config", 9 | "towbootctl", 10 | "tests", 11 | ] 12 | 13 | [workspace.package] 14 | # these apply to all crates 15 | version = "0.9.4" 16 | authors = ["Niklas Sombert "] 17 | license = "MPL-2.0" 18 | repository = "https://github.com/hhuOS/towboot" 19 | 20 | [profile.release] 21 | # these settings are only really interesting for towbootctl, 22 | # but lto can't be set for just one crate 23 | strip = true 24 | lto = true 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # towboot 2 | 3 | a bootloader for Multiboot kernels (version 1 and version 2) on UEFI systems 4 | 5 | ## usage 6 | 7 | towboot is a UEFI application. If you're developing an operating system or just 8 | want to create a boot medium, there are several possible options for where to 9 | place towboot and its configuration: 10 | 11 | ### removable media 12 | 13 | This is the easiest one: It works for all architectures and requires no 14 | configuration of the system. 15 | Simply place the 32-bit build at `\EFI\boot\bootia32.efi`, the 64-bit build at 16 | `\EFI\boot\bootx64.efi` and a configuration file at `\towboot.toml` on the ESP. 17 | 18 | You can also use the provided `towbootctl` binary to do this. 19 | 20 | ```sh 21 | towbootctl install --removable -- -config towboot.toml 22 | ``` 23 | 24 | This will parse the configuration file and copy the configuration itself, 25 | the referenced kernels and modules and towboot binaries for 32-bit and 64-bit 26 | to the target directory. 27 | 28 | ### installed system 29 | 30 | Place an appropriate build at `\EFI\yourOS\towboot.efi` and the configuration 31 | at `\EFI\yourOS\towboot.toml` on the ESP and add a boot option for 32 | `\EFI\yourOS\towboot.efi -c \EFI\yourOS\towboot.toml`. 33 | 34 | towbootctl can help you a bit with this: 35 | 36 | ```sh 37 | towbootctl install --name yourOS -- -config towboot.toml 38 | ``` 39 | 40 | (You can also configure towboot just with command line arguments instead of 41 | using a configuration file; see below.) 42 | 43 | ### image 44 | 45 | If you're not installing to physical media but instead want to create an image, 46 | towbootctl can do this as well: 47 | 48 | ```sh 49 | towbootctl image --target yourOS.img -- -config towboot.toml 50 | towbootctl boot-image --image yourOS.img 51 | ``` 52 | 53 | ### chainloading from another bootloader 54 | 55 | If you already have a bootloader capable of loading UEFI applications but 56 | without support for Multiboot, you can add an entry like 57 | `towboot.efi -kernel "mykernel.elf quiet" -module "initramfs.img initrd"`. 58 | 59 | (You can use a configuration file instead of passing the information directly 60 | on the command line; see above.) 61 | 62 | ### paths 63 | 64 | Paths given in a configuration file or on the command line are interpreted as 65 | follows: 66 | * absolute if they start with a volume identifier (`fs?:`) 67 | * relative to the volume towboot itself is on if they start with a backslash (`\`) 68 | * relative to the configuration file 69 | 70 | Paths relative to the UEFI shell's current working directory are not supported, yet. 71 | 72 | Paths for kernel and modules given on the commandline can't contain spaces, 73 | use a configuration file for this. 74 | 75 | ### quirks 76 | 77 | You can override some specifics of how the kernel is loaded at runtime by 78 | adding quirks. They can be configured either in the `quirk` key of a kernel 79 | entry (if the kernel is loaded via a configuration file) or via the `-quirk` 80 | command line option (if the kernel is loaded via `-kernel`). 81 | 82 | Available quirks are: 83 | 84 | * `DontExitBootServices`: do not exit Boot Services 85 | This starts the kernel with more privileges and less available memory. 86 | In some cases this might also display more helpful error messages. 87 | * `ForceElf`: always treat the kernel as an ELF file 88 | * `ForceOverwrite`: ignore the memory map when loading the kernel 89 | (This might damage your hardware!) 90 | * `KeepResolution`: ignore the kernel's preferred resolution 91 | * `ModulesBelow200Mb`: keep allocations for modules below 200 MB 92 | 93 | ## development 94 | 95 | If you want to compile towboot yourself, here are the instructions: 96 | 97 | ### dependencies 98 | 99 | You'll need a nightly Rust compiler. 100 | The version doesn't really matter, 101 | though `1.88.0-nightly (6bc57c6bf 2025-04-22)` definitely works. 102 | If you don't know how to install one, 103 | please take a look at [rustup.rs](https://rustup.rs/). 104 | 105 | To boot the disk image in a virtual machine, QEMU is recommended. 106 | You'll need OVMF for that, too, but the build script downloads it by itself. 107 | 108 | ### building 109 | 110 | ```sh 111 | cargo build --package towboot 112 | ``` 113 | 114 | creates a `towboot.efi` file inside the `target` folder. 115 | By default, this is a debug build for `i686-unknown-uefi`. 116 | You can change this by appending `--release` 117 | or by setting `--target x86_64_unknown_uefi` (for example). 118 | 119 | Running `cargo xtask build` will do that and also create a disk image, 120 | so just may just want to run this. To boot the resulting image with QEMU, 121 | you can use `cargo xtask boot-image`. 122 | 123 | You can configure whether to create a `debug` or `release` build for 124 | either `i686` or `x86_64`, whether to enable KVM or wait for a GDB to attach 125 | by specifying command line options. 126 | 127 | You can also run towbootctl directly from the source directory (building it will 128 | also build towboot, in turn): 129 | 130 | ```sh 131 | cargo run --package towbootctl --features binary 132 | ``` 133 | 134 | ### running the tests 135 | 136 | The integration tests can be run with: 137 | 138 | ```sh 139 | cargo test --package tests 140 | ``` 141 | 142 | ## project structure 143 | 144 | This project is a Cargo workspace consisting of the multiple packages. 145 | More documentation for each of them is available by running: 146 | 147 | ```sh 148 | cargo doc --package --open 149 | ``` 150 | 151 | ### towboot 152 | 153 | This is the actual bootloader. 154 | 155 | ### towboot_config 156 | 157 | This is a library containing the configuration structs. 158 | It is used by towboot and towbootctl. 159 | 160 | ### towboot_ia32 / towboot_x64 161 | 162 | These are dummy crates that just exists to provide the towboot binary in library form. 163 | 164 | ### towbootctl 165 | 166 | This is both a library and a command line utility that can create images, 167 | install towboot to disk, and so on. 168 | 169 | ### tests 170 | 171 | This contains the integration tests. 172 | 173 | ### xtask 174 | 175 | This contains build tooling. 176 | 177 | ## contributing 178 | 179 | This project follows the usual GitHub workflow consisting of fork, pull request 180 | and merge. If you don't have a GitHub account, you can also send patches per 181 | e-mail. 182 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | 4 | targets = [ "i686-unknown-uefi", "x86_64-unknown-uefi" ] 5 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | kernel 3 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | edition = "2024" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | anyhow = "1.0" 11 | ctor = "0.4" 12 | tempfile = "3.8" 13 | env_logger = { version = "0.11", default-features = false, features = ["auto-color"] } 14 | towboot_ia32 = { path = "../towboot_ia32" } 15 | towboot_x64 = { path = "../towboot_x64" } 16 | 17 | towbootctl = { path = "../towbootctl" } 18 | -------------------------------------------------------------------------------- /tests/multiboot1/Makefile: -------------------------------------------------------------------------------- 1 | SHARED_FLAGS=-nostdinc -fno-builtin -m32 -ffreestanding -no-pie 2 | CFLAGS=$(SHARED_FLAGS) 3 | ASFLAGS=$(SHARED_FLAGS) 4 | LDFLAGS=-nostdlib 5 | 6 | kernel: boot.o 7 | -------------------------------------------------------------------------------- /tests/multiboot1/README.md: -------------------------------------------------------------------------------- 1 | # multiboot1 example kernel 2 | 3 | This is the example kernel taken from [the Multiboot specification](https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Example-OS-code). 4 | 5 | It has been slighlty altered to print to the serial output (see the Git commits). 6 | -------------------------------------------------------------------------------- /tests/multiboot1/boot.S: -------------------------------------------------------------------------------- 1 | /* boot.S - bootstrap the kernel */ 2 | /* Copyright (C) 1999, 2001, 2010 Free Software Foundation, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #define ASM_FILE 1 19 | #include "multiboot.h" 20 | 21 | /* C symbol format. HAVE_ASM_USCORE is defined by configure. */ 22 | #ifdef HAVE_ASM_USCORE 23 | # define EXT_C(sym) _ ## sym 24 | #else 25 | # define EXT_C(sym) sym 26 | #endif 27 | 28 | /* The size of our stack (16KB). */ 29 | #define STACK_SIZE 0x4000 30 | 31 | /* The flags for the Multiboot header. */ 32 | #ifdef __ELF__ 33 | # define AOUT_KLUDGE 0 34 | #else 35 | # define AOUT_KLUDGE MULTIBOOT_AOUT_KLUDGE 36 | #endif 37 | #define MULTIBOOT_HEADER_FLAGS MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_VIDEO_MODE | AOUT_KLUDGE 38 | 39 | .text 40 | 41 | .globl start, _start 42 | start: 43 | _start: 44 | jmp multiboot_entry 45 | 46 | /* Align 32 bits boundary. */ 47 | .align 4 48 | 49 | /* Multiboot header. */ 50 | multiboot_header: 51 | /* magic */ 52 | .long MULTIBOOT_HEADER_MAGIC 53 | /* flags */ 54 | .long MULTIBOOT_HEADER_FLAGS 55 | /* checksum */ 56 | .long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) 57 | #ifndef __ELF__ 58 | /* header_addr */ 59 | .long multiboot_header 60 | /* load_addr */ 61 | .long _start 62 | /* load_end_addr */ 63 | .long _edata 64 | /* bss_end_addr */ 65 | .long _end 66 | /* entry_addr */ 67 | .long multiboot_entry 68 | #else /* ! __ELF__ */ 69 | .long 0 70 | .long 0 71 | .long 0 72 | .long 0 73 | .long 0 74 | #endif /* __ELF__ */ 75 | .long 0 76 | .long 1024 77 | .long 768 78 | .long 32 79 | 80 | multiboot_entry: 81 | /* Initialize the stack pointer. */ 82 | movl $(stack + STACK_SIZE), %esp 83 | 84 | /* Reset EFLAGS. */ 85 | pushl $0 86 | popf 87 | 88 | /* Push the pointer to the Multiboot information structure. */ 89 | pushl %ebx 90 | /* Push the magic value. */ 91 | pushl %eax 92 | 93 | /* Now enter the C main function... */ 94 | call EXT_C(cmain) 95 | 96 | /* Halt. */ 97 | pushl $halt_message 98 | call EXT_C(printf) 99 | 100 | loop: hlt 101 | jmp loop 102 | 103 | halt_message: 104 | .asciz "Halted." 105 | 106 | /* Our stack area. */ 107 | .comm stack, STACK_SIZE 108 | -------------------------------------------------------------------------------- /tests/multiboot1/kernel.c: -------------------------------------------------------------------------------- 1 | /* kernel.c - the C part of the kernel */ 2 | /* Copyright (C) 1999, 2010 Free Software Foundation, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "multiboot.h" 19 | 20 | /* Macros. */ 21 | 22 | /* Check if the bit BIT in FLAGS is set. */ 23 | #define CHECK_FLAG(flags,bit) ((flags) & (1 << (bit))) 24 | #define SER(v) __asm__("outb %b0, %w1"::"a" (v), "d"(0x3f8)) 25 | 26 | /* Some screen stuff. */ 27 | /* The number of columns. */ 28 | #define COLUMNS 80 29 | /* The number of lines. */ 30 | #define LINES 24 31 | /* The attribute of an character. */ 32 | #define ATTRIBUTE 7 33 | /* The video memory address. */ 34 | #define VIDEO 0xB8000 35 | 36 | /* Variables. */ 37 | /* Save the X position. */ 38 | static int xpos; 39 | /* Save the Y position. */ 40 | static int ypos; 41 | /* Point to the video memory. */ 42 | static volatile unsigned char *video; 43 | 44 | /* Forward declarations. */ 45 | void cmain (unsigned long magic, unsigned long addr); 46 | static void cls (void); 47 | static void itoa (char *buf, int base, int d); 48 | static void putchar (int c); 49 | void printf (const char *format, ...); 50 | 51 | /* Check if MAGIC is valid and print the Multiboot information structure 52 | pointed by ADDR. */ 53 | void 54 | cmain (unsigned long magic, unsigned long addr) 55 | { 56 | multiboot_info_t *mbi; 57 | 58 | /* Clear the screen. */ 59 | cls (); 60 | 61 | /* Am I booted by a Multiboot-compliant boot loader? */ 62 | if (magic != MULTIBOOT_BOOTLOADER_MAGIC) 63 | { 64 | printf ("Invalid magic number: 0x%x\n", (unsigned) magic); 65 | return; 66 | } 67 | 68 | /* Set MBI to the address of the Multiboot information structure. */ 69 | mbi = (multiboot_info_t *) addr; 70 | 71 | /* Print out the flags. */ 72 | printf ("flags = 0x%x\n", (unsigned) mbi->flags); 73 | 74 | /* Are mem_* valid? */ 75 | if (CHECK_FLAG (mbi->flags, 0)) 76 | printf ("mem_lower = %uKB, mem_upper = %uKB\n", 77 | (unsigned) mbi->mem_lower, (unsigned) mbi->mem_upper); 78 | 79 | /* Is boot_device valid? */ 80 | if (CHECK_FLAG (mbi->flags, 1)) 81 | printf ("boot_device = 0x%x\n", (unsigned) mbi->boot_device); 82 | 83 | /* Is the command line passed? */ 84 | if (CHECK_FLAG (mbi->flags, 2)) 85 | printf ("cmdline = %s\n", (char *) mbi->cmdline); 86 | 87 | /* Are mods_* valid? */ 88 | if (CHECK_FLAG (mbi->flags, 3)) 89 | { 90 | multiboot_module_t *mod; 91 | int i; 92 | 93 | printf ("mods_count = %d, mods_addr = 0x%x\n", 94 | (int) mbi->mods_count, (int) mbi->mods_addr); 95 | for (i = 0, mod = (multiboot_module_t *) mbi->mods_addr; 96 | i < mbi->mods_count; 97 | i++, mod++) 98 | printf (" mod_start = 0x%x, mod_end = 0x%x, cmdline = %s\n", 99 | (unsigned) mod->mod_start, 100 | (unsigned) mod->mod_end, 101 | (char *) mod->cmdline); 102 | } 103 | 104 | /* Bits 4 and 5 are mutually exclusive! */ 105 | if (CHECK_FLAG (mbi->flags, 4) && CHECK_FLAG (mbi->flags, 5)) 106 | { 107 | printf ("Both bits 4 and 5 are set.\n"); 108 | return; 109 | } 110 | 111 | /* Is the symbol table of a.out valid? */ 112 | if (CHECK_FLAG (mbi->flags, 4)) 113 | { 114 | multiboot_aout_symbol_table_t *multiboot_aout_sym = &(mbi->u.aout_sym); 115 | 116 | printf ("multiboot_aout_symbol_table: tabsize = 0x%0x, " 117 | "strsize = 0x%x, addr = 0x%x\n", 118 | (unsigned) multiboot_aout_sym->tabsize, 119 | (unsigned) multiboot_aout_sym->strsize, 120 | (unsigned) multiboot_aout_sym->addr); 121 | } 122 | 123 | /* Is the section header table of ELF valid? */ 124 | if (CHECK_FLAG (mbi->flags, 5)) 125 | { 126 | multiboot_elf_section_header_table_t *multiboot_elf_sec = &(mbi->u.elf_sec); 127 | 128 | printf ("multiboot_elf_sec: num = %u, size = 0x%x," 129 | " addr = 0x%x, shndx = 0x%x\n", 130 | (unsigned) multiboot_elf_sec->num, (unsigned) multiboot_elf_sec->size, 131 | (unsigned) multiboot_elf_sec->addr, (unsigned) multiboot_elf_sec->shndx); 132 | } 133 | 134 | /* Are mmap_* valid? */ 135 | if (CHECK_FLAG (mbi->flags, 6)) 136 | { 137 | multiboot_memory_map_t *mmap; 138 | 139 | printf ("mmap_addr = 0x%x, mmap_length = 0x%x\n", 140 | (unsigned) mbi->mmap_addr, (unsigned) mbi->mmap_length); 141 | for (mmap = (multiboot_memory_map_t *) mbi->mmap_addr; 142 | (unsigned long) mmap < mbi->mmap_addr + mbi->mmap_length; 143 | mmap = (multiboot_memory_map_t *) ((unsigned long) mmap 144 | + mmap->size + sizeof (mmap->size))) 145 | printf (" size = 0x%x, base_addr = 0x%x%08x," 146 | " length = 0x%x%08x, type = 0x%x\n", 147 | (unsigned) mmap->size, 148 | (unsigned) (mmap->addr >> 32), 149 | (unsigned) (mmap->addr & 0xffffffff), 150 | (unsigned) (mmap->len >> 32), 151 | (unsigned) (mmap->len & 0xffffffff), 152 | (unsigned) mmap->type); 153 | } 154 | 155 | /* Is the bootloader name passed? */ 156 | if (CHECK_FLAG (mbi->flags, 9)) 157 | printf ("boot_loader_name = %s\n", (char *) mbi->boot_loader_name); 158 | 159 | /* Draw diagonal blue line. */ 160 | if (CHECK_FLAG (mbi->flags, 12)) 161 | { 162 | multiboot_uint32_t color; 163 | unsigned i; 164 | void *fb = (void *) (unsigned long) mbi->framebuffer_addr; 165 | 166 | switch (mbi->framebuffer_type) 167 | { 168 | case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED: 169 | { 170 | unsigned best_distance, distance; 171 | struct multiboot_color *palette; 172 | 173 | palette = (struct multiboot_color *) mbi->framebuffer_palette_addr; 174 | 175 | color = 0; 176 | best_distance = 4*256*256; 177 | 178 | for (i = 0; i < mbi->framebuffer_palette_num_colors; i++) 179 | { 180 | distance = (0xff - palette[i].blue) * (0xff - palette[i].blue) 181 | + palette[i].red * palette[i].red 182 | + palette[i].green * palette[i].green; 183 | if (distance < best_distance) 184 | { 185 | color = i; 186 | best_distance = distance; 187 | } 188 | } 189 | } 190 | break; 191 | 192 | case MULTIBOOT_FRAMEBUFFER_TYPE_RGB: 193 | color = ((1 << mbi->framebuffer_blue_mask_size) - 1) 194 | << mbi->framebuffer_blue_field_position; 195 | break; 196 | 197 | case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT: 198 | color = '\\' | 0x0100; 199 | break; 200 | 201 | default: 202 | color = 0xffffffff; 203 | break; 204 | } 205 | for (i = 0; i < mbi->framebuffer_width 206 | && i < mbi->framebuffer_height; i++) 207 | { 208 | switch (mbi->framebuffer_bpp) 209 | { 210 | case 8: 211 | { 212 | multiboot_uint8_t *pixel = fb + mbi->framebuffer_pitch * i + i; 213 | *pixel = color; 214 | } 215 | break; 216 | case 15: 217 | case 16: 218 | { 219 | multiboot_uint16_t *pixel 220 | = fb + mbi->framebuffer_pitch * i + 2 * i; 221 | *pixel = color; 222 | } 223 | break; 224 | case 24: 225 | { 226 | multiboot_uint32_t *pixel 227 | = fb + mbi->framebuffer_pitch * i + 3 * i; 228 | *pixel = (color & 0xffffff) | (*pixel & 0xff000000); 229 | } 230 | break; 231 | 232 | case 32: 233 | { 234 | multiboot_uint32_t *pixel 235 | = fb + mbi->framebuffer_pitch * i + 4 * i; 236 | *pixel = color; 237 | } 238 | break; 239 | } 240 | } 241 | } 242 | 243 | } 244 | 245 | /* Clear the screen and initialize VIDEO, XPOS and YPOS. */ 246 | static void 247 | cls (void) 248 | { 249 | int i; 250 | 251 | video = (unsigned char *) VIDEO; 252 | 253 | for (i = 0; i < COLUMNS * LINES * 2; i++) 254 | *(video + i) = 0; 255 | 256 | xpos = 0; 257 | ypos = 0; 258 | } 259 | 260 | /* Convert the integer D to a string and save the string in BUF. If 261 | BASE is equal to ’d’, interpret that D is decimal, and if BASE is 262 | equal to ’x’, interpret that D is hexadecimal. */ 263 | static void 264 | itoa (char *buf, int base, int d) 265 | { 266 | char *p = buf; 267 | char *p1, *p2; 268 | unsigned long ud = d; 269 | int divisor = 10; 270 | 271 | /* If %d is specified and D is minus, put ‘-’ in the head. */ 272 | if (base == 'd' && d < 0) 273 | { 274 | *p++ = '-'; 275 | buf++; 276 | ud = -d; 277 | } 278 | else if (base == 'x') 279 | divisor = 16; 280 | 281 | /* Divide UD by DIVISOR until UD == 0. */ 282 | do 283 | { 284 | int remainder = ud % divisor; 285 | 286 | *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10; 287 | } 288 | while (ud /= divisor); 289 | 290 | /* Terminate BUF. */ 291 | *p = 0; 292 | 293 | /* Reverse BUF. */ 294 | p1 = buf; 295 | p2 = p - 1; 296 | while (p1 < p2) 297 | { 298 | char tmp = *p1; 299 | *p1 = *p2; 300 | *p2 = tmp; 301 | p1++; 302 | p2--; 303 | } 304 | } 305 | 306 | /* Put the character C on the screen. */ 307 | static void 308 | putchar (int c) 309 | { 310 | // serial port 311 | SER(c & 0xff); 312 | 313 | if (c == '\n' || c == '\r') 314 | { 315 | newline: 316 | SER('\r' & 0xff); 317 | xpos = 0; 318 | ypos++; 319 | if (ypos >= LINES) 320 | ypos = 0; 321 | return; 322 | } 323 | 324 | *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF; 325 | *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE; 326 | 327 | xpos++; 328 | if (xpos >= COLUMNS) 329 | goto newline; 330 | } 331 | 332 | /* Format a string and print it on the screen, just like the libc 333 | function printf. */ 334 | void 335 | printf (const char *format, ...) 336 | { 337 | char **arg = (char **) &format; 338 | int c; 339 | char buf[20]; 340 | 341 | arg++; 342 | 343 | while ((c = *format++) != 0) 344 | { 345 | if (c != '%') 346 | putchar (c); 347 | else 348 | { 349 | char *p, *p2; 350 | int pad0 = 0, pad = 0; 351 | 352 | c = *format++; 353 | if (c == '0') 354 | { 355 | pad0 = 1; 356 | c = *format++; 357 | } 358 | 359 | if (c >= '0' && c <= '9') 360 | { 361 | pad = c - '0'; 362 | c = *format++; 363 | } 364 | 365 | switch (c) 366 | { 367 | case 'd': 368 | case 'u': 369 | case 'x': 370 | itoa (buf, c, *((int *) arg++)); 371 | p = buf; 372 | goto string; 373 | break; 374 | 375 | case 's': 376 | p = *arg++; 377 | if (! p) 378 | p = "(null)"; 379 | 380 | string: 381 | for (p2 = p; *p2; p2++); 382 | for (; p2 < p + pad; p2++) 383 | putchar (pad0 ? '0' : ' '); 384 | while (*p) 385 | putchar (*p++); 386 | break; 387 | 388 | default: 389 | putchar (*((int *) arg++)); 390 | break; 391 | } 392 | } 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /tests/multiboot1/multiboot.h: -------------------------------------------------------------------------------- 1 | /* multiboot.h - Multiboot header file. */ 2 | /* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY 17 | * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 19 | * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef MULTIBOOT_HEADER 23 | #define MULTIBOOT_HEADER 1 24 | 25 | /* How many bytes from the start of the file we search for the header. */ 26 | #define MULTIBOOT_SEARCH 8192 27 | #define MULTIBOOT_HEADER_ALIGN 4 28 | 29 | /* The magic field should contain this. */ 30 | #define MULTIBOOT_HEADER_MAGIC 0x1BADB002 31 | 32 | /* This should be in %eax. */ 33 | #define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 34 | 35 | /* Alignment of multiboot modules. */ 36 | #define MULTIBOOT_MOD_ALIGN 0x00001000 37 | 38 | /* Alignment of the multiboot info structure. */ 39 | #define MULTIBOOT_INFO_ALIGN 0x00000004 40 | 41 | /* Flags set in the ’flags’ member of the multiboot header. */ 42 | 43 | /* Align all boot modules on i386 page (4KB) boundaries. */ 44 | #define MULTIBOOT_PAGE_ALIGN 0x00000001 45 | 46 | /* Must pass memory information to OS. */ 47 | #define MULTIBOOT_MEMORY_INFO 0x00000002 48 | 49 | /* Must pass video information to OS. */ 50 | #define MULTIBOOT_VIDEO_MODE 0x00000004 51 | 52 | /* This flag indicates the use of the address fields in the header. */ 53 | #define MULTIBOOT_AOUT_KLUDGE 0x00010000 54 | 55 | /* Flags to be set in the ’flags’ member of the multiboot info structure. */ 56 | 57 | /* is there basic lower/upper memory information? */ 58 | #define MULTIBOOT_INFO_MEMORY 0x00000001 59 | /* is there a boot device set? */ 60 | #define MULTIBOOT_INFO_BOOTDEV 0x00000002 61 | /* is the command-line defined? */ 62 | #define MULTIBOOT_INFO_CMDLINE 0x00000004 63 | /* are there modules to do something with? */ 64 | #define MULTIBOOT_INFO_MODS 0x00000008 65 | 66 | /* These next two are mutually exclusive */ 67 | 68 | /* is there a symbol table loaded? */ 69 | #define MULTIBOOT_INFO_AOUT_SYMS 0x00000010 70 | /* is there an ELF section header table? */ 71 | #define MULTIBOOT_INFO_ELF_SHDR 0X00000020 72 | 73 | /* is there a full memory map? */ 74 | #define MULTIBOOT_INFO_MEM_MAP 0x00000040 75 | 76 | /* Is there drive info? */ 77 | #define MULTIBOOT_INFO_DRIVE_INFO 0x00000080 78 | 79 | /* Is there a config table? */ 80 | #define MULTIBOOT_INFO_CONFIG_TABLE 0x00000100 81 | 82 | /* Is there a boot loader name? */ 83 | #define MULTIBOOT_INFO_BOOT_LOADER_NAME 0x00000200 84 | 85 | /* Is there a APM table? */ 86 | #define MULTIBOOT_INFO_APM_TABLE 0x00000400 87 | 88 | /* Is there video information? */ 89 | #define MULTIBOOT_INFO_VBE_INFO 0x00000800 90 | #define MULTIBOOT_INFO_FRAMEBUFFER_INFO 0x00001000 91 | 92 | #ifndef ASM_FILE 93 | 94 | typedef unsigned char multiboot_uint8_t; 95 | typedef unsigned short multiboot_uint16_t; 96 | typedef unsigned int multiboot_uint32_t; 97 | typedef unsigned long long multiboot_uint64_t; 98 | 99 | struct multiboot_header 100 | { 101 | /* Must be MULTIBOOT_MAGIC - see above. */ 102 | multiboot_uint32_t magic; 103 | 104 | /* Feature flags. */ 105 | multiboot_uint32_t flags; 106 | 107 | /* The above fields plus this one must equal 0 mod 2^32. */ 108 | multiboot_uint32_t checksum; 109 | 110 | /* These are only valid if MULTIBOOT_AOUT_KLUDGE is set. */ 111 | multiboot_uint32_t header_addr; 112 | multiboot_uint32_t load_addr; 113 | multiboot_uint32_t load_end_addr; 114 | multiboot_uint32_t bss_end_addr; 115 | multiboot_uint32_t entry_addr; 116 | 117 | /* These are only valid if MULTIBOOT_VIDEO_MODE is set. */ 118 | multiboot_uint32_t mode_type; 119 | multiboot_uint32_t width; 120 | multiboot_uint32_t height; 121 | multiboot_uint32_t depth; 122 | }; 123 | 124 | /* The symbol table for a.out. */ 125 | struct multiboot_aout_symbol_table 126 | { 127 | multiboot_uint32_t tabsize; 128 | multiboot_uint32_t strsize; 129 | multiboot_uint32_t addr; 130 | multiboot_uint32_t reserved; 131 | }; 132 | typedef struct multiboot_aout_symbol_table multiboot_aout_symbol_table_t; 133 | 134 | /* The section header table for ELF. */ 135 | struct multiboot_elf_section_header_table 136 | { 137 | multiboot_uint32_t num; 138 | multiboot_uint32_t size; 139 | multiboot_uint32_t addr; 140 | multiboot_uint32_t shndx; 141 | }; 142 | typedef struct multiboot_elf_section_header_table multiboot_elf_section_header_table_t; 143 | 144 | struct multiboot_info 145 | { 146 | /* Multiboot info version number */ 147 | multiboot_uint32_t flags; 148 | 149 | /* Available memory from BIOS */ 150 | multiboot_uint32_t mem_lower; 151 | multiboot_uint32_t mem_upper; 152 | 153 | /* "root" partition */ 154 | multiboot_uint32_t boot_device; 155 | 156 | /* Kernel command line */ 157 | multiboot_uint32_t cmdline; 158 | 159 | /* Boot-Module list */ 160 | multiboot_uint32_t mods_count; 161 | multiboot_uint32_t mods_addr; 162 | 163 | union 164 | { 165 | multiboot_aout_symbol_table_t aout_sym; 166 | multiboot_elf_section_header_table_t elf_sec; 167 | } u; 168 | 169 | /* Memory Mapping buffer */ 170 | multiboot_uint32_t mmap_length; 171 | multiboot_uint32_t mmap_addr; 172 | 173 | /* Drive Info buffer */ 174 | multiboot_uint32_t drives_length; 175 | multiboot_uint32_t drives_addr; 176 | 177 | /* ROM configuration table */ 178 | multiboot_uint32_t config_table; 179 | 180 | /* Boot Loader Name */ 181 | multiboot_uint32_t boot_loader_name; 182 | 183 | /* APM table */ 184 | multiboot_uint32_t apm_table; 185 | 186 | /* Video */ 187 | multiboot_uint32_t vbe_control_info; 188 | multiboot_uint32_t vbe_mode_info; 189 | multiboot_uint16_t vbe_mode; 190 | multiboot_uint16_t vbe_interface_seg; 191 | multiboot_uint16_t vbe_interface_off; 192 | multiboot_uint16_t vbe_interface_len; 193 | 194 | multiboot_uint64_t framebuffer_addr; 195 | multiboot_uint32_t framebuffer_pitch; 196 | multiboot_uint32_t framebuffer_width; 197 | multiboot_uint32_t framebuffer_height; 198 | multiboot_uint8_t framebuffer_bpp; 199 | #define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 200 | #define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1 201 | #define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 202 | multiboot_uint8_t framebuffer_type; 203 | union 204 | { 205 | struct 206 | { 207 | multiboot_uint32_t framebuffer_palette_addr; 208 | multiboot_uint16_t framebuffer_palette_num_colors; 209 | }; 210 | struct 211 | { 212 | multiboot_uint8_t framebuffer_red_field_position; 213 | multiboot_uint8_t framebuffer_red_mask_size; 214 | multiboot_uint8_t framebuffer_green_field_position; 215 | multiboot_uint8_t framebuffer_green_mask_size; 216 | multiboot_uint8_t framebuffer_blue_field_position; 217 | multiboot_uint8_t framebuffer_blue_mask_size; 218 | }; 219 | }; 220 | }; 221 | typedef struct multiboot_info multiboot_info_t; 222 | 223 | struct multiboot_color 224 | { 225 | multiboot_uint8_t red; 226 | multiboot_uint8_t green; 227 | multiboot_uint8_t blue; 228 | }; 229 | 230 | struct multiboot_mmap_entry 231 | { 232 | multiboot_uint32_t size; 233 | multiboot_uint64_t addr; 234 | multiboot_uint64_t len; 235 | #define MULTIBOOT_MEMORY_AVAILABLE 1 236 | #define MULTIBOOT_MEMORY_RESERVED 2 237 | #define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 238 | #define MULTIBOOT_MEMORY_NVS 4 239 | #define MULTIBOOT_MEMORY_BADRAM 5 240 | multiboot_uint32_t type; 241 | } __attribute__((packed)); 242 | typedef struct multiboot_mmap_entry multiboot_memory_map_t; 243 | 244 | struct multiboot_mod_list 245 | { 246 | /* the memory used goes from bytes ’mod_start’ to ’mod_end-1’ inclusive */ 247 | multiboot_uint32_t mod_start; 248 | multiboot_uint32_t mod_end; 249 | 250 | /* Module command line */ 251 | multiboot_uint32_t cmdline; 252 | 253 | /* padding to take it to 16 bytes (must be zero) */ 254 | multiboot_uint32_t pad; 255 | }; 256 | typedef struct multiboot_mod_list multiboot_module_t; 257 | 258 | /* APM BIOS info. */ 259 | struct multiboot_apm_info 260 | { 261 | multiboot_uint16_t version; 262 | multiboot_uint16_t cseg; 263 | multiboot_uint32_t offset; 264 | multiboot_uint16_t cseg_16; 265 | multiboot_uint16_t dseg; 266 | multiboot_uint16_t flags; 267 | multiboot_uint16_t cseg_len; 268 | multiboot_uint16_t cseg_16_len; 269 | multiboot_uint16_t dseg_len; 270 | }; 271 | 272 | #endif /* ! ASM_FILE */ 273 | 274 | #endif /* ! MULTIBOOT_HEADER */ -------------------------------------------------------------------------------- /tests/multiboot1/towboot.toml: -------------------------------------------------------------------------------- 1 | default = "multiboot1" 2 | timeout = 0 3 | log_level = "warn" 4 | 5 | [entries] 6 | 7 | [entries.multiboot1] 8 | name = "multiboot1" 9 | image = "kernel" 10 | argv = "test of a cmdline" 11 | -------------------------------------------------------------------------------- /tests/multiboot2/Makefile: -------------------------------------------------------------------------------- 1 | SHARED_FLAGS=-nostdinc -fno-builtin -m32 -ffreestanding -no-pie 2 | CFLAGS=$(SHARED_FLAGS) 3 | ASFLAGS=$(SHARED_FLAGS) 4 | LDFLAGS=-nostdlib 5 | 6 | kernel: boot.o 7 | -------------------------------------------------------------------------------- /tests/multiboot2/README.md: -------------------------------------------------------------------------------- 1 | # multiboot2 example kernel 2 | 3 | This is the example kernel taken from [the Multiboot 2 specification](https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html#Example-OS-code). 4 | 5 | It has been slighlty altered to print to the serial output (see the Git commits). 6 | -------------------------------------------------------------------------------- /tests/multiboot2/boot.S: -------------------------------------------------------------------------------- 1 | /* boot.S - bootstrap the kernel */ 2 | /* Copyright (C) 1999, 2001, 2010 Free Software Foundation, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #define ASM_FILE 1 19 | #include "multiboot2.h" 20 | 21 | /* C symbol format. HAVE_ASM_USCORE is defined by configure. */ 22 | #ifdef HAVE_ASM_USCORE 23 | # define EXT_C(sym) _ ## sym 24 | #else 25 | # define EXT_C(sym) sym 26 | #endif 27 | 28 | /* The size of our stack (16KB). */ 29 | #define STACK_SIZE 0x4000 30 | 31 | /* The flags for the Multiboot header. */ 32 | #ifdef __ELF__ 33 | # define AOUT_KLUDGE 0 34 | #else 35 | # define AOUT_KLUDGE MULTIBOOT_AOUT_KLUDGE 36 | #endif 37 | 38 | .text 39 | 40 | .globl start, _start 41 | start: 42 | _start: 43 | jmp multiboot_entry 44 | 45 | /* Align 64 bits boundary. */ 46 | .align 8 47 | 48 | /* Multiboot header. */ 49 | multiboot_header: 50 | /* magic */ 51 | .long MULTIBOOT2_HEADER_MAGIC 52 | /* ISA: i386 */ 53 | .long MULTIBOOT_ARCHITECTURE_I386 54 | /* Header length. */ 55 | .long multiboot_header_end - multiboot_header 56 | /* checksum */ 57 | .long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header)) 58 | #ifndef __ELF__ 59 | .align 8 60 | address_tag_start: 61 | .short MULTIBOOT_HEADER_TAG_ADDRESS 62 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 63 | .long address_tag_end - address_tag_start 64 | /* header_addr */ 65 | .long multiboot_header 66 | /* load_addr */ 67 | .long _start 68 | /* load_end_addr */ 69 | .long _edata 70 | /* bss_end_addr */ 71 | .long _end 72 | address_tag_end: 73 | .align 8 74 | entry_address_tag_start: 75 | .short MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 76 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 77 | .long entry_address_tag_end - entry_address_tag_start 78 | /* entry_addr */ 79 | .long multiboot_entry 80 | entry_address_tag_end: 81 | #endif /* __ELF__ */ 82 | framebuffer_tag_start: 83 | .short MULTIBOOT_HEADER_TAG_FRAMEBUFFER 84 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 85 | .long framebuffer_tag_end - framebuffer_tag_start 86 | .long 1024 87 | .long 768 88 | .long 32 89 | framebuffer_tag_end: 90 | .align 8 91 | end_tag_start: 92 | .short MULTIBOOT_HEADER_TAG_END 93 | .short 0 94 | .long end_tag_end - end_tag_start 95 | end_tag_end: 96 | multiboot_header_end: 97 | multiboot_entry: 98 | /* Initialize the stack pointer. */ 99 | movl $(stack + STACK_SIZE), %esp 100 | 101 | /* Reset EFLAGS. */ 102 | pushl $0 103 | popf 104 | 105 | /* Push the pointer to the Multiboot information structure. */ 106 | pushl %ebx 107 | /* Push the magic value. */ 108 | pushl %eax 109 | 110 | /* Now enter the C main function... */ 111 | call EXT_C(cmain) 112 | 113 | /* Halt. */ 114 | pushl $halt_message 115 | call EXT_C(printf) 116 | 117 | loop: hlt 118 | jmp loop 119 | 120 | halt_message: 121 | .asciz "Halted." 122 | 123 | /* Our stack area. */ 124 | .comm stack, STACK_SIZE 125 | -------------------------------------------------------------------------------- /tests/multiboot2/kernel.c: -------------------------------------------------------------------------------- 1 | /* kernel.c - the C part of the kernel */ 2 | /* Copyright (C) 1999, 2010 Free Software Foundation, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "multiboot2.h" 19 | 20 | /* Macros. */ 21 | #define SER(v) __asm__("outb %b0, %w1"::"a" (v), "d"(0x3f8)) 22 | 23 | /* Some screen stuff. */ 24 | /* The number of columns. */ 25 | #define COLUMNS 80 26 | /* The number of lines. */ 27 | #define LINES 24 28 | /* The attribute of an character. */ 29 | #define ATTRIBUTE 7 30 | /* The video memory address. */ 31 | #define VIDEO 0xB8000 32 | 33 | /* Variables. */ 34 | /* Save the X position. */ 35 | static int xpos; 36 | /* Save the Y position. */ 37 | static int ypos; 38 | /* Point to the video memory. */ 39 | static volatile unsigned char *video; 40 | 41 | /* Forward declarations. */ 42 | void cmain (unsigned long magic, unsigned long addr); 43 | static void cls (void); 44 | static void itoa (char *buf, int base, int d); 45 | static void putchar (int c); 46 | void printf (const char *format, ...); 47 | 48 | /* Check if MAGIC is valid and print the Multiboot information structure 49 | pointed by ADDR. */ 50 | void 51 | cmain (unsigned long magic, unsigned long addr) 52 | { 53 | struct multiboot_tag *tag; 54 | unsigned size; 55 | 56 | /* Clear the screen. */ 57 | cls (); 58 | 59 | /* Am I booted by a Multiboot-compliant boot loader? */ 60 | if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) 61 | { 62 | printf ("Invalid magic number: 0x%x\n", (unsigned) magic); 63 | return; 64 | } 65 | 66 | if (addr & 7) 67 | { 68 | printf ("Unaligned mbi: 0x%x\n", addr); 69 | return; 70 | } 71 | 72 | size = *(unsigned *) addr; 73 | printf ("Announced mbi size 0x%x\n", size); 74 | for (tag = (struct multiboot_tag *) (addr + 8); 75 | tag->type != MULTIBOOT_TAG_TYPE_END; 76 | tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 77 | + ((tag->size + 7) & ~7))) 78 | { 79 | printf ("Tag 0x%x, Size 0x%x\n", tag->type, tag->size); 80 | switch (tag->type) 81 | { 82 | case MULTIBOOT_TAG_TYPE_CMDLINE: 83 | printf ("Command line = %s\n", 84 | ((struct multiboot_tag_string *) tag)->string); 85 | break; 86 | case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: 87 | printf ("Boot loader name = %s\n", 88 | ((struct multiboot_tag_string *) tag)->string); 89 | break; 90 | case MULTIBOOT_TAG_TYPE_MODULE: 91 | printf ("Module at 0x%x-0x%x. Command line %s\n", 92 | ((struct multiboot_tag_module *) tag)->mod_start, 93 | ((struct multiboot_tag_module *) tag)->mod_end, 94 | ((struct multiboot_tag_module *) tag)->cmdline); 95 | break; 96 | case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: 97 | printf ("mem_lower = %uKB, mem_upper = %uKB\n", 98 | ((struct multiboot_tag_basic_meminfo *) tag)->mem_lower, 99 | ((struct multiboot_tag_basic_meminfo *) tag)->mem_upper); 100 | break; 101 | case MULTIBOOT_TAG_TYPE_BOOTDEV: 102 | printf ("Boot device 0x%x,%u,%u\n", 103 | ((struct multiboot_tag_bootdev *) tag)->biosdev, 104 | ((struct multiboot_tag_bootdev *) tag)->slice, 105 | ((struct multiboot_tag_bootdev *) tag)->part); 106 | break; 107 | case MULTIBOOT_TAG_TYPE_MMAP: 108 | { 109 | multiboot_memory_map_t *mmap; 110 | 111 | printf ("mmap\n"); 112 | 113 | for (mmap = ((struct multiboot_tag_mmap *) tag)->entries; 114 | (multiboot_uint8_t *) mmap 115 | < (multiboot_uint8_t *) tag + tag->size; 116 | mmap = (multiboot_memory_map_t *) 117 | ((unsigned long) mmap 118 | + ((struct multiboot_tag_mmap *) tag)->entry_size)) 119 | printf (" base_addr = 0x%x%x," 120 | " length = 0x%x%x, type = 0x%x\n", 121 | (unsigned) (mmap->addr >> 32), 122 | (unsigned) (mmap->addr & 0xffffffff), 123 | (unsigned) (mmap->len >> 32), 124 | (unsigned) (mmap->len & 0xffffffff), 125 | (unsigned) mmap->type); 126 | } 127 | break; 128 | case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: 129 | { 130 | multiboot_uint32_t color; 131 | unsigned i; 132 | struct multiboot_tag_framebuffer *tagfb 133 | = (struct multiboot_tag_framebuffer *) tag; 134 | void *fb = (void *) (unsigned long) tagfb->common.framebuffer_addr; 135 | 136 | switch (tagfb->common.framebuffer_type) 137 | { 138 | case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED: 139 | { 140 | unsigned best_distance, distance; 141 | struct multiboot_color *palette; 142 | 143 | palette = tagfb->framebuffer_palette; 144 | 145 | color = 0; 146 | best_distance = 4*256*256; 147 | 148 | for (i = 0; i < tagfb->framebuffer_palette_num_colors; i++) 149 | { 150 | distance = (0xff - palette[i].blue) 151 | * (0xff - palette[i].blue) 152 | + palette[i].red * palette[i].red 153 | + palette[i].green * palette[i].green; 154 | if (distance < best_distance) 155 | { 156 | color = i; 157 | best_distance = distance; 158 | } 159 | } 160 | } 161 | break; 162 | 163 | case MULTIBOOT_FRAMEBUFFER_TYPE_RGB: 164 | color = ((1 << tagfb->framebuffer_blue_mask_size) - 1) 165 | << tagfb->framebuffer_blue_field_position; 166 | break; 167 | 168 | case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT: 169 | color = '\\' | 0x0100; 170 | break; 171 | 172 | default: 173 | color = 0xffffffff; 174 | break; 175 | } 176 | 177 | for (i = 0; i < tagfb->common.framebuffer_width 178 | && i < tagfb->common.framebuffer_height; i++) 179 | { 180 | switch (tagfb->common.framebuffer_bpp) 181 | { 182 | case 8: 183 | { 184 | multiboot_uint8_t *pixel = fb 185 | + tagfb->common.framebuffer_pitch * i + i; 186 | *pixel = color; 187 | } 188 | break; 189 | case 15: 190 | case 16: 191 | { 192 | multiboot_uint16_t *pixel 193 | = fb + tagfb->common.framebuffer_pitch * i + 2 * i; 194 | *pixel = color; 195 | } 196 | break; 197 | case 24: 198 | { 199 | multiboot_uint32_t *pixel 200 | = fb + tagfb->common.framebuffer_pitch * i + 3 * i; 201 | *pixel = (color & 0xffffff) | (*pixel & 0xff000000); 202 | } 203 | break; 204 | 205 | case 32: 206 | { 207 | multiboot_uint32_t *pixel 208 | = fb + tagfb->common.framebuffer_pitch * i + 4 * i; 209 | *pixel = color; 210 | } 211 | break; 212 | } 213 | } 214 | break; 215 | } 216 | 217 | } 218 | } 219 | tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 220 | + ((tag->size + 7) & ~7)); 221 | printf ("Total mbi size 0x%x\n", (unsigned) tag - addr); 222 | } 223 | 224 | /* Clear the screen and initialize VIDEO, XPOS and YPOS. */ 225 | static void 226 | cls (void) 227 | { 228 | int i; 229 | 230 | video = (unsigned char *) VIDEO; 231 | 232 | for (i = 0; i < COLUMNS * LINES * 2; i++) 233 | *(video + i) = 0; 234 | 235 | xpos = 0; 236 | ypos = 0; 237 | } 238 | 239 | /* Convert the integer D to a string and save the string in BUF. If 240 | BASE is equal to ’d’, interpret that D is decimal, and if BASE is 241 | equal to ’x’, interpret that D is hexadecimal. */ 242 | static void 243 | itoa (char *buf, int base, int d) 244 | { 245 | char *p = buf; 246 | char *p1, *p2; 247 | unsigned long ud = d; 248 | int divisor = 10; 249 | 250 | /* If %d is specified and D is minus, put ‘-’ in the head. */ 251 | if (base == 'd' && d < 0) 252 | { 253 | *p++ = '-'; 254 | buf++; 255 | ud = -d; 256 | } 257 | else if (base == 'x') 258 | divisor = 16; 259 | 260 | /* Divide UD by DIVISOR until UD == 0. */ 261 | do 262 | { 263 | int remainder = ud % divisor; 264 | 265 | *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10; 266 | } 267 | while (ud /= divisor); 268 | 269 | /* Terminate BUF. */ 270 | *p = 0; 271 | 272 | /* Reverse BUF. */ 273 | p1 = buf; 274 | p2 = p - 1; 275 | while (p1 < p2) 276 | { 277 | char tmp = *p1; 278 | *p1 = *p2; 279 | *p2 = tmp; 280 | p1++; 281 | p2--; 282 | } 283 | } 284 | 285 | /* Put the character C on the screen. */ 286 | static void 287 | putchar (int c) 288 | { 289 | // serial port 290 | SER(c & 0xff); 291 | if (c == '\n' || c == '\r') 292 | { 293 | newline: 294 | SER('\r' & 0xff); 295 | xpos = 0; 296 | ypos++; 297 | if (ypos >= LINES) 298 | ypos = 0; 299 | return; 300 | } 301 | 302 | *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF; 303 | *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE; 304 | 305 | xpos++; 306 | if (xpos >= COLUMNS) 307 | goto newline; 308 | } 309 | 310 | /* Format a string and print it on the screen, just like the libc 311 | function printf. */ 312 | void 313 | printf (const char *format, ...) 314 | { 315 | char **arg = (char **) &format; 316 | int c; 317 | char buf[20]; 318 | 319 | arg++; 320 | 321 | while ((c = *format++) != 0) 322 | { 323 | if (c != '%') 324 | putchar (c); 325 | else 326 | { 327 | char *p, *p2; 328 | int pad0 = 0, pad = 0; 329 | 330 | c = *format++; 331 | if (c == '0') 332 | { 333 | pad0 = 1; 334 | c = *format++; 335 | } 336 | 337 | if (c >= '0' && c <= '9') 338 | { 339 | pad = c - '0'; 340 | c = *format++; 341 | } 342 | 343 | switch (c) 344 | { 345 | case 'd': 346 | case 'u': 347 | case 'x': 348 | itoa (buf, c, *((int *) arg++)); 349 | p = buf; 350 | goto string; 351 | break; 352 | 353 | case 's': 354 | p = *arg++; 355 | if (! p) 356 | p = "(null)"; 357 | 358 | string: 359 | for (p2 = p; *p2; p2++); 360 | for (; p2 < p + pad; p2++) 361 | putchar (pad0 ? '0' : ' '); 362 | while (*p) 363 | putchar (*p++); 364 | break; 365 | 366 | default: 367 | putchar (*((int *) arg++)); 368 | break; 369 | } 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /tests/multiboot2/multiboot2.h: -------------------------------------------------------------------------------- 1 | /* multiboot2.h - Multiboot 2 header file. */ 2 | /* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY 17 | * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 19 | * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef MULTIBOOT_HEADER 23 | #define MULTIBOOT_HEADER 1 24 | 25 | /* How many bytes from the start of the file we search for the header. */ 26 | #define MULTIBOOT_SEARCH 32768 27 | #define MULTIBOOT_HEADER_ALIGN 8 28 | 29 | /* The magic field should contain this. */ 30 | #define MULTIBOOT2_HEADER_MAGIC 0xe85250d6 31 | 32 | /* This should be in %eax. */ 33 | #define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289 34 | 35 | /* Alignment of multiboot modules. */ 36 | #define MULTIBOOT_MOD_ALIGN 0x00001000 37 | 38 | /* Alignment of the multiboot info structure. */ 39 | #define MULTIBOOT_INFO_ALIGN 0x00000008 40 | 41 | /* Flags set in the ’flags’ member of the multiboot header. */ 42 | 43 | #define MULTIBOOT_TAG_ALIGN 8 44 | #define MULTIBOOT_TAG_TYPE_END 0 45 | #define MULTIBOOT_TAG_TYPE_CMDLINE 1 46 | #define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2 47 | #define MULTIBOOT_TAG_TYPE_MODULE 3 48 | #define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4 49 | #define MULTIBOOT_TAG_TYPE_BOOTDEV 5 50 | #define MULTIBOOT_TAG_TYPE_MMAP 6 51 | #define MULTIBOOT_TAG_TYPE_VBE 7 52 | #define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8 53 | #define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9 54 | #define MULTIBOOT_TAG_TYPE_APM 10 55 | #define MULTIBOOT_TAG_TYPE_EFI32 11 56 | #define MULTIBOOT_TAG_TYPE_EFI64 12 57 | #define MULTIBOOT_TAG_TYPE_SMBIOS 13 58 | #define MULTIBOOT_TAG_TYPE_ACPI_OLD 14 59 | #define MULTIBOOT_TAG_TYPE_ACPI_NEW 15 60 | #define MULTIBOOT_TAG_TYPE_NETWORK 16 61 | #define MULTIBOOT_TAG_TYPE_EFI_MMAP 17 62 | #define MULTIBOOT_TAG_TYPE_EFI_BS 18 63 | #define MULTIBOOT_TAG_TYPE_EFI32_IH 19 64 | #define MULTIBOOT_TAG_TYPE_EFI64_IH 20 65 | #define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21 66 | 67 | #define MULTIBOOT_HEADER_TAG_END 0 68 | #define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1 69 | #define MULTIBOOT_HEADER_TAG_ADDRESS 2 70 | #define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3 71 | #define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4 72 | #define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5 73 | #define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6 74 | #define MULTIBOOT_HEADER_TAG_EFI_BS 7 75 | #define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8 76 | #define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9 77 | #define MULTIBOOT_HEADER_TAG_RELOCATABLE 10 78 | 79 | #define MULTIBOOT_ARCHITECTURE_I386 0 80 | #define MULTIBOOT_ARCHITECTURE_MIPS32 4 81 | #define MULTIBOOT_HEADER_TAG_OPTIONAL 1 82 | 83 | #define MULTIBOOT_LOAD_PREFERENCE_NONE 0 84 | #define MULTIBOOT_LOAD_PREFERENCE_LOW 1 85 | #define MULTIBOOT_LOAD_PREFERENCE_HIGH 2 86 | 87 | #define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1 88 | #define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2 89 | 90 | #ifndef ASM_FILE 91 | 92 | typedef unsigned char multiboot_uint8_t; 93 | typedef unsigned short multiboot_uint16_t; 94 | typedef unsigned int multiboot_uint32_t; 95 | typedef unsigned long long multiboot_uint64_t; 96 | 97 | struct multiboot_header 98 | { 99 | /* Must be MULTIBOOT_MAGIC - see above. */ 100 | multiboot_uint32_t magic; 101 | 102 | /* ISA */ 103 | multiboot_uint32_t architecture; 104 | 105 | /* Total header length. */ 106 | multiboot_uint32_t header_length; 107 | 108 | /* The above fields plus this one must equal 0 mod 2^32. */ 109 | multiboot_uint32_t checksum; 110 | }; 111 | 112 | struct multiboot_header_tag 113 | { 114 | multiboot_uint16_t type; 115 | multiboot_uint16_t flags; 116 | multiboot_uint32_t size; 117 | }; 118 | 119 | struct multiboot_header_tag_information_request 120 | { 121 | multiboot_uint16_t type; 122 | multiboot_uint16_t flags; 123 | multiboot_uint32_t size; 124 | multiboot_uint32_t requests[0]; 125 | }; 126 | 127 | struct multiboot_header_tag_address 128 | { 129 | multiboot_uint16_t type; 130 | multiboot_uint16_t flags; 131 | multiboot_uint32_t size; 132 | multiboot_uint32_t header_addr; 133 | multiboot_uint32_t load_addr; 134 | multiboot_uint32_t load_end_addr; 135 | multiboot_uint32_t bss_end_addr; 136 | }; 137 | 138 | struct multiboot_header_tag_entry_address 139 | { 140 | multiboot_uint16_t type; 141 | multiboot_uint16_t flags; 142 | multiboot_uint32_t size; 143 | multiboot_uint32_t entry_addr; 144 | }; 145 | 146 | struct multiboot_header_tag_console_flags 147 | { 148 | multiboot_uint16_t type; 149 | multiboot_uint16_t flags; 150 | multiboot_uint32_t size; 151 | multiboot_uint32_t console_flags; 152 | }; 153 | 154 | struct multiboot_header_tag_framebuffer 155 | { 156 | multiboot_uint16_t type; 157 | multiboot_uint16_t flags; 158 | multiboot_uint32_t size; 159 | multiboot_uint32_t width; 160 | multiboot_uint32_t height; 161 | multiboot_uint32_t depth; 162 | }; 163 | 164 | struct multiboot_header_tag_module_align 165 | { 166 | multiboot_uint16_t type; 167 | multiboot_uint16_t flags; 168 | multiboot_uint32_t size; 169 | }; 170 | 171 | struct multiboot_header_tag_relocatable 172 | { 173 | multiboot_uint16_t type; 174 | multiboot_uint16_t flags; 175 | multiboot_uint32_t size; 176 | multiboot_uint32_t min_addr; 177 | multiboot_uint32_t max_addr; 178 | multiboot_uint32_t align; 179 | multiboot_uint32_t preference; 180 | }; 181 | 182 | struct multiboot_color 183 | { 184 | multiboot_uint8_t red; 185 | multiboot_uint8_t green; 186 | multiboot_uint8_t blue; 187 | }; 188 | 189 | struct multiboot_mmap_entry 190 | { 191 | multiboot_uint64_t addr; 192 | multiboot_uint64_t len; 193 | #define MULTIBOOT_MEMORY_AVAILABLE 1 194 | #define MULTIBOOT_MEMORY_RESERVED 2 195 | #define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 196 | #define MULTIBOOT_MEMORY_NVS 4 197 | #define MULTIBOOT_MEMORY_BADRAM 5 198 | multiboot_uint32_t type; 199 | multiboot_uint32_t zero; 200 | }; 201 | typedef struct multiboot_mmap_entry multiboot_memory_map_t; 202 | 203 | struct multiboot_tag 204 | { 205 | multiboot_uint32_t type; 206 | multiboot_uint32_t size; 207 | }; 208 | 209 | struct multiboot_tag_string 210 | { 211 | multiboot_uint32_t type; 212 | multiboot_uint32_t size; 213 | char string[0]; 214 | }; 215 | 216 | struct multiboot_tag_module 217 | { 218 | multiboot_uint32_t type; 219 | multiboot_uint32_t size; 220 | multiboot_uint32_t mod_start; 221 | multiboot_uint32_t mod_end; 222 | char cmdline[0]; 223 | }; 224 | 225 | struct multiboot_tag_basic_meminfo 226 | { 227 | multiboot_uint32_t type; 228 | multiboot_uint32_t size; 229 | multiboot_uint32_t mem_lower; 230 | multiboot_uint32_t mem_upper; 231 | }; 232 | 233 | struct multiboot_tag_bootdev 234 | { 235 | multiboot_uint32_t type; 236 | multiboot_uint32_t size; 237 | multiboot_uint32_t biosdev; 238 | multiboot_uint32_t slice; 239 | multiboot_uint32_t part; 240 | }; 241 | 242 | struct multiboot_tag_mmap 243 | { 244 | multiboot_uint32_t type; 245 | multiboot_uint32_t size; 246 | multiboot_uint32_t entry_size; 247 | multiboot_uint32_t entry_version; 248 | struct multiboot_mmap_entry entries[0]; 249 | }; 250 | 251 | struct multiboot_vbe_info_block 252 | { 253 | multiboot_uint8_t external_specification[512]; 254 | }; 255 | 256 | struct multiboot_vbe_mode_info_block 257 | { 258 | multiboot_uint8_t external_specification[256]; 259 | }; 260 | 261 | struct multiboot_tag_vbe 262 | { 263 | multiboot_uint32_t type; 264 | multiboot_uint32_t size; 265 | 266 | multiboot_uint16_t vbe_mode; 267 | multiboot_uint16_t vbe_interface_seg; 268 | multiboot_uint16_t vbe_interface_off; 269 | multiboot_uint16_t vbe_interface_len; 270 | 271 | struct multiboot_vbe_info_block vbe_control_info; 272 | struct multiboot_vbe_mode_info_block vbe_mode_info; 273 | }; 274 | 275 | struct multiboot_tag_framebuffer_common 276 | { 277 | multiboot_uint32_t type; 278 | multiboot_uint32_t size; 279 | 280 | multiboot_uint64_t framebuffer_addr; 281 | multiboot_uint32_t framebuffer_pitch; 282 | multiboot_uint32_t framebuffer_width; 283 | multiboot_uint32_t framebuffer_height; 284 | multiboot_uint8_t framebuffer_bpp; 285 | #define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 286 | #define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1 287 | #define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 288 | multiboot_uint8_t framebuffer_type; 289 | multiboot_uint16_t reserved; 290 | }; 291 | 292 | struct multiboot_tag_framebuffer 293 | { 294 | struct multiboot_tag_framebuffer_common common; 295 | 296 | union 297 | { 298 | struct 299 | { 300 | multiboot_uint16_t framebuffer_palette_num_colors; 301 | struct multiboot_color framebuffer_palette[0]; 302 | }; 303 | struct 304 | { 305 | multiboot_uint8_t framebuffer_red_field_position; 306 | multiboot_uint8_t framebuffer_red_mask_size; 307 | multiboot_uint8_t framebuffer_green_field_position; 308 | multiboot_uint8_t framebuffer_green_mask_size; 309 | multiboot_uint8_t framebuffer_blue_field_position; 310 | multiboot_uint8_t framebuffer_blue_mask_size; 311 | }; 312 | }; 313 | }; 314 | 315 | struct multiboot_tag_elf_sections 316 | { 317 | multiboot_uint32_t type; 318 | multiboot_uint32_t size; 319 | multiboot_uint32_t num; 320 | multiboot_uint32_t entsize; 321 | multiboot_uint32_t shndx; 322 | char sections[0]; 323 | }; 324 | 325 | struct multiboot_tag_apm 326 | { 327 | multiboot_uint32_t type; 328 | multiboot_uint32_t size; 329 | multiboot_uint16_t version; 330 | multiboot_uint16_t cseg; 331 | multiboot_uint32_t offset; 332 | multiboot_uint16_t cseg_16; 333 | multiboot_uint16_t dseg; 334 | multiboot_uint16_t flags; 335 | multiboot_uint16_t cseg_len; 336 | multiboot_uint16_t cseg_16_len; 337 | multiboot_uint16_t dseg_len; 338 | }; 339 | 340 | struct multiboot_tag_efi32 341 | { 342 | multiboot_uint32_t type; 343 | multiboot_uint32_t size; 344 | multiboot_uint32_t pointer; 345 | }; 346 | 347 | struct multiboot_tag_efi64 348 | { 349 | multiboot_uint32_t type; 350 | multiboot_uint32_t size; 351 | multiboot_uint64_t pointer; 352 | }; 353 | 354 | struct multiboot_tag_smbios 355 | { 356 | multiboot_uint32_t type; 357 | multiboot_uint32_t size; 358 | multiboot_uint8_t major; 359 | multiboot_uint8_t minor; 360 | multiboot_uint8_t reserved[6]; 361 | multiboot_uint8_t tables[0]; 362 | }; 363 | 364 | struct multiboot_tag_old_acpi 365 | { 366 | multiboot_uint32_t type; 367 | multiboot_uint32_t size; 368 | multiboot_uint8_t rsdp[0]; 369 | }; 370 | 371 | struct multiboot_tag_new_acpi 372 | { 373 | multiboot_uint32_t type; 374 | multiboot_uint32_t size; 375 | multiboot_uint8_t rsdp[0]; 376 | }; 377 | 378 | struct multiboot_tag_network 379 | { 380 | multiboot_uint32_t type; 381 | multiboot_uint32_t size; 382 | multiboot_uint8_t dhcpack[0]; 383 | }; 384 | 385 | struct multiboot_tag_efi_mmap 386 | { 387 | multiboot_uint32_t type; 388 | multiboot_uint32_t size; 389 | multiboot_uint32_t descr_size; 390 | multiboot_uint32_t descr_vers; 391 | multiboot_uint8_t efi_mmap[0]; 392 | }; 393 | 394 | struct multiboot_tag_efi32_ih 395 | { 396 | multiboot_uint32_t type; 397 | multiboot_uint32_t size; 398 | multiboot_uint32_t pointer; 399 | }; 400 | 401 | struct multiboot_tag_efi64_ih 402 | { 403 | multiboot_uint32_t type; 404 | multiboot_uint32_t size; 405 | multiboot_uint64_t pointer; 406 | }; 407 | 408 | struct multiboot_tag_load_base_addr 409 | { 410 | multiboot_uint32_t type; 411 | multiboot_uint32_t size; 412 | multiboot_uint32_t load_base_addr; 413 | }; 414 | 415 | #endif /* ! ASM_FILE */ 416 | 417 | #endif /* ! MULTIBOOT_HEADER */ -------------------------------------------------------------------------------- /tests/multiboot2/towboot.toml: -------------------------------------------------------------------------------- 1 | default = "multiboot2" 2 | timeout = 0 3 | log_level = "warn" 4 | 5 | [entries] 6 | 7 | [entries.multiboot2] 8 | name = "multiboot2" 9 | image = "kernel" 10 | argv = "test of a cmdline" 11 | -------------------------------------------------------------------------------- /tests/multiboot2_x64/Makefile: -------------------------------------------------------------------------------- 1 | SHARED_FLAGS=-nostdinc -fno-builtin -m64 -ffreestanding -no-pie 2 | CFLAGS=$(SHARED_FLAGS) 3 | ASFLAGS=$(SHARED_FLAGS) 4 | LDFLAGS=-nostdlib 5 | 6 | kernel: boot.o 7 | -------------------------------------------------------------------------------- /tests/multiboot2_x64/README.md: -------------------------------------------------------------------------------- 1 | # multiboot2, but with EFI amd64 entry 2 | 3 | This is the example kernel taken from [the Multiboot 2 specification](https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html#Example-OS-code), but altered to be a 64-bit kernel 4 | that starts with an EFI amd64 entry tag. 5 | 6 | It also prints to the serial output. 7 | -------------------------------------------------------------------------------- /tests/multiboot2_x64/boot.S: -------------------------------------------------------------------------------- 1 | /* boot.S - bootstrap the kernel */ 2 | /* Copyright (C) 1999, 2001, 2010 Free Software Foundation, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #define ASM_FILE 1 19 | #include "multiboot2.h" 20 | 21 | /* C symbol format. HAVE_ASM_USCORE is defined by configure. */ 22 | #ifdef HAVE_ASM_USCORE 23 | # define EXT_C(sym) _ ## sym 24 | #else 25 | # define EXT_C(sym) sym 26 | #endif 27 | 28 | /* The size of our stack (16KB). */ 29 | #define STACK_SIZE 0x4000 30 | 31 | /* The flags for the Multiboot header. */ 32 | #ifdef __ELF__ 33 | # define AOUT_KLUDGE 0 34 | #else 35 | # define AOUT_KLUDGE MULTIBOOT_AOUT_KLUDGE 36 | #endif 37 | 38 | .text 39 | 40 | .globl start, _start 41 | .code32 42 | /* this just exists to crash gracefully on 32-bit systems */ 43 | start: 44 | _start: 45 | hlt 46 | jmp _start 47 | .code64 48 | 49 | /* Align 64 bits boundary. */ 50 | .align 8 51 | 52 | /* Multiboot header. */ 53 | multiboot_header: 54 | /* magic */ 55 | .long MULTIBOOT2_HEADER_MAGIC 56 | /* ISA: i386 */ 57 | .long MULTIBOOT_ARCHITECTURE_I386 58 | /* Header length. */ 59 | .long multiboot_header_end - multiboot_header 60 | /* checksum */ 61 | .long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header)) 62 | #ifndef __ELF__ 63 | .align 8 64 | address_tag_start: 65 | .short MULTIBOOT_HEADER_TAG_ADDRESS 66 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 67 | .long address_tag_end - address_tag_start 68 | /* header_addr */ 69 | .long multiboot_header 70 | /* load_addr */ 71 | .long _start 72 | /* load_end_addr */ 73 | .long _edata 74 | /* bss_end_addr */ 75 | .long _end 76 | address_tag_end: 77 | #endif /* __ELF__ */ 78 | .align 8 79 | entry_address_tag_start: 80 | .short MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 81 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 82 | .long entry_address_tag_end - entry_address_tag_start 83 | /* entry_addr */ 84 | .long multiboot_entry 85 | entry_address_tag_end: 86 | .align 8 87 | boot_services_tag_start: 88 | .short MULTIBOOT_HEADER_TAG_EFI_BS 89 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 90 | .long boot_services_tag_end - boot_services_tag_start 91 | boot_services_tag_end: 92 | .align 8 93 | framebuffer_tag_start: 94 | .short MULTIBOOT_HEADER_TAG_FRAMEBUFFER 95 | .short MULTIBOOT_HEADER_TAG_OPTIONAL 96 | .long framebuffer_tag_end - framebuffer_tag_start 97 | .long 1024 98 | .long 768 99 | .long 32 100 | framebuffer_tag_end: 101 | .align 8 102 | end_tag_start: 103 | .short MULTIBOOT_HEADER_TAG_END 104 | .short 0 105 | .long end_tag_end - end_tag_start 106 | end_tag_end: 107 | .align 8 108 | multiboot_header_end: 109 | multiboot_entry: 110 | /* Initialize the stack pointer. */ 111 | movq $(stack + STACK_SIZE), %rsp 112 | 113 | /* Reset EFLAGS. */ 114 | pushq $0 115 | popf 116 | 117 | /* parameter 2: the pointer to the Multiboot information structure. */ 118 | movq $0, %rsi 119 | mov %ebx, %esi 120 | /* parameter 1: the magic value. */ 121 | movq $0, %rdi 122 | mov %eax, %edi 123 | 124 | /* Now enter the C main function... */ 125 | call EXT_C(cmain) 126 | 127 | /* Halt. */ 128 | movq $halt_message, %rdi 129 | call EXT_C(puts) 130 | 131 | loop: hlt 132 | jmp loop 133 | 134 | halt_message: 135 | .asciz "Halted." 136 | 137 | /* Our stack area. */ 138 | .comm stack, STACK_SIZE 139 | -------------------------------------------------------------------------------- /tests/multiboot2_x64/kernel.c: -------------------------------------------------------------------------------- 1 | /* kernel.c - the C part of the kernel */ 2 | /* Copyright (C) 1999, 2010 Free Software Foundation, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "multiboot2.h" 19 | 20 | /* Macros. */ 21 | #define SER(v) __asm__("outb %b0, %w1"::"a" (v), "d"(0x3f8)) 22 | 23 | /* Some screen stuff. */ 24 | /* The number of columns. */ 25 | #define COLUMNS 80 26 | /* The number of lines. */ 27 | #define LINES 24 28 | /* The attribute of an character. */ 29 | #define ATTRIBUTE 7 30 | /* The video memory address. */ 31 | #define VIDEO 0xB8000 32 | 33 | /* Variables. */ 34 | /* Save the X position. */ 35 | static int xpos; 36 | /* Save the Y position. */ 37 | static int ypos; 38 | /* Point to the video memory. */ 39 | static volatile unsigned char *video; 40 | 41 | /* Forward declarations. */ 42 | void cmain(multiboot_uint32_t magic, multiboot_uint64_t addr); 43 | static void cls (void); 44 | static void itoa (char *buf, int base, int d); 45 | static void putchar (int c); 46 | #define printf(format, ...) { void* arg[] = {format, __VA_ARGS__}; printf_(arg); } 47 | void printf_(void* arg[]); 48 | 49 | void puts(char* text) { 50 | printf(text); 51 | } 52 | 53 | /* Check if MAGIC is valid and print the Multiboot information structure 54 | pointed by ADDR. */ 55 | void cmain(multiboot_uint32_t magic, multiboot_uint64_t addr) 56 | { 57 | struct multiboot_tag *tag; 58 | multiboot_uint32_t size; 59 | 60 | /* Clear the screen. */ 61 | cls (); 62 | 63 | /* Am I booted by a Multiboot-compliant boot loader? */ 64 | if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) 65 | { 66 | printf ("Invalid magic number: 0x%x\n", magic); 67 | return; 68 | } 69 | 70 | if (addr & 7) 71 | { 72 | printf ("Unaligned mbi: 0x%x\n", addr); 73 | return; 74 | } 75 | 76 | size = *(multiboot_uint32_t *)addr; 77 | printf("Announced mbi size 0x%x\n", size); 78 | for (tag = (struct multiboot_tag *)(addr + 8); 79 | tag->type != MULTIBOOT_TAG_TYPE_END; 80 | tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag + ((tag->size + 7) & ~7))) 81 | { 82 | printf ("Tag 0x%x, Size 0x%x\n", tag->type, tag->size); 83 | switch (tag->type) 84 | { 85 | case MULTIBOOT_TAG_TYPE_CMDLINE: 86 | printf ("Command line = %s\n", 87 | ((struct multiboot_tag_string *) tag)->string); 88 | break; 89 | case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: 90 | printf ("Boot loader name = %s\n", 91 | ((struct multiboot_tag_string *) tag)->string); 92 | break; 93 | case MULTIBOOT_TAG_TYPE_MODULE: 94 | printf ("Module at 0x%x-0x%x. Command line %s\n", 95 | ((struct multiboot_tag_module *) tag)->mod_start, 96 | ((struct multiboot_tag_module *) tag)->mod_end, 97 | ((struct multiboot_tag_module *) tag)->cmdline); 98 | break; 99 | case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: 100 | printf ("mem_lower = %uKB, mem_upper = %uKB\n", 101 | ((struct multiboot_tag_basic_meminfo *) tag)->mem_lower, 102 | ((struct multiboot_tag_basic_meminfo *) tag)->mem_upper); 103 | break; 104 | case MULTIBOOT_TAG_TYPE_BOOTDEV: 105 | printf ("Boot device 0x%x,%u,%u\n", 106 | ((struct multiboot_tag_bootdev *) tag)->biosdev, 107 | ((struct multiboot_tag_bootdev *) tag)->slice, 108 | ((struct multiboot_tag_bootdev *) tag)->part); 109 | break; 110 | case MULTIBOOT_TAG_TYPE_MMAP: 111 | { 112 | multiboot_memory_map_t *mmap; 113 | 114 | printf ("mmap\n"); 115 | 116 | for (mmap = ((struct multiboot_tag_mmap *) tag)->entries; 117 | (multiboot_uint8_t *) mmap 118 | < (multiboot_uint8_t *) tag + tag->size; 119 | mmap = (multiboot_memory_map_t *) 120 | ((unsigned long) mmap 121 | + ((struct multiboot_tag_mmap *) tag)->entry_size)) 122 | printf (" base_addr = 0x%x%x," 123 | " length = 0x%x%x, type = 0x%x\n", 124 | (unsigned) (mmap->addr >> 32), 125 | (unsigned) (mmap->addr & 0xffffffff), 126 | (unsigned) (mmap->len >> 32), 127 | (unsigned) (mmap->len & 0xffffffff), 128 | (unsigned) mmap->type); 129 | } 130 | break; 131 | case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: 132 | { 133 | multiboot_uint32_t color; 134 | unsigned i; 135 | struct multiboot_tag_framebuffer *tagfb 136 | = (struct multiboot_tag_framebuffer *) tag; 137 | void *fb = (void *) (unsigned long) tagfb->common.framebuffer_addr; 138 | 139 | switch (tagfb->common.framebuffer_type) 140 | { 141 | case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED: 142 | { 143 | unsigned best_distance, distance; 144 | struct multiboot_color *palette; 145 | 146 | palette = tagfb->framebuffer_palette; 147 | 148 | color = 0; 149 | best_distance = 4*256*256; 150 | 151 | for (i = 0; i < tagfb->framebuffer_palette_num_colors; i++) 152 | { 153 | distance = (0xff - palette[i].blue) 154 | * (0xff - palette[i].blue) 155 | + palette[i].red * palette[i].red 156 | + palette[i].green * palette[i].green; 157 | if (distance < best_distance) 158 | { 159 | color = i; 160 | best_distance = distance; 161 | } 162 | } 163 | } 164 | break; 165 | 166 | case MULTIBOOT_FRAMEBUFFER_TYPE_RGB: 167 | color = ((1 << tagfb->framebuffer_blue_mask_size) - 1) 168 | << tagfb->framebuffer_blue_field_position; 169 | break; 170 | 171 | case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT: 172 | color = '\\' | 0x0100; 173 | break; 174 | 175 | default: 176 | color = 0xffffffff; 177 | break; 178 | } 179 | 180 | for (i = 0; i < tagfb->common.framebuffer_width 181 | && i < tagfb->common.framebuffer_height; i++) 182 | { 183 | switch (tagfb->common.framebuffer_bpp) 184 | { 185 | case 8: 186 | { 187 | multiboot_uint8_t *pixel = fb 188 | + tagfb->common.framebuffer_pitch * i + i; 189 | *pixel = color; 190 | } 191 | break; 192 | case 15: 193 | case 16: 194 | { 195 | multiboot_uint16_t *pixel 196 | = fb + tagfb->common.framebuffer_pitch * i + 2 * i; 197 | *pixel = color; 198 | } 199 | break; 200 | case 24: 201 | { 202 | multiboot_uint32_t *pixel 203 | = fb + tagfb->common.framebuffer_pitch * i + 3 * i; 204 | *pixel = (color & 0xffffff) | (*pixel & 0xff000000); 205 | } 206 | break; 207 | 208 | case 32: 209 | { 210 | multiboot_uint32_t *pixel 211 | = fb + tagfb->common.framebuffer_pitch * i + 4 * i; 212 | *pixel = color; 213 | } 214 | break; 215 | } 216 | } 217 | break; 218 | } 219 | 220 | } 221 | } 222 | tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 223 | + ((tag->size + 7) & ~7)); 224 | printf ("Total mbi size 0x%x\n", (unsigned) tag - addr); 225 | } 226 | 227 | /* Clear the screen and initialize VIDEO, XPOS and YPOS. */ 228 | static void 229 | cls (void) 230 | { 231 | int i; 232 | 233 | video = (unsigned char *) VIDEO; 234 | 235 | for (i = 0; i < COLUMNS * LINES * 2; i++) 236 | *(video + i) = 0; 237 | 238 | xpos = 0; 239 | ypos = 0; 240 | } 241 | 242 | /* Convert the integer D to a string and save the string in BUF. If 243 | BASE is equal to ’d’, interpret that D is decimal, and if BASE is 244 | equal to ’x’, interpret that D is hexadecimal. */ 245 | static void 246 | itoa (char *buf, int base, int d) 247 | { 248 | char *p = buf; 249 | char *p1, *p2; 250 | unsigned long ud = d; 251 | int divisor = 10; 252 | 253 | /* If %d is specified and D is minus, put ‘-’ in the head. */ 254 | if (base == 'd' && d < 0) 255 | { 256 | *p++ = '-'; 257 | buf++; 258 | ud = -d; 259 | } 260 | else if (base == 'x') 261 | divisor = 16; 262 | 263 | /* Divide UD by DIVISOR until UD == 0. */ 264 | do 265 | { 266 | int remainder = ud % divisor; 267 | 268 | *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10; 269 | } 270 | while (ud /= divisor); 271 | 272 | /* Terminate BUF. */ 273 | *p = 0; 274 | 275 | /* Reverse BUF. */ 276 | p1 = buf; 277 | p2 = p - 1; 278 | while (p1 < p2) 279 | { 280 | char tmp = *p1; 281 | *p1 = *p2; 282 | *p2 = tmp; 283 | p1++; 284 | p2--; 285 | } 286 | } 287 | 288 | /* Put the character C on the screen. */ 289 | static void 290 | putchar (int c) 291 | { 292 | // serial port 293 | SER(c & 0xff); 294 | if (c == '\n' || c == '\r') 295 | { 296 | newline: 297 | SER('\r' & 0xff); 298 | xpos = 0; 299 | ypos++; 300 | if (ypos >= LINES) 301 | ypos = 0; 302 | return; 303 | } 304 | 305 | *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF; 306 | *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE; 307 | 308 | xpos++; 309 | if (xpos >= COLUMNS) 310 | goto newline; 311 | } 312 | 313 | /* Format a string and print it on the screen, just like the libc 314 | function printf. */ 315 | void 316 | printf_(void* arg[]) 317 | { 318 | char* format = *(char**)arg++; 319 | int c; 320 | char buf[20]; 321 | 322 | while ((c = *format++) != 0) 323 | { 324 | if (c != '%') 325 | putchar (c); 326 | else 327 | { 328 | char *p, *p2; 329 | int pad0 = 0, pad = 0; 330 | 331 | c = *format++; 332 | if (c == '0') 333 | { 334 | pad0 = 1; 335 | c = *format++; 336 | } 337 | 338 | if (c >= '0' && c <= '9') 339 | { 340 | pad = c - '0'; 341 | c = *format++; 342 | } 343 | 344 | switch (c) 345 | { 346 | case 'd': 347 | case 'u': 348 | case 'x': 349 | itoa (buf, c, *((int *) arg++)); 350 | p = buf; 351 | goto string; 352 | break; 353 | 354 | case 's': 355 | p = *arg++; 356 | if (! p) 357 | p = "(null)"; 358 | 359 | string: 360 | for (p2 = p; *p2; p2++); 361 | for (; p2 < p + pad; p2++) 362 | putchar (pad0 ? '0' : ' '); 363 | while (*p) 364 | putchar (*p++); 365 | break; 366 | 367 | default: 368 | putchar (*((int *) arg++)); 369 | break; 370 | } 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /tests/multiboot2_x64/multiboot2.h: -------------------------------------------------------------------------------- 1 | /* multiboot2.h - Multiboot 2 header file. */ 2 | /* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY 17 | * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 19 | * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef MULTIBOOT_HEADER 23 | #define MULTIBOOT_HEADER 1 24 | 25 | /* How many bytes from the start of the file we search for the header. */ 26 | #define MULTIBOOT_SEARCH 32768 27 | #define MULTIBOOT_HEADER_ALIGN 8 28 | 29 | /* The magic field should contain this. */ 30 | #define MULTIBOOT2_HEADER_MAGIC 0xe85250d6 31 | 32 | /* This should be in %eax. */ 33 | #define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289 34 | 35 | /* Alignment of multiboot modules. */ 36 | #define MULTIBOOT_MOD_ALIGN 0x00001000 37 | 38 | /* Alignment of the multiboot info structure. */ 39 | #define MULTIBOOT_INFO_ALIGN 0x00000008 40 | 41 | /* Flags set in the ’flags’ member of the multiboot header. */ 42 | 43 | #define MULTIBOOT_TAG_ALIGN 8 44 | #define MULTIBOOT_TAG_TYPE_END 0 45 | #define MULTIBOOT_TAG_TYPE_CMDLINE 1 46 | #define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2 47 | #define MULTIBOOT_TAG_TYPE_MODULE 3 48 | #define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4 49 | #define MULTIBOOT_TAG_TYPE_BOOTDEV 5 50 | #define MULTIBOOT_TAG_TYPE_MMAP 6 51 | #define MULTIBOOT_TAG_TYPE_VBE 7 52 | #define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8 53 | #define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9 54 | #define MULTIBOOT_TAG_TYPE_APM 10 55 | #define MULTIBOOT_TAG_TYPE_EFI32 11 56 | #define MULTIBOOT_TAG_TYPE_EFI64 12 57 | #define MULTIBOOT_TAG_TYPE_SMBIOS 13 58 | #define MULTIBOOT_TAG_TYPE_ACPI_OLD 14 59 | #define MULTIBOOT_TAG_TYPE_ACPI_NEW 15 60 | #define MULTIBOOT_TAG_TYPE_NETWORK 16 61 | #define MULTIBOOT_TAG_TYPE_EFI_MMAP 17 62 | #define MULTIBOOT_TAG_TYPE_EFI_BS 18 63 | #define MULTIBOOT_TAG_TYPE_EFI32_IH 19 64 | #define MULTIBOOT_TAG_TYPE_EFI64_IH 20 65 | #define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21 66 | 67 | #define MULTIBOOT_HEADER_TAG_END 0 68 | #define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1 69 | #define MULTIBOOT_HEADER_TAG_ADDRESS 2 70 | #define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3 71 | #define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4 72 | #define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5 73 | #define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6 74 | #define MULTIBOOT_HEADER_TAG_EFI_BS 7 75 | #define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8 76 | #define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9 77 | #define MULTIBOOT_HEADER_TAG_RELOCATABLE 10 78 | 79 | #define MULTIBOOT_ARCHITECTURE_I386 0 80 | #define MULTIBOOT_ARCHITECTURE_MIPS32 4 81 | #define MULTIBOOT_HEADER_TAG_OPTIONAL 1 82 | 83 | #define MULTIBOOT_LOAD_PREFERENCE_NONE 0 84 | #define MULTIBOOT_LOAD_PREFERENCE_LOW 1 85 | #define MULTIBOOT_LOAD_PREFERENCE_HIGH 2 86 | 87 | #define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1 88 | #define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2 89 | 90 | #ifndef ASM_FILE 91 | 92 | typedef unsigned char multiboot_uint8_t; 93 | typedef unsigned short multiboot_uint16_t; 94 | typedef unsigned int multiboot_uint32_t; 95 | typedef unsigned long long multiboot_uint64_t; 96 | 97 | struct multiboot_header 98 | { 99 | /* Must be MULTIBOOT_MAGIC - see above. */ 100 | multiboot_uint32_t magic; 101 | 102 | /* ISA */ 103 | multiboot_uint32_t architecture; 104 | 105 | /* Total header length. */ 106 | multiboot_uint32_t header_length; 107 | 108 | /* The above fields plus this one must equal 0 mod 2^32. */ 109 | multiboot_uint32_t checksum; 110 | }; 111 | 112 | struct multiboot_header_tag 113 | { 114 | multiboot_uint16_t type; 115 | multiboot_uint16_t flags; 116 | multiboot_uint32_t size; 117 | }; 118 | 119 | struct multiboot_header_tag_information_request 120 | { 121 | multiboot_uint16_t type; 122 | multiboot_uint16_t flags; 123 | multiboot_uint32_t size; 124 | multiboot_uint32_t requests[0]; 125 | }; 126 | 127 | struct multiboot_header_tag_address 128 | { 129 | multiboot_uint16_t type; 130 | multiboot_uint16_t flags; 131 | multiboot_uint32_t size; 132 | multiboot_uint32_t header_addr; 133 | multiboot_uint32_t load_addr; 134 | multiboot_uint32_t load_end_addr; 135 | multiboot_uint32_t bss_end_addr; 136 | }; 137 | 138 | struct multiboot_header_tag_entry_address 139 | { 140 | multiboot_uint16_t type; 141 | multiboot_uint16_t flags; 142 | multiboot_uint32_t size; 143 | multiboot_uint32_t entry_addr; 144 | }; 145 | 146 | struct multiboot_header_tag_console_flags 147 | { 148 | multiboot_uint16_t type; 149 | multiboot_uint16_t flags; 150 | multiboot_uint32_t size; 151 | multiboot_uint32_t console_flags; 152 | }; 153 | 154 | struct multiboot_header_tag_framebuffer 155 | { 156 | multiboot_uint16_t type; 157 | multiboot_uint16_t flags; 158 | multiboot_uint32_t size; 159 | multiboot_uint32_t width; 160 | multiboot_uint32_t height; 161 | multiboot_uint32_t depth; 162 | }; 163 | 164 | struct multiboot_header_tag_module_align 165 | { 166 | multiboot_uint16_t type; 167 | multiboot_uint16_t flags; 168 | multiboot_uint32_t size; 169 | }; 170 | 171 | struct multiboot_header_tag_relocatable 172 | { 173 | multiboot_uint16_t type; 174 | multiboot_uint16_t flags; 175 | multiboot_uint32_t size; 176 | multiboot_uint32_t min_addr; 177 | multiboot_uint32_t max_addr; 178 | multiboot_uint32_t align; 179 | multiboot_uint32_t preference; 180 | }; 181 | 182 | struct multiboot_color 183 | { 184 | multiboot_uint8_t red; 185 | multiboot_uint8_t green; 186 | multiboot_uint8_t blue; 187 | }; 188 | 189 | struct multiboot_mmap_entry 190 | { 191 | multiboot_uint64_t addr; 192 | multiboot_uint64_t len; 193 | #define MULTIBOOT_MEMORY_AVAILABLE 1 194 | #define MULTIBOOT_MEMORY_RESERVED 2 195 | #define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 196 | #define MULTIBOOT_MEMORY_NVS 4 197 | #define MULTIBOOT_MEMORY_BADRAM 5 198 | multiboot_uint32_t type; 199 | multiboot_uint32_t zero; 200 | }; 201 | typedef struct multiboot_mmap_entry multiboot_memory_map_t; 202 | 203 | struct multiboot_tag 204 | { 205 | multiboot_uint32_t type; 206 | multiboot_uint32_t size; 207 | }; 208 | 209 | struct multiboot_tag_string 210 | { 211 | multiboot_uint32_t type; 212 | multiboot_uint32_t size; 213 | char string[0]; 214 | }; 215 | 216 | struct multiboot_tag_module 217 | { 218 | multiboot_uint32_t type; 219 | multiboot_uint32_t size; 220 | multiboot_uint32_t mod_start; 221 | multiboot_uint32_t mod_end; 222 | char cmdline[0]; 223 | }; 224 | 225 | struct multiboot_tag_basic_meminfo 226 | { 227 | multiboot_uint32_t type; 228 | multiboot_uint32_t size; 229 | multiboot_uint32_t mem_lower; 230 | multiboot_uint32_t mem_upper; 231 | }; 232 | 233 | struct multiboot_tag_bootdev 234 | { 235 | multiboot_uint32_t type; 236 | multiboot_uint32_t size; 237 | multiboot_uint32_t biosdev; 238 | multiboot_uint32_t slice; 239 | multiboot_uint32_t part; 240 | }; 241 | 242 | struct multiboot_tag_mmap 243 | { 244 | multiboot_uint32_t type; 245 | multiboot_uint32_t size; 246 | multiboot_uint32_t entry_size; 247 | multiboot_uint32_t entry_version; 248 | struct multiboot_mmap_entry entries[0]; 249 | }; 250 | 251 | struct multiboot_vbe_info_block 252 | { 253 | multiboot_uint8_t external_specification[512]; 254 | }; 255 | 256 | struct multiboot_vbe_mode_info_block 257 | { 258 | multiboot_uint8_t external_specification[256]; 259 | }; 260 | 261 | struct multiboot_tag_vbe 262 | { 263 | multiboot_uint32_t type; 264 | multiboot_uint32_t size; 265 | 266 | multiboot_uint16_t vbe_mode; 267 | multiboot_uint16_t vbe_interface_seg; 268 | multiboot_uint16_t vbe_interface_off; 269 | multiboot_uint16_t vbe_interface_len; 270 | 271 | struct multiboot_vbe_info_block vbe_control_info; 272 | struct multiboot_vbe_mode_info_block vbe_mode_info; 273 | }; 274 | 275 | struct multiboot_tag_framebuffer_common 276 | { 277 | multiboot_uint32_t type; 278 | multiboot_uint32_t size; 279 | 280 | multiboot_uint64_t framebuffer_addr; 281 | multiboot_uint32_t framebuffer_pitch; 282 | multiboot_uint32_t framebuffer_width; 283 | multiboot_uint32_t framebuffer_height; 284 | multiboot_uint8_t framebuffer_bpp; 285 | #define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 286 | #define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1 287 | #define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 288 | multiboot_uint8_t framebuffer_type; 289 | multiboot_uint16_t reserved; 290 | }; 291 | 292 | struct multiboot_tag_framebuffer 293 | { 294 | struct multiboot_tag_framebuffer_common common; 295 | 296 | union 297 | { 298 | struct 299 | { 300 | multiboot_uint16_t framebuffer_palette_num_colors; 301 | struct multiboot_color framebuffer_palette[0]; 302 | }; 303 | struct 304 | { 305 | multiboot_uint8_t framebuffer_red_field_position; 306 | multiboot_uint8_t framebuffer_red_mask_size; 307 | multiboot_uint8_t framebuffer_green_field_position; 308 | multiboot_uint8_t framebuffer_green_mask_size; 309 | multiboot_uint8_t framebuffer_blue_field_position; 310 | multiboot_uint8_t framebuffer_blue_mask_size; 311 | }; 312 | }; 313 | }; 314 | 315 | struct multiboot_tag_elf_sections 316 | { 317 | multiboot_uint32_t type; 318 | multiboot_uint32_t size; 319 | multiboot_uint32_t num; 320 | multiboot_uint32_t entsize; 321 | multiboot_uint32_t shndx; 322 | char sections[0]; 323 | }; 324 | 325 | struct multiboot_tag_apm 326 | { 327 | multiboot_uint32_t type; 328 | multiboot_uint32_t size; 329 | multiboot_uint16_t version; 330 | multiboot_uint16_t cseg; 331 | multiboot_uint32_t offset; 332 | multiboot_uint16_t cseg_16; 333 | multiboot_uint16_t dseg; 334 | multiboot_uint16_t flags; 335 | multiboot_uint16_t cseg_len; 336 | multiboot_uint16_t cseg_16_len; 337 | multiboot_uint16_t dseg_len; 338 | }; 339 | 340 | struct multiboot_tag_efi32 341 | { 342 | multiboot_uint32_t type; 343 | multiboot_uint32_t size; 344 | multiboot_uint32_t pointer; 345 | }; 346 | 347 | struct multiboot_tag_efi64 348 | { 349 | multiboot_uint32_t type; 350 | multiboot_uint32_t size; 351 | multiboot_uint64_t pointer; 352 | }; 353 | 354 | struct multiboot_tag_smbios 355 | { 356 | multiboot_uint32_t type; 357 | multiboot_uint32_t size; 358 | multiboot_uint8_t major; 359 | multiboot_uint8_t minor; 360 | multiboot_uint8_t reserved[6]; 361 | multiboot_uint8_t tables[0]; 362 | }; 363 | 364 | struct multiboot_tag_old_acpi 365 | { 366 | multiboot_uint32_t type; 367 | multiboot_uint32_t size; 368 | multiboot_uint8_t rsdp[0]; 369 | }; 370 | 371 | struct multiboot_tag_new_acpi 372 | { 373 | multiboot_uint32_t type; 374 | multiboot_uint32_t size; 375 | multiboot_uint8_t rsdp[0]; 376 | }; 377 | 378 | struct multiboot_tag_network 379 | { 380 | multiboot_uint32_t type; 381 | multiboot_uint32_t size; 382 | multiboot_uint8_t dhcpack[0]; 383 | }; 384 | 385 | struct multiboot_tag_efi_mmap 386 | { 387 | multiboot_uint32_t type; 388 | multiboot_uint32_t size; 389 | multiboot_uint32_t descr_size; 390 | multiboot_uint32_t descr_vers; 391 | multiboot_uint8_t efi_mmap[0]; 392 | }; 393 | 394 | struct multiboot_tag_efi32_ih 395 | { 396 | multiboot_uint32_t type; 397 | multiboot_uint32_t size; 398 | multiboot_uint32_t pointer; 399 | }; 400 | 401 | struct multiboot_tag_efi64_ih 402 | { 403 | multiboot_uint32_t type; 404 | multiboot_uint32_t size; 405 | multiboot_uint64_t pointer; 406 | }; 407 | 408 | struct multiboot_tag_load_base_addr 409 | { 410 | multiboot_uint32_t type; 411 | multiboot_uint32_t size; 412 | multiboot_uint32_t load_base_addr; 413 | }; 414 | 415 | #endif /* ! ASM_FILE */ 416 | 417 | #endif /* ! MULTIBOOT_HEADER */ -------------------------------------------------------------------------------- /tests/multiboot2_x64/towboot.toml: -------------------------------------------------------------------------------- 1 | default = "multiboot2" 2 | timeout = 0 3 | log_level = "warn" 4 | 5 | [entries] 6 | 7 | [entries.multiboot2] 8 | name = "multiboot2" 9 | image = "kernel" 10 | argv = "test of a cmdline" 11 | -------------------------------------------------------------------------------- /tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains integration tests. 2 | #![cfg(test)] 3 | #![feature(exit_status_error)] 4 | use std::error::Error; 5 | use std::io::Write; 6 | use std::path::{Path, PathBuf}; 7 | use std::process::{Command, Stdio}; 8 | use std::thread::sleep; 9 | use std::time::Duration; 10 | 11 | use tempfile::NamedTempFile; 12 | use towbootctl::{boot_image, create_image}; 13 | 14 | #[derive(PartialEq, Clone, Copy)] 15 | enum Arch { 16 | I686, 17 | X86_64, 18 | } 19 | 20 | #[cfg(test)] 21 | #[ctor::ctor] 22 | fn init() { 23 | if std::env::var("RUST_LOG").is_err() { 24 | unsafe { std::env::set_var("RUST_LOG", "warn"); } 25 | } 26 | env_logger::init(); 27 | } 28 | 29 | /// Builds the given folder as an image and boots it. 30 | fn build_and_boot( 31 | folder: &Path, towboot_arch: Arch, machine_arch: Arch, firmware_arch: Arch, 32 | ) -> Result> { 33 | // get towboot 34 | let mut towboot_temp_ia32 = NamedTempFile::new()?; 35 | towboot_temp_ia32.as_file_mut().write_all(towboot_ia32::TOWBOOT)?; 36 | let mut towboot_temp_x64 = NamedTempFile::new()?; 37 | towboot_temp_x64.as_file_mut().write_all(towboot_x64::TOWBOOT)?; 38 | let towboot_temp_ia32_path = towboot_temp_ia32.into_temp_path(); 39 | let towboot_temp_x64_path = towboot_temp_x64.into_temp_path(); 40 | let i686: Option<&Path> = matches!(towboot_arch, Arch::I686) 41 | .then_some(&towboot_temp_ia32_path); 42 | let x86_64: Option<&Path> = matches!(towboot_arch, Arch::X86_64) 43 | .then_some(&towboot_temp_x64_path); 44 | 45 | // make sure that the kernel is built 46 | Command::new("make") 47 | .current_dir(folder) 48 | .status()?.exit_ok()?; 49 | 50 | // build the image 51 | let image_path = NamedTempFile::new()?.into_temp_path(); 52 | let mut config_path = folder.to_path_buf(); 53 | config_path.push("towboot.toml"); 54 | create_image( 55 | &image_path, &[ 56 | "-config".to_string(), 57 | config_path.to_str().unwrap().to_string(), 58 | ], i686.as_deref(), x86_64.as_deref(), 59 | )?; 60 | 61 | // boot it 62 | assert!(firmware_arch == machine_arch); // TODO 63 | let (mut qemu_command, _temp_files) = boot_image( 64 | None, 65 | &image_path, 66 | matches!(machine_arch, Arch::X86_64), 67 | false, 68 | true, // the firmware seems to boot only on KVM 69 | false, 70 | )?; 71 | let mut qemu_process = qemu_command 72 | .stdin(Stdio::null()) 73 | .stdout(Stdio::piped()) 74 | .stderr(Stdio::inherit()) 75 | .arg("-display").arg("none") 76 | .spawn()?; 77 | sleep(Duration::from_secs(5)); // TODO: kernels should probably terminate the VM 78 | qemu_process.kill()?; // there's no terminate here 79 | let qemu_output = qemu_process.wait_with_output()?; 80 | Ok(String::from_utf8(qemu_output.stdout)?) 81 | } 82 | 83 | #[test] 84 | fn multiboot1() { 85 | for arch in [Arch::I686, Arch::X86_64] { 86 | let stdout = build_and_boot( 87 | &PathBuf::from("multiboot1"), 88 | arch, arch, arch, 89 | ).expect("failed to run"); 90 | println!("{}", stdout); 91 | assert!(stdout.contains("cmdline = test of a cmdline")); 92 | assert!(stdout.contains("boot_loader_name = towboot")); 93 | assert!(stdout.contains("mods_count = 0")); 94 | assert!(stdout.contains("mem_lower = 640KB")); 95 | assert!(stdout.ends_with("Halted.")); 96 | } 97 | } 98 | 99 | #[test] 100 | fn multiboot2() { 101 | for arch in [Arch::I686, Arch::X86_64] { 102 | let stdout = build_and_boot( 103 | &PathBuf::from("multiboot2"), 104 | arch, arch, arch, 105 | ).expect("failed to run"); 106 | println!("{}", stdout); 107 | assert!(stdout.contains("Command line = test of a cmdline")); 108 | assert!(stdout.contains("Boot loader name = towboot")); 109 | assert!(!stdout.contains("Module at")); 110 | assert!(stdout.contains("mem_lower = 640KB")); 111 | assert!(stdout.ends_with("Halted.")); 112 | } 113 | } 114 | 115 | #[test] 116 | fn multiboot2_x64() { 117 | // it should boot on x86_64 118 | let stdout = build_and_boot( 119 | &PathBuf::from("multiboot2_x64"), 120 | Arch::X86_64, Arch::X86_64, Arch::X86_64, 121 | ).expect("failed to run"); 122 | println!("{}", stdout); 123 | assert!(stdout.contains("Command line = test of a cmdline")); 124 | assert!(stdout.contains("Boot loader name = towboot")); 125 | assert!(!stdout.contains("Module at")); 126 | assert!(stdout.contains("mem_lower = 640KB")); 127 | assert!(stdout.ends_with("Halted.")); 128 | // it should not boot on i686 129 | let stdout = build_and_boot( 130 | &PathBuf::from("multiboot2_x64"), 131 | Arch::I686, Arch::I686, Arch::I686, 132 | ).expect("failed to run"); 133 | println!("{}", stdout); 134 | assert!(stdout.contains("The kernel supports 64-bit UEFI systems, but we're running on 32-bit")); 135 | assert!(!stdout.contains("Halted.")); 136 | } 137 | -------------------------------------------------------------------------------- /towboot.toml: -------------------------------------------------------------------------------- 1 | default = "multiboot" 2 | timeout = 3 3 | log_level = "trace" 4 | 5 | [entries] 6 | 7 | [entries.multiboot] 8 | name = "multiboot" 9 | image = "multiboot.elf" 10 | argv = "" 11 | -------------------------------------------------------------------------------- /towboot/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["per-package-target"] 2 | 3 | [package] 4 | name = "towboot" 5 | version.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | edition = "2024" 10 | default-target = "i686-unknown-uefi" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | uefi = { version = "0.35", features = ["alloc", "global_allocator", "logger", "panic_handler"] } 16 | acpi = { version = "5.0", default-features = false } 17 | smbios-lib = { git = "https://github.com/hhuOS/smbios-lib.git", branch = "main", default-features = false, features = ["no_std"] } 18 | x86 = "0.52" 19 | 20 | log = { version = "0.4", default-features = false } 21 | 22 | # tomling is a nice and no_std toml parser, but sadly it fails on our modules :/ 23 | toml = { git = "https://github.com/thomcc/toml-rs.git", branch = "nostd", default-features = false } 24 | multiboot12 = { git = "https://github.com/YtvwlD/multiboot12", tag = "towboot-0.9.1" } 25 | goblin = { version = "0.9", default-features = false, features = ["elf32", "elf64", "endian_fd"] } 26 | scroll = { version = "0.12", default-features = false } 27 | 28 | towboot_config = { path = "../towboot_config" } 29 | 30 | [build-dependencies] 31 | built = { version = "0.8", features = ["git2"] } 32 | -------------------------------------------------------------------------------- /towboot/build.rs: -------------------------------------------------------------------------------- 1 | //! Our build script. 2 | //! 3 | //! Currently, this just makes certain compile-time information visible to 4 | //! the application using built. 5 | 6 | fn main() { 7 | built::write_built_file().expect("Failed to acquire build-time information"); 8 | } 9 | -------------------------------------------------------------------------------- /towboot/src/boot/config_tables.rs: -------------------------------------------------------------------------------- 1 | //! Handle UEFI config tables. 2 | use alloc::slice; 3 | use alloc::vec::Vec; 4 | 5 | use log::{debug, warn}; 6 | use multiboot12::information::InfoBuilder; 7 | use acpi::rsdp::Rsdp; 8 | use smbioslib::{SMBiosEntryPoint32, SMBiosEntryPoint64}; 9 | use uefi::system::with_config_table; 10 | use uefi::table::cfg::{ 11 | ConfigTableEntry, ACPI_GUID, ACPI2_GUID, DEBUG_IMAGE_INFO_GUID, 12 | DXE_SERVICES_GUID, HAND_OFF_BLOCK_LIST_GUID, LZMA_COMPRESS_GUID, 13 | MEMORY_STATUS_CODE_RECORD_GUID, MEMORY_TYPE_INFORMATION_GUID, SMBIOS_GUID, 14 | SMBIOS3_GUID, 15 | }; 16 | 17 | /// Go through all of the configuration tables. 18 | /// Some of them are interesting for Multiboot2. 19 | pub(super) fn parse_for_multiboot(info_builder: &mut InfoBuilder) { 20 | // first, copy all config table pointers 21 | // TODO: remove this when with_config_table takes a FnMut 22 | let config_tables: Vec = with_config_table(|s| 23 | s.to_vec() 24 | ); 25 | debug!("going through configuration tables..."); 26 | for table in config_tables { 27 | match table.guid { 28 | ACPI_GUID => handle_acpi(&table, info_builder), 29 | ACPI2_GUID => handle_acpi(&table, info_builder), 30 | DEBUG_IMAGE_INFO_GUID => debug!("ignoring image debug info"), 31 | DXE_SERVICES_GUID => debug!("ignoring dxe services table"), 32 | HAND_OFF_BLOCK_LIST_GUID => debug!("ignoring hand-off block list"), 33 | LZMA_COMPRESS_GUID => debug!("ignoring lzma filesystem"), 34 | MEMORY_STATUS_CODE_RECORD_GUID => debug!("ignoring early memory info"), 35 | MEMORY_TYPE_INFORMATION_GUID => debug!("ignoring early memory info"), 36 | SMBIOS_GUID => handle_smbios(&table, info_builder), 37 | SMBIOS3_GUID => handle_smbios(&table, info_builder), 38 | guid => debug!("ignoring table {guid}"), 39 | } 40 | } 41 | } 42 | 43 | /// Parse the ACPI RSDP and create the Multiboot struct for it. 44 | fn handle_acpi(table: &ConfigTableEntry, info_builder: &mut InfoBuilder) { 45 | debug!("handling ACPI RSDP"); 46 | let rsdp = unsafe { *(table.address as *const Rsdp) }; 47 | if rsdp.validate().is_err() { 48 | warn!("the RSDP is invalid"); 49 | return; 50 | } 51 | 52 | match table.guid { 53 | ACPI_GUID => { 54 | if rsdp.revision() != 0 { 55 | warn!("expected RSDP version 0, but got {}", rsdp.revision()); 56 | } 57 | info_builder.set_rsdp_v1( 58 | rsdp.signature(), rsdp.checksum(), 59 | rsdp.oem_id().as_bytes()[0..6].try_into().unwrap(), 60 | rsdp.revision(), rsdp.rsdt_address(), 61 | ); 62 | } 63 | ACPI2_GUID => { 64 | if rsdp.revision() != 2 { 65 | warn!("expected RSDP version 2, but got {}", rsdp.revision()); 66 | } 67 | if rsdp.revision() == 0 { 68 | // some u-boot versions do this 69 | warn!("RSDP revision is 0, forcing v1"); 70 | info_builder.set_rsdp_v1( 71 | rsdp.signature(), rsdp.checksum(), 72 | rsdp.oem_id().as_bytes()[0..6].try_into().unwrap(), 73 | rsdp.revision(), rsdp.rsdt_address(), 74 | ); 75 | return; 76 | } 77 | info_builder.set_rsdp_v2( 78 | rsdp.signature(), rsdp.checksum(), 79 | rsdp.oem_id().as_bytes()[0..6].try_into().unwrap(), 80 | rsdp.revision(), rsdp.rsdt_address(), rsdp.length(), 81 | rsdp.xsdt_address(), rsdp.ext_checksum(), 82 | ); 83 | } 84 | _ => warn!("'handle_acpi()' called with wrong config table entry") 85 | } 86 | } 87 | 88 | /// The entry point for SMBIOS. 89 | enum EntryPoint { 90 | SMBIOS2(SMBiosEntryPoint32), 91 | SMBIOS3(SMBiosEntryPoint64), 92 | } 93 | 94 | impl EntryPoint { 95 | fn major_version(&self) -> u8 { 96 | match self { 97 | Self::SMBIOS2(e) => e.major_version(), 98 | Self::SMBIOS3(e) => e.major_version(), 99 | } 100 | } 101 | 102 | fn minor_version(&self) -> u8 { 103 | match self { 104 | Self::SMBIOS2(e) => e.minor_version(), 105 | Self::SMBIOS3(e) => e.minor_version(), 106 | } 107 | } 108 | 109 | fn entry_point_length(&self) -> u8 { 110 | match self { 111 | Self::SMBIOS2(e) => e.entry_point_length(), 112 | Self::SMBIOS3(e) => e.entry_point_length(), 113 | } 114 | } 115 | 116 | fn structure_table_address(&self) -> u64 { 117 | match self { 118 | Self::SMBIOS2(e) => e.structure_table_address().into(), 119 | Self::SMBIOS3(e) => e.structure_table_address(), 120 | } 121 | } 122 | 123 | fn structure_table_length(&self) -> u32 { 124 | match self { 125 | Self::SMBIOS2(e) => e.structure_table_length().into(), 126 | Self::SMBIOS3(e) => e.structure_table_maximum_size(), 127 | } 128 | } 129 | } 130 | 131 | 132 | /// Copy the SMBIOS tables. 133 | /// This is a copy of the Entry Point and the Structure Table. 134 | /// Caveat: The Structure Table pointer in the Entry Point is not adjusted. 135 | fn handle_smbios(table: &ConfigTableEntry, info_builder: &mut InfoBuilder) { 136 | debug!("handling SMBIOS table"); 137 | let bigger_slice = unsafe { slice::from_raw_parts( 138 | table.address as *const u8, 100 + match table.guid { 139 | SMBIOS_GUID => SMBiosEntryPoint32::MINIMUM_SIZE, 140 | SMBIOS3_GUID => SMBiosEntryPoint64::MINIMUM_SIZE, 141 | guid => panic!("{guid} is not a SMBIOS table"), 142 | } 143 | ) }; 144 | let entry_point = match table.guid { 145 | SMBIOS_GUID => EntryPoint::SMBIOS2( 146 | SMBiosEntryPoint32::try_scan_from_raw(bigger_slice) 147 | .expect("the 32 bit SMBIOS table to be parseable") 148 | ), 149 | SMBIOS3_GUID => EntryPoint::SMBIOS3( 150 | SMBiosEntryPoint64::try_scan_from_raw(bigger_slice) 151 | .expect("the 64 bit SMBIOS table to be parseable") 152 | ), 153 | guid => panic!("{guid} is not a SMBIOS table"), 154 | }; 155 | let mut bytes = bigger_slice[0..entry_point.entry_point_length().into()].to_vec(); 156 | // TODO: replace structure_table_address afterwards 157 | let structure_table_address: usize = entry_point.structure_table_address().try_into().unwrap(); 158 | bytes.extend_from_slice(unsafe { slice::from_raw_parts( 159 | structure_table_address as *const u8, 160 | entry_point.structure_table_length().try_into().unwrap(), 161 | ) }); 162 | info_builder.add_smbios_tag( 163 | entry_point.major_version(), entry_point.minor_version(), bytes.as_slice(), 164 | ); 165 | } -------------------------------------------------------------------------------- /towboot/src/boot/elf.rs: -------------------------------------------------------------------------------- 1 | //! Handling of ELF files 2 | 3 | use alloc::collections::btree_map::BTreeMap; 4 | use alloc::collections::btree_set::BTreeSet; 5 | use alloc::vec::Vec; 6 | 7 | use log::{trace, debug, warn}; 8 | 9 | use goblin::elf; 10 | use goblin::container; 11 | use scroll::ctx::IntoCtx; 12 | 13 | use multiboot12::header::Header; 14 | use multiboot12::information::Symbols; 15 | use towboot_config::Quirk; 16 | 17 | use super::super::mem::Allocation; 18 | 19 | /// Load ELF binaries. 20 | pub(super) struct OurElfLoader { 21 | // maps virtual to physical addresses 22 | allocations: BTreeMap, 23 | virtual_entry_point: u64, 24 | physical_entry_point: Option, 25 | } 26 | 27 | impl OurElfLoader { 28 | /// Create a new instance. 29 | /// 30 | /// The parameter is the virtual address of the entry point. 31 | pub(super) fn new(entry_point: u64) -> Self { 32 | OurElfLoader { 33 | allocations: BTreeMap::new(), 34 | virtual_entry_point: entry_point, 35 | physical_entry_point: None, 36 | } 37 | } 38 | 39 | /// Load an ELF. 40 | pub(super) fn load_elf( 41 | &mut self, 42 | binary: &elf::Elf, 43 | data: &[u8], 44 | quirks: &BTreeSet, 45 | ) -> Result<(), &'static str> { 46 | for program_header in &binary.program_headers { 47 | if program_header.p_type == elf::program_header::PT_LOAD { 48 | self.allocate(program_header, quirks)?; 49 | self.load(program_header.p_vaddr, &data[program_header.file_range()])?; 50 | } 51 | } 52 | Ok(()) 53 | } 54 | 55 | /// Gets the entry point. 56 | /// 57 | /// We should have found it in `allocate`, 58 | /// else fall back to the virtual one and hope for the best. 59 | pub(super) fn entry_point(&self) -> usize { 60 | if let Some(a) = self.physical_entry_point { 61 | a 62 | } else { 63 | warn!("didn't find the entry point while loading sections, assuming virtual = physical"); 64 | self.virtual_entry_point.try_into().unwrap() 65 | } 66 | } 67 | 68 | /// Allocate memory for a segment. 69 | fn allocate( 70 | &mut self, 71 | header: &elf::program_header::ProgramHeader, 72 | quirks: &BTreeSet, 73 | ) -> Result<(), &'static str> { 74 | trace!("header: {header:?}"); 75 | debug!( 76 | "allocating {} {} bytes at {:#x} for {:#x}", 77 | header.p_memsz, header.p_flags, header.p_paddr, header.p_vaddr 78 | ); 79 | let mut allocation = Allocation::new_at( 80 | header.p_paddr.try_into().unwrap(), 81 | header.p_memsz.try_into().unwrap(), 82 | quirks, 83 | ) 84 | .map_err(|_e| "failed to allocate memory for the kernel")?; 85 | let mem_slice = allocation.as_mut_slice(); 86 | mem_slice.fill(0); 87 | self.allocations.insert(header.p_vaddr, allocation); 88 | if header.p_vaddr <= self.virtual_entry_point 89 | && header.p_vaddr + header.p_memsz >= self.virtual_entry_point 90 | { 91 | self.physical_entry_point = Some( 92 | (header.p_paddr + self.virtual_entry_point - header.p_vaddr) 93 | .try_into() 94 | .unwrap(), 95 | ); 96 | debug!( 97 | "(this segment will contain the entry point {:#x} at {:#x})", 98 | self.virtual_entry_point, 99 | self.physical_entry_point.unwrap(), 100 | ); 101 | } 102 | Ok(()) 103 | } 104 | 105 | /// Load a segment. 106 | fn load(&mut self, base: u64, region: &[u8]) -> Result<(), &'static str> { 107 | // check whether we actually allocated this 108 | match self.allocations.get_mut(&base) { 109 | None => panic!("we didn't allocate {base:#x}, but tried to write to it o.O"), 110 | Some(alloc) => { 111 | assert!( 112 | alloc.len >= region.len(), 113 | "{base:#x} doesn't fit into the memory allocated for it" 114 | ); 115 | let ptr = alloc.as_ptr(); 116 | debug!( 117 | "load {} bytes into {:#x} (at {:#x})", region.len(), base, ptr as usize 118 | ); 119 | alloc.as_mut_slice()[0..region.len()].clone_from_slice(region); 120 | Ok(()) 121 | }, 122 | } 123 | } 124 | } 125 | 126 | impl From for Vec { 127 | /// Gets the allocated memory. 128 | fn from(loader: OurElfLoader) -> Vec { 129 | // using .values() would just borrow the values from the hash map 130 | loader.allocations.into_values().collect() 131 | } 132 | } 133 | 134 | /// Bring the binary's symbols in a format for Multiboot. 135 | /// 136 | /// Returns a tuple of informations struct and vector containing the symbols. 137 | pub(super) fn symbols( 138 | header: &Header, binary: &mut elf::Elf, data: &[u8] 139 | ) -> (Symbols, Vec) { 140 | // Let's just hope they fit into u32s. 141 | let num: u32 = binary.header.e_shnum.into(); 142 | let size: u32 = binary.header.e_shentsize.into(); 143 | 144 | // allocate memory to place the section headers and sections 145 | let mut memory = Vec::with_capacity(( 146 | u64::from(size * num) 147 | + binary.section_headers.iter().filter(|s| s.sh_addr == 0).map(|s| s.sh_size).sum::() 148 | ).try_into().unwrap()); 149 | let ptr = memory.as_ptr(); 150 | 151 | // copy the symbols 152 | // only copy sections that are not already loaded 153 | for section in binary.section_headers.iter_mut().filter( 154 | |s| s.sh_addr == 0 && s.file_range().is_some() 155 | ) { 156 | let index = memory.len(); 157 | memory.extend_from_slice(&data[section.file_range().unwrap()]); 158 | section.sh_addr = (index + ptr as usize).try_into().unwrap(); 159 | trace!("Loaded section {:?} to {:#x}", section, section.sh_addr); 160 | } 161 | 162 | // copy the section headers 163 | let shdr_begin = memory.len(); 164 | // make sure that resizing won't reallocate 165 | assert!(memory.capacity() >= shdr_begin + (size * num) as usize); 166 | memory.resize(shdr_begin + (size * num) as usize, 0); 167 | // we can't copy from data as it still just contains null pointers 168 | let ctx = container::Ctx::new( 169 | if binary.is_64 { container::Container::Big } else { container::Container::Little }, 170 | if binary.little_endian { container::Endian::Little } else { container::Endian::Big }, 171 | ); 172 | let mut begin_idx = shdr_begin; 173 | for section in &binary.section_headers { 174 | section.clone().into_ctx(&mut memory[begin_idx..begin_idx+size as usize], ctx); 175 | begin_idx += size as usize; 176 | } 177 | let shndx = binary.header.e_shstrndx.into(); 178 | ( 179 | header.new_elf_symbols( 180 | num, size, ptr as usize + shdr_begin, shndx 181 | ), 182 | memory // we could drop this for multiboot2 183 | ) 184 | } 185 | -------------------------------------------------------------------------------- /towboot/src/boot/video.rs: -------------------------------------------------------------------------------- 1 | //! Management of the video mode. 2 | 3 | use alloc::collections::btree_set::BTreeSet; 4 | use alloc::vec::Vec; 5 | 6 | use uefi::prelude::*; 7 | use uefi::boot::{ 8 | find_handles, image_handle, open_protocol, 9 | OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, 10 | }; 11 | use uefi::proto::console::gop::{GraphicsOutput, Mode, PixelBitmask, PixelFormat}; 12 | 13 | use log::{debug, warn, info, error}; 14 | 15 | use multiboot12::header::Header; 16 | use multiboot12::information::{InfoBuilder, ColorInfo}; 17 | 18 | use towboot_config::Quirk; 19 | 20 | /// Try to get the video in a mode the kernel wants. 21 | /// 22 | /// If there are multiple GPUs available, simply choose the first one. 23 | /// If there is no available mode that matches, just use the one we're already in. 24 | pub fn setup_video( 25 | header: &Header, quirks: &BTreeSet, 26 | ) -> Option> { 27 | info!("setting up the video..."); 28 | let wanted_resolution = match ( 29 | header.get_preferred_video_mode(), 30 | quirks.contains(&Quirk::KeepResolution), 31 | ) { 32 | (Some(mode), false) => { 33 | if mode.is_graphics() { 34 | // lets just hope that the firmware supports 24-bit RGB 35 | // the other modes are way too obscure 36 | // 0 means "no preference" 37 | if mode.depth().unwrap() != 24 || mode.depth().unwrap() == 0 { 38 | warn!( 39 | "color depth will be 24-bit, but the kernel wants {}", 40 | mode.depth().unwrap() 41 | ); 42 | } 43 | Some((mode.width().unwrap(), mode.height().unwrap())) 44 | } else { 45 | // We could set the console to this resolution, 46 | // but if the kernel doesn't have any EFI support, it won't be able to use it. 47 | // So, just chose a video mode and hope that the kernel supports video. 48 | // TODO: Perhaps support EFI text mode later on. 49 | warn!("text mode is not implemented"); 50 | None 51 | } 52 | }, 53 | _ => None, 54 | }; 55 | // just get the first one 56 | let handles = find_handles::() 57 | .expect("failed to list available graphics outputs"); 58 | let handle = handles.first().or_else(|| { 59 | warn!("Failed to find a graphics output. Do you have a graphics card (and a driver)?"); 60 | None 61 | })?; 62 | // Opening a protocol non-exclusively is unsafe, but otherwise we won't get 63 | // to see any new log messages. 64 | let mut output: ScopedProtocol = unsafe { open_protocol( 65 | OpenProtocolParams { 66 | handle: *handle, 67 | agent: image_handle(), 68 | controller: None, 69 | }, 70 | OpenProtocolAttributes::GetProtocol, 71 | ).ok() }?; 72 | let modes: Vec = output.modes().collect(); 73 | debug!( 74 | "available video modes: {:?}", 75 | modes.iter().map(Mode::info).map(|i| (i.resolution(), i.pixel_format())) 76 | .collect::>() 77 | ); 78 | // try to see, if we find a matching mode 79 | if let Some(mode) = match wanted_resolution { 80 | Some((w, h)) => { 81 | modes.iter().find(|m| 82 | m.info().resolution() == (w as usize, h as usize) 83 | ).or_else(|| { 84 | warn!("failed to find a matching video mode (kernel wants {w}x{h})"); 85 | None 86 | }) 87 | }, 88 | None => None, 89 | // in that case: set it 90 | } { 91 | debug!("chose {:?} as the video mode", mode.info().resolution()); 92 | output.set_mode(mode).map_err(|e| { 93 | error!("failed to set video mode: {e:?}"); 94 | Status::DEVICE_ERROR 95 | }).ok()?; 96 | info!("set {:?} as the video mode", mode.info().resolution()); 97 | } 98 | Some(output) 99 | } 100 | 101 | /// Pass the framebuffer information to the kernel. 102 | pub fn prepare_information( 103 | multiboot: &mut InfoBuilder, mut graphics_output: ScopedProtocol, 104 | ) { 105 | let address = graphics_output.frame_buffer().as_mut_ptr(); 106 | let mode = graphics_output.current_mode_info(); 107 | debug!("gop mode: {mode:?}"); 108 | let (width, height) = mode.resolution(); 109 | let mut bpp = 32; 110 | let color_info = match mode.pixel_format() { 111 | PixelFormat::Rgb => multiboot.new_color_info_rgb( 112 | 0, 113 | 8, 114 | 8, 115 | 8, 116 | 6, 117 | 8, 118 | ), 119 | PixelFormat::Bgr => multiboot.new_color_info_rgb( 120 | 16, 121 | 8, 122 | 8, 123 | 8, 124 | 0, 125 | 8, 126 | ), 127 | PixelFormat::Bitmask => { 128 | let bitmask = mode.pixel_bitmask().unwrap(); 129 | bpp = bitmask_to_bpp(bitmask); 130 | bitmask_to_color_info(multiboot, bitmask) 131 | }, 132 | PixelFormat::BltOnly => panic!("GPU doesn't support pixel access"), 133 | }; 134 | let pitch = mode.stride() * (bpp / 8) as usize; 135 | let framebuffer_table = color_info.to_framebuffer_info( 136 | address as u64, 137 | pitch.try_into().unwrap(), 138 | width.try_into().unwrap(), 139 | height.try_into().unwrap(), 140 | bpp, 141 | ); 142 | debug!("passing {framebuffer_table:?}"); 143 | multiboot.set_framebuffer_table(Some(framebuffer_table)); 144 | } 145 | 146 | /// Converts UEFI's `PixelBitmask` to Multiboot's `ColorInfoRGB`. 147 | fn bitmask_to_color_info( 148 | info_builder: &InfoBuilder, pixel_bitmask: PixelBitmask 149 | ) -> ColorInfo { 150 | let (red_field_position, red_mask_size) = parse_color_bitmap(pixel_bitmask.red); 151 | let (green_field_position, green_mask_size) = parse_color_bitmap(pixel_bitmask.green); 152 | let (blue_field_position, blue_mask_size) = parse_color_bitmap(pixel_bitmask.blue); 153 | info_builder.new_color_info_rgb( 154 | red_field_position, red_mask_size, 155 | green_field_position, green_mask_size, 156 | blue_field_position, blue_mask_size, 157 | ) 158 | } 159 | 160 | macro_rules! check_bit { 161 | ($var:expr, $bit:expr) => { 162 | ($var & (1 << $bit) == (1 << $bit)) 163 | }; 164 | } 165 | 166 | /// Converts UEFI's `PixelBitmask` to Multiboot's `bpp` (bits per pixel). 167 | fn bitmask_to_bpp(pixel_bitmask: PixelBitmask) -> u8 { 168 | let combined_bitmask = pixel_bitmask.red | pixel_bitmask.green | pixel_bitmask.blue; 169 | assert_eq!(pixel_bitmask.red & pixel_bitmask.green, 0); 170 | assert_eq!(pixel_bitmask.red & pixel_bitmask.blue, 0); 171 | assert_eq!(pixel_bitmask.green & pixel_bitmask.blue, 0); 172 | let mut bpp = 0; 173 | for i in 0..31 { 174 | if check_bit!(combined_bitmask, i) { 175 | bpp += 1; 176 | } 177 | } 178 | bpp 179 | } 180 | 181 | /// Converts a bitmask into a tuple of `field_position`, `mask_size`. 182 | fn parse_color_bitmap(bitmask: u32) -> (u8, u8) { 183 | // find the first set bit 184 | let mut field_position = 0; 185 | for i in 0..31 { 186 | if check_bit!(bitmask, i) { 187 | field_position = i; 188 | break; 189 | } 190 | } 191 | // count how many bits are set 192 | let mut mask_size = 0; 193 | for i in field_position..31 { 194 | if !check_bit!(bitmask, i) { 195 | break; 196 | } 197 | mask_size += 1; 198 | } 199 | // check whether there are remaining bits set 200 | for i in field_position+mask_size..31 { 201 | if check_bit!(bitmask, i) { 202 | panic!("color bitmask is not continuous"); 203 | } 204 | } 205 | (field_position, mask_size) 206 | } 207 | -------------------------------------------------------------------------------- /towboot/src/config.rs: -------------------------------------------------------------------------------- 1 | //! This module contains functions to load the configuration. 2 | //! 3 | //! The configuration can come from a file or from the command line. 4 | //! The command line options take precedence if they are specified. 5 | //! 6 | //! Most of the actual structs can be found in the [`towboot_config`] crate. 7 | //! The towbootctl package has its own config.rs. 8 | use alloc::format; 9 | use alloc::string::{String, ToString}; 10 | use alloc::vec::Vec; 11 | 12 | use log::error; 13 | use uefi::prelude::*; 14 | 15 | use towboot_config::{Config, ConfigSource, parse_load_options}; 16 | 17 | use super::file::File; 18 | 19 | /// Generate the output for `-version`. 20 | fn version_info() -> String { 21 | #[allow(dead_code)] 22 | mod built_info { 23 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 24 | } 25 | format!( 26 | "This is {} {}{}, built as {} for {} on {}. It is licensed under the {}.", 27 | built_info::PKG_NAME, 28 | built_info::GIT_VERSION.unwrap(), 29 | if built_info::GIT_DIRTY.unwrap() { 30 | " (dirty)" 31 | } else { 32 | "" 33 | }, 34 | built_info::PROFILE, 35 | built_info::TARGET, 36 | built_info::HOST, 37 | built_info::PKG_LICENSE, 38 | ) 39 | } 40 | 41 | /// Get the config. 42 | /// If we were called with command line options, try them first. 43 | /// Otherwise, read and parse a configuration file. 44 | /// 45 | /// Returns None if just a help text has been displayed. 46 | pub fn get( 47 | image_fs_handle: Handle, load_options: &str, 48 | ) -> Result, Status> { 49 | match parse_load_options(load_options, &version_info()) { 50 | Ok(Some(ConfigSource::File(s))) => Ok(Some(read_file(image_fs_handle, &s)?)), 51 | Ok(Some(ConfigSource::Given(c))) => Ok(Some(c)), 52 | Ok(None) => Ok(None), 53 | Err(()) => Err(Status::INVALID_PARAMETER), 54 | } 55 | } 56 | 57 | /// Try to read and parse the configuration from the given file. 58 | fn read_file(image_fs_handle: Handle, file_name: &str) -> Result { 59 | let bytes: Vec = File::open(file_name, image_fs_handle)?.try_into()?; 60 | let text = str::from_utf8(&bytes).map_err(|e| { 61 | error!("configuration file contains invalid bytes: {e:?}"); 62 | Status::UNSUPPORTED 63 | })?; 64 | let mut config: Config = toml::from_str(text).map_err(|e| { 65 | error!("configuration file could not be parsed: {e:?}"); 66 | Status::UNSUPPORTED 67 | })?; 68 | config.src = file_name.to_string(); 69 | Ok(config) 70 | } 71 | -------------------------------------------------------------------------------- /towboot/src/file.rs: -------------------------------------------------------------------------------- 1 | //! File handling 2 | 3 | use alloc::borrow::ToOwned; 4 | use alloc::collections::btree_set::BTreeSet; 5 | use alloc::format; 6 | use alloc::{vec::Vec, vec}; 7 | use alloc::string::ToString; 8 | 9 | use log::{info, error}; 10 | 11 | use uefi::prelude::*; 12 | use uefi::boot::{find_handles, open_protocol_exclusive}; 13 | use uefi::fs::{Path, PathBuf}; 14 | use uefi::data_types::CString16; 15 | use uefi::proto::media::fs::SimpleFileSystem; 16 | use uefi::proto::media::file::{ 17 | File as UefiFile, FileAttribute, FileInfo, FileMode, FileType, RegularFile 18 | }; 19 | 20 | use towboot_config::Quirk; 21 | use super::mem::Allocation; 22 | 23 | /// An opened file. 24 | pub(crate) struct File<'a> { 25 | name: &'a str, 26 | file: RegularFile, 27 | size: usize, 28 | } 29 | 30 | impl<'a> File<'a> { 31 | /// Opens a file. 32 | /// 33 | /// The path can be: 34 | /// * relative to the volume we're loaded from 35 | /// * on a different volume (if it starts with `fs?:`) 36 | /// 37 | /// Possible errors: 38 | /// * `Status::INVALID_PARAMETER`: the volume identifier is invalid 39 | /// * `Status::NOT_FOUND`: the file does not exist 40 | /// * `Status::PROTOCOL_ERROR`: the file name is not a valid string 41 | /// * `Status::UNSUPPORTED`: the given path does exist, but it's a directory 42 | pub(crate) fn open(name: &'a str, image_fs_handle: Handle) -> Result { 43 | info!("loading file '{name}'..."); 44 | let file_name = CString16::try_from(name) 45 | .map_err(|e| { 46 | error!("filename is invalid because of {e:?}"); 47 | Status::PROTOCOL_ERROR 48 | })?; 49 | let file_path = Path::new(&file_name); 50 | let mut file_path_components = file_path.components(); 51 | let ( 52 | fs_handle, file_name, 53 | ) = if let Some(root) = file_path_components.next() && root.to_string().ends_with(':') { 54 | if let Some(idx) = root 55 | .to_string() 56 | .to_lowercase() 57 | .strip_suffix(':') 58 | .unwrap() 59 | .strip_prefix("fs") { 60 | let filesystems = find_handles::() 61 | .map_err(|e| e.status())?; 62 | let fs = filesystems.into_iter().nth( 63 | idx.parse::().map_err(|_| { 64 | error!("{idx} is not a number"); 65 | Status::INVALID_PARAMETER 66 | })? 67 | ).ok_or(Status::NOT_FOUND)?; 68 | let mut file_path = PathBuf::new(); 69 | for c in file_path_components { 70 | file_path.push(c.as_ref()); 71 | } 72 | Ok((fs, file_path.to_cstr16().to_owned())) 73 | } else { 74 | error!("don't know how to open {root}"); 75 | Err(Status::INVALID_PARAMETER) 76 | }? 77 | } else { 78 | (image_fs_handle, file_name) 79 | }; 80 | let mut fs = open_protocol_exclusive::(fs_handle) 81 | .map_err(|e| e.status())?; 82 | let file_handle = match fs.open_volume().map_err(|e| e.status())?.open( 83 | &file_name, 84 | FileMode::Read, 85 | FileAttribute::READ_ONLY, 86 | ) { 87 | Ok(file_handle) => file_handle, 88 | Err(e) => return { 89 | error!("Failed to find file '{name}': {e:?}"); 90 | Err(Status::NOT_FOUND) 91 | } 92 | }; 93 | let mut file = match file_handle.into_type() 94 | .expect(&format!("Failed to open file '{name}'")) { 95 | FileType::Regular(file) => file, 96 | FileType::Dir(_) => return { 97 | error!("File '{name}' is a directory"); 98 | Err(Status::UNSUPPORTED) 99 | } 100 | }; 101 | let mut info_vec = Vec::::new(); 102 | 103 | // we try to get the metadata with a zero-sized buffer 104 | // this should throw BUFFER_TOO_SMALL and give us the needed size 105 | let info_result = file.get_info::(info_vec.as_mut_slice()); 106 | assert_eq!(info_result.status(), Status::BUFFER_TOO_SMALL); 107 | let info_size: usize = info_result.expect_err("metadata is 0 bytes").data() 108 | .expect("failed to get size of file metadata"); 109 | info_vec.resize(info_size, 0); 110 | 111 | let size: usize = file.get_info::(info_vec.as_mut_slice()) 112 | .expect(&format!("Failed to get metadata of file '{name}'")) 113 | .file_size().try_into().unwrap(); 114 | Ok(Self { name, file, size }) 115 | } 116 | 117 | /// Read a whole file into memory and return the resulting allocation. 118 | /// 119 | /// (The difference to `TryInto>` is that the allocated memory 120 | /// is page-aligned and under 4GB.) 121 | pub(crate) fn try_into_allocation( 122 | mut self, quirks: &BTreeSet, 123 | ) -> Result { 124 | let mut allocation = Allocation::new_under_4gb(self.size, quirks)?; 125 | let read_size = self.file.read(allocation.as_mut_slice()) 126 | .map_err(|e| { 127 | error!("Failed to read from file '{}': {:?}", self.name, e); 128 | e.status() 129 | })?; 130 | if read_size == self.size { 131 | Ok(allocation) 132 | } else { 133 | error!("Failed to fully read from file '{}", self.name); 134 | Err(Status::END_OF_FILE) 135 | } 136 | } 137 | } 138 | 139 | impl TryFrom> for Vec { 140 | type Error = Status; 141 | 142 | /// Read a whole file into memory and return the resulting byte vector. 143 | fn try_from(mut file: File) -> Result { 144 | // Vec::with_size would allocate enough space, but won't fill it with zeros. 145 | // file.read seems to need this. 146 | let mut content_vec = vec![0; file.size]; 147 | let read_size = file.file.read(content_vec.as_mut_slice()) 148 | .map_err(|e| { 149 | error!("Failed to read from file '{}': {:?}", file.name, e); 150 | e.status() 151 | })?; 152 | if read_size == file.size { 153 | Ok(content_vec) 154 | } else { 155 | error!("Failed to fully read from file '{}", file.name); 156 | Err(Status::END_OF_FILE) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /towboot/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | //! towboot – a bootloader for Multiboot kernels on UEFI systems 5 | 6 | extern crate alloc; 7 | 8 | use core::str::FromStr; 9 | use alloc::string::ToString; 10 | 11 | use uefi::prelude::*; 12 | use uefi::boot::{image_handle, open_protocol_exclusive}; 13 | use uefi::fs::PathBuf; 14 | use uefi::data_types::CString16; 15 | use uefi::proto::loaded_image::{LoadedImage, LoadOptionsError}; 16 | 17 | use log::{debug, info, warn, error}; 18 | 19 | mod boot; 20 | mod config; 21 | mod file; 22 | mod mem; 23 | mod menu; 24 | 25 | #[entry] 26 | /// This is the main function. Startup happens here. 27 | fn main() -> Status { 28 | uefi::helpers::init().expect("Failed to initialize utilities"); 29 | log::set_max_level(log::LevelFilter::Info); 30 | 31 | // get information about the way we were loaded 32 | // the interesting thing here is the partition handle 33 | let loaded_image = open_protocol_exclusive::(image_handle()) 34 | .expect("Failed to open loaded image protocol"); 35 | 36 | // get the load options 37 | let load_options = match loaded_image.load_options_as_cstr16() { 38 | Ok(s) => { 39 | debug!("got load options: {s:}"); 40 | Some(s.to_string()) 41 | } 42 | Err(LoadOptionsError::NotSet) => { 43 | debug!("got no load options"); 44 | None 45 | } 46 | Err(e) => { 47 | warn!("failed to get load options: {e:?}"); 48 | warn!("assuming there were none"); 49 | None 50 | } 51 | }; 52 | 53 | // get the filesystem 54 | let image_fs_handle = loaded_image.device().expect("the image to be loaded from a device"); 55 | 56 | let mut config = match config::get( 57 | image_fs_handle, load_options.as_deref().unwrap_or_default(), 58 | ) { 59 | Ok(Some(c)) => c, 60 | Ok(None) => return Status::SUCCESS, 61 | Err(e) => { 62 | error!("failed to get config: {e:?}"); 63 | return Status::INVALID_PARAMETER; 64 | } 65 | }; 66 | if let Some(level) = &config.log_level { 67 | if let Ok(level) = log::LevelFilter::from_str(level) { 68 | log::set_max_level(level); 69 | } else { 70 | warn!("'{level}' is not a valid log level, using default"); 71 | } 72 | } 73 | // resolve paths relative to the config file itself 74 | if let Some(config_parent) = PathBuf::from( 75 | CString16::try_from(config.src.as_str()) 76 | .expect("paths to be valid strings") 77 | ).parent() { 78 | for path in config.needed_files() { 79 | if path.starts_with('\\') { 80 | continue; 81 | } 82 | let mut buf = config_parent.clone(); 83 | buf.push(PathBuf::from(CString16::try_from(path.as_str()) 84 | .expect("paths to be valid strings") 85 | )); 86 | *path = buf.to_string(); 87 | } 88 | } 89 | debug!("config: {config:?}"); 90 | let entry_to_boot = menu::choose(&config); 91 | debug!("okay, trying to load {entry_to_boot:?}"); 92 | info!("loading {entry_to_boot}..."); 93 | 94 | match boot::PreparedEntry::new(entry_to_boot, image_fs_handle) { 95 | Ok(e) => { 96 | info!("booting {entry_to_boot}..."); 97 | e.boot(); 98 | }, 99 | Err(e) => { 100 | error!("failed to prepare the entry: {e:?}"); 101 | e // give up 102 | // TODO: perhaps redisplay the menu or something like that 103 | }, 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /towboot/src/mem.rs: -------------------------------------------------------------------------------- 1 | //! Memory management 2 | //! 3 | //! In some situations we need to allocate memory at a specific address. 4 | //! Rust's `alloc` can't do this, so we need to use the UEFI API directly. 5 | //! This creates the problem that the allocated memory is not tracked by the borrow checker. 6 | //! We solve this by encapsulating it into a struct that implements `Drop`. 7 | //! 8 | //! Also, gathering memory map information for the kernel happens here. 9 | 10 | use core::mem::size_of; 11 | use core::ptr::NonNull; 12 | 13 | use alloc::boxed::Box; 14 | use alloc::collections::btree_set::BTreeSet; 15 | use alloc::vec::Vec; 16 | 17 | use uefi::prelude::*; 18 | use uefi::boot::{AllocateType, allocate_pages, free_pages, memory_map}; 19 | use uefi::mem::memory_map::{ 20 | MemoryDescriptor, MemoryMap, MemoryMapMut, MemoryMapOwned, MemoryType 21 | }; 22 | 23 | use log::{debug, warn, error}; 24 | 25 | use towboot_config::Quirk; 26 | 27 | // no multiboot import here as some of the types have the same name as the UEFI ones 28 | 29 | pub(super) const PAGE_SIZE: usize = 4096; 30 | 31 | /// Tracks our own allocations. 32 | #[derive(Debug)] 33 | pub(super) struct Allocation { 34 | ptr: NonNull, 35 | pub len: usize, 36 | pages: usize, 37 | /// the address of memory where it should have been allocated 38 | /// (only when it differs from ptr) 39 | should_be_at: Option, 40 | } 41 | 42 | impl Drop for Allocation { 43 | /// Free the associated memory. 44 | fn drop(&mut self) { 45 | // We can't free memory after we've exited boot services. 46 | // But this only happens in `PreparedEntry::boot` and this function doesn't return. 47 | unsafe { free_pages(self.ptr, self.pages) } 48 | // let's just panic if we can't free 49 | .expect("failed to free the allocated memory"); 50 | } 51 | } 52 | 53 | impl Allocation { 54 | /// Allocate memory at a specific position. 55 | /// 56 | /// Note: This will round up to whole pages. 57 | /// 58 | /// If the memory can't be allocated at the specified address, 59 | /// it will print a warning and allocate it somewhere else instead. 60 | /// You can move the allocated memory later to the correct address by calling 61 | /// [`move_to_where_it_should_be`], but please keep its safety implications in mind. 62 | /// This only works for our code and data by default, but this can be 63 | /// overridden with the ForceOverwrite quirk. 64 | /// 65 | /// [`move_to_where_it_should_be`]: struct.Allocation.html#method.move_to_where_it_should_be 66 | pub(crate) fn new_at( 67 | address: usize, 68 | size: usize, 69 | quirks: &BTreeSet, 70 | ) -> Result{ 71 | let count_pages = Self::calculate_page_count(size); 72 | match allocate_pages( 73 | AllocateType::Address(address.try_into().unwrap()), 74 | MemoryType::LOADER_DATA, 75 | count_pages, 76 | ) { 77 | Ok(ptr) => Ok(Allocation { 78 | ptr, 79 | len: size, 80 | pages: count_pages, 81 | should_be_at: None, 82 | }), 83 | Err(e) => { 84 | warn!("failed to allocate 0x{size:x} bytes of memory at 0x{address:x}: {e:?}"); 85 | // find out why that part of memory is occupied 86 | let memory_map = get_memory_map(); 87 | let mut types_in_the_way = BTreeSet::new(); 88 | warn!("the following sections are in the way:"); 89 | for entry in memory_map.entries() { 90 | // if it's after the space we need, ignore it 91 | if entry.phys_start > (address + size).try_into().unwrap() { 92 | continue; 93 | } 94 | // if it's before the space we need, ignore it 95 | if entry.phys_start + entry.page_count * 4096 < address.try_into().unwrap() { 96 | continue; 97 | } 98 | // if it's empty, ignore it 99 | if entry.ty == MemoryType::CONVENTIONAL { 100 | continue; 101 | } 102 | warn!("{entry:x?}"); 103 | types_in_the_way.insert(entry.ty); 104 | } 105 | // if the allocation is only blocked by our code or data, 106 | // allocate it somewhere else and move later 107 | // TODO: or by Boot Services, but we'll have to determine if 108 | // we're going to exit them 109 | types_in_the_way.remove(&MemoryType::LOADER_CODE); 110 | types_in_the_way.remove(&MemoryType::LOADER_DATA); 111 | if types_in_the_way.is_empty() || quirks.contains(&Quirk::ForceOverwrite) { 112 | warn!("going to allocate it somewhere else and try to move it later"); 113 | warn!("this might fail without notice"); 114 | Self::new_under_4gb(size, &BTreeSet::default()).map(|mut allocation| { 115 | allocation.should_be_at = Some(address.try_into().unwrap()); 116 | allocation 117 | }) 118 | } else { 119 | error!("Cannot allocate memory for the kernel, it might be too big."); 120 | warn!( 121 | "If you're in a virtual machine, you could try passing the ForceOverwrite quirk." 122 | ); 123 | Err(Status::LOAD_ERROR) 124 | } 125 | } 126 | } 127 | } 128 | 129 | /// Allocate memory page-aligned below 4GB. 130 | /// 131 | /// Note: This will round up to whole pages. 132 | pub(crate) fn new_under_4gb(size: usize, quirks: &BTreeSet) -> Result { 133 | let count_pages = Self::calculate_page_count(size); 134 | let ptr = allocate_pages( 135 | AllocateType::MaxAddress(if quirks.contains(&Quirk::ModulesBelow200Mb) { 136 | 200 * 1024 * 1024 137 | } else { 138 | u32::MAX.into() 139 | }), 140 | MemoryType::LOADER_DATA, 141 | count_pages 142 | ) 143 | .map_err(|e| { 144 | error!("failed to allocate {size} bytes of memory: {e:?}"); 145 | get_memory_map(); 146 | Status::LOAD_ERROR 147 | })?; 148 | Ok(Allocation { ptr, len:size, pages: count_pages, should_be_at: None }) 149 | } 150 | 151 | /// Calculate how many pages to allocate for the given amount of bytes. 152 | const fn calculate_page_count(size: usize) -> usize { 153 | (size / PAGE_SIZE) // full pages 154 | + if (size % PAGE_SIZE) == 0 { 0 } else { 1 } // perhaps one page more 155 | } 156 | 157 | /// Return a slice that references the associated memory. 158 | pub(crate) fn as_mut_slice(&mut self) -> &mut [u8] { 159 | unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.pages * PAGE_SIZE) } 160 | } 161 | 162 | /// Get the pointer inside. 163 | pub(crate) fn as_ptr(&self) -> *const u8 { 164 | self.ptr.as_ptr() 165 | } 166 | 167 | /// Move to the desired location. 168 | /// 169 | /// This is unsafe: In the worst case we could overwrite ourselves, our 170 | /// variables, firmware code, firmware data, ACPI memory, the Multiboot info 171 | /// struct or anything referenced therein. 172 | /// 173 | /// See the checks in [`new_at`]. 174 | pub(crate) unsafe fn move_to_where_it_should_be(&mut self) { 175 | if let Some(a) = self.should_be_at { 176 | debug!("trying to write {self:?}..."); 177 | // checks already happened in new_at 178 | let dest: usize = a.try_into().unwrap(); 179 | unsafe { 180 | core::ptr::copy(self.ptr.as_ptr(), dest as *mut u8, self.len); 181 | } 182 | self.ptr = NonNull::new(a as *mut u8).unwrap(); 183 | self.should_be_at = None; 184 | } 185 | } 186 | } 187 | 188 | /// Get the current memory map. 189 | /// 190 | /// If the log level is set to debug, the memory map is also logged. 191 | fn get_memory_map() -> MemoryMapOwned { 192 | debug!("memory map:"); 193 | let mut memory_map = memory_map(MemoryType::LOADER_DATA).expect("failed to get memory map"); 194 | memory_map.sort(); 195 | for descriptor in memory_map.entries() { 196 | debug!("{descriptor:x?}"); 197 | } 198 | memory_map 199 | } 200 | 201 | 202 | /// Pass the memory map to the kernel. 203 | /// 204 | /// This needs to have a buffer to write to because we can't allocate memory anymore. 205 | /// (The buffer may be too large.) 206 | pub(super) fn prepare_information( 207 | info_bytes: &mut [u8], 208 | mut update_memory_info: Box, 211 | )>, 212 | efi_mmap: &uefi::mem::memory_map::MemoryMapOwned, 213 | mb_mmap_vec: &mut Vec, 214 | mb_efi_mmap_vec: &mut Vec, 215 | boot_services_exited: bool, 216 | ) { 217 | // Descriptors are the ones from UEFI, Entries are the ones from Multiboot. 218 | let empty_entry = mb_mmap_vec[0].clone(); 219 | let mut count = 0; 220 | let mut entry_iter = mb_mmap_vec.iter_mut(); 221 | let mut current_entry = entry_iter.next().unwrap(); 222 | for descriptor in efi_mmap.entries() { 223 | let next_entry = empty_entry.with( 224 | descriptor.phys_start, descriptor.page_count * PAGE_SIZE as u64, match descriptor.ty { 225 | // after we've started the kernel, no-one needs our code or data 226 | MemoryType::LOADER_CODE | MemoryType::LOADER_DATA 227 | => multiboot12::information::MemoryType::Available, 228 | // have Boot Services been exited? 229 | MemoryType::BOOT_SERVICES_CODE | MemoryType::BOOT_SERVICES_DATA 230 | => match boot_services_exited { 231 | true => multiboot12::information::MemoryType::Available, 232 | false => multiboot12::information::MemoryType::Reserved, 233 | }, 234 | // the kernel may want to use UEFI Runtime Services 235 | MemoryType::RUNTIME_SERVICES_CODE | MemoryType::RUNTIME_SERVICES_DATA 236 | => multiboot12::information::MemoryType::Reserved, 237 | // it's free memory! 238 | MemoryType::CONVENTIONAL => multiboot12::information::MemoryType::Available, 239 | MemoryType::UNUSABLE => multiboot12::information::MemoryType::Defective, 240 | MemoryType::ACPI_RECLAIM => multiboot12::information::MemoryType::AcpiAvailable, 241 | MemoryType::ACPI_NON_VOLATILE => multiboot12::information::MemoryType::ReservedHibernate, 242 | MemoryType::MMIO | MemoryType::MMIO_PORT_SPACE | MemoryType::PAL_CODE 243 | => multiboot12::information::MemoryType::Reserved, 244 | MemoryType::PERSISTENT_MEMORY => multiboot12::information::MemoryType::Available, 245 | _ => multiboot12::information::MemoryType::Reserved, // better be safe than sorry 246 | } 247 | ); 248 | if count == 0 { 249 | *current_entry = next_entry; 250 | count += 1; 251 | } else { 252 | // join adjacent entries of the same type 253 | if ( 254 | next_entry.memory_type() == current_entry.memory_type() 255 | ) && ( 256 | next_entry.base_address() == ( 257 | current_entry.base_address() + current_entry.length() 258 | ) 259 | ) { 260 | *current_entry = empty_entry.with( 261 | current_entry.base_address(), 262 | current_entry.length() + next_entry.length(), 263 | current_entry.memory_type(), 264 | ); 265 | } else { 266 | current_entry = entry_iter.next().unwrap(); 267 | *current_entry = next_entry; 268 | count += 1; 269 | } 270 | } 271 | } 272 | debug!("shrunk memory areas down to {count}"); 273 | mb_mmap_vec.truncate(count); 274 | assert_eq!(mb_mmap_vec.len(), count); 275 | 276 | // "Lower" and "upper" memory as understood by a BIOS in kilobytes. 277 | // This means: 278 | // Lower memory is the part of the memory from beginning to the first memory hole, 279 | // adressable by just 20 bits (because the 8086's address bus had just 20 pins). 280 | // Upper memory is the part of the memory from 1 MB to the next memory hole 281 | // (usually a few megabytes). 282 | let lower = 640; // If we had less than 640KB, we wouldn't fit into memory. 283 | let upper = mb_mmap_vec.iter().find(|e| e.base_address() == 1024 * 1024) 284 | .unwrap().length() / 1024; 285 | 286 | // When updating either uefi.rs or multiboot2, make sure that the types 287 | // still match. 288 | // We can at least check whether they have the same size. 289 | assert_eq!( 290 | size_of::(), 291 | size_of::(), 292 | ); 293 | // We need to copy all entries, because we can't access `efi_mmap.buf`. 294 | // It might be safer to create new `EFIMemoryDesc`s instead of transmuting. 295 | efi_mmap.entries().zip(mb_efi_mmap_vec.iter_mut()) 296 | .for_each( 297 | |(src, dst)| 298 | *dst = unsafe { core::mem::transmute::(*src) } 299 | ); 300 | 301 | update_memory_info( 302 | info_bytes, lower.try_into().unwrap(), upper.try_into().unwrap(), 303 | mb_mmap_vec.as_slice(), Some(mb_efi_mmap_vec.as_slice()), 304 | ); 305 | // dropping this box breaks on Multiboot1, when Boot Services have been exited 306 | if boot_services_exited { 307 | core::mem::forget(update_memory_info); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /towboot/src/menu.rs: -------------------------------------------------------------------------------- 1 | //! Select an entry to boot by displaying a menu. 2 | use core::fmt::Write; 3 | use alloc::collections::btree_map::BTreeMap; 4 | use alloc::string::String; 5 | 6 | use uefi::prelude::*; 7 | use uefi::boot::{EventType, TimerTrigger, Tpl, create_event, set_timer, wait_for_event}; 8 | use uefi::proto::console::text::{Key, ScanCode}; 9 | use uefi::system::{with_stdin, with_stdout}; 10 | 11 | use log::{error, warn}; 12 | 13 | use towboot_config::{Config, Entry}; 14 | 15 | /// Choose an entry to boot. 16 | /// 17 | /// Pass in a parsed config, get out the entry portion that was selected. 18 | /// This will print a message and then wait for the timeout or for the escape key to be pressed. 19 | /// On timeout, it will boot the default entry. 20 | /// On escape, it will list the available entries and ask which one to boot. 21 | /// 22 | /// If the default entry is missing, it will try to use the first one instead. 23 | /// If there are no entries, it will panic. 24 | // TODO: perhaps this should return a Result? 25 | pub fn choose(config: &Config) -> &Entry { 26 | let default_entry = config.entries.get(&config.default).unwrap_or_else(|| { 27 | warn!("default entry is missing, trying the first one"); 28 | config.entries.values().next().expect("no entries") 29 | }); 30 | if let Some(0) = config.timeout { 31 | return default_entry 32 | } 33 | match display_menu(config, default_entry) { 34 | Ok(entry) => entry, 35 | Err(err) => { 36 | error!("failed to display menu: {err:?}"); 37 | warn!("booting default entry"); 38 | default_entry 39 | } 40 | } 41 | } 42 | 43 | /// Display the menu. This can fail. 44 | fn display_menu<'a>( 45 | config: &'a Config, default_entry: &'a Entry, 46 | ) -> uefi::Result<&'a Entry> { 47 | if let Some(timeout) = config.timeout { 48 | with_stdout(|stdout | writeln!( 49 | stdout, 50 | "towboot: booting {} ({}) in {} seconds... (press ESC to change)", 51 | config.default, default_entry.name.as_ref().unwrap_or(&config.default), timeout, 52 | )).unwrap(); 53 | // This is safe because there is no callback. 54 | let timer = unsafe { create_event( 55 | EventType::TIMER, Tpl::APPLICATION, None, None 56 | ) }?; 57 | set_timer( 58 | &timer, TimerTrigger::Relative(u64::from(timeout) * 10_000_000) 59 | )?; 60 | let key_event = with_stdin(|stdin| stdin.wait_for_key_event()) 61 | .expect("to be able to wait for key events"); 62 | loop { 63 | match wait_for_event( 64 | // this is safe because we're never calling close_event 65 | &mut [ 66 | unsafe { key_event.unsafe_clone() }, 67 | unsafe { timer.unsafe_clone() }, 68 | ] 69 | ).discard_errdata()? { 70 | // key 71 | 0 => match with_stdin(|stdin| stdin.read_key())? { 72 | Some(Key::Special(ScanCode::ESCAPE)) => break, 73 | _ => (), 74 | }, 75 | // timer 76 | 1 => return Ok(default_entry), 77 | e => warn!("firmware returned invalid event {e}"), 78 | } 79 | } 80 | set_timer(&timer, TimerTrigger::Cancel)?; 81 | } 82 | with_stdout(|stdout| { 83 | writeln!(stdout, "available entries:").unwrap(); 84 | for (index, (key, entry)) in config.entries.iter().enumerate() { 85 | writeln!(stdout, "{index}. [{key}] {entry}").unwrap(); 86 | } 87 | }); 88 | loop { 89 | match select_entry(&config.entries) { 90 | Ok(entry) => return Ok(entry), 91 | Err(err) => { 92 | with_stdout(|stdout| writeln!(stdout, "invalid choice: {err:?}")).unwrap(); 93 | } 94 | } 95 | } 96 | } 97 | 98 | /// Try to select an entry. 99 | fn select_entry(entries: &BTreeMap) -> uefi::Result<&Entry> { 100 | let mut value = String::new(); 101 | let key_event = with_stdin(|stdin| stdin.wait_for_key_event()) 102 | .expect("to be able to wait for key events"); 103 | loop { 104 | with_stdout(|stdout| write!( 105 | stdout, "\rplease select an entry to boot: {value} ", 106 | )).unwrap(); 107 | wait_for_event( 108 | // this is safe because we're never calling close_event 109 | &mut [unsafe { key_event.unsafe_clone() }] 110 | ).discard_errdata()?; 111 | if let Some(Key::Printable(c)) = with_stdin( 112 | |stdin| stdin.read_key() 113 | )? { 114 | match c.into() { 115 | '\r' => break, // enter 116 | '\u{8}' => {value.pop();}, // backspace 117 | chr => value.push(chr), 118 | } 119 | } 120 | } 121 | with_stdout(|stdout| writeln!(stdout,)).unwrap(); 122 | // support lookup by both index and key 123 | match value.parse::() { 124 | Ok(index) => entries.values().nth(index), 125 | Err(_) => entries.get(&value), 126 | }.ok_or(Status::INVALID_PARAMETER.into()) 127 | } 128 | -------------------------------------------------------------------------------- /towboot_config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "towboot_config" 3 | version.workspace = true 4 | authors.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | edition = "2024" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [features] 12 | default = ["options"] 13 | options = ["miniarg"] 14 | 15 | [dependencies] 16 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 17 | log = "0.4.4" 18 | miniarg = { version = "0.4", default-features = false, features = ["alloc", "derive"], optional = true } 19 | -------------------------------------------------------------------------------- /towboot_config/src/config.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use alloc::collections::{BTreeMap, BTreeSet}; 3 | use alloc::vec::Vec; 4 | use alloc::string::String; 5 | 6 | use serde::{Deserialize, Serialize}; 7 | 8 | /// The main configuration struct 9 | #[derive(Deserialize, Debug, Serialize)] 10 | pub struct Config { 11 | pub default: String, 12 | pub timeout: Option, 13 | pub log_level: Option, 14 | pub entries: BTreeMap, 15 | #[serde(skip)] 16 | /// the path of the configuration file itself 17 | pub src: String, 18 | } 19 | 20 | impl Config { 21 | /// Determine which files are referenced in the configuration. 22 | pub fn needed_files(self: &mut Config) -> Vec<&mut String> { 23 | let mut files = Vec::new(); 24 | for (_name, entry) in self.entries.iter_mut() { 25 | files.push(&mut entry.image); 26 | for module in &mut entry.modules { 27 | files.push(&mut module.image); 28 | } 29 | } 30 | files 31 | } 32 | } 33 | 34 | /// A menu entry -- an operating system to be booted. 35 | #[derive(Deserialize, Debug, Serialize)] 36 | pub struct Entry { 37 | pub argv: Option, 38 | pub image: String, 39 | pub name: Option, 40 | #[serde(default)] 41 | pub quirks: BTreeSet, 42 | #[serde(default)] 43 | pub modules: Vec, 44 | } 45 | 46 | impl fmt::Display for Entry { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | write!(f, "{}", self.name.as_ref().unwrap_or(&self.image)) 49 | } 50 | } 51 | 52 | /// Information about a module 53 | #[derive(Deserialize, Debug, Serialize)] 54 | pub struct Module { 55 | pub argv: Option, 56 | pub image: String, 57 | } 58 | 59 | /// Runtime options to override information in kernel images. 60 | #[derive(Deserialize, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] 61 | pub enum Quirk { 62 | /// Do not exit Boot Services. 63 | /// This starts the kernel with more privileges and less available memory. 64 | /// In some cases this might also display more helpful error messages. 65 | DontExitBootServices, 66 | /// Treat the kernel always as an ELF file. 67 | /// This ignores bit 16 of the kernel's Multiboot header. 68 | ForceElf, 69 | /// Ignore the memory map when loading the kernel. 70 | /// This might damage your hardware! 71 | ForceOverwrite, 72 | /// Ignore the kernel's preferred resolution and just keep the current one. 73 | KeepResolution, 74 | /// Place modules below 200 MB. 75 | ModulesBelow200Mb, 76 | } 77 | -------------------------------------------------------------------------------- /towboot_config/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library contains configuration structs and functions to load them. 2 | //! 3 | //! The configuration can come from a file or from the command line. 4 | //! The command line options take precedence if they are specified. 5 | #![no_std] 6 | extern crate alloc; 7 | 8 | mod config; 9 | pub use config::{Config, Entry, Module, Quirk}; 10 | 11 | #[cfg(feature = "options")] 12 | mod options; 13 | #[cfg(feature = "options")] 14 | pub use options::{CONFIG_FILE, ConfigSource, LoadOptionKey, parse_load_options}; 15 | -------------------------------------------------------------------------------- /towboot_config/src/options.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; 2 | use alloc::fmt; 3 | use alloc::string::{String, ToString}; 4 | use alloc::vec::Vec; 5 | 6 | use log::{info, error, trace}; 7 | use miniarg::{ArgumentIterator, Key}; 8 | use serde::Deserialize; 9 | use serde::de::{IntoDeserializer, value}; 10 | 11 | use super::{Config, Entry, Module, Quirk}; 12 | 13 | /// The default path to the configuration file. 14 | pub const CONFIG_FILE: &str = "towboot.toml"; 15 | 16 | /// Where to load the configuration from 17 | pub enum ConfigSource { 18 | /// Load the configuration from a file 19 | File(String), 20 | /// Use the configuration specified in here 21 | Given(Config), 22 | } 23 | 24 | /// Available options. 25 | #[derive(Debug, Key)] 26 | pub enum LoadOptionKey { 27 | /// Load the specified configuration file instead of the default one. 28 | Config, 29 | /// Don't load a configuration file, instead boot the specified kernel. 30 | Kernel, 31 | /// Set the log level. (This only applies if `-kernel` is specified.) 32 | LogLevel, 33 | /// Load a module with the given args. Can be specified multiple times. 34 | Module, 35 | /// Enable a specific quirk. (Only applies when loading a kernel.) 36 | Quirk, 37 | /// Displays all available options and how to use them. 38 | Help, 39 | /// Displays the version of towboot 40 | #[cfg(target_os = "uefi")] 41 | Version, 42 | } 43 | 44 | /// Parse the command line options. 45 | /// 46 | /// See [`LoadOptionKey`] for available options. 47 | /// 48 | /// This function returns None if the user just asked for help or the version. 49 | /// This function errors, if the command line options are not valid. 50 | /// That is: 51 | /// * general reasons 52 | /// * keys without values 53 | /// * values without keys 54 | /// * invalid keys 55 | /// 56 | /// [`LoadOptionKey`]: enum.LoadOptionKey.html 57 | pub fn parse_load_options( 58 | load_options: &str, 59 | #[allow(unused_variables)] 60 | version_info: &str, 61 | ) -> Result, ()> { 62 | let options = LoadOptionKey::parse(load_options); 63 | let mut config_file = None; 64 | let mut kernel = None; 65 | let mut log_level = None; 66 | let mut modules = Vec::<&str>::new(); 67 | let mut quirks = BTreeSet::::new(); 68 | for option in options { 69 | match option { 70 | Ok((key, value)) => { 71 | trace!("option: {key} => {value}"); 72 | match key { 73 | LoadOptionKey::Config => config_file = Some(value), 74 | LoadOptionKey::Kernel => kernel = Some(value), 75 | LoadOptionKey::LogLevel => log_level = Some(value), 76 | LoadOptionKey::Module => modules.push(value), 77 | LoadOptionKey::Quirk => { 78 | let parsed: Result = Quirk::deserialize( 79 | value.into_deserializer() 80 | ); 81 | if let Ok(parsed) = parsed { 82 | quirks.insert(parsed); 83 | } else { 84 | error!("invalid value for quirk: {value}"); 85 | return Err(()); 86 | } 87 | }, 88 | LoadOptionKey::Help => { 89 | info!("Usage:\n{}", LoadOptionKey::help_text()); 90 | return Ok(None) 91 | } 92 | #[cfg(target_os = "uefi")] 93 | LoadOptionKey::Version => { 94 | info!("{version_info}"); 95 | return Ok(None) 96 | } 97 | } 98 | }, 99 | Err(e) => { 100 | error!("failed parsing load options: {e:?}"); 101 | return Err(()) 102 | }, 103 | } 104 | } 105 | if let Some(kernel) = kernel { 106 | let modules = modules.iter().map(|m| { 107 | let (image, argv) = m.split_once(' ').unwrap_or((m, "")); 108 | Module { 109 | image: image.to_string(), 110 | argv: Some(argv.to_string()), 111 | } 112 | }).collect(); 113 | let (kernel_image, kernel_argv) = kernel.split_once(' ').unwrap_or((kernel, "")); 114 | let mut entries = BTreeMap::new(); 115 | entries.insert("cli".to_string(), Entry { 116 | argv: Some(kernel_argv.to_string()), 117 | image: kernel_image.to_string(), 118 | name: None, 119 | quirks, 120 | modules, 121 | }); 122 | Ok(Some(ConfigSource::Given(Config { 123 | default: "cli".to_string(), 124 | timeout: Some(0), 125 | log_level: log_level.map(ToString::to_string), 126 | entries, 127 | src: ".".to_string(), // TODO: put the CWD here 128 | }))) 129 | } else if let Some(c) = config_file { 130 | Ok(Some(ConfigSource::File(c.to_string()))) 131 | } else { 132 | Ok(Some(ConfigSource::File(CONFIG_FILE.to_string()))) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /towboot_ia32/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "towboot_ia32" 3 | version.workspace = true 4 | authors.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | edition = "2024" 8 | 9 | [dependencies] 10 | towboot = { path = "../towboot", artifact = "bin", target = "i686-unknown-uefi" } 11 | -------------------------------------------------------------------------------- /towboot_ia32/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a dummy crate that contains a towboot binary for i686. 2 | //! 3 | //! This is needed because 4 | //! is not ready yet. 5 | 6 | /// The towboot binary for i686. 7 | pub const TOWBOOT: &[u8] = include_bytes!(env!("CARGO_BIN_FILE_TOWBOOT_towboot")); 8 | -------------------------------------------------------------------------------- /towboot_x64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "towboot_x64" 3 | version.workspace = true 4 | authors.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | edition = "2024" 8 | 9 | [dependencies] 10 | towboot = { path = "../towboot", artifact = "bin", target = "x86_64-unknown-uefi" } 11 | -------------------------------------------------------------------------------- /towboot_x64/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a dummy crate that contains a towboot binary for x86_64. 2 | //! 3 | //! This is needed because 4 | //! is not ready yet. 5 | 6 | /// The towboot binary for x86_64. 7 | pub const TOWBOOT: &[u8] = include_bytes!(env!("CARGO_BIN_FILE_TOWBOOT_towboot")); 8 | -------------------------------------------------------------------------------- /towbootctl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "towbootctl" 3 | version.workspace = true 4 | authors.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | edition = "2024" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = "1.0" 13 | cached-path = "0.6" 14 | directories = "6" 15 | gpt = { version = "4.0", features = ["log"] } 16 | fscommon = "0.1" 17 | fatfs = "0.3" 18 | log = "0.4.4" 19 | tempfile = "3.8" 20 | toml = "0.8" 21 | 22 | towboot_config = { path = "../towboot_config" } 23 | 24 | # these dependencies are only for the binary 25 | argh = { version = "0.1", optional = true } 26 | env_logger = { version = "0.11", default-features = false, features = ["auto-color"], optional = true } 27 | towboot_ia32 = { path = "../towboot_ia32", optional = true } 28 | towboot_x64 = { path = "../towboot_x64", optional = true } 29 | 30 | [build-dependencies] 31 | built = { version = "0.8", features = ["git2"] } 32 | 33 | [features] 34 | args = ["argh"] 35 | binary = ["args", "env_logger", "towboot_ia32", "towboot_x64"] 36 | 37 | [[bin]] 38 | name = "towbootctl" 39 | required-features = ["binary"] 40 | 41 | -------------------------------------------------------------------------------- /towbootctl/build.rs: -------------------------------------------------------------------------------- 1 | //! Our build script. 2 | //! 3 | //! It makes certain compile-time information visible to the application using built. 4 | use std::env; 5 | 6 | fn main() { 7 | if env::var("CARGO_FEATURE_BINARY").is_ok() { 8 | built::write_built_file().expect("Failed to acquire build-time information"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /towbootctl/src/bochs.rs: -------------------------------------------------------------------------------- 1 | //! This module allows booting with Bochs. 2 | use std::io::Write; 3 | use std::path::Path; 4 | 5 | use anyhow::Result; 6 | use tempfile::NamedTempFile; 7 | 8 | /// Generate a appropriate bochrs file. 9 | pub fn bochsrc(ovmf: &Path, image: &Path, gdb: bool) -> Result { 10 | let ovmf = ovmf.display(); 11 | let image = image.display(); 12 | let gdb: u8 = gdb.into(); 13 | let mut file = NamedTempFile::new()?; 14 | write!(file.as_file_mut(), " 15 | # partly taken from https://forum.osdev.org/viewtopic.php?f=1&t=33440 16 | display_library: x 17 | megs: 768 18 | romimage: file=\"{ovmf}\", address=0x0, options=none 19 | vgaromimage: file=\"/usr/share/bochs/VGABIOS-lgpl-latest\" 20 | ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 21 | ata0-master: type=disk, path=\"{image}\", mode=flat, cylinders=0, heads=0, spt=0, sect_size=512, model=\"Generic 1234\", biosdetect=auto, translation=auto 22 | ata0-slave: type=none 23 | pci: enabled=1, chipset=i440fx, slot1=cirrus 24 | vga: extension=cirrus, update_freq=5, realtime=1 25 | print_timestamps: enabled=0 26 | port_e9_hack: enabled=0 27 | private_colormap: enabled=0 28 | clock: sync=none, time0=local, rtc_sync=0 29 | log: - 30 | logprefix: %t%e%d 31 | debug: action=ignore 32 | info: action=report 33 | error: action=report 34 | panic: action=ask 35 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 36 | mouse: type=ps2, enabled=0, toggle=ctrl+mbutton 37 | sound: waveoutdrv=win, waveout=none, waveindrv=win, wavein=none, midioutdrv=win, midiout=none 38 | speaker: enabled=1, mode=sound 39 | parport1: enabled=1, file=none 40 | com1: enabled=1, mode=null 41 | gdbstub: enabled={gdb}, port=1234, text_base=0, data_base=0, bss_base=0 42 | ")?; 43 | Ok(file) 44 | } 45 | -------------------------------------------------------------------------------- /towbootctl/src/config.rs: -------------------------------------------------------------------------------- 1 | //! This module contains functions to load the configuration. 2 | //! 3 | //! The configuration can come from a file or from the command line. 4 | //! The command line options take precedence if they are specified. 5 | //! 6 | //! Most of the actual structs can be found in the [`towboot_config`] crate. 7 | //! The towboot package has its own config.rs. 8 | use std::fs::read_to_string; 9 | 10 | use anyhow::{Result, anyhow}; 11 | 12 | use towboot_config::{Config, ConfigSource, parse_load_options}; 13 | 14 | /// Get the config. 15 | /// If there are command line options, try them first. 16 | /// Otherwise, read and parse a configuration file. 17 | /// 18 | /// Returns None if just a help text has been displayed. 19 | pub fn get(load_options: &str) -> Result> { 20 | match parse_load_options(load_options, "") { 21 | Ok(Some(ConfigSource::File(s))) => Ok(Some(read_file(&s)?)), 22 | Ok(Some(ConfigSource::Given(c))) => Ok(Some(c)), 23 | Ok(None) => Ok(None), 24 | Err(()) => Err(anyhow!("invalid parameters")), 25 | } 26 | } 27 | 28 | /// Try to read and parse the configuration from the given file. 29 | fn read_file(file_name: &str) -> Result { 30 | let text = read_to_string(file_name)?; 31 | let mut config: Config = toml::from_str(&text)?; 32 | config.src = file_name.to_string(); 33 | Ok(config) 34 | } 35 | -------------------------------------------------------------------------------- /towbootctl/src/firmware.rs: -------------------------------------------------------------------------------- 1 | //! This module downloads and provides current builds of OVMF. 2 | //! 3 | //! It uses [retrage/edk2-nightly](https://retrage.github.io/edk2-nightly/), 4 | //! as this provides builds for both x64 and ia32 as single files. 5 | //! When is merged, 6 | //! we might want to switch back to the Arch Linux builds. 7 | 8 | use std::path::PathBuf; 9 | 10 | use anyhow::Result; 11 | use cached_path::Cache; 12 | use directories::ProjectDirs; 13 | 14 | const OVMF_X64_URL: &str = "https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd"; 15 | const OVMF_IA32_URL: &str = "https://retrage.github.io/edk2-nightly/bin/RELEASEIa32_OVMF.fd"; 16 | 17 | /// Download the firmware and provide a path to it. 18 | /// It is cached to prevent unneccessary downloads. 19 | fn get_firmware(url: &str) -> Result { 20 | let mut cache = Cache::new()?; 21 | if let Some(dirs) = ProjectDirs::from_path("towbootctl".into()) { 22 | cache.dir = dirs.cache_dir().to_path_buf(); 23 | }; 24 | Ok( 25 | cache.cached_path(url)? 26 | ) 27 | } 28 | 29 | /// Get OVMF for x64. 30 | pub fn x64() -> Result { 31 | get_firmware(OVMF_X64_URL) 32 | } 33 | 34 | /// Get OVMF for ia32. 35 | pub fn ia32() -> Result { 36 | get_firmware(OVMF_IA32_URL) 37 | } 38 | -------------------------------------------------------------------------------- /towbootctl/src/image.rs: -------------------------------------------------------------------------------- 1 | //! This module contains functionality to work with images. 2 | use std::error::Error; 3 | use std::collections::BTreeMap; 4 | use std::fs::{File, OpenOptions}; 5 | use std::io::{Write, Read}; 6 | use std::path::Path; 7 | 8 | use fscommon::StreamSlice; 9 | use gpt::{GptConfig, disk::LogicalBlockSize, mbr::ProtectiveMBR, partition_types}; 10 | use log::debug; 11 | use fatfs::{FileSystem, format_volume, FormatVolumeOptions, FsOptions}; 12 | 13 | /// An image that is currently being constructed. 14 | pub struct Image { 15 | fs: FileSystem>>, 16 | } 17 | 18 | impl Image { 19 | /// Create a new image at the given location with the given size. 20 | /// If the file exists already, it will be overwritten. 21 | pub fn new(path: &Path, size: u64) -> Result> { 22 | debug!("creating disk image"); 23 | let mut file = Box::new(OpenOptions::new() 24 | .read(true) 25 | .write(true) 26 | .create(true) 27 | .truncate(true) 28 | .open(path)?); 29 | // make sure the image size is a multiple of 512 30 | // gdisk warns otherwise and OVMF seems to ignore the disk in some cases 31 | // also, keep a spare megabyte for the partition table and alignment 32 | let size = size.next_multiple_of(512) + 1024 * 1024; 33 | file.set_len(size)?; 34 | // protective MBR 35 | let mbr = ProtectiveMBR::with_lb_size( 36 | u32::try_from((size / 512) - 1)? 37 | ); 38 | mbr.overwrite_lba0(&mut file)?; 39 | let mut disk = GptConfig::new() 40 | .writable(true) 41 | .logical_block_size(LogicalBlockSize::Lb512) 42 | .create_from_device(file, None)?; 43 | disk.update_partitions(BTreeMap::new())?; 44 | debug!("creating partition"); 45 | // the partition needs to be slightly smaller than the disk image 46 | disk.add_partition( 47 | "towboot", size - 64 * 1024, partition_types::EFI, 0, None, 48 | )?; 49 | let partitions = disk.partitions().clone(); 50 | let (_, partition) = partitions.iter().next().unwrap(); 51 | let file = disk.write()?; 52 | let mut part = StreamSlice::new( 53 | file, partition.first_lba * 512, partition.last_lba * 512, 54 | )?; 55 | debug!("formatting {partition}"); 56 | format_volume(&mut part, FormatVolumeOptions::new())?; 57 | Ok(Self { fs: FileSystem::new(part, FsOptions::new())? }) 58 | } 59 | 60 | /// Copy a file from the local filesystem to the image. 61 | pub fn add_file(&mut self, source: &Path, dest: &Path) -> Result<(), Box> { 62 | debug!("adding {} as {}", source.display(), dest.display()); 63 | let mut source_file = File::open(source)?; 64 | let mut dir = self.fs.root_dir(); 65 | let components: Vec<_> = dest.components().collect(); 66 | let (file_name, dir_names) = components.split_last().unwrap(); 67 | for dir_name in dir_names { 68 | dir = dir.create_dir(dir_name.as_os_str().to_str().unwrap())?; 69 | } 70 | let mut dest_file = dir.create_file( 71 | file_name.as_os_str().to_str().unwrap() 72 | )?; 73 | let mut buf = Vec::new(); 74 | source_file.read_to_end(&mut buf)?; 75 | dest_file.write_all(&buf)?; 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /towbootctl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate offers functionality to use towboot for your own operating system. 2 | #![cfg_attr(feature = "args", feature(exit_status_error))] 3 | use std::error::Error; 4 | use std::fs::OpenOptions; 5 | use std::io::Write; 6 | use std::path::{Path, PathBuf}; 7 | use std::process::Command; 8 | 9 | use anyhow::anyhow; 10 | #[cfg(feature = "args")] 11 | use argh::FromArgs; 12 | use log::info; 13 | use tempfile::{NamedTempFile, TempPath}; 14 | 15 | use towboot_config::Config; 16 | 17 | mod bochs; 18 | pub mod config; 19 | mod firmware; 20 | mod image; 21 | use bochs::bochsrc; 22 | use image::Image; 23 | 24 | /// Where to place the 32-bit EFI file 25 | pub const IA32_BOOT_PATH: &str = "EFI/Boot/bootia32.efi"; 26 | 27 | /// Where to place the 64-bit EFI file 28 | pub const X64_BOOT_PATH: &str = "EFI/Boot/bootx64.efi"; 29 | 30 | /// Get the source and destination paths of all files referenced in the config. 31 | fn get_config_files( 32 | config: &mut Config, 33 | ) -> Result, Box> { 34 | let mut paths = Vec::<(PathBuf, PathBuf)>::new(); 35 | let mut config_path = PathBuf::from(config.src.clone()); 36 | config_path.pop(); 37 | 38 | // go through all needed files; including them (but without the original path) 39 | for src_file in config.needed_files() { 40 | let src_path = config_path.join(PathBuf::from(&src_file)); 41 | let dst_file = src_path.file_name().unwrap(); 42 | let dst_path = PathBuf::from(&dst_file); 43 | src_file.clear(); 44 | src_file.push_str(dst_file.to_str().unwrap()); 45 | paths.push((src_path, dst_path)); 46 | } 47 | 48 | Ok(paths) 49 | } 50 | 51 | /// Joins a slice of strings. 52 | pub fn runtime_args_to_load_options(runtime_args: &[String]) -> String { 53 | let mut load_options = "towboot.efi".to_owned(); 54 | for string in runtime_args.iter() { 55 | load_options.push(' '); 56 | if string.contains(' ') { 57 | load_options.push('"'); 58 | } 59 | load_options.push_str(string); 60 | if string.contains(' ') { 61 | load_options.push('"'); 62 | } 63 | } 64 | load_options 65 | } 66 | 67 | /// Create an image, containing a configuration file, kernels, modules and towboot. 68 | pub fn create_image( 69 | target: &Path, runtime_args: &[String], i686: Option<&Path>, x86_64: Option<&Path>, 70 | ) -> Result> { 71 | info!("calculating image size"); 72 | let mut paths = Vec::<(PathBuf, PathBuf)>::new(); 73 | 74 | // generate a configuration file from the load options 75 | let load_options = runtime_args_to_load_options(runtime_args); 76 | let mut config_file = NamedTempFile::new()?; 77 | if let Some(mut config) = config::get(&load_options)? { 78 | // get paths to all files referenced by config 79 | // this also sets the correct config file paths inside the image 80 | let mut config_paths = get_config_files(&mut config)?; 81 | paths.append(&mut config_paths); 82 | 83 | // generate temp config file 84 | config_file.as_file_mut().write_all( 85 | toml::to_string(&config)?.as_bytes() 86 | )?; 87 | paths.push((PathBuf::from(config_file.path()), PathBuf::from("towboot.toml"))); 88 | } 89 | 90 | // add towboot itself 91 | if let Some(src) = i686 { 92 | paths.push((PathBuf::from(src), PathBuf::from(IA32_BOOT_PATH))); 93 | } 94 | if let Some(src) = x86_64 { 95 | paths.push((PathBuf::from(src), PathBuf::from(X64_BOOT_PATH))); 96 | } 97 | 98 | let mut image_size = 0; 99 | for pair in paths.iter() { 100 | let file = OpenOptions::new() 101 | .read(true) 102 | .open(PathBuf::from(&pair.0))?; 103 | image_size += file.metadata()?.len(); 104 | } 105 | 106 | info!( 107 | "creating image at {} (size: {} MiB)", 108 | target.display(), 109 | image_size.div_ceil(1024).div_ceil(1024), 110 | ); 111 | let mut image = Image::new(target, image_size)?; 112 | for pair in paths { 113 | image.add_file(pair.0.as_path(), pair.1.as_path())? 114 | } 115 | 116 | Ok(image) 117 | } 118 | 119 | /// Boot a built image, returning the running process. 120 | pub fn boot_image( 121 | firmware: Option<&Path>, image: &Path, is_x86_64: bool, use_bochs: bool, 122 | use_kvm: bool, use_gdb: bool, 123 | ) -> Result<(Command, Vec), Box> { 124 | info!("getting firmware"); 125 | let firmware_path = if let Some(path) = firmware { 126 | assert!(path.exists()); 127 | path.to_path_buf() 128 | } else { 129 | match is_x86_64 { 130 | false => firmware::ia32()?, 131 | true => firmware::x64()?, 132 | } 133 | }; 134 | Ok(if use_bochs { 135 | info!("spawning Bochs"); 136 | if use_kvm { 137 | return Err(anyhow!("can't do KVM in Bochs").into()); 138 | } 139 | let config = bochsrc(&firmware_path, image, use_gdb)?.into_temp_path(); 140 | let mut bochs = Command::new("bochs"); 141 | bochs.arg("-qf").arg(config.as_os_str()); 142 | (bochs, vec![config]) 143 | } else { 144 | info!("spawning QEMU"); 145 | let mut qemu = Command::new(match is_x86_64 { 146 | false => "qemu-system-i386", 147 | true => "qemu-system-x86_64", 148 | }); 149 | qemu 150 | .arg("-m").arg("256") 151 | .arg("-hda").arg(image) 152 | .arg("-serial").arg("stdio") 153 | .arg("-bios").arg(firmware_path); 154 | if use_kvm { 155 | qemu.arg("-machine").arg("pc,accel=kvm"); 156 | } 157 | if use_gdb { 158 | info!("The machine starts paused, waiting for GDB to attach to localhost:1234."); 159 | qemu.arg("-s").arg("-S"); 160 | } 161 | (qemu, vec![]) 162 | }) 163 | } 164 | 165 | #[cfg(feature = "args")] 166 | #[derive(Debug, FromArgs)] 167 | #[argh(subcommand, name = "boot-image")] 168 | /// Boot an image. 169 | pub struct BootImageCommand { 170 | /// what image to boot 171 | #[argh(option, default = "PathBuf::from(\"image.img\")")] 172 | image: PathBuf, 173 | 174 | /// use x86_64 instead of i686 175 | #[argh(switch)] 176 | x86_64: bool, 177 | 178 | /// enable KVM 179 | #[argh(switch)] 180 | kvm: bool, 181 | 182 | /// use Bochs instead of QEMU 183 | #[argh(switch)] 184 | bochs: bool, 185 | 186 | /// wait for GDB to attach 187 | #[argh(switch)] 188 | gdb: bool, 189 | 190 | /// use the specified firmware instead of OVMF 191 | #[argh(option)] 192 | firmware: Option, 193 | 194 | /// additional arguments to pass to the hypervisor 195 | #[argh(positional, greedy)] 196 | args: Vec, 197 | } 198 | 199 | #[cfg(feature = "args")] 200 | impl BootImageCommand { 201 | pub fn r#do(&self) -> Result<(), Box> { 202 | let (mut process, _temp_files) = boot_image( 203 | self.firmware.as_deref(), &self.image, self.x86_64, self.bochs, 204 | self.kvm, self.gdb, 205 | )?; 206 | process 207 | .args(&self.args) 208 | .status()? 209 | .exit_ok()?; 210 | Ok(()) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /towbootctl/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A companion utility for towboot. 2 | use std::error::Error; 3 | use std::fs; 4 | use std::env; 5 | use std::io::Write; 6 | use std::path::{Path, PathBuf}; 7 | 8 | use argh::{FromArgs, from_env}; 9 | use log::info; 10 | use tempfile::NamedTempFile; 11 | 12 | use towbootctl::{BootImageCommand, create_image, config, runtime_args_to_load_options}; 13 | 14 | #[allow(dead_code)] 15 | mod built_info { 16 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 17 | } 18 | 19 | #[derive(Debug, FromArgs)] 20 | /// Top-level command. 21 | struct Cli { 22 | #[argh(subcommand)] 23 | command: Command, 24 | } 25 | 26 | #[derive(Debug, FromArgs)] 27 | #[argh(subcommand)] 28 | enum Command { 29 | BootImage(BootImageCommand), 30 | Image(ImageCommand), 31 | Install(InstallCommand), 32 | Version(VersionCommand), 33 | } 34 | 35 | #[derive(Debug, FromArgs)] 36 | #[argh(subcommand, name = "image")] 37 | /// Build a bootable image containing towboot, kernels and their modules. 38 | struct ImageCommand { 39 | /// where to place the image 40 | #[argh(option, default = "PathBuf::from(\"image.img\")")] 41 | target: PathBuf, 42 | 43 | /// runtime options to pass to towboot 44 | #[argh(positional, greedy)] 45 | runtime_args: Vec, 46 | } 47 | 48 | impl ImageCommand { 49 | fn r#do(&self) -> Result<(), Box> { 50 | let mut towboot_temp_ia32 = NamedTempFile::new()?; 51 | towboot_temp_ia32.as_file_mut().write_all(towboot_ia32::TOWBOOT)?; 52 | let mut towboot_temp_x64 = NamedTempFile::new()?; 53 | towboot_temp_x64.as_file_mut().write_all(towboot_x64::TOWBOOT)?; 54 | 55 | create_image( 56 | &self.target, 57 | &self.runtime_args, 58 | Some(&towboot_temp_ia32.into_temp_path()), 59 | Some(&towboot_temp_x64.into_temp_path()), 60 | )?; 61 | 62 | Ok(()) 63 | } 64 | } 65 | 66 | #[derive(Debug, FromArgs)] 67 | #[argh(subcommand, name = "install")] 68 | /// Install towboot, the configuration file, its kernels and modules to a disk. 69 | struct InstallCommand { 70 | /// whether to do a removable install (meaning writing to /EFI/BOOT/) 71 | #[argh(switch)] 72 | removable: bool, 73 | 74 | /// whether to register the install with the firmware (otherwise it can only be chain-loaded) 75 | #[argh(switch)] 76 | register: bool, 77 | 78 | /// the operating system's name 79 | /// This is being used as the folder name inside /EFI and as the name for 80 | /// the boot entry. 81 | #[argh(option)] 82 | name: Option, 83 | 84 | #[argh(positional)] 85 | /// the root of the mounted ESP 86 | esp_path: PathBuf, 87 | 88 | /// runtime options to pass to towboot 89 | #[argh(positional, greedy)] 90 | runtime_args: Vec, 91 | } 92 | 93 | impl InstallCommand { 94 | fn r#do(&self) -> Result<(), Box> { 95 | assert!(self.esp_path.is_dir()); 96 | let mut install_path = self.esp_path.clone(); 97 | install_path.push("EFI"); 98 | if !install_path.exists() { 99 | fs::create_dir(&install_path)?; 100 | } 101 | install_path.push(if self.removable { 102 | "BOOT" 103 | } else { 104 | self.name.as_ref().expect("non-removable installs must have a name") 105 | }); 106 | if !install_path.exists() { 107 | fs::create_dir(&install_path)?; 108 | } 109 | info!("installing to {}", install_path.display()); 110 | if !self.runtime_args.is_empty() { 111 | let load_options = runtime_args_to_load_options(&self.runtime_args); 112 | if let Some(mut config) = config::get(&load_options)? { 113 | // Write the given configuration to the ESP. 114 | let mut config_path = PathBuf::from(config.src.clone()); 115 | config_path.pop(); 116 | // go through all needed files; including them (but without the original path) 117 | for src_file in config.needed_files() { 118 | let src_path = config_path.join(PathBuf::from(&src_file)); 119 | let dst_file = src_path.file_name().unwrap(); 120 | let mut dst_path = if self.removable { 121 | self.esp_path.clone() 122 | } else { 123 | install_path.clone() 124 | }; 125 | dst_path.push(dst_file); 126 | src_file.clear(); 127 | src_file.push_str(dst_file.to_str().unwrap()); 128 | fs::copy(&src_path, &dst_path)?; 129 | } 130 | // write the configuration itself 131 | let mut config_path = if self.removable { 132 | self.esp_path.clone() 133 | } else { 134 | install_path.clone() 135 | }; 136 | config_path.push("towboot.toml"); 137 | fs::write(&config_path, toml::to_string(&config)?)?; 138 | } else { 139 | // Exit if the options were just -help. 140 | return Ok(()) 141 | } 142 | } 143 | // add towboot itself 144 | // TODO: rename this maybe for non-removable installs? 145 | fs::write(Path::join(&install_path, "BOOTIA32.efi"), towboot_ia32::TOWBOOT)?; 146 | fs::write(Path::join(&install_path, "BOOTX64.efi"), towboot_x64::TOWBOOT)?; 147 | if self.register { 148 | assert!(!self.removable); 149 | todo!("registration with the firmware is not supported, yet"); 150 | } 151 | Ok(()) 152 | } 153 | } 154 | 155 | #[derive(Debug, FromArgs)] 156 | #[argh(subcommand, name = "version")] 157 | /// Display information about this application. 158 | struct VersionCommand {} 159 | 160 | impl VersionCommand { 161 | fn r#do(&self) -> Result<(), Box> { 162 | println!( 163 | "This is {} {}{}, built as {} for {} on {}.", 164 | built_info::PKG_NAME, 165 | built_info::GIT_VERSION.unwrap(), 166 | if built_info::GIT_DIRTY.unwrap() { 167 | " (dirty)" 168 | } else { 169 | "" 170 | }, 171 | built_info::PROFILE, 172 | built_info::TARGET, 173 | built_info::HOST, 174 | ); 175 | Ok(()) 176 | } 177 | } 178 | 179 | /// This gets started from the command line. 180 | fn main() -> Result<(), Box> { 181 | if env::var("RUST_LOG").is_err() { 182 | unsafe { env::set_var("RUST_LOG", "info"); } 183 | } 184 | env_logger::init(); 185 | let args: Cli = from_env(); 186 | match args.command { 187 | Command::BootImage(boot_image_command) => boot_image_command.r#do(), 188 | Command::Image(image_command) => image_command.r#do(), 189 | Command::Install(install_command) => install_command.r#do(), 190 | Command::Version(version_command) => version_command.r#do(), 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version.workspace = true 4 | authors.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | edition = "2024" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | argh = "0.1" 13 | log = "0.4" 14 | env_logger = { version = "0.11", default-features = false, features = ["auto-color"] } 15 | anyhow = "1.0" 16 | 17 | towbootctl = { path = "../towbootctl", features = ["args"] } 18 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(exit_status_error)] 2 | use std::env; 3 | use std::error::Error; 4 | use std::path::PathBuf; 5 | use std::process; 6 | 7 | use argh::{FromArgs, from_env}; 8 | use log::info; 9 | 10 | use towbootctl::{BootImageCommand, create_image}; 11 | 12 | #[derive(Debug, FromArgs)] 13 | /// Top-level command. 14 | struct Cli { 15 | #[argh(subcommand)] 16 | command: Command, 17 | } 18 | 19 | #[derive(Debug, FromArgs)] 20 | #[argh(subcommand)] 21 | enum Command { 22 | Build(Build), 23 | BootImage(BootImageCommand), 24 | } 25 | 26 | #[derive(Debug, FromArgs)] 27 | #[argh(subcommand, name = "build")] 28 | /// Build a bootable image containing, towboot, kernels and their modules. 29 | struct Build { 30 | /// do release builds 31 | #[argh(switch)] 32 | release: bool, 33 | 34 | /// do not include i686 build 35 | #[argh(switch)] 36 | no_i686: bool, 37 | 38 | /// do not include x86_64 build 39 | #[argh(switch)] 40 | no_x86_64: bool, 41 | 42 | /// where to place the image 43 | #[argh(option, default = "PathBuf::from(\"image.img\")")] 44 | target: PathBuf, 45 | 46 | /// runtime options to pass to towboot 47 | #[argh(positional, greedy)] 48 | runtime_args: Vec, 49 | } 50 | 51 | impl Build { 52 | fn r#do(self) -> Result<(), Box> { 53 | let mut cargo_command = process::Command::new("cargo"); 54 | let mut build_command = cargo_command 55 | .arg("build") 56 | .arg("--package") 57 | .arg("towboot"); 58 | if self.release { 59 | build_command = cargo_command.arg("--release"); 60 | } 61 | if !self.no_i686 { 62 | info!("building for i686, pass --no-i686 to skip this"); 63 | build_command 64 | .arg("--target") 65 | .arg("i686-unknown-uefi") 66 | .status()?.exit_ok()?; 67 | } 68 | if !self.no_x86_64 { 69 | info!("building for x86_64, pass --no-x86-64 to skip this"); 70 | build_command 71 | .arg("--target") 72 | .arg("x86_64-unknown-uefi") 73 | .status()?.exit_ok()?; 74 | } 75 | let build = match self.release { 76 | true => "release", 77 | false => "debug", 78 | }; 79 | let i686: Option = (!self.no_i686).then_some( 80 | ["target", "i686-unknown-uefi", build, "towboot.efi"].into_iter().collect() 81 | ); 82 | let x86_64: Option = (!self.no_x86_64).then_some( 83 | ["target", "x86_64-unknown-uefi", build, "towboot.efi"].into_iter().collect() 84 | ); 85 | create_image(&self.target, &self.runtime_args, i686.as_deref(), x86_64.as_deref())?; 86 | Ok(()) 87 | } 88 | } 89 | 90 | /// This gets started from the command line. 91 | fn main() -> Result<(), Box> { 92 | if env::var("RUST_LOG").is_err() { 93 | unsafe { env::set_var("RUST_LOG", "info"); } 94 | } 95 | env_logger::init(); 96 | let args: Cli = from_env(); 97 | match args.command { 98 | Command::Build(build) => build.r#do(), 99 | Command::BootImage(boot_image) => boot_image.r#do(), 100 | } 101 | } 102 | --------------------------------------------------------------------------------